Find the contiguous subarray within an array (containing at least one number) which has the largest product.
For example, given the array [2,3,-2,4],
the contiguous subarray [2,3] has the largest product = 6.
这道题应当算是第一个独立做出来的DP了~撒花
经历了MLE、TLE和各种WA,终于找到了几乎是最优的解答方式(和Gas Station有点类似)
首先考虑这是一个DP问题。
因为首先,一个区间的最大值可以归结为其子区间的最大值问题,当前做了决策之后对后续不会产生影响(后续可以利用当前计算的结果)
一开始想是dp[i][j]与dp[i+1][j-1]之间的关系,随后想到如何处理负数的问题。
于是开启两个dp数组,一个存最大一个存最小,判断最大的时候也需要判断最小*当前值【因为有可能出现负的】
于是一开始写出的类似
dp1[i][j]=max{dp1[i+1][j-]*nums[j],dp2[i+1][j-1]}
随后问题来了,为何元素的扩展状态是2个方向??
之前回文问题可以这么做是因为回文要求两端相等,而这个问题则需要仔细考虑。
于是写出:
利用dp[i][j]记录从i~j位置乘积的最大值(正数)
dp[i][j]=max{dp[i][j-1]*nums[j],nums[j]}
意思也就是,第j位的元素等于前i~j-1位乘积最大值乘以当前值,如果比当前值大,就更新dp[i][j],否则就继续从j开始计算,dp[i][j]更新为nums[j]
如果遇到负数,那么这个数在将来有可能成为最大的数,所以负数需要额外开辟一个dp数组,专门用于存储最小值,这些最小值(负数)是“有潜力”成为最大值的数。
所以dp方程相应变为
dp[i][j]=max{dp[i][j-1]*nums[j],nums[j],dp2[i][j-1]*nums[j]}
dp2[i][j]=min{dp[i][j-1]*nums[j],nums[j],dp2[i][j-1]*nums[j]}
到这里终于正确了,不过一运行MLE。。。说明空间申请了太多了,看用例是给了一个大数组,这样n*n肯定是要爆掉的。
于是考虑可能并不需要数组就可以记录?
可以看到dp方程里面,i的值始终未变化,而产生变化的只有j。这说明dp[i][j]并不依赖于i点的状态,而是依赖于j-1点的状态。
所以直接考虑的是每次遍历的时候拿一个maxx和minn分别记录从i到当前位置的最大值。
因为原循环中也仅仅是利用了历史状态,记忆化搜索而已,但这些搜索的结果我们并不是完全都要,只要记录之前记录到的最大值并对它进行更新就好了
// for(int i=0;i<n;i++){
// int maxx=0,minn=0;
// for(int j=i;j<n;j++){
int maxv=maxx,minv=minn;
maxx=maxNum(maxv*nums[j],nums[j],minv*nums[j]);
minn=minNum(maxv*nums[j],nums[j],minv*nums[j]);
}
}
这样确实直接省了2个数组,时间复杂度还是O(n^2) 但是空间复杂度由O(n^2)降到了O(1);
very nice;
跑一遍,TLE。。。
说明了什么,说明了二重循环也是没有必要的…
仔细想想,在i=0的时候,其实已经计算了从0开始,到n-1的最大值。
那为何还要计算从非第一个起点开始的呢?不还是遍历整个数组吗?
以此为启发,想到删除里层循环,只保留一个maxx和minn以及记录中间结果的val。
一次遍历,即可解决
class Solution {
public:
int maxProduct(vector<int>& nums) {
int n=nums.size();
if(n==1) return nums[0];
//选取第i位为结尾的子串作为最大值
// vector<int> dp1(n,0);
// vector<int> dp2(n,0);
// int val=INT_MIN;
// for(int i=0;i<n;i++){
// dp1[i]=maxNum(dp1[i-1]*nums[i],nums[i],dp2[i-1]*nums[i]);
// dp2[i]=minNum(dp1[i-1]*nums[i],nums[i],dp2[i-1]*nums[i]);
// }
// for(int i=0;i<n;i++)
// if(dp1[i]>val) val=dp1[i];
//如果是以i开始j结束为标记,需要n*n大小的标记
// vector<vector<int> > dp1;
// vector<vector<int> > dp2;
// dp1.resize(n,vector<int>(n));
// dp2.resize(n,vector<int>(n));
// for(int i=0;i<n;i++){
// for(int j=0;j<n;j++){
// dp1[i][j]=dp2[i][j]=0;
// }
// }
//最优解法
int val=nums[0],maxx=nums[0],minn=nums[0];
for(int j=1;j<n;j++){
int maxv=maxx,minv=minn;
maxx=maxNum(maxv*nums[j],nums[j],minv*nums[j]);
minn=minNum(maxv*nums[j],nums[j],minv*nums[j]);
val=maxx>val?maxx:val;
}
// int val=INT_MIN;
// for(int i=0;i<n;i++){
// int maxx=0,minn=0;
// for(int j=i;j<n;j++){
// //超时
// int maxv=maxx,minv=minn;
// maxx=maxNum(maxv*nums[j],nums[j],minv*nums[j]);
// minn=minNum(maxv*nums[j],nums[j],minv*nums[j]);
// // 超内存
// 记录每一行每一列的的dp[i][j]表示以i开头j结尾的子串的乘积
// // dp1[i][j]=maxNum(dp1[i][j-1]*nums[j],nums[j],dp2[i][j-1]*nums[j]);
// // dp2[i][j]=minNum(dp1[i][j-1]*nums[j],nums[j],dp2[i][j-1]*nums[j]);
// val=maxx>val?maxx:val;
// // if(nums[i] * nums[j]>0){
// // //这种模拟最长公共子序列的有可能是不连续的
// // dp1[i][j]=dp1[i+1][j-1]*nums[i]*nums[j];
// // dp2[i][j]=dp2[i+1][j-1]*nums[i]*nums[j];
// // }
// // else if(nums[i] * nums[j]<0){
// // dp1[i][j]=max(dp1[i+1][j-1]*nums[i],dp2[i+1][j-1]*nums[j]);
// // dp2[i][j]=min(dp1[i+1][j-1]*nums[j],dp2[i+1][j-1]*nums[i]);
// // }
// // else if(nums[i]<0 && nums[j]>0){
// // dp1[i][j]=max(dp2[i+1][j-1]*nums[i],dp1[i+1][j-1]*nums[j]);
// // dp2[i][j]=min(dp2[i+1][j-1]*nums[j],dp1[i+1][j-1]*nums[i]);
// // }
// // else if(nums[i]*nums[j]==0){
// // }
// }
// }
// output(dp1);
return val;
// return getMax(dp1);
}
int maxNum(int a,int b,int c){
int val=a>b?a:b;
return val>c?val:c;
}
int minNum(int a,int b,int c){
int val=a<b?a:b;
return val<c?val:c;
}
int getMax(vector<vector<int>> &result){
int val=INT_MIN;
for(int i=0;i<result.size();i++){
for(int j=0;j<result[0].size();j++){
if(result[i][j]>val)
val=result[i][j];
}
}
return val;
}
void output(vector<vector<int>> &result){
for(int i=0;i<result.size();i++){
for(int j=0;j<result[i].size();j++){
printf("%d ",result[i][j]);
}
printf("\n");
}
}
};