关于算法-数据结构:PAT甲级1007-Maximum-Subsequence-Sum

5次阅读

共计 2712 个字符,预计需要花费 7 分钟才能阅读完成。

题目粗心:

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

算法思路:

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


如上图所示,实际上该问题,等价与初始和为 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;
    }
}

其过程如下图:

提交后果:

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;
}
正文完
 0