关于序列求和

最大子段和

前缀和

利用前缀和求解,计算前缀和 s u m sum sum,枚举区间 [ l , r ] [l,r] [l,r],时间复杂度O(n^2)

int maxSubArray(vector<int>& nums) {
        if(nums.empty())
        return 0;
        vector<int> sum(nums.size(),0);
        for(int i=0;i<nums.size();i++)
        if(i!=0)
            sum[i]=sum[i-1]+nums[i];
        else
            sum[i]=nums[i];
        int n = nums.size(),ans = nums[0];
        for(int i=0;i<n;i++)
        {
            for(int j=i;j<n;j++)
            {
                if(i!=0)
                ans=max(ans,sum[j]-sum[i-1]);
                else
                ans=max(ans,sum[j]);
            }
        }
        return ans;
    }

分治算法:

一个序列可以分成两段,那么一个最大子段和有三种可能,分别是在左侧区间,在右侧区间,以及横跨两个区间。如果横跨两个区间,那么需要从中间位置mid连续的向两侧扩展并求和,同时维护最大值。
最终比左端区间,右端区间以及横跨中间部分的子段和即可

int solve(int L,int R,vector<int>& A)
    {
        if(R-L==1)
            return A[L];
        int m = L + (R-L)/2;

        int a = solve(L,m,A);
        int b = solve(m,R,A);
        int Max = max(a,b);

        int tmp = 0,lhs=A[m-1],rhs=A[m];

        for(int i=m-1;i>=L;i--)
        {
            tmp+=A[i];
            lhs = max(lhs,tmp);
        }
        tmp = 0;
        for(int i=m;i<R;i++)
        {
            tmp+=A[i];
            rhs = max(rhs,tmp);
        }
        return max(Max,rhs+lhs);
    }
    int maxSubArray(vector<int>& nums) {
        if(nums.empty())
        return 0;
        int ans = solve(0,nums.size(),nums);
        return ans;
    }

动态规划:

设置状态 d p [ i ] dp[i] dp[i]表示枚举到当前位置时的最大子段和,那么可以选择的转移状态有两种,一种是将第i个数加到前面的子段和,另一种是第i个数自己成为一个子段和。最终得到的 d p [ n ] dp[n] dp[n]不是最优解,最优解是计算的过程中产生的。

int maxSubArray(vector<int>& nums) {
        if(nums.empty())
        return 0;
        vector<int> dp(nums.size(),0);
        int ans = nums.front();
        for(int i=0;i<nums.size();i++)
        {
            if(i!=0)
            {
                dp[i]=max(dp[i-1]+nums[i],nums[i]);
                ans=max(ans,dp[i]);
            }
            else
            {
                dp[i] = nums[0];
            }
        }
        return ans;
    }

这里状态转移方程每次查找的状态都是上一个状态,即 d p [ i − 1 ] + n u m s [ i ] dp[i-1]+nums[i] dp[i1]+nums[i],所以可以使用常数维持之前累加的求和项

    
   int maxSubArray(vector<int>& nums) {
       if(nums.empty())
       return 0;
       int res = 0;
       int ans = nums.front();
       for(int i=0;i<nums.size();i++)
       {
           if(i!=0)
           {
               res = max(res+nums[i],nums[i]);
               ans=max(ans,res);
           }
           else
           {
               res = nums[0];
           }
       }
       return ans;
   }

观察程序可以发现,每次都是进行累加,并判断累加到 r e s + n u m s [ i ] res+nums[i] res+nums[i] n u m s [ i ] nums[i] nums[i]比较,所以可以直接求累加和,如果当前累加和res大于0,那么直接继续累加。如果当前求和res小于0,无论 n u m s [ i ] nums[i] nums[i]是否小于0都不直接更新为 n u m s [ i ] nums[i] nums[i]
计算的同时要维护当前的最大值

int maxSubArray(vector<int>& nums) {
        if(nums.empty())
        return 0;
        int res = 0;
        int ans = nums.front();
        for(int i=0;i<nums.size();i++)
        {
            if(res>0)
                res+=nums[i];
            else
                res=nums[i];
            ans=max(res,ans);
        }
        return ans;
    }

leetcode152 乘积最大子序列:

类似于上面的最大子段和的思想

需要维持两个变量分别为res1与res2
其中res1保存当前最大子段和,res2保存当前的最小子段和
如果遇到负数,那么继续相乘,最大子段和会和最小子段和调换

int maxProduct(vector<int>& nums) 
    {
        if(nums.empty())
        return 0;
        int res1=1,res2=1 ;
        int ans = nums[0];
        for(int i=0;i<nums.size();i++)
        {
            if(nums[i]<0)
                swap(res1,res2);
            res1=max(res1*nums[i],nums[i]);
            res2=min(res2*nums[i],nums[i]);
            ans=max(res1,ans);
        }
        return ans;
    }

cf Edu 63 D. Beautiful Array
题目和解答
给你n个整数a[i],然后给你一个数x,你可以将其中某个连续的子段乘上x,只能乘一次,现在让你求这个序列的最大字段和。

同样是求解最大子段和的变种问题,在计算很容易相当转移过程:
即,当前的连续序列是否需要乘以x
设置状态 d p [ i ] [ 0 ] dp[i][0] dp[i][0]表示前i个序列求子序列最大的过程中,从未乘以过x(1)
设置状态 d p [ i ] [ 1 ] dp[i][1] dp[i][1]表示前i个序列求子序列最大的过程中,乘以过x(2)

如果仅设置如上面两个状态方程,是有问题的

因为(2)的状态转移方程在计算第i个状态时会有两钟状态转移,类似于求解最大子段和,即

第一,对前面已经乘以x的连续子序列,继续乘以x d p [ i − 1 ] [ 1 ] + a [ i ] ∗ x dp[i-1][1]+a[i]*x dp[i1][1]+a[i]x,这里要求第 i − 1 i-1 i1个状态也是乘以过x的;(3)

第二,仅对当前的 a [ i ] a[i] a[i]乘以x,即 d p [ i − 1 ] [ 0 ] + a [ i ] ∗ x dp[i-1][0]+a[i]*x dp[i1][0]+a[i]x(3)

在(3)中存在这样一个问题,可能在第 i − 1 i-1 i1个状态之前有某个连续的子序列乘以过x,但是在第 i − 1 i-1 i1个状态断了,即 a [ i − 1 ] a[i-1] a[i1]没有乘以过x,因为x只能乘1次,所以这样处理是有问题的

解决方案?

将问题分解,多加一个状态
设置 d p [ i ] [ 0 ] dp[i][0] dp[i][0]表示不乘以x的最大子段和
设置 d p [ i ] [ 1 ] dp[i][1] dp[i][1]表示,在第i个状态之前乘以过x,但是第i个状态断了
设置 d p [ i ] [ 2 ] dp[i][2] dp[i][2]表示,表示第i个状态正在乘以x

那么状态转移方程有如下
d p [ i ] [ 0 ] = m a x ( d p [ i − 1 ] [ 0 ] + a [ i ] , a [ i ] ) ; dp[i][0] = max(dp[i - 1][0] + a[i], a[i]); dp[i][0]=max(dp[i1][0]+a[i],a[i]);

d p [ i ] [ 2 ] = m a x ( d p [ i − 1 ] [ 0 ] + a [ i ] ∗ x , m a x ( d p [ i − 1 ] [ 2 ] + a [ i ] ∗ x , a [ i ] ∗ x ) ) ; dp[i][2] = max(dp[i - 1][0] + a[i] * x, max(dp[i - 1][2] + a[i] * x, a[i] * x)); dp[i][2]=max(dp[i1][0]+a[i]x,max(dp[i1][2]+a[i]x,a[i]x));

d p [ i ] [ 1 ] = m a x ( d p [ i − 1 ] [ 1 ] + a [ i ] , d p [ i − 1 ] [ 2 ] + a [ i ] ) ; dp[i][1] = max(dp[i - 1][1] + a[i], dp[i - 1][2] + a[i]); dp[i][1]=max(dp[i1][1]+a[i],dp[i1][2]+a[i]);

代码见上面的连接

循环子段和:

记得当时大二还是大一,参加校赛的时候做过,记忆犹新

给你n个数,首尾相连,然呢求这个环的最大子段和

其实这个问题可以借鉴一下那个分治的思想,分治算法的意义不仅仅是能找到一个logn时间复杂的解决方案,而是能把问题分解开,至于是不是分解成相似的子问题无所谓。就像上面那道题,如果能把问题分解,且做到不漏解,那么解决这些子问题后就能得到答案。

此题可以将计算方案分解成两个,即最大子段和所在的位置分别是在[1,n]之间,与横跨首位两段的区间

第一个子问题很好搞,就是裸的最大子段和。

第二个问题,如果最大子段和的区横跨首位两端,说明中间剩下的那部分肯定是最小子段和,如果把中间这些数加进去,一定会使最大子段和减小。那么,只需要求解这个序列的最小子段和,并用这n个数的总和减去这个最小子段和就是跨区间的结果了。

两个子问题取最大值即可

代码没必要写了

to be continue~

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值