LeetCode-53-最大子数组和

题目

给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
子数组 是数组中的一个连续部分。

示例1

输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。

示例2

输入:nums = [1]
输出:1

示例3

输入:nums = [5,4,-1,7,8]
输出:23

    看到这个问题的时候我首先想到是:实例化一个新数组,用数组元素作为下标,元素出现的个数作为数组值,但是想想又发现不行;因为数组里的值不确定,因此实例化数组的时候数组大小就不确定,所以不能用这种方法。

接下来看一下解题思路:

思路一:

    举例分析数组的规律:
    假设数组为{1, -2, 3, 10,-4, 7, 2, -5}
从头到尾累加数组,第一步加上第一个数字 1 和为 1 ,第二步加上数字 -2,和为 -1 ,注意:由于之前累计的和为 -1,小于0,那么之后和3加为 2 小于 3本身。也就是说从第一个开始的字数组累加的和会小于从 3 开始的累加的和。因此之前的和就可以舍弃。
从三个数字重新开始累加,得到的和为 3, 第四步加 10,得到13,第五步加上 -4和为 9,有与 -4 是负数,因此累加之后的和比之前小,所以需要一个变量把之前的和保存起来,因为有可能这个和就是最后的最大值。
代码示例:

public int FindGreatestSumOfSubArray(int[] array) {
	//临界条件判断
	if(array == null || array.length <= 0) {
		return 0;
	}
	//记录当前累加的和
	int record = 0;
	//用来保存最大值,赋值为0x80000000,可能数组都为负数
	int max = Integer.MIN_VALUE;
	for(int i = 0; i < array.length; i++) {
		if(record <= 0) {
			record = array[i];
		} else {
			record += array[i];
		}
		if(record > max) {
			max = record;
		}
	}
	return max;
}
总结

这里需要注意的是

  1. 这里判断无效输入返回值也是0;
  2. 若是需要判断是无效输入还是子数组的最大和就是0,可以增加一个boolean类型的变量来标记是否是无效输入。
思路二

    使用动态规划实现:
F(i):以array[i]为末尾元素的子数组的和的最大值,子数组的元素的相对位置不变
F(i)=max(F(i-1)+ array[i] , array[i])
record:所有子数组的和的最大值
record=max(record,F(i))

如数组[6, -3, -2, 7, -15, 1, 2, 2]
初始状态:
F(0)= 6
record = 6
i=1:
F(1)=max(F(0) - 3,-3)= max(6 - 3,3)= 3
record= max(F(1),record)= max(3,6)= 6
i=2:
F(2)= max(F(1)- 2,-2)= max(3 - 2,-2)= 1
record= max(F(2),record)= max(1,6)= 6
i=3:
F(3)= max(F(2)+ 7,7)= max(1+7,7)= 8
record= max(F(2),record)= max(8,6)= 8
i=4:
F(4)= max(F(3)-15,-15)= max(8-15,-15)= -7
record=max(F(4),record)= max(-7,8)= 8
以此类推
最终record的值为8

代码示例:

public int FindGreatestSumOfSubArray(int[] array) {
	//临界条件判断
	if(array == null || array.length <= 0) {
		return 0;
	}
	//记录当前所有子数组和的最大值
	int record = array[0];
	//包含array[i]的连续数组最大值
	int max = array[0];
	for(int i = 1; i < array.length; i++) {
		max = Math.max(max + array[i], array[i]);
		record = Math.max(max, record);
	}
	
	return record;
}
动态规划的求解过程

经典动态规划问题(理解「无后效性」)

1.定义状态(子问题)

设计状态思路: 动态规划的思想是通过解决一个一个简单的问题,进而把简单的问题的解组成复杂的问题的解。

例如,示例 1 输入数组是 [-2,1,-3,4,-1,2,1,-5,4] ,我们可以求出以下子问题:

  • 子问题 1:以 −2 结尾的连续子数组的最大和是多少;
  • 子问题 2:以 1 结尾的连续子数组的最大和是多少;
  • 子问题 3:以−3 结尾的连续子数组的最大和是多少;
  • 子问题 4:以 4 结尾的连续子数组的最大和是多少;
  • 子问题 5:以 −1 结尾的连续子数组的最大和是多少;
  • 子问题 6:以 2 结尾的连续子数组的最大和是多少;
  • 子问题 7:以 1 结尾的连续子数组的最大和是多少;
  • 子问题 8:以 −5 结尾的连续子数组的最大和是多少;
  • 子问题 9:以 4 结尾的连续子数组的最大和是多少;

子问题 1: 以 −2 结尾的连续子数组的最大和是多少?
以 −2 结尾的连续子数组是 [-2],因此最大和就是 −2。
子问题 2: 以 1 结尾的连续子数组的最大和是多少?
以 1 结尾的连续子数组有 [-2,1][1] ,其中 [-2,1] 就是在「子问题 1」的后面加上 1 得到。
−2+1=−1<1 ,因此「子问题 2」 的答案是 1。
可以得出: 如果编号为 i 的子问题的结果是负数或者 0 ,那么编号为 i + 1 的子问题就可以把编号为 i 的子问题的结果舍弃掉,这是因为:

  • 一个数 a 加上负数的结果比 a 更小;
  • 一个数 a 加上 0 的结果不会比 a 更大;
  • 而子问题的定义必须以一个数结尾,因此如果子问题 i 的结果是负数或者 0,那么子问题 i + 1 的答案就是以 nums[i] 结尾的那个数。
定义状态(定义子问题)

dp[i]:表示以 nums[i] 结尾连续 子数组的最大和。

说明:「结尾」和「连续」是关键字。

状态转移方程

由于 nums[i] 一定会被选取,并且以 nums[i] 结尾的连续子数组与以 nums[i - 1] 结尾的连续子数组只相差一个元素 nums[i] 。
假设数组 nums 的值全都严格大于 0,那么一定有 dp[i] = dp[i - 1] + nums[i]。
可是 dp[i - 1] 有可能是负数,于是分类讨论:

  • 如果 dp[i - 1] > 0,那么可以把 nums[i] 直接接在 dp[i - 1] 表示的那个数组的后面,得到和更大的连续子数组;
  • 如果 dp[i - 1] <= 0,那么 nums[i] 加上前面的数 dp[i - 1] 以后值不会变大。于是 dp[i] 从数组当前数开始,此时单独的一个 nums[i] 的值,就是 dp[i]。
  • 以上两种情况的最大值就是 dp[i] 的值,写出如下状态转移方程:

dp [ i ] = { dp [ i − 1 ] + n u m s [ i ] , d p [ i − 1 ] > 0 nums [ i ] , d p [ i − 1 ] ≤ 0 \textit{dp}[i] = \begin{cases} \textit{dp}[i-1]+nums[i], & dp[i−1]>0 \\ \textit{nums}[i], & dp[i−1]≤0 \end{cases} dp[i]={dp[i1]+nums[i],nums[i],dp[i1]>0dp[i1]0

​状态转移方程还可以这样写,反正求的是最大值,也不用分类讨论了,就这两种情况,取最大即可,因此还可以写出状态转移方程如下:
dp [ i ] = m a x { n u m s [ i ] , d p [ i − 1 ] + n u m s [ i ] } \textit{dp}[i] = max\{nums[i],dp[i−1]+nums[i]\} dp[i]=max{nums[i],dp[i1]+nums[i]}

思考初始值

dp[0] 根据定义,只有 1 个数,一定以 nums[0] 结尾,因此 dp[0] = nums[0]

思考输出

这里每一小步的状态定义不是题目中的问题的定义,不能直接将最后一个状态返回回去;
应该在所有的状态里面找最大值,并返回

public static int maxSubArray(int[] nums) {
    if (nums == null || nums.length < 1) {
        return 0;
    }

    int[] dp = new int[nums.length];
    dp[0] = nums[0];
    int max = dp[0];

    for (int i = 1; i < nums.length; ++i) {
        if (dp[i - 1] < 0) {
            dp[i] = nums[i];
        } else {
            dp[i] = dp[i - 1] + nums[i];
        }

        max = Math.max(max, dp[i]);
    }

    return max;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值