classSolution { public: intsplitArray(vector<int>& nums, int k){ int n = nums.size(); vector<int> s(n + 1, 0); // prefix sum for (int i = 0; i < n; i++) s[i+1] = s[i] + nums[i];
vector<vector<int>> memo(n + 1, vector<int>(k + 1, INT_MAX)); function<int(int, int)> dfs = [&](int i, int j) -> int { if (j == 1) return s[i]; if (j > i) return INT_MAX; int& res = memo[i][j]; if (res != INT_MAX) return res; for (int x = 0; x < i; x++) { res = min(res, max(dfs(x, j - 1), s[i] - s[x])); } return res; }; returndfs(n, k); } };
狀態轉移:對於每個 i 和 j ,可以枚舉上一段的結尾位置 x ,並計算出子問題中 最大子陣列和 的最小值、以及當前子陣列的總和,兩者的最大值,在其中取最小值即可。因此,狀態轉移方程為 dp[i][j]=x=0mini−1(max(dp[x][j−1],s[i]−s[x])) 。
使用三重循環來計算所有子問題的答案,最後返回 dp[n][k] 即可。需要注意,在確定 i 後,j 的範圍需要在 [1,min(i,k)] 之間,否則無法將前 i 個數字分成 j 個子陣列。
複雜度分析
時間複雜度:O(n2⋅k) ,其中 n 為陣列 nums 的長度,k 為分割成的子陣列個數。
空間複雜度:O(n⋅k) ,動態規劃表格 dp 需要的空間。
1 2 3 4 5 6 7 8 9 10 11 12
classSolution: defsplitArray(self, nums: List[int], k: int) -> int: n = len(nums) s = list(accumulate(nums, initial=0)) # prefix sum
dp = [[10**10] * (k + 1) for _ inrange(n + 1)] dp[0][0] = 0 for i inrange(1, n + 1): for j inrange(1, min(i, k) + 1): for x inrange(i): dp[i][j] = min(dp[i][j], max(dp[x][j - 1], s[i] - s[x])) return dp[n][k]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
classSolution { public: intsplitArray(vector<int>& nums, int k){ int n = nums.size(); vector<int> s(n + 1, 0); // prefix sum for (int i = 0; i < n; i++) s[i+1] = s[i] + nums[i];
vector<vector<int>> dp(n + 1, vector<int>(k + 1, INT_MAX)); dp[0][0] = 0; for (int i = 1; i <= n; i++) for (int j = 1; j <= min(i, k); j++) for (int x = 0; x < i; x++) dp[i][j] = min(dp[i][j], max(dp[x][j - 1], s[i] - s[x])); return dp[n][k]; } };
方法三:二分搜尋(Binary Search)
雖然我們可以通過動態規劃的方式來解決這個問題,但是我們也能將問題轉換為判斷問題,即「是否可以將陣列分成 k 個子陣列,使得每個子陣列的和都小於等於 d」,如此便可以使用 二分搜尋(Binary Search) 來解決。
定義一個函數 check(d) 來檢查是否可以將陣列分成 k 個子陣列,使得每個子陣列的和都小於等於 d。
利用 cnt 來記錄 子陣列和≤d 的子陣列個數,s 來記錄當前子陣列的和。
遍歷陣列 nums 中的每個元素 x ,如果 s+x>d ,則表示需要開始一個新的子陣列,並將 s 設為 x ;否則,將 s 加上 x 。
classSolution: defsplitArray(self, nums: List[int], k: int) -> int: # 是否可以將 nums 分成 k 個子陣列,使得每個子陣列的和都小於等於 d defcheck(d: int) -> bool: s, cnt = 0, 1 for x in nums: if s + x > d: cnt += 1 s = x else: s += x return cnt <= k left, right = max(nums), sum(nums) while left <= right: mid = (left + right) // 2 if check(mid): right = mid - 1 else: left = mid + 1 return left
classSolution { public: intsplitArray(vector<int>& nums, int k){ auto check = [&](int d) -> bool { int s = 0, cnt = 1; for (int x : nums) { if (s + x > d) { cnt++; s = x; } else { s += x; } } return cnt <= k; };
int left = *max_element(nums.begin(), nums.end()); int right = accumulate(nums.begin(), nums.end(), 0); while (left <= right) { int mid = (left + right) / 2; if (check(mid)) right = mid - 1; else left = mid + 1; } return left; } };
寫在最後
Cover photo is generated by @たろたろ, thanks for their work!