PAT_甲级_1007 Maximum Subsequence Sum

乔梓鑫

题目大意:

给定一个序列,需要求出该序列的最大子序列和及其第一个数字和最后一个数字,如果所有的数字都是负数,输出0和第一个数字及其最后一个数字。

算法思路:

算法思路1(暴力递归):

截屏2020-12-08 下午2.16.29.png
如上图所示,实际上该问题,等价与初始和为0,取得每一个数字作为起点的所有子序列和的最大值中的最大值,如图中所标注的20.直接使用深度优先搜索解决。代码如下:

/*
 * index为当前所选择的数字下标 * sum为[i,index]的子序列和,其中i为起点 
 */
int DFS(const int *nums,int N,int index,int sum){
    // [i,index+1]的最大子序列和
    int current = -0x7fffffff;
    if(index+1<N){
        current = DFS(nums,N,index+1,sum+nums[index+1]);
    }
    return max(sum,current);
}

int process(const int *nums,int N,int index){
    int max_sum = nums[index];
    for (int i = index; i < N; ++i) {
        int next = DFS(nums,N,i,nums[i]);
        max_sum = max(max_sum,next);
    }
    return max_sum;
}

由于该方法会导致一个测试点超时,而且不太好求解左右端点,这里不再给出完整代码,但是其搜索过程值得借鉴,也是接下来优化的依据。

算法思路2(双重循环):

我们枚举每一个左端点i,使用temp记录当前端点i的每一个子序列和,并使用max_sum记录所有子序列和中的最大值,L和R为其左右下标,然后使用j遍历每一个右端点,起始为i,每一次temp累加nums[j],判断temp是否大于max_sum,如果是,就更新max_sum,L,R。最后根据max_sum进行输出即可。代码如下:

int max_sum = nums[0];
int L=0,R=0;
for(int i=0;i<N;++i){
    int temp = 0;// 暂存每一个起点的部分子序列和
    for(int j=i;j<N;++j){
        temp += nums[j];
        if(temp>max_sum){
            max_sum = temp;
            L = i;
            R = j;
        }
    }
}
算法思路3(动态规划):

仔细观察上面的图就知道,在最左边的选择,一定是包含了右边的选择的,也即是说,如果左边的选择在某个时刻选了一个负数,而下一个选择是一个正数,我们完全可以将前面选择的最大子序列和舍弃掉,为什么呢?因为下一个选择的正数,恰好是右边第一个选择的数字,其后面的结果一定比左边的结果大,比如前面2条分支,最开始选择了-2和11,左边的那支在选择-2之后的下一次选择就是11,正好是右边当前的选择,那么左边的选择的数字的最大子序列和=-2+右边选择的数字的最大子序列和,自然就没有必要还要保留之前选择的小于0的子序列和。
我们可以使用一个数组dp[n]保存从起点(不确定,因为会出现舍弃后会从新的起点开始)到当前数字n的最大子序列和,如果前面的子序列和小于0,就舍弃dp[n-1],让dp[n]=nums[n]即可,如果dp[n-1]大于等于0,就保留dp[n]=dp[n-1]+nums[n](因为它可能会让后面的子序列和更大),这样就得到了前一个状态和后一个状态的关系,初始状态为dp[0]=nums[0]。代码如下:

int dp[N];
dp[0] = nums[0];
int max_sum = nums[0];
int L=0,R=0;// 最大子序列的左和右端点
int left=0,right=0;// 当前子序列的左和右端点
for (int j = 1; j < N; ++j) {
    if(dp[j-1]>0){
        // 前面的[0,j-1]的子序列和大于0,就讲其加到当前数字上作为[0,j]的最大子序列和
        dp[j] = dp[j-1] + nums[j];
        right = j;// 只改变了右端点
    } else {
        dp[j] = nums[j];// 改变了左端点和右端点
        left = j;
        right = j;
    }
    if(dp[j]>max_sum){// 记录最大子序列和及其左右端点
        max_sum = dp[j];
        L = left;
        R = right;
    }
}

其过程如下图:
截屏2020-12-08 下午2.36.33.png

提交结果:

截屏2020-12-08 下午2.37.25.png

AC代码1:

#include<cstdio>
#include<algorithm>

using namespace std;

int main(){
    int N;
    scanf("%d",&N);
    int nums[N];
    for (int i = 0; i < N; ++i) {
        scanf("%d",&nums[i]);
    }
    int max_sum = nums[0];
    int L=0,R=0;
    for(int i=0;i<N;++i){
        int temp = 0;// 暂存每一个起点的部分子序列和
        for(int j=i;j<N;++j){
            temp += nums[j];
            if(temp>max_sum){
                max_sum = temp;
                L = i;
                R = j;
            }
        }
    }
    if(max_sum>=0){
        printf("%d %d %d",max_sum,nums[L],nums[R]);
    } else {
        printf("0 %d %d",nums[0],nums[N-1]);
    }
    return 0;
}

AC代码2:

#include<cstdio>
#include<algorithm>

using namespace std;

int main(){
    int N;
    scanf("%d",&N);
    int nums[N];
    for (int i = 0; i < N; ++i) {
        scanf("%d",&nums[i]);
    }
    int dp[N];
    dp[0] = nums[0];
    int max_sum = nums[0];
    int L=0,R=0;// 最大子序列的左和右端点
    int left=0,right=0;// 当前子序列的左和右端点
    for (int j = 1; j < N; ++j) {
        if(dp[j-1]>0){
            // 前面的[0,j-1]的子序列和大于0,就讲其加到当前数字上作为[0,j]的最大子序列和
            dp[j] = dp[j-1] + nums[j];
            right = j;// 只改变了右端点
        } else {
            dp[j] = nums[j];// 改变了左端点和右端点
            left = j;
            right = j;
        }
        if(dp[j]>max_sum){// 记录最大子序列和及其左右端点
            max_sum = dp[j];
            L = left;
            R = right;
        }
    }
    if(max_sum>=0){
        printf("%d %d %d",max_sum,nums[L],nums[R]);
    } else {
        printf("0 %d %d",nums[0],nums[N-1]);
    }
    return 0;
}
阅读 134

主要分享个人学习经验和心得

543 声望
5 粉丝
0 条评论

主要分享个人学习经验和心得

543 声望
5 粉丝
宣传栏