最大子序列和——简单问题的不简单之处

题目:

给定一整型数列{a1,a2...,an},找出连续非空子串{ax,ax+1,...,ay},使得该子序列的和最大,其中,1<=x<=y<=n。

最终代码:

 1 import java.util.*;
 2 import java.math.*;
 3 public class Main {
 4 
 5     public static void main(String[] args) {
 6         Solution s = new Solution();
 7         Scanner sc = new Scanner(System.in);
 8         int N = sc.nextInt();
 9         int[] result = new int[N];
10         for(int i = 0; i < N; i++) {
11             int length = sc.nextInt();
12             int[] array = new int[length]; 
13             for(int j = 0; j < length; j++) {
14                 array[j] = sc.nextInt();
15             }
16             result[i] = s.getResult(array, length);
17         }
18         sc.close();
19         for(int i : result) {
20             System.out.println(i);
21         }
22     }
23 
24 }
25 class Solution {
26     public int getResult(int[] array, int length) {
27         int max = array[0];
28         
29         for(int i : array) {
30             max = max >= i ? max : i;
31         }
32         if(max < 0)
33             return max;
34         else
35             max = 0;
36         
37         int sum = 0;
38         for(int i = 0; i < length; i++) {
39             sum += array[i];
40             max = sum > max ? sum : max;
41             if(sum <= 0) 
42                 sum = 0;
43         }
44         return max;
45     }
46 }

首先,这确实不是一道很难的题目。相信很多人第一次看见都在《数据结构和算法分析》这本书里面。

当然,题目似乎在不同的地方有点不同,比如某个网站上的这道题说如果输入都是负数就输出0(这实质上倒是简化了程序,但不是重点)。

我主要想通过这篇博客记录的是一种动态规划的思想过程。我是在动态规划问题里面找到这个问题的,并且一时间没有想起来这道问题的那个最优解,便开始用动态规划的思想解决这道题目,反而出现了不小的问题。到现在,我依然只是在理解动态规划的路上,所以文章必然有错误。谬误之处,望请海涵。

众所周知的是动态规划最困难的点是在明确一个具体问题的状态,和状态转移方程。这道题目我一开始一直认为状态以下两者之一:

1,以当前数字结尾的序列的最大和

2,是在当前数字之前的序列的最大和。

这里所谓的当前数字是指,比如用一个i循环这个数组,i循环到的地方。代码需要根据之前的状态和Array[i]的大小来确定决策。而当i循环到输入序列的末尾,自然求出来的就是问题要求的这个序列的最大子序列和。

然而我还没开始编写程序就感觉十分怪异,因为如果按照以上两种状态的设置方式都无法想到有一个明确而高效的状态转移方程来求出最优解。(事实上动态规划的问题如果自己都没有想通整个过程做出来很大几率是错的)

然后我查了下,想起了这道题的出处,用记忆中的算法很快解决了这一个问题,

那么重点来了,我一开始的思路哪里错了呢?通过最佳做法倒过来分析,我一开始的想法基于:状态就是目前这个数字i之前或以之结尾的的最大子序列和。(可是事实上都有很多问题)。

最后看来:

1,状态是:从某个起始节点j到当前i的序列和。

2,决策是:确定起始节点j的位置。

3,状态转移方程是:如果当前序列的序列和小于0了,就“断开”,从i + 1重新开始,否则当前序列直接加上Array[i]这个数字。

4,阶段是:没有遇到“断点”的一个序列。

断开是指:如果当前的序列和小于0,可以认定它是一个“累赘”,也就是说,把它连接到后面的序列所产生的新的序列和的值,不可能比后面的序列自己的序列和还大,因为这是一个负数。

这么做的话,即使在i的遍历中遇到负数,也无需多虑,因为max只保持最大值。而“断开”的做法,能保证j,i(j <= i)只会在需要考察的点出现,不会去在不可能出现max的地方寻找。

这道题真不是难题,可是分析一下可以得到很多。我用动态规划已开始没有分析出来,原因是我先入为主地认为状态一定和所求的东西有关,所以我先认定的状态都是max,而不是sum。而实际上这道题的一个点就在所求的东西和需要的状态不一致,也不是简单的到了最后比较一下大小就可以得到的。是在用状态约束范围,在一定范围里面用一个Max来进行比较。

算是我学习动态规划路上的一点思考吧,肯定还有不完善的地方,我以后再补上。

原文地址:https://www.cnblogs.com/dsj2016/p/5244173.html