【算法】数组分段和最大值最小问题
描述
题目:给定一个数组,和一个值k,数组分成k段。要求这k段子段和最大值最小。求出这个值。
解析
(1)暴力搜索
n n-1
M[n, k] = min { max { M[j, k-1], ∑ Ai } }
j=1 i=j
n表示数组长度,k表示数组分成几段。初始化条件:
M[1, k] = A0
n-1
M[n, 1] = ∑ Ai
i=0
(2)动态规划
递归算法拥有指数时间的复杂度,并且会重复计算一些M值。这类的算法一般可以使用动态规划进行优化。使用数组保存一些已经计算得到的值,采用自底向上进行计算
(3)二分查找
此题可以想象成把数据按顺序装入桶中,m即是给定的桶数,问桶的容量至少应该为多少才能恰好把这些数装入k个桶中(按顺序装的)。
首先我们可以知道,桶的容量最少不会小于数组中的最大值,即桶容量的最小值(小于的话,这个数没法装进任何桶中),假设只需要一个桶,那么其容量应该是数组所有元素的和,即桶容量的最大值;其次,桶数量越多,需要的桶的容量就可以越少,即随着桶容量的增加,需要的桶的数量非递增的(二分查找就是利用这点);我们要求的就是在给定的桶数量m的时候,找最小的桶容量就可以把所有的数依次装入k个桶中。在二分查找的过程中,对于当前的桶容量,我们可以计算出需要的最少桶数requiredPainters,如果需要的桶数量大于给定的桶数量k,说明桶容量太小了,只需在后面找对应的最小容量使需要的桶数恰好等于k;如果计算需要的桶数量小于等于k,说明桶容量可能大了(也可能正好是要找的最小桶容量),不管怎样,该桶容量之后的桶容量肯定不用考虑了(肯定大于最小桶容量),这样再次缩小查找的范围,继续循环直到终止,终止时,当前的桶容量既是最小的桶容量。
对于数组 1 2 3 4 5 6 7,假设k=3,最小桶容量为7(要5个桶),最大桶容量为28(一个桶)
第一行表示桶容量,第二行表示需要的桶数,即要求桶数量恰为k的最小桶容量
代码
#include <iostream> #include <vector> #include <algorithm> #include <limits.h> #include <numeric> #include "Solution.h" using namespace std; //===================方案1========================== //递归的暴力搜素算法 , 指数时间的复杂度 int partition(vector<int> nums, int len, int k) { if (k == 1) return accumulate(nums.begin(), nums.begin()+len, 0); if (len == 1) return nums[0]; int best = INT_MAX; for (int j = 1; j <= len; j++) best = min(best, max(partition(nums, j, k-1), accumulate(nums.begin()+j, nums.begin()+len, 0))); return best; } //==================方案二====================== //改进的动态规划算法 //时间复杂度:O(kN2) 空间复杂度:O(kN) int DP_findMax(vector<int> nums, int k){ vector<vector<int>> dp(nums.size()+1,vector<int>(k,0)); vector<int> sums(nums.size()+1,0); for(int i = 1;i<=nums.size();++i){ sums[i] = sums[i-1] + nums[i]; } for (int i = 1; i <= nums.size(); i++) dp[i][1] = sums[i]; for (int i = 1; i <= k; i++) dp[1][i] = nums[0]; for (int i = 2; i <= k; i++) { for (int j = 2; j <= nums.size(); j++) { int best = INT_MAX; for (int p = 1; p <= j; p++) { best = min(best,max(dp[p][i-1],sums[j] - sums[p])); } dp[j][i] = best; } } return dp[nums.size()][k]; } int getRequiredPainters(vector<int> nums, int mid) { int total = 0, numPainters = 1; for (int i = 0; i < nums.size(); i++) { total += nums[i]; if (total > mid) { total = nums[i]; numPainters++; } } return numPainters; } //========================方案三======================== // 二分查找算法 //时间复杂度:O(N log ( ∑ Ai )). 空间复杂度:0(1) int BinarySearch(vector<int> nums, int k) { int max_val = INT_MIN; for (int i = 0; i < nums.size(); i++) { max_val = max(max_val, nums[i]); } int lo = max_val; //数组中最大元素 int hi = accumulate(nums.begin(), nums.end(), 0); //数组求和 while (lo < hi) { //二分查找 int mid = (lo + hi)/2; int requiredPainters = getRequiredPainters(nums, mid); if (requiredPainters <= k) hi = mid; else lo = mid+1; } return lo; } int main() { int a[] = {5,1,4,2,3}; vector<int> va(a, a+5); int k = 3; cout<<partition(va,va.size(),k)<<endl; cout<<DP_findMax(va,k)<<endl; cout<<BinarySearch(va,k)<<endl; return 0; }