-
Notifications
You must be signed in to change notification settings - Fork 0
209. Minimum Size Subarray Sum #46
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,200 @@ | ||
| 問題: https://leetcode.com/problems/minimum-size-subarray-sum/description/ | ||
|
|
||
| ### Step 1 | ||
| - 大まかな方針はすぐに立ったが仕事の引き継ぎのイメージが甘くて詰まった | ||
| - 時間計算量: O(2n) = O(n) | ||
| - 空間計算量: O(1) | ||
| - target 以上の subarray が存在しない場合の処理をどうしようか迷った。 | ||
| 下記の方法以外には、result の他に found のようなフラグを用意する方法もある | ||
|
|
||
| ```Go | ||
| func minSubArrayLen(target int, nums []int) int { | ||
| result := math.MaxInt | ||
| subarraySum := 0 | ||
| left := 0 | ||
| for right := range len(nums) { | ||
| subarraySum += nums[right] | ||
| if subarraySum < target { | ||
| continue | ||
| } | ||
| for ; target <= subarraySum && left <= right; left++ { | ||
| subarraySum -= nums[left] | ||
| result = min(result, right-left+1) | ||
| } | ||
| } | ||
| if result == math.MaxInt { | ||
| return 0 | ||
| } | ||
| return result | ||
| } | ||
| ``` | ||
|
|
||
| ### Step 2 | ||
| #### 2a | ||
| - step1 の修正 | ||
| - 答え(result)の初期値について | ||
| - https://github.com/olsen-blue/Arai60/pull/50/files#r2005904280 | ||
| - 確かに答えの最大値は len(nums) なので初期値を len(nums) + 1 としても動く | ||
| - https://github.com/Yoshiki-Iwasa/Arai60/pull/43/files#r1709694524 | ||
| - 定数を宣言すると丁寧 | ||
| - https://github.com/olsen-blue/Arai60/pull/50/files#diff-6d4eb2707ed57d8037bfa2e5985424b237a407741a7602ba92b31e090d1cb096R163 | ||
| - left == right であれば subarraySum は 0 になり、target <= subarraySum でなくなるので | ||
| `&& left <= right` のチェックは不要 | ||
| - https://github.com/olsen-blue/Arai60/pull/50/files#diff-6d4eb2707ed57d8037bfa2e5985424b237a407741a7602ba92b31e090d1cb096R164-R165 | ||
| - `min_length` と `prefix_sum` の更新順が自分のとは逆 | ||
|
Comment on lines
+43
to
+44
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ここのコードの意図ですが、下記のようなイメージでした。
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. はい、そちらの方が自然だと思い、見習わせていただきました! |
||
| - リンク先の方がしっくりくる。 | ||
| というか自分が step1 でつまずいていたのは処理の理解が自然でなかったことが伺える | ||
| - https://github.com/olsen-blue/Arai60/pull/50/files#r2008742099 | ||
| - nums に 0 が含まれる場合の挙動について | ||
|
|
||
| ```Go | ||
| func minSubArrayLen(target int, nums []int) int { | ||
| const notFound = 0 | ||
| result := notFound | ||
| subarraySum := 0 | ||
| left := 0 | ||
| for right := range len(nums) { | ||
| subarraySum += nums[right] | ||
| if subarraySum < target { | ||
| continue | ||
| } | ||
| for target <= subarraySum { | ||
| if result == notFound { | ||
| result = right - left + 1 | ||
| } else { | ||
| result = min(result, right-left+1) | ||
| } | ||
| subarraySum -= nums[left] | ||
| left++ | ||
| } | ||
| } | ||
| return result | ||
| } | ||
| ``` | ||
|
|
||
| #### 2b | ||
| - 二分探索を用いた解法 | ||
| 1. nums の累積和配列を作る | ||
| 2. nums[i] が先頭となる部分配列で総和が target 以上となるものの中で最も要素数の少ないものを探せば良い | ||
| 3. そこで登場するのが二分探索。(0~nums[i-1]の累積和) + target の insert position を探せば良い | ||
| - 時間計算量: O(n logn) | ||
| - 空間計算量: O(n) | ||
| - https://github.com/Yoshiki-Iwasa/Arai60/pull/43/files#r1712678111 | ||
| - not found の場合の判定を prefixSums を作り終わった時にできる | ||
| - (追記) https://github.com/fhiyo/leetcode/pull/49/files#r1685369428 | ||
| - 関数の一番最初に `sum(nums) < target` でも判定できるので 2d で実装 | ||
| - https://github.com/Yoshiki-Iwasa/Arai60/pull/43/files#r1709772635 | ||
| - この指摘の「prefix_sum の要素を詰め終わったら、nums は役割を全うして prefix_sum が代わりに進む準備ができたような気持ちになりました」に納得 | ||
| - 「変数のスコープはできたら短くしたい」の部分についてはそこまで考慮できていなかった | ||
|
|
||
| ```Go | ||
| func minSubArrayLen(target int, nums []int) int { | ||
| // prefixSums[0] = 0 | ||
| // prefixSums[i] = nums[0] + ... + nums[i-1] | ||
| prefixSums := make([]int, len(nums)+1) | ||
| for i := range nums { | ||
| prefixSums[i+1] = prefixSums[i] + nums[i] | ||
| } | ||
| if prefixSums[len(prefixSums)-1] < target { | ||
| return 0 | ||
| } | ||
| result := len(prefixSums) | ||
| for left := range nums { | ||
| right, _ := slices.BinarySearch(prefixSums, prefixSums[left]+target) | ||
| if right == len(prefixSums) { | ||
| break | ||
| } | ||
| result = min(result, right-left) | ||
| } | ||
| return result | ||
| } | ||
| ``` | ||
|
|
||
| #### 2c | ||
| - 2b の二分探索の区間を (left, len(prefixSums)) の開区間にしてみた | ||
| - `if left+right == len(prefixSums) { break }` の部分で可読性が下がったような気がする | ||
|
|
||
| ```Go | ||
| func minSubArrayLen(target int, nums []int) int { | ||
| // prefixSums[0] = 0 | ||
| // prefixSums[i] = nums[0] + ... + nums[i-1] | ||
| prefixSums := make([]int, len(nums)+1) | ||
| for i := range nums { | ||
| prefixSums[i+1] = prefixSums[i] + nums[i] | ||
| } | ||
| if prefixSums[len(prefixSums)-1] < target { | ||
| return 0 | ||
| } | ||
| result := math.MaxInt | ||
| for left := range nums { | ||
| right, _ := slices.BinarySearch(prefixSums[left:], prefixSums[left]+target) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 前回の探索結果の right よりも右くらいまではいえますかね。あんまり速度には影響がないかもしれませんが。そうなると、right から右に走査するのも一つですね。(そうすると step 1 に近い物になるでしょう。)
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 書いてみました。 func minSubArrayLen(target int, nums []int) int {
prefixSums := make([]int, len(nums)+1)
for i := range nums {
prefixSums[i+1] = prefixSums[i] + nums[i]
}
if prefixSums[len(prefixSums)-1] < target {
return 0
}
result := math.MaxInt
lastRight := 0
for left := range nums {
subarrayRight, _ := slices.BinarySearch(prefixSums[lastRight+1:], prefixSums[left]+target)
if lastRight+subarrayRight+1 == len(prefixSums) {
break
}
result = min(result, (lastRight+subarrayRight+1)-left)
}
return result
}right から右に走査する。二分探索の対象が prefixSums[0:right] と始点を固定できるのがありがたい。二分探索で見つけるべきは prefixSums[right] - target を挿入できる位置のすぐ左のインデックスなので、BisectRight を自作した。 func BisectRight(nums []int, target int) int {
left := 0
right := len(nums) - 1
for left < right {
middle := (left+right+1) / 2
if nums[middle] <= target {
left = middle
} else {
right = middle - 1
}
}
return left
}
func minSubArrayLen(target int, nums []int) int {
prefixSums := make([]int, len(nums)+1)
for i := range nums {
prefixSums[i+1] = prefixSums[i] + nums[i]
}
if prefixSums[len(prefixSums)-1] < target {
return 0
}
result := math.MaxInt
for right := 1; right < len(prefixSums); right++ {
if prefixSums[right] < target {
continue
}
left := BisectRight(prefixSums[:right], prefixSums[right]-target)
result = min(result, right-left)
}
return result
}There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. はい。前回の結果を覚えておくならば、Binary Search よりも線形で探したほうが速そうですね。 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 私は、 また、スライスすることで、
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 後から見返したところ left は prefixSums 全体の中のインデックスで right はスライスされたものの中でのインデックスとなっており、対称性がないのがちょっと読み手にとってトラップかもしれません。 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. たしかに非対称なのが認知不可があるかもですね。 |
||
| if left+right == len(prefixSums) { | ||
| break | ||
| } | ||
| result = min(result, right) | ||
| } | ||
| return result | ||
| } | ||
| ``` | ||
|
|
||
| #### 2d | ||
| - https://github.com/fhiyo/leetcode/pull/49/files#diff-b4505bb135c82fffdf6750131a4227b712965b52787f0d78ace5983440d8b88aR139 | ||
| - 左半開区間でやってみる | ||
| - 一番最初に not found の場合を処理する | ||
|
|
||
| ```Go | ||
| func SumInt(nums []int) int { | ||
| sum := 0 | ||
| for _, n := range nums { | ||
| sum += n | ||
| } | ||
| return sum | ||
| } | ||
|
|
||
| func minSubArrayLen(target int, nums []int) int { | ||
| if SumInt(nums) < target { | ||
| return 0 | ||
| } | ||
| result := math.MaxInt | ||
| subarraySum := 0 | ||
| left := -1 | ||
| for right := range nums { | ||
| subarraySum += nums[right] | ||
| for target <= subarraySum { | ||
| result = min(result, right-left) | ||
| left++ | ||
| subarraySum -= nums[left] | ||
| } | ||
| } | ||
| return result | ||
| } | ||
| ``` | ||
|
|
||
| ### Step 3 | ||
| - left を含めない subarray を考えた方が left が right を追い越す瞬間がなくてわかりやすかった | ||
|
|
||
| ```Go | ||
| func minSubArrayLen(target int, nums []int) int { | ||
| const notFound = math.MaxInt | ||
| minLength := notFound | ||
| subarraySum := 0 | ||
| left := -1 | ||
| for right := range nums { | ||
| subarraySum += nums[right] | ||
| if subarraySum < target { | ||
| continue | ||
| } | ||
| for subarraySum >= target { | ||
| minLength = min(minLength, right-left) | ||
| left++ | ||
| subarraySum -= nums[left] | ||
| } | ||
| } | ||
| if minLength == notFound { | ||
| return 0 | ||
| } | ||
| return minLength | ||
| } | ||
| ``` | ||
|
|
||
| ### CS | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
この if 文は削除しても同じ結果になると思います。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
はい、ここは敢えて書くかどうか迷いましたが、目の動きを減らすために入れてみました。