最大连续子序列和
问:给定一个长度为n的整数序列,求它的最大连续子序列和
比如:-2, 1, -3, 4, -1, 2, 1, -5, 4的最大连续子序列和是4 + (-1) + 2 + 1 = 6
是否为最值问题?
首先,这是一个求最值的问题,可以考虑使用DP
能否使用DP?
-
原问题可以拆解为子问题
可以求-2结尾的最大连续子序列
求1结尾的最大连续子序列
…
求4结尾的最大连续子序列 -
无后效性
以-5结尾的最大连续子序列
只需要找出它前面的1,以1结尾的最大连续子序列的和dp(6),
如果这个和是负数,则以-5结尾的最大连续子序列和就是-5,前面的dp(6)舍弃不要
如果这个和是正数,则以-5结尾的最大连续子序列和就是dp(6) + -5
这样看来,后面的数完全可以依赖后面的值,也就是具有无后效性。
如何使用DP?
-
确定含义
假设dp(i)是以nums[i]结尾的最大连续子序列和(nums是整个序列) -
边界值
dp(0) = nums[0] -
确定转移方程
如果dp(i -1) <= 0,则dp(i) = nums[i];
如果dp(i - 1) > 0,则dp(i) = dp(i - 1) + nums[i];
最终的解:
max{dp(i)}, i属于[0, nums.length)
逻辑屡明白了,我们尝试写代码
package dynamicProgramming;
public class MaxSubArray {
public static void main(String[] args)
{
int[] nums = {-2, 1, -3, 4, -1, 2, 1, -5, 4};
System.out.println(maxSubArray(nums));
}
public static int maxSubArray(int[] nums)
{
if(nums == null || nums.length == 0) 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(dp[i], max);
}
return max;
}
}
复杂度分析
时间复杂度:O(n)
空间复杂度:需要额外用到一个dp数组,长度为nums的长度,因此,空间复杂度为O(n)
观察代码,我们可以发现,dp(i)的值只需要dp(i - 1)即可,并不需要dp(i - 2)和它以前的值,这么算来,我们并不是真正需要一个数组,只需要一个值记录前一个dp(i - 1)的值即可。
因此,我们可以对上述代码进行空间上的优化:
优化
package dynamicProgramming;
public class MaxSubArray {
public static void main(String[] args)
{
int[] nums = {-2, 1, -3, 4, -1, 2, 1, -5, 4};
System.out.println(maxSubArray(nums));
}
public static int maxSubArray(int[] nums)
{
if(nums == null || nums.length == 0) return 0;
int dp = nums[0];
int max = dp;
for (int i = 1; i < nums.length; i++) {
if(dp <= 0)
{
dp = nums[i];
}else {
dp = dp + nums[i];
}
max = Math.max(dp, max);
}
return max;
}
}
复杂度分析
时间复杂度:O(n)
空间复杂度:O(1)
最长上升子序列
最长上升子序列,也称为最长递增子序列(Longest Increasing Subsequence,LIS)
给定一个无序的整数数组,找到其中最长上升子序列的长度。
输入: [10,2,2,5,1,7,101,18]
输出: 4
解释: 最长的上升子序列是 [2,5,7,101]、[2,5,7,18],它的长度是 4
解题思路
是否为最值问题?
是
能否使用DP?
-
原问题拆解为子问题
求出10结尾的最长上升子序列dp(0) = 1;
求出2结尾的最长上升子序列dp(1) = 1;
求出2结尾的最长上升子序列dp(2) = 1;
求出5结尾的最长上升子序列dp(3) = dp(1) + 1 = dp(2) + 1 = 2;
求出1结尾的最长上升子序列dp(4) = 1;
求出7结尾的最长上升子序列2、5、7,dp(5) = dp(3) + 1;
…
求出18结尾的最长上升子序列
然后,选出值最大的即可:dp(i) = max{dp(i)}, i属于[0, nums.length) -
无后效性
以101结尾的最长上升子序列
前面的已经确定,不会再修改,因此,满足无后效性
DP解题步骤?
-
确定含义
dp(i) 以i结尾的最长上升子序列个数 -
边界值
dp(0) = 1;
所有的dp(i)默认的初始化都是1(自己) -
转移方程
遍历j,j属于[0, i)
-
如果nums[i] > nums[j]
nums[i] 可以拼接到nums[j] 后面,形成一个比dp(j)更长的上升子序列,长度为dp(j) + 1
dp(i) = max{dp(i), dp(j) + 1}; -
如果nums[i] <= nums[j]
nums[i]不能拼接到nums[j]后面,跳过此次遍历(continue)
package dynamicProgramming;
public class LIS {
public static void main(String[] args)
{
int[] nums = {10,2,2,5,1,7,101,18};
System.out.println(lis(nums));
}
static int lis(int[] nums)
{
if (nums == null || nums.length == 0) return 0;
int[] dp = new int[nums.length];
dp[0] = 1;
int max = dp[0];
for (int i = 1; i < nums.length; i++) {
dp[i] = 1;//默认是1
for (int j = 0; j < i; j++) {
if (nums[i] > nums[j]) {
dp[i] = Math.max(dp[i], dp[j] + 1);
}else {
continue;
}
}
max = Math.max(dp[i], max);
}
return max;
}
}
复杂度分析
时间复杂度:O(n^2)
空间复杂度:O(n)