題目描述
給定一個非負整數數組和一個整數 m,你需要將這個數組分成 m 個非空的連續子數組。設計一個算法使得這 m 個子數組各自和的最大值最小。
注意:
數組長度 n 滿足以下條件:
1 ≤ n ≤ 1000
1 ≤ m ≤ min(50, n)
示例:
輸入:
nums = [7,2,5,10,8]
m = 2
輸出:
18
解釋:
一共有四種方法將nums分割爲2個子數組。
其中最好的方式是將其分爲[7,2,5] 和 [10,8],
因爲此時這兩個子數組各自的和的最大值爲18,在所有情況中最小。
解決方案
class Solution {
fun splitArray(nums: IntArray, m: Int): Int {
// 由於是非負整數組,所以最小值爲1,用Long保存的原因是防止數字越界
var left = 1L
// 最大值爲總和+1 的作用是保證結果在left和right範圍內,否則left可能會一直擴大,導致ans無法賦值
var right = 1L
nums.forEach {
right += it
}
// 二分求解
var ans = 0L
while (left < right) {
val middle = (left + right) / 2
if (canSplit(nums, m, middle)) {
// 可行,記錄答案,縮小數組總和
ans = middle
right = middle
} else {
// 不可行,嘗試更大的數組總和
left = middle + 1
}
}
return ans.toInt()
}
/**
* 判斷nums能否分成不大於maxCount組,且每組總和不大於maxSum的數組
*/
private fun canSplit(nums: IntArray, maxCount: Int, maxSum: Long): Boolean {
// 記錄當前分組的總和
var sum = 0L
// 記錄當前已分組的數量
var count = 0
// 嘗試分組
for (num in nums) {
// 如果單個數字大於maxSum,無法分割出和小於maxSum的一組
if (num > maxSum) return false
// 如果和超過了maxSum,另起一組
if (sum + num > maxSum) {
count++
sum = num.toLong()
} else {
// 貪心策略,一組中放的數字儘可能多
sum += num
}
}
// count < maxCount 而不是 count <= maxCount 的原因是:分割完成後,sum中的數字是一組,沒計入count中
return count < maxCount
}
}
解題思路
1.本題的單調關係關係比較隱蔽,單調關係是:數組和的最大值越小,分組數越大。數組和的範圍是可以確定的。
2.根據單調關係,將題目轉換爲:當子數組的和最大爲maxSum時,至少需要分多少組,能否在最多m組的限制範圍內完成分割。
3.由於是非空非負整數數組,最小值可設爲1;數組和的最大值設置爲nums的總和+1,+1的作用是保證結果在最小值和最大值範圍內。採用Long保存中間值的作用是防止數組越界。
4.canSplit中用到了貪心策略,在子數組中儘可能多的放置元素,直到放不下,另起一組。
5.如果當前值滿足分割條件,記錄當前值,利用二分法,縮小子數組總和。否則擴大子數組總和,直到找到最佳答案。
推薦閱讀:
我的LeetCode題解,使用kotlin語言:
注:本文排版仿照LeetCode官方公衆號,侵刪