题目:
给定一个整数数组 nums
,找出一个序列中乘积最大的连续子序列(该序列至少包含一个数)。
输入: [2,3,-2,4] 输出: 6 解释: 子数组 [2,3] 有最大乘积 6
输入: [-2,0,-1] 输出: 0 解释: 结果不能为 2, 因为 [-2,-1] 不是子数组
这一题刚开始,我想到的是加法的最大子序列和,于是我用来最巧妙的解法,就是遍历一遍,但是发现乘法和加法还是不太一样,这里前面如果有负数的累积,则在遇到一个负数,就可以变成一个很大的数,我当时并没有细想,于是,我就用了我的笨办法,就是分治;将原问题变成左右两个子问题,分别计算情况然后将左右情况汇总,得到最终情况;
class Solution {
public:
array<int,6> sub_Pro(vector<int> &nums,int lo,int hi)
{
array<int,6> sub_res;
if(lo==hi){
if(nums[lo]>0){
sub_res[0]=nums[lo];
sub_res[1]=INT_MAX;
sub_res[2]=nums[lo];
sub_res[3]=INT_MAX;
}
else if(nums[lo]<0){
sub_res[0]=INT_MIN;
sub_res[1]=nums[lo];
sub_res[2]=INT_MIN;
sub_res[3]=nums[lo];
}
else{
sub_res[0]=INT_MIN;
sub_res[1]=INT_MAX;
sub_res[2]=INT_MIN;
sub_res[3]=INT_MAX;
}
sub_res[4]=nums[lo];
sub_res[5]=nums[lo];
return sub_res;
}
int mi=lo+(hi-lo)/2;
array<int,6> left=sub_Pro(nums,lo,mi);
array<int,6> right=sub_Pro(nums,mi+1,hi);
if(left[4]>0){
sub_res[0]=(right[0]>0)?left[4]*right[0]:left[4];
sub_res[1]=(right[1]>0)?left[1]:left[4]*right[1];
}
else if(left[4]<0){
sub_res[0]=(right[1]>0)?left[0]:left[4]*right[1];
sub_res[1]=(right[0]<0)?left[4]:left[4]*right[0];
}
else {
sub_res[0]=left[0];
sub_res[1]=left[1];
}
if(right[4]>0){
sub_res[2]=(left[2]>0)?right[4]*left[2]:right[4];
sub_res[3]=(left[3]>0)?right[3]:right[4]*left[3];
}
else if(right[4]<0){
sub_res[2]=(left[3]>0)?right[2]:right[4]*left[3];
sub_res[3]=(left[2]<0)?right[4]:right[4]*left[2];
}
else{
sub_res[2]=right[2];
sub_res[3]=right[3];
}
sub_res[4]=left[4]*right[4];
int temp=max(sub_res[0],sub_res[2]);
if(left[2]>0&&right[0]>0) temp=max(temp,left[2]*right[0]);
if(left[3]<0&&right[1]<0) temp=max(temp,left[3]*right[1]);
sub_res[5]=max(left[5],right[5]);
sub_res[5]=max(sub_res[5],sub_res[4]);
sub_res[5]=max(sub_res[5],temp);
return sub_res;
}
int maxProduct(vector<int>& nums) {
array<int,6> res=sub_Pro(nums,0,nums.size()-1);
return res[5];
}
};
说实话,这样判断,真的很费时间去把条件都找对,真的写的很蠢;
时间复杂度为O(klgn),k表示每一次的操作时间;
刚才说到,就是遍历一遍,但是发现乘法和加法还是不太一样,这里前面如果有负数的累积,则在遇到一个负数,就可以变成一个很大的数,我看了别人的解法后,才知道,我其实可以每一次都把遍历到当前位置的最大值和最小值记下来,然后和下一个遍历的值进行计算;这样便于以后的每一个节点的计算。
在序列中计算出以任一个节点为终结点的子序列乘积,取最大值返回即可。
首先不妨尝试以 up(i) 表示第 i 个元素为子序列终结点的最大乘积:
若 nums[i]≥0,则有推导式 up(i)=max(up(i−1)∗nums[i],nums[i])
若 nums[i]<0,则以上推导式不成立。不妨以down(i) 表示第 i 个元素为子序列终结点的最小乘积,则有 up(i)=max(down(i−1)∗nums[i],nums[i])
因为涉及到 down(i) 函数,同理可得:
若 nums[i]≥0,则有推导式 down(i)=min(down(i−1)∗nums[i],nums[i])
若 nums[i]<0,则有推导式 down(i)=min(up(i−1)∗nums[i],nums[i])
因为 up(i),down(i)只与 up(i−1),down(i−1) 存在递推关系,不妨以up,down 表示每个位置上的最大、最小序列乘积。
code:
class Solution {
public:
int maxProduct(vector<int>& nums) {
if(nums.empty()) return 0;
int i, ret, pos, neg, temp;
pos = nums[0];
neg = nums[0];
ret = nums[0];
for(i = 1; i < nums.size(); i++){
temp = pos;
pos = max(nums[i], max(pos * nums[i], neg * nums[i]));
neg = min(nums[i], min(temp * nums[i], neg * nums[i]));
ret = max(pos, ret);
}
return ret;
}
};