diff --git a/problems/560.subarray-sum-equals-k/memo.md b/problems/560.subarray-sum-equals-k/memo.md new file mode 100644 index 0000000..f39795b --- /dev/null +++ b/problems/560.subarray-sum-equals-k/memo.md @@ -0,0 +1,97 @@ +## step1 +- 合計してkになる配列の選び方を何通り作れるかという問題 +- nums.lengthが2*10^4なので全探索は無理そう +- Counterを使ってkeyに合計の数字、valueに現在のパターン数を表すと解けそう? + - 元々のkeyのvalueにloopで回ってきたnumを足してkey+num = + - O(N^2)で解けそう + - counterじゃなくて普通のdefaultdictでいい + - for文を回すときに更新したkeyを使うとバグってしまいそう + - いい感じではないがdictを更新する時用のdictを毎回作る + - sequenceという条件を見落としていた。。。 + - 下記に書いて成仏する +```py +import collections + + +class Solution: + def subarraySum(self, nums: List[int], k: int) -> int: + sum_to_patterns = collections.defaultdict(int) + sum_to_patterns_before_update = sum_to_patterns.copy() + + for num in nums: + for sum_ in sum_to_patterns_before_update.keys(): + sum_to_patterns[sum_ + num] += sum_to_patterns_before_update[sum_] + + sum_to_patterns[num] += 1 + sum_to_patterns_before_update = sum_to_patterns.copy() + + return sum_to_patterns[k] +``` + +- sequenceの場合は視点と終点の取り方で1/2N^2通りくらいあり +- 合計の数字を計算するときに毎回計算すると時間がかかるので累積和を取っておくと1回のloopでO(1)で計算できるのでうまくいきそう + - 2*10^8程の計算だがTLEしてしまう、、、 + - これ以上早い方法が思いつかない、と思ったが累積和を一旦累積和をkey,出てきた回数をvalueのCounterにしておいて、startPointだけfor文を回して出てきた数字のカウントを減らしていくようにすればいける? + +バグをとるのでめちゃめちゃ時間がかかっている(20分以上。。。) +現状は下記だがなぜダメかわからない +```py +import itertools +import collections + + +# @lc code=start +class Solution: + def subarraySum(self, nums: list[int], k: int) -> int: + accumlate_nums = list(itertools.accumulate(nums, initial=0)) + remain_accumlate_counter = collections.Counter(accumlate_nums) + # print(remain_accumlate_counter) + total_patterns = 0 + for start in range(len(accumlate_nums) - 1): + accumlate_num = accumlate_nums[start] + remain_accumlate_counter[accumlate_num] -= 1 + total_patterns += remain_accumlate_counter[k - accumlate_num] + print(remain_accumlate_counter) + print(start,accumlate_num,total_patterns) + + + return total_patterns +``` +- `k - accumlate_num` => `accumlate_num + k`だった、、、 +- 諦めてChatGPTに聞いてしまった +- バグらせるとだいぶ疲労感が溜まるので早く見つけられるようになりたい +- 今回だと数式の条件が誤っていると早めに検討をつけられるようになりたい + +## step2 +- step1から5日経ってstep2に進みコードを見返すと自然な流れに感じる +- `remain_accumlate_counter`でニュアンスは伝わるが100点の命名ではない気がするが思いつかない +- 計算量はO(N)(累積和の計算でO(N)で累積和からの集計でO(N)) +- データの偏りや傾向があることがわかっている場合にやり方は変わりそうか? + - 配列が100くらいの場合はわかりやすさを重視してO(N^2)の解法でもいいのではないか? + - データが正しか入らない場合は累積和は単調増加になるので二分探索で満たすkを探しにいく方法も考えられるが、Counterの方が計算量は少なくて済みそう +- 累積和を使わない方法もあるか? + - データが全て正の場合は右側の捜索範囲を広げ、kをこした場合に左を削っていく芋虫みたいな方法で解けそう + - これもO(N)になりそう +- 他の人のコードを読んでみる +- https://github.com/Hiroto-Iizuka/coding_practice/pull/16/files + - `count += cumsum_to_freq.get(total - k, 0)`getでkeyに値がなかった場合の値を指定できるのは読めるが書くときの選択肢になかったので使えるようにしておきたい + - `itertools.accumulate()で累積和のiteratorを作れますが、本問は使わない方が自然ですね。` + - 使わない方が自然なのは自分の感覚になかった + - むしろ使えそうな標準ライブラリは積極的に使うスタンスだったので意外だった + - 一気に累積和を計算すると2周分確認が必要になるからだと理解した + - `prefix_sum_to_count = defaultdict(int, {0: 1})`defaultdictで初期の状態を指定する方法は知らなかったので覚えておきたい + - 自分のやり方だとふfor文を2周分(累積和とカウント)していたが、累積和を計算しながら現在の累積の数とすでに出てきた累積和の差がkになるものを探していけば1周でいける +- https://github.com/mamo3gr/arai60/pull/17/files + - `累積和は prefix_sum, running_total ともいう。`見た時にわかるようにしておきたい + +- 他の人のコードを見た後の振り返り + - ライブラリの名前から累積和の変数名はaccumlateなんとかにしていたがあまり一般的ではなさそう、使うにしてもそのままだと動詞なのでaccumlatedにした方が良さそう + - for文1周で済むところを2周していたところに気づけたらよかった + - 多分解法をじっくり頭でイメージできていたら左を固定する方法ではなく、右を固定する方法もあることに気づけていたかもしれない + - `for start in range(len(accumlate_nums) - 1):`も分かりにくい部分だと思った + + +## step3 +- step1だと結構複雑に考えていたが、step2で書き換えたことによってロジックも扱う変数もスッキリしたので段違いでわかりやすく感じる +- 毎回right - left = k => left = right - k を考え`prefix_sum_to_count[prefix_sum - k]`としている + - この辺がスムーズに変換するのが苦手みたいなので訓練したい diff --git a/problems/560.subarray-sum-equals-k/step1.py b/problems/560.subarray-sum-equals-k/step1.py new file mode 100644 index 0000000..509009c --- /dev/null +++ b/problems/560.subarray-sum-equals-k/step1.py @@ -0,0 +1,24 @@ +# +# @lc app=leetcode id=560 lang=python3 +# +# [560] Subarray Sum Equals K +# +import itertools +import collections + + +# @lc code=start +class Solution: + def subarraySum(self, nums: list[int], k: int) -> int: + accumlate_nums = list(itertools.accumulate(nums, initial=0)) + remain_accumlate_counter = collections.Counter(accumlate_nums) + total_patterns = 0 + for start in range(len(accumlate_nums) - 1): + accumlate_num = accumlate_nums[start] + remain_accumlate_counter[accumlate_num] -= 1 + total_patterns += remain_accumlate_counter[accumlate_num + k] + + return total_patterns + + +# @lc code=end diff --git a/problems/560.subarray-sum-equals-k/step2.py b/problems/560.subarray-sum-equals-k/step2.py new file mode 100644 index 0000000..e21b7a2 --- /dev/null +++ b/problems/560.subarray-sum-equals-k/step2.py @@ -0,0 +1,24 @@ +# +# @lc app=leetcode id=560 lang=python3 +# +# [560] Subarray Sum Equals K +# + +# @lc code=start +import collections + + +class Solution: + def subarraySum(self, nums: List[int], k: int) -> int: + total_count = 0 + prefix_sum = 0 + prefix_sum_to_count = collections.defaultdict(int, {0: 1}) + + for num in nums: + prefix_sum += num + total_count += prefix_sum_to_count[prefix_sum - k] + prefix_sum_to_count[prefix_sum] += 1 + return total_count + + +# @lc code=end diff --git a/problems/560.subarray-sum-equals-k/step3.py b/problems/560.subarray-sum-equals-k/step3.py new file mode 100644 index 0000000..1180a8c --- /dev/null +++ b/problems/560.subarray-sum-equals-k/step3.py @@ -0,0 +1,24 @@ +# +# @lc app=leetcode id=560 lang=python3 +# +# [560] Subarray Sum Equals K +# +import collections + + +# @lc code=start +class Solution: + def subarraySum(self, nums: List[int], k: int) -> int: + total_count = 0 + prefix_sum = 0 + prefix_sum_to_count = collections.defaultdict(int, {0: 1}) + + for num in nums: + prefix_sum += num + total_count += prefix_sum_to_count[prefix_sum - k] + prefix_sum_to_count[prefix_sum] += 1 + + return total_count + + +# @lc code=end