From 3c002beabd7db2ac7e5d619c8e92cf77270b8292 Mon Sep 17 00:00:00 2001 From: mayota Date: Mon, 22 Jun 2026 22:32:41 +0900 Subject: [PATCH 1/4] step1 --- 560-Subarray-Sum-Equals-K/note.md | 30 ++++++++++++++++++++++++++++++ 560-Subarray-Sum-Equals-K/step1.py | 24 ++++++++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 560-Subarray-Sum-Equals-K/note.md create mode 100644 560-Subarray-Sum-Equals-K/step1.py diff --git a/560-Subarray-Sum-Equals-K/note.md b/560-Subarray-Sum-Equals-K/note.md new file mode 100644 index 0000000..4f2d87d --- /dev/null +++ b/560-Subarray-Sum-Equals-K/note.md @@ -0,0 +1,30 @@ +# 560. Subarray Sum Equals K + + + +## step1(まず通す) + +### サブアレイの数はいくつあるか + +開始位置と終了位置を選べばサブアレイが決まるので、配列長さ n に対して +`nC2 = n(n-1) /2` +サブアレイを全部見るだけで計算量がすごいことになりそう + +よくあるのは、先頭から一つずつ伸ばして和を計算(累積和)する方法。これなら O(n)。 +先頭から始まるサブアレイ限定で {和: サブアレイ数} を持つと和の部分の組み合わせを考えて条件を満たすサブアレイ数が計算できそう。 +ただし、サブアレイの終端位置が小さいものから大きいものは引けないので注意する必要がある。 + +書いてみたけど、計算時間はどれくらいだろう? +一番気にしてるのは、後半の3重ループが長くなりすぎないかということだが、 +cumulative_sum_to_tail のキー数を x とすると、各キーに対応する value であるリストの平均長さは約 n/x なので、3重ループは平均で `x * n/x * n/x = n^2/x` 回程度? +各要素の符号が揃っていて累積和が単調だとループ回さず掛け算で済むので楽になりそう。 + +`nums = [1,-1] * 10 ^ 4, k = 1` みたいな時には 2 * 10 ^ 8 回ループが回るわけで、python だと数秒以上かかりかねないのでは、という気がする。 + +実際、leetcode のオンラインジャッジで 10 秒くらいかかってる + +あと complement は (sum を基準とした時の k の)補数なので命名としてちょっと気持ち悪い。sum の〇〇という命名の方がスッキリくる。 + +## step2(整形&他の人のコードを読む) + +## step3(10分以内にさっとかける * 3回) diff --git a/560-Subarray-Sum-Equals-K/step1.py b/560-Subarray-Sum-Equals-K/step1.py new file mode 100644 index 0000000..1ffaa96 --- /dev/null +++ b/560-Subarray-Sum-Equals-K/step1.py @@ -0,0 +1,24 @@ +from collections import defaultdict + + +class Solution: + def subarraySum(self, nums: list[int], k: int) -> int: + cumulative_sum_to_tail = defaultdict(list) + # 先頭から始まるサブアレイ自体も考慮するために和が0の空アレイを想定 + cumulative_sum_to_tail[0].append(-1) + cumulative_sum = 0 + for i, num in enumerate(nums): + cumulative_sum += num + cumulative_sum_to_tail[cumulative_sum].append(i) + + num_sum_equals_k = 0 + for sum in cumulative_sum_to_tail: + complement = sum - k + if complement not in cumulative_sum_to_tail: + continue + for i in cumulative_sum_to_tail[sum]: + for j in cumulative_sum_to_tail[complement]: + if i > j: + num_sum_equals_k += 1 + + return num_sum_equals_k From c3d6f263a80020a7f8ef3b88d456a4e20bf52468 Mon Sep 17 00:00:00 2001 From: mayota Date: Mon, 22 Jun 2026 22:49:24 +0900 Subject: [PATCH 2/4] =?UTF-8?q?step2=20=E5=89=8D=E5=8D=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 560-Subarray-Sum-Equals-K/note.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/560-Subarray-Sum-Equals-K/note.md b/560-Subarray-Sum-Equals-K/note.md index 4f2d87d..c856661 100644 --- a/560-Subarray-Sum-Equals-K/note.md +++ b/560-Subarray-Sum-Equals-K/note.md @@ -27,4 +27,13 @@ cumulative_sum_to_tail のキー数を x とすると、各キーに対応する ## step2(整形&他の人のコードを読む) +他の人のコードを読む前に、cumulative_sum_to_tail の values のリストはソートされているのでインデックスの大小関係を確認するのに i,j で2重ループを回す必要がないだろうということに気がついた。 + +思いつく中では短い方でループを回してバイナリサーチが一番早そう。 +`x * n/x * log(n/x) = n (log n - logx) < n log n` 回くらいにはなる。 + +n が 2 * 10 ^ 4 なので `n log n ~ 30 * 10 ^ 5 で` 数百 ms くらいにはなるか。 + +### 他の方のコードを見る + ## step3(10分以内にさっとかける * 3回) From 467bcb82999be0b9ab073181ef91fd046e3a91e3 Mon Sep 17 00:00:00 2001 From: mayota Date: Tue, 23 Jun 2026 21:31:49 +0900 Subject: [PATCH 3/4] step2, 3 --- 560-Subarray-Sum-Equals-K/note.md | 11 +++++++++++ 560-Subarray-Sum-Equals-K/step2.py | 13 +++++++++++++ 560-Subarray-Sum-Equals-K/step3.py | 13 +++++++++++++ 3 files changed, 37 insertions(+) create mode 100644 560-Subarray-Sum-Equals-K/step2.py create mode 100644 560-Subarray-Sum-Equals-K/step3.py diff --git a/560-Subarray-Sum-Equals-K/note.md b/560-Subarray-Sum-Equals-K/note.md index c856661..8cf20d0 100644 --- a/560-Subarray-Sum-Equals-K/note.md +++ b/560-Subarray-Sum-Equals-K/note.md @@ -36,4 +36,15 @@ n が 2 * 10 ^ 4 なので `n log n ~ 30 * 10 ^ 5 で` 数百 ms くらいには ### 他の方のコードを見る +- + - 累積和を計算するのと一緒に差が k になるものを探せば、その時点で和を計算したものより短い prefix array しかないので長さの比較が不要になり O(n) にできる。 + - こうすると、prefix array の末尾のインデックスを覚えている必要もなくなるので単にカウントでよくなる + - `defaultdict` の初期値は初期化の時に引数で渡せる + - `itertools.accumulate()` という関数があるようだ + - これに限らず [itertools](https://docs.python.org/ja/3/library/itertools.html)は非常に便利そう +- + - ↑と同じようにされている + - 自分も for ループの先頭でハッシュマップをインクリメントするよりは、リスト先頭の直前に空の prefix array を想定して初期化する方が好き +- prefix sum という命名が多め?に感じる + ## step3(10分以内にさっとかける * 3回) diff --git a/560-Subarray-Sum-Equals-K/step2.py b/560-Subarray-Sum-Equals-K/step2.py new file mode 100644 index 0000000..6a6d213 --- /dev/null +++ b/560-Subarray-Sum-Equals-K/step2.py @@ -0,0 +1,13 @@ +from collections import defaultdict +from itertools import accumulate + + +class Solution: + def subarraySum(self, nums: list[int], k: int) -> int: + prefix_sum_count = defaultdict(int, {0: 1}) + equals_k_count = 0 + for prefix_sum in accumulate(nums): + equals_k_count += prefix_sum_count[prefix_sum - k] + prefix_sum_count[prefix_sum] += 1 + + return equals_k_count diff --git a/560-Subarray-Sum-Equals-K/step3.py b/560-Subarray-Sum-Equals-K/step3.py new file mode 100644 index 0000000..6a6d213 --- /dev/null +++ b/560-Subarray-Sum-Equals-K/step3.py @@ -0,0 +1,13 @@ +from collections import defaultdict +from itertools import accumulate + + +class Solution: + def subarraySum(self, nums: list[int], k: int) -> int: + prefix_sum_count = defaultdict(int, {0: 1}) + equals_k_count = 0 + for prefix_sum in accumulate(nums): + equals_k_count += prefix_sum_count[prefix_sum - k] + prefix_sum_count[prefix_sum] += 1 + + return equals_k_count From c7db41cdc4bb79543964e1ca4b2795e0b01fb25c Mon Sep 17 00:00:00 2001 From: mayota Date: Tue, 23 Jun 2026 21:38:42 +0900 Subject: [PATCH 4/4] minor fix --- 560-Subarray-Sum-Equals-K/note.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/560-Subarray-Sum-Equals-K/note.md b/560-Subarray-Sum-Equals-K/note.md index 8cf20d0..d0530c9 100644 --- a/560-Subarray-Sum-Equals-K/note.md +++ b/560-Subarray-Sum-Equals-K/note.md @@ -37,7 +37,7 @@ n が 2 * 10 ^ 4 なので `n log n ~ 30 * 10 ^ 5 で` 数百 ms くらいには ### 他の方のコードを見る - - - 累積和を計算するのと一緒に差が k になるものを探せば、その時点で和を計算したものより短い prefix array しかないので長さの比較が不要になり O(n) にできる。 + - 累積和を計算するのと一緒に差が k になるものを探せば、そのとき新たに和を計算したものより短い prefix array しかないので長さの比較が不要になり O(n) にできる。 - こうすると、prefix array の末尾のインデックスを覚えている必要もなくなるので単にカウントでよくなる - `defaultdict` の初期値は初期化の時に引数で渡せる - `itertools.accumulate()` という関数があるようだ