算法题:数组划分为和最相近2个子数组
将一个数组划分为2个子数组,要求子数组的和尽可能接近。
思路
- 将数组排序,并计算出整个数组和。
- 计算区间和。从最小元素开始计算一个连续区间的和,当和小于数组和一半时,区间右边界右移,当和大于数组和一半时,区间左边界右移。当区间和最接近数组和的一半时记录左右边界的位置和区间和。当区间和等于数组一半时,此时区间中的元素即为一个划分方案中的子数组,直接返回。退出条件为区间左边界小于区间右边界,且右边界不超过数组最后一个元素。
分析
从最小的元素开始计算区间和,当和小于中值右边界右移。由于是从最小的元素开始的,可以知道右边界右移后区间后肯定是增加的。当区间和大于中值时,将左边界右移,也就是从区间中剔除一个较小的数,以最小的步长向中值靠拢。
时间复杂度 。空间复杂度O(n)。
代码
vector<vector<int> > Leetcode::partitionNearestSumSubArr(const vector<int> &arr)
{
if(arr.size()<2){
return vector<vector<int> >({arr,vector<int>()});
}
vector<int> sortedArr=arr;
SortAlgorithmn::quickSort(sortedArr);
int sum=0;
for(const auto& i : sortedArr) sum+=i;
auto lClose=sortedArr.begin();
auto rOpen=sortedArr.begin()+1;
auto minDist=std::numeric_limits<int>::max();
int curSum=*sortedArr.begin();
for(auto i=sortedArr.begin(),j=sortedArr.begin()+1;i<j && j<=sortedArr.end();){
int curDist=curSum*2-sum;
if(abs(curDist)<minDist){
lClose=i;
rOpen=j;
minDist=abs(curDist);
if(minDist == 0) break;
}
if(curDist>0){
curSum-=(*i);
++i;
}else if(curDist<0){
curSum+=(*j);
++j;
}else{
lClose=i;
rOpen=j;
break;
}
}
vector<int> first;
first.assign(lClose,rOpen);
sortedArr.erase(lClose,rOpen);;
return vector<vector<int> >({first,sortedArr});
}
排序的函数可以用stl中的算法。
注意左右区间是左闭右开。
对于中值的处理不要用总和除以2,直接用区间和乘以2减总和来表示与中值的距离。
完整代码见github: https://github.com/junbujianwpl/LeetcodePro.git
总结
对于最接近的问题,可以考虑下用夹逼法。当然夹逼法是需要排序的。
对于不连续的区间,如头加尾,可以求其互补区间。