假设一个数组仅仅由1和-1组成,求该数组的和为0的最长子串的长度。
例如:
{1,-1,1,-1,1,1,1} 输出:4.
昨天机试的时候做到这道题,不会做,今天思考一下。
普通的解法
枚举所有的子串,然后得到和为0的最长的长度,复杂度是O(n*n),这个应该会超时,昨天只能保存,不能运行,我猜这样应该不行,需要下面方法。其实看到这题,我想到的是最长回文子串这道题,仔细想想,还是挺像的,该搞一个传送门的。
正确的解法
解法:动态规划,dp0[i]表示以i结尾的加和为0 得到最长子串长度,dp1[i] 表示以i结尾的加和为1的最长子串长度,dp2[i]表示以i结尾的加和为-1 的最长子串长度。于是可以得到下面的递推关系:
上面这段话摘自那个博客,n的平方复杂度不行,那大概就是nlogn或者n,需要考虑dp,然后分析,数组只有1或者-1,然后需要获得结果为0的长度,所以碰到1,我们希望能知道前面和为-1的最长,遇到-1,我们需要知道和为1的最长,所以我们就需要维护到当前位置,以当前位置结尾的和为0,1,-1的最长长度,然后思考怎么递推。显然,1 + 0 = 1, 1 + (-1) = 0,转移到1和0,很容易,转移到-1,怎么处理,什么结果加上1能得到结果是-1,当然是-2,但是我没维护和为2的,这就需要巧妙的思想。
利用当前段的结果为0,加上这段之前的结果为-1,这样就能导出当前段为-1的,这样的思想很巧妙,仔细想想怎么回事,是不是有点kmp,马拉车算法之类的思想。
好了,贴一下那位大神的代码:
#include<iostream>
#include<vector>
using namespace std;
int getLongestLength(int array[],int size){
if(size<=1) return 0;
vector<int>dp0(size);
vector<int>dp1(size);
vector<int>dp2(size);
dp0[0]=0;
if(array[0]==1){
dp1[0]=1;
dp2[0]=0;
}else{
dp1[0]=0;
dp2[0]=1;
}
int result =0;
for(int i =1;i<size;i++){
if(array[i]==1){
dp0[i]=dp2[i-1]==0?0:dp2[i-1]+1;
dp1[i]=dp0[i-1]==0?1:dp0[i-1]+1;
dp2[i]=dp0[i]==0?0:dp2[i-dp0[i]]+dp0[i];
}else{
dp0[i]=dp1[i-1]==0?0:dp1[i-1]+ 1;
dp1[i]=dp0[i]==0?0:dp1[i-dp0[i]]+dp0[0];
dp2[i]=dp0[i-1]==0?0:dp0[i-1]+1;
}
if(result<dp0[i]) result =dp0[i];
}
return result;
}
int main(){
int nums[]={-1,1,-1,1};
cout<<getLongestLength(nums,4);
}
我认为也需要判断一下dp2[i-dp0[i]]的结果不为0,才行。没有画图,不知道解释清楚没有。
还可以这样做,维护前缀和s,i<j, s(j) = s(i)的时候,可以知道i + 1 到j和为0,即可满足要求。这个好像容易理解一些。
百度搜索1,-1,最长字串 可以找到解法。延伸到数组里面只有0和1,比如只有a和b,求最长子串a和b的个数相等之类的。还有什么变形,想出来再记下来吧。
int getLongestString(vector<int> & v) {
int n = v.size();
if(n < 2) return 0;
map<int, int> m;
int s = 0;
int res = 0;
for(int i = 0; i < n; i++) {
s += v[i];
if(s == 0) {
res = max(res, i + 1); continue;
}
if(!m.count(s)) {
m[s] = i;
} else {
int len = i - m[s];
res = max(res, len);
}
}
return res;
}