From 7ea3c5b915980b4095dde22ba10d0996d26e5052 Mon Sep 17 00:00:00 2001 From: mori Date: Fri, 4 Oct 2024 23:55:13 +0900 Subject: [PATCH 1/4] add .gitignore --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bf95788 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +start_new_problem.sh +main.go +go.mod +go.sum \ No newline at end of file From b21100f849e0a06a0b167c75ec3321d75add2c52 Mon Sep 17 00:00:00 2001 From: mori Date: Thu, 10 Oct 2024 10:40:47 +0900 Subject: [PATCH 2/4] add .gitignore --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index bf95788..f7ee915 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ start_new_problem.sh main.go go.mod -go.sum \ No newline at end of file +go.sum +*.go \ No newline at end of file From 04732b31406cf0606ac22c7479297c9dafe1c179 Mon Sep 17 00:00:00 2001 From: mori Date: Wed, 26 Feb 2025 21:23:27 +0900 Subject: [PATCH 3/4] step3 done --- 1011CapacityToShipPackagesWithinDDays.md | 235 +++++++++++++++++++++++ 1 file changed, 235 insertions(+) create mode 100644 1011CapacityToShipPackagesWithinDDays.md diff --git a/1011CapacityToShipPackagesWithinDDays.md b/1011CapacityToShipPackagesWithinDDays.md new file mode 100644 index 0000000..f336079 --- /dev/null +++ b/1011CapacityToShipPackagesWithinDDays.md @@ -0,0 +1,235 @@ +問題: https://leetcode.com/problems/capacity-to-ship-packages-within-d-days/description/ + +### Step 1 +- 見当もつかなかったので他の人のプルリクを見た + - https://github.com/hayashi-ay/leetcode/pull/55/files#diff-4e146417f14c744a10f851601f26cd2cb17b420ff966720e568f6f5679aa475eR1 + - https://github.com/Mike0121/LeetCode/pull/46/files +- なるほど、答えのcapacityは[weightsの最大要素, weightsの総計]の区間に存在することになるので、その範囲を探索すれば良いのか +- まずは線形に探索していく。時間計算量はO(len(weights) * (weightsSum-maxWeight))になるので、weightsの要素が大きいと時間がかかりそう +- 案の定TLEした + +```Go +func shipWithinDays(weights []int, days int) int { + maxWeight := slices.Max(weights) + weightSum := 0 + for _, w := range weights { + weightSum += w + } + for capacity := maxWeight; capacity <= weightSum; capacity++ { + if isShipableWithinCapacity(weights, days, capacity) { + return capacity + } + } + panic("unreacheable") +} + +func isShipableWithinCapacity(weights []int, days int, capacity int) bool { + day := 1 + weight := 0 + for _, w := range weights { + if weight+w > capacity { + day++ + weight = 0 + } + weight += w + if day > days { + return false + } + } + return true +} +``` + +- ここで登場するのが二分探索 +- [weightsの最大要素, weightsの総計]の区間を、 +true: 与えられたdays以内に出荷できるcapacity と定義すると、 +[false,...,false,true,...,true]という配列になり、一番左のtrueの位置を求める +- trueは必ず存在するので、閉区間を使って範囲を狭めていき、left==rightとなったらその値が求めたいcapacity +- Goではcapはbuilt-in関数名になっているため、変数名として使用することは避ける +- Goにはスライスの要素の総計を求める標準ライブラリ関数が存在しない + +```Go +func shipWithinDays(weights []int, days int) int { + maxWeight := slices.Max(weights) + weightsSum := 0 + for _, w := range weights { + weightsSum += w + } + low := maxWeight + high := weightsSum + for low < high { + middle := low + (high-low)/2 + if isShipableWithinCapacity(weights, days, middle) { + high = middle + } else { + low = middle + 1 + } + } + return low +} + +func isShipableWithinCapacity(weights []int, days int, capacity int) bool { + day := 1 + weight := 0 + for _, w := range weights { + if weight+w > capacity { + day++ + weight = 0 + } + weight += w + if day > days { + return false + } + } + return true +} +``` + +### Step 2 +#### 2a +- shipableではなくshippable +- ヘルパー関数をshippableかどうかのbool値で出力する方法以外に、 +capacityに対して何日必要かを返してそれがdays以下かどうかを呼び出し元で確認する方法もある。 +このほうがヘルパー関数がシンプルになる一方、daysを超えた時点でfalseを返す方が速い + +```Go +func shipWithinDays(weights []int, days int) int { + maxWeight := slices.Max(weights) + weightsSum := 0 + for _, w := range weights { + weightsSum += w + } + low := maxWeight + high := weightsSum + for low < high { + middle := low + (high-low)/2 + requiredDays := requiredDaysToShip(weights, middle) + if requiredDays <= days { + high = middle + } else { + low = middle + 1 + } + } + return low +} + +// requiredDaysToShip returns the minimum days to ship within the given capacity. +func requiredDaysToShip(weights []int, capacity int) int { + days := 1 + weight := 0 + for _, w := range weights { + if weight+w > capacity { + days++ + weight = w + continue + } + weight += w + } + return days +} +``` + +#### 2b +- 標準ライブラリ関数のslices.BinarySearchFuncを使ってみる +- [maxWeight,weightsSum]区間のスライスを作成しないといけないため、 +空間計算量がO(n)になる + - pythonのbisect_leftならrangeを使って空間計算量をO(1)に抑えられるらしい + - https://github.com/fhiyo/leetcode/pull/45#discussion_r1682470839 +- shippableなcapacityを1, そうでないものを0として +[0,...,0,1,...,1]という配列の左端の1の位置を返す +- bool値を使わなかったのは、BinarySearchFuncの第三引数がint型を返す関数であり、bool値同士の演算をint型に変換するのが面倒だったから + +```Go +func SumInt(s []int) int { + sum := 0 + for _, v := range s { + sum += v + } + return sum +} + +func shipWithinDays(weights []int, days int) int { + maxWeight := slices.Max(weights) + weightsSum := SumInt(weights) + capacities := make([]int, weightsSum-maxWeight+1) + for i := range capacities { + capacities[i] = maxWeight + i + } + index, _ := slices.BinarySearchFunc(capacities, 1, func(capacity int, t int) int { + return isShippable(weights, days, capacity) - t + }) + return capacities[index] +} + +// isShippable returns 1 if shippable and 0 if not +func isShippable(weights []int, days int, capacity int) int { + day := 1 + weight := 0 + for _, w := range weights { + if weight+w > capacity { + day++ + weight = 0 + } + weight += w + if day > days { + return 0 + } + } + return 1 +} +``` + +### Step 3 +- 日数を調べるよりヘルパー関数で判定できた方が自分は好きなのでstep1の方法 +- `isShippable`より`canShip`の方がシンプル + - https://github.com/goto-untrapped/Arai60/pull/41/files#diff-cc45bac68955c702274e070386dd9a6db7bad032fdc75cf32cbea4781f618685R17 +- `low := slices.Max(weights)`とすることに抵抗があったが、割とそうしている人がいる + - https://github.com/fhiyo/leetcode/pull/45/files#diff-3e42d068b82e2a1be434dc989edc077d304c433f9a25ad4a2b3bc8f9223e43bcR33 +- `day` -> `daysRequired` + - https://github.com/fhiyo/leetcode/pull/45/files#r1682612140 +- ヘルパー関数の`canShip()`をinner functionにするかどうか迷った。 + - inner functionにするメリットは、依存関係が生じるリスクをなくせること。 + 引数が減ること + - Goのスライスは参照渡しなのでメモリ使用量が増える心配はしなくてよい + - inner functionではなく、外で定義した方が個人的には見やすい + - 機能面的にはinner functionにした方がメリットが多そう + +```Go +func SumInts(s []int) int { + sum := 0 + for _, v := range s { + sum += v + } + return sum +} + +func shipWithinDays(weights []int, days int) int { + canShip := func(capacity int) bool { + daysRequired := 1 + loadedWeight := 0 + for _, weight := range weights { + if loadedWeight+weight > capacity { + daysRequired++ + loadedWeight = 0 + } + if daysRequired > days { + return false + } + loadedWeight += weight + } + return true + } + + low := slices.Max(weights) + high := SumInts(weights) + for low < high { + middle := low + (high-low)/2 + if canShip(middle) { + high = middle + } else { + low = middle + 1 + } + } + return low +} +``` \ No newline at end of file From 69cfc26c6fc262d8d3b044295ad2f9f7921e80c2 Mon Sep 17 00:00:00 2001 From: mori Date: Thu, 27 Feb 2025 01:00:11 +0900 Subject: [PATCH 4/4] add step4 --- 1011CapacityToShipPackagesWithinDDays.md | 58 ++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/1011CapacityToShipPackagesWithinDDays.md b/1011CapacityToShipPackagesWithinDDays.md index f336079..2602201 100644 --- a/1011CapacityToShipPackagesWithinDDays.md +++ b/1011CapacityToShipPackagesWithinDDays.md @@ -193,6 +193,7 @@ func isShippable(weights []int, days int, capacity int) int { - Goのスライスは参照渡しなのでメモリ使用量が増える心配はしなくてよい - inner functionではなく、外で定義した方が個人的には見やすい - 機能面的にはinner functionにした方がメリットが多そう +- weightsが空の時、slices.Maxでpanicする ```Go func SumInts(s []int) int { @@ -232,4 +233,61 @@ func shipWithinDays(weights []int, days int) int { } return low } +``` + +### Step 4 +- slices.BinarySearchFuncの内部実装を見る +- ⚠️質問 + - https://cs.opensource.google/go/go/+/master:src/slices/sort.go;l=158 + - middleを計算する際にoverflow対策のため、 + `middle := int(uint(low+high) >> 1)`としている + - これがoverflow対策になっているのは、int型よりuint型の方が大きい正の数を表現できるから?? + - すなわち、32bitマシンにおいてint型はint32と同じ大きさで、 + 同様にuint型はuint32と同じになり、 + 表現できる最大値がそれぞれ2^31-1と2^32-1で後者の方が大きいからuint(low+high)でoverflowしない + - 計算結果は`(low+high) / 2`と同じ(overflowしなければ)?? +- 以前から気になっていたが、Goの標準ライブラリの内部実装を見ると、 +一文字変数や省略形が多用されている印象を受ける + - 今回も、i -> low, j -> high, h -> middle としたいところ + +```Go +func BinarySearchFunc[S ~[]E, E, T any](x S, target T, cmp func(E, T) int) (int, bool) { + n := len(x) + i, j := 0, n + for i < j { + h := int(uint(i+j) >> 1) + if cmp(x[h], target) < 0 { + i = h + 1 + } else { + j = h + } + } + return i, i < n && cmp(x[i], target) == 0 +} +``` + +- slices.BinarySearchもやってみる +- isNaN関数の実装がいまいちよくわからなかった + - math.IsNaNの説明に書いてあった + - https://cs.opensource.google/go/go/+/refs/tags/go1.24.0:src/math/bits.go;l=35 + - IEEE754でNaNだけx!=xになると定義されているのか + +```Go +func BinarySearch[S ~[]E, E cmp.Ordered](x S, target E) (int, bool) { + n := len(x) + i, j := 0, n + for i < j { + h := int(uint(i+j) >> 1) + if cmp.Less(x[h], target) { + i = h + 1 + } else { + j = h + } + } + return i, i < n && (x[i] == target || (isNaN(x[i]) && isNaN(target))) +} + +func isNaN[T cmp.Ordered](x T) bool { + return x != x +} ``` \ No newline at end of file