数据结构与算法---动态规划---最大连续子序列和、最长上升子序列

本文详细解析了两个经典的动态规划问题——最大连续子序列和与最长上升子序列,通过逐步分解问题并给出具体实例,展示了如何运用动态规划解决这两类问题,并提供了优化的空间复杂度方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最大连续子序列和

问:给定一个长度为n的整数序列,求它的最大连续子序列和
比如:-2, 1, -3, 4, -1, 2, 1, -5, 4的最大连续子序列和是4 + (-1) + 2 + 1 = 6

是否为最值问题?

首先,这是一个求最值的问题,可以考虑使用DP

能否使用DP?
  1. 原问题可以拆解为子问题
    可以求-2结尾的最大连续子序列
    求1结尾的最大连续子序列

    求4结尾的最大连续子序列

  2. 无后效性
    以-5结尾的最大连续子序列
    只需要找出它前面的1,以1结尾的最大连续子序列的和dp(6),
    如果这个和是负数,则以-5结尾的最大连续子序列和就是-5,前面的dp(6)舍弃不要
    如果这个和是正数,则以-5结尾的最大连续子序列和就是dp(6) + -5
    这样看来,后面的数完全可以依赖后面的值,也就是具有无后效性。

如何使用DP?
  1. 确定含义
    假设dp(i)是以nums[i]结尾的最大连续子序列和(nums是整个序列)

  2. 边界值
    dp(0) = nums[0]

  3. 确定转移方程
    如果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

300. 最长上升子序列

解题思路

是否为最值问题?

能否使用DP?
  1. 原问题拆解为子问题
    求出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)

  2. 无后效性
    以101结尾的最长上升子序列
    前面的已经确定,不会再修改,因此,满足无后效性

DP解题步骤?
  1. 确定含义
    dp(i) 以i结尾的最长上升子序列个数

  2. 边界值
    dp(0) = 1;
    所有的dp(i)默认的初始化都是1(自己)

  3. 转移方程
    遍历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)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值