【恋上数据结构(1),尚硅谷Java全套视频

本文详细介绍了动态规划在求解最大连续子序列和(如-2,1,-3,4,-1,2,1,-5,4)和最长上升子序列(如[10,2,2,5,1,7,101,18])问题中的应用。涉及状态定义、初始状态设置、状态转移方程和递推过程,以及常见问题如无后效性的理解。并提供了代码实现和优化策略。
摘要由CSDN通过智能技术生成

③ 递推(自底向上

动态规划中的 “动态” 可以理解为是 “会变化的状态”

  • 定义状态状态是原问题、子问题的解

    比如定义 d p ( i ) dp(i) dp(i) 的含义

  • 设置初始状态边界

    比如设置 d p ( 0 ) dp(0) dp(0) 的值

  • 确定状态转移方程

    比如确定 d p ( i ) dp(i) dp(i) 和 d p ( i − 1 ) dp(i - 1) dp(i−1) 的关系

动态规划的一些概念


维基百科的解释

Dynamic Programming is a method for solving a complex problem by breaking it down into a collection of simpler subproblems, solving each of those subproblems just once, and storing their solutions.

① 将复杂的原问题拆解成若干个简单的子问题

② 每个子问题仅仅解决1次,并保存它们的解

③ 最后推导出原问题的解

可以用动态规划来解决的问题,通常具备2个特点:

  • 最优子结构最优化原理):通过求解子问题的最优解,可以获得原问题的最优解

  • 无后效性

    某阶段的状态一旦确定,则此后过程的演变不再受此前各状态及决策的影响(未来与过去无关

    在推导后面阶段的状态时,只关心前面阶段的具体状态值,不关心这个状态是怎么一步步推导出来的

有后效性与无后效性


首先了解一下什么是有后效性

在这里插入图片描述

然后再去理解什么是无后效性

在这里插入图片描述

练习2:最大连续子序列和

===============================================================================

题目:给定一个长度为 n 的整数序列,求它的最大连续子序列和

  • 比如 -2、1、-3、4、-1、2、1、-5、4 的最大连续子序列和是 4 + (-1) + 2 + 1 = 6;

    nums[0] -2 结尾的最大连续子序列是 -2,所以 d p ( 0 ) = − 2 dp(0) = -2 dp(0)=−2

    nums[1] 1 结尾的最大连续子序列是 1,所以 d p ( 1 ) = 1 dp(1) = 1 dp(1)=1

    nums[2] -3 结尾的最大连续子序列是 1、-3,所以 d p ( 2 ) = d p ( 1 ) + ( − 3 ) = − 2 dp(2) = dp(1) + (-3) = -2 dp(2)=dp(1)+(−3)=−2

    nums[3] 4 结尾的最大连续子序列是 4,所以 d p ( 3 ) = 4 dp(3) = 4 dp(3)=4

    nums[4] -1 结尾的最大连续子序列是 4、-1,所以 d p ( 4 ) = d p ( 3 ) + ( − 1 ) = 3 dp(4) = dp(3) + (-1) = 3 dp(4)=dp(3)+(−1)=3

    nums[5] 2 结尾的最大连续子序列是 4、-1、2,所以 d p ( 5 ) = d p ( 4 ) + 2 = 5 dp(5) = dp(4) + 2 = 5 dp(5)=dp(4)+2=5

    nums[6] 1 结尾的最大连续子序列是 4、-1、2、1,所以 d p ( 6 ) = d p ( 5 ) + 1 = 6 dp(6) = dp(5) + 1 = 6 dp(6)=dp(5)+1=6

    nums[7] -5 结尾的最大连续子序列是 4、-1、2、1、-5,所以 d p ( 7 ) = d p ( 6 ) + ( − 5 ) = 1 dp(7) = dp(6) + (-5) = 1 dp(7)=dp(6)+(−5)=1

    nums[8] 4 结尾的最大连续子序列是 4、-1、2、1、-5、4,所以 d p ( 8 ) = d p ( 7 ) + 4 = 5 dp(8) = dp(7) + 4 = 5 dp(8)=dp(7)+4=5

状态转移方程和初始状态

状态转移方程

  • 如果 d p ( i – 1 ) ≤ 0 dp(i – 1) ≤ 0 dp(i–1)≤0,那么 d p ( i ) = n u m s [ i ] dp(i) = nums[i] dp(i)=nums[i]

  • 如果 d p ( i – 1 ) > 0 dp(i – 1) > 0 dp(i–1)>0,那么 d p ( i ) = d p ( i – 1 ) + n u m s [ i ] dp(i) = dp(i – 1) + nums[i] dp(i)=dp(i–1)+nums[i]

初始状态

  • d p ( 0 ) dp(0) dp(0) 的值是 n u m s [ 0 ] nums[0] nums[0]

最终的解

  • 最大连续子序列和是所有 d p ( i ) dp(i) dp(i) 中的最大值 m a x { d p ( i ) } , i ∈ [ 0 , n u m s . l e n g t h ) max \{ dp(i) \},i ∈ [0, nums.length) max{dp(i)},i∈[0,nums.length)

最大连续子序列和 – 实现



static int maxSubArray(int[] nums) {

	if (nums == null || nums.length == 0) return 0;

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

	int max = dp[0] = nums[0];

	for (int i = 1; i < dp.length; i++) {

		if (dp[i - 1] > 0) {

			dp[i] = dp[i - 1] + nums[i];

		} else {

			dp[i] = nums[i];

		}

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

	}

	return max;

} 

最大连续子序列和 – 优化



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 = dp + nums[i];

		} else {

			dp = nums[i];

		}

		max = Math.max(max, dp);

	}

	return max;

} 

练习3:最长上升子序列(LIS)

===================================================================================

最长上升子序列最长递增子序列,Longest Increasing Subsequence,LIS)

leetcode_300_最长上升子序列: https://leetcode-cn.com/problems/longest-increasing-subsequence/

题目:给定一个无序的整数序列,求出它最长上升子序列的长度(要求严格上升)

  • 比如 [10, 2, 2, 5, 1, 7, 101, 18] 的最长上升子序列是 [2, 5, 7, 101]、[2, 5, 7, 18],长度是 4

假设数组是 nums, [10, 2, 2, 5, 1, 7, 101, 18]

  • d p ( i ) dp(i) dp(i) 是以 n u m s [ i ] nums[i] nums[i] 结尾的最长上升子序列的长度, i ∈ [ 0 , n u m s . l e n g t h ) i ∈ [0, nums.length) i∈[0,nums.length)

    nums[0] 10 结尾的最长上升子序列是 10,所以 d p ( 0 ) = 1 dp(0) = 1 dp(0)=1

    nums[1] 2 结尾的最长上升子序列是 2,所以 d p ( 1 ) = 1 dp(1) = 1 dp(1)=1

    nums[2] 2 结尾的最长上升子序列是 2,所以 d p ( 2 ) = 1 dp(2) = 1 dp(2)=1

    nums[3] 5 结尾的最长上升子序列是 25,所以 d p ( 3 ) = d p ( 1 ) + 1 = d p ( 2 ) + 1 = 2 dp(3) = dp(1) + 1 = dp(2) + 1 = 2 dp(3)=dp(1)+1=dp(2)+1=2

    nums[4] 1 结尾的最长上升子序列是 1,所以 d p ( 4 ) = 1 dp(4) = 1 dp(4)=1

    nums[5] 7 结尾的最长上升子序列是 257,所以 d p ( 5 ) = d p ( 3 ) + 1 = 3 dp(5) = dp(3) + 1 = 3 dp(5)=dp(3)+1=3

    nums[6] 101 结尾的最长上升子序列是 257101,所以 d p ( 6 ) = d p ( 5 ) + 1 = 4 dp(6) = dp(5) + 1 = 4 dp(6)=dp(5)+1=4

    nums[7] 18 结尾的最长上升子序列是 25718,所以 d p ( 7 ) = d p ( 5 ) + 1 = 4 dp(7) = dp(5) + 1 = 4 dp(7)=dp(5)+1=4

状态方程

在这里插入图片描述

状态的初始值

  • d p ( 0 ) = 1 dp(0) = 1 dp(0)=1

  • 所有的 d p ( i ) dp(i) dp(i) 默认都初始化为 1

最终的解

  • 最长上升子序列的长度是所有 d p ( i ) dp(i) dp(i) 中的最大值 m a x { d p ( i ) } , i ∈ [ 0 , n u m s . l e n g t h ) max \{ dp(i) \},i ∈ [0, nums.length) max{dp(i)},i∈[0,nums.length)

最长上升子序列 – 实现


时间复杂度:O(n2),空间复杂度:O(n)


static int lengthOfLIS(int[] nums) {

  	if (nums == null || nums.length == 0) return 0;

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

  	int max = dp[0] = 1; // 只有一个元素则长度为1

  	for (int i = 1; i < dp.length; i++) {

  		dp[i] = 1; // 默认只有一个元素时长度为1

  		for (int j = 0; j < i; j++) {

  			// 无法成为一个上升子序列

  			if (nums[j] >= nums[i]) continue;

  			dp[i] = Math.max(dp[j] + 1, dp[i]);

  		}

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

  	}

  	return max;

  } 

最长上升子序列 – 二分搜索实现


在这里插入图片描述

在这里插入图片描述

普通实现(非二分搜索):


static int lengthOfLIS(int[] nums) {

	if (nums == null || nums.length == 0) return 0;

	// 牌堆的数量

	int len = 0;

	// 牌顶数组

	int[] top = new int[nums.length];

	// 遍历所有的牌

	for (int num : nums) {

		int j = 0;

		while (j < len) {

			// 找到一个>=nums的牌顶

			if (top[j] >= num) {

				top[j] = num;

				break;

			}

			// 牌顶 < nums

			j++;

		}

		if (j == len) { // 新建一个牌堆

			len++;

			top[j] = num;

		}

	}

	return len;

} 

二分搜索实现:


static int lengthOfLIS(int[] nums) {

	if (nums == null || nums.length == 0) return 0;

	// 牌堆的数量

	int len = 0;

	// 牌顶数组

	int[] top = new int[nums.length];

	// 遍历所有的牌(二分搜索)

	for (int num : nums) {

		int begin = 0;

		int end = len;

		while (begin < end) {

			int mid = (begin + end) >> 1;

			if (num <= top[mid]) {

				end = mid;

			} else {

				begin = mid + 1;

			}

		}

		// 覆盖牌顶

		top[begin] = num;

		// 检查是否要新建一个牌堆

		if (begin == len) len++;

	}

	return len;

} 

练习4 – 最长公共子序列(LCS)

=====================================================================================

最长公共子序列(Longest Common Subsequence,LCS)

leetcode_1143_最长公共子序列:https://leetcode-cn.com/problems/longest-common-subsequence/

题目:求两个序列的最长公共子序列长度

  • [1, 3, 5, 9, 10][1, 4, 9, 10] 的最长公共子序列是 [1, 9, 10],长度为 3

  • ABCBDABBDCABA 的最长公共子序列长度是 4,可能是

    ABCBDAB 和 BDCABA > BDAB

    ABCBDAB 和 BDCABA > BDAB

    ABCBDAB 和 BDCABA > BCAB

    ABCBDAB 和 BDCABA > BCBA

思路

在这里插入图片描述

最长公共子序列 – 递归实现


  • 空间复杂度:O(k) , k = min{n, m},n、m 是 2 个序列的长度

  • 时间复杂度:O(2n) ,当 n = m 时


/**

 * 递归实现

 */

static int lcs(int[] nums1, int[] nums2) {

	if (nums1 == null || nums1.length == 0) return 0; // 检测非法数据

	if (nums2 == null || nums2.length == 0) return 0; // 检测非法数据

	return lcs(nums1, nums1.length, nums2, nums2.length);

}

/**

 * 求nums1前i个元素和nums2前j个元素的最长g公共子序列长度

 * @param nums1

 * @param i

 * @param nums2

 * @param j

 */

static int lcs(int[] nums1, int i, int[] nums2, int j) {

	if (i == 0 || j == 0) return 0;

	// 最后一个元素相等, 返回前面的公共子序列长度 + 1

	if (nums1[i - 1] == nums2[j - 1]) {

		return lcs(nums1, i - 1, nums2, j - 1) + 1;

	}

	return Math.max(

		lcs(nums1, i - 1, nums2, j), 

		lcs(nums1, i, nums2, j - 1)

	);

} 

在这里插入图片描述

最长公共子序列 – 非递归实现


在这里插入图片描述

  • 空间复杂度:O(n ∗ m)

  • 时间复杂度:O(n ∗ m)


static int lcs(int[] nums1, int[] nums2) {

	if (nums1 == null || nums1.length == 0) return 0;

	if (nums2 == null || nums2.length == 0) return 0;

	int[][] dp = new int[nums1.length + 1][nums2.length + 1];

	

	for (int i = 1; i <= nums1.length; i++) {

		for (int j = 1; j <= nums2.length; j++) {

			if (nums1[i - 1] == nums2[j - 1]) {

				dp[i][j] = dp[i - 1][j - 1] + 1;

			} else {

				dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);

			}

		}

	}

	return dp[nums1.length][nums2.length];

} 

最长公共子序列 – 滚动数组优化


可以使用滚动数组化空间复杂度


/**

 * 非递归实现(滚动数组优化)

 */

static int lcs(int[] nums1, int[] nums2) {

	if (nums1 == null || nums1.length == 0) return 0;

	if (nums2 == null || nums2.length == 0) return 0;

	int[][] dp = new int[2][nums2.length + 1];

	

	for (int i = 1; i <= nums1.length; i++) {

		int row = i & 1;

		int prevRow = (i - 1) & 1;

		for (int j = 1; j <= nums2.length; j++) {

			if (nums1[i - 1] == nums2[j - 1]) {

				dp[row][j] = dp[prevRow][j - 1] + 1;

			} else {

				dp[row][j] = Math.max(dp[prevRow][j], dp[row][j - 1]);

			}

		}

	}

	return dp[nums1.length & 1][nums2.length];

} 

最长公共子序列 – 一维数组优化


练习5 – 最长公共子串

===============================================================================

最长公共子串(Longest Common Substring)

  • 子串是连续的子序列

题目:求两个字符串的最长公共子串长度

  • ABCBA 和 BABCA 的最长公共子串是 ABC,长度为 3

假设 2 个字符串分别是 str1、str2:

  • i ∈ [1, str1.length]

  • j ∈ [1, str2.length]

假设 d p ( i , j ) dp(i, j) dp(i,j) 是以 str1[i – 1]str2[j – 1] 结尾的最长公共子串长度:

  • d p ( i , 0 ) dp(i, 0) dp(i,0)、 d p ( 0 , j ) dp(0, j) dp(0,j) 初始值均为 0

  • 如果 str1[i – 1] = str2[j – 1],那么 d p ( i , j ) = d p ( i – 1 , j – 1 ) + 1 dp(i, j) = dp(i – 1, j – 1) + 1 dp(i,j)=dp(i–1,j–1)+1

  • 如果 str1[i – 1]str2[j – 1],那么 d p ( i , j ) = 0 dp(i, j) = 0 dp(i,j)=0

最长公共子串 – 实现


  • 空间复杂度:O(n ∗ m)

  • 时间复杂度:O(n ∗ m)


 static int lcs(String str1, String str2) {

		if (str1 == null || str2 == null) return 0;

		char[] chars1 = str1.toCharArray();

		if (chars1.length == 0) return 0;

		char[] chars2 = str2.toCharArray();

		if (chars2.length == 0) return 0;

		

		int [][] dp = new int [chars1.length + 1][chars2.length + 1];

		int max = 0;

		for (int i = 1; i <= chars1.length; i++) {

			for (int j = 1; j <= chars2.length; j++) {

				if (chars1[i - 1] != chars2[j - 1]) continue;

					dp[i][j] = dp[i -1][j - 1] + 1;

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

			}

		}

		return max;

	} 

最长公共子串 – 一维数组优化


  • 空间复杂度:O(k) , k = min{n, m}

  • 时间复杂度:O(n ∗ m)


static int lcs(String str1, String str2) {

	if (str1 == null || str2 == null) return 0;

	char[] chars1 = str1.toCharArray();

	if (chars1.length == 0) return 0;

	char[] chars2 = str2.toCharArray();

	if (chars2.length == 0) return 0;

	// 选取长度较短的作为列

	char[] rowsChars = chars1, colsChars = chars2;

	if (chars1.length < chars2.length) {

		colsChars = chars1;

		rowsChars = chars2;

	}

	

	int[] dp = new int[colsChars.length + 1];

	int max = 0;

	for (int row = 1; row <= rowsChars.length; row++) {

		int cur = 0;

		for (int col = 1; col <= colsChars.length; col++) {

			int leftTop = cur;

			cur = dp[col];

			if (chars1[row - 1] != chars2[col - 1]) {

				dp[col] = 0;

			} else {

				dp[col] = leftTop + 1;

				max = Math.max(max, dp[col]);

			}

		}

	}

	return max;

} 

我觉得可以修改成这样:


static int lcs(String str1, String str2) {

if (str1 == null || str2 == null) return 0;

	char[] chars1 = str1.toCharArray();

	if (chars1.length == 0) return 0;

	char[] chars2 = str2.toCharArray();

	if (chars2.length == 0) return 0;

	// 选取长度较短的作为列

	int rowsLength = chars1.length;

	int colsLength = chars2.length;

	if (chars1.length < chars2.length) {

		colsLength = chars1.length;

		rowsLength = chars2.length;

	}

	

	int[] dp = new int[colsLength + 1];

	int max = 0;

	for (int row = 1; row <= rowsLength; row++) {

		int cur = 0;

		for (int col = 1; col <= colsLength; col++) {

			int leftTop = cur;

			cur = dp[col];

			if (chars1[row - 1] != chars2[col - 1]) {

				dp[col] = 0;

			} else {

				dp[col] = leftTop + 1;

				max = Math.max(max, dp[col]);

			}

		}

	}

	return max;

} 

练习6 – 0-1背包

==============================================================================

在这里插入图片描述

0-1背包 – 实现



static int maxValue(int[] values, int[] weights, int capacity) {

	// 检测非法输入

	if (values == null || values.length == 0) return 0;

	if (weights == null || weights.length == 0) return 0;

	if (weights.length != values.length) return 0;

	if (capacity <= 0) return 0;

	

	// 特征方程: dp(i, j) 是最大承重为 j、有前 i 件物品可选时的最大总价值;

	int[][] dp = new int[values.length + 1][capacity + 1];

	// dp 初始化的值默认是0,Java中数组默认初始值即为0

	for (int i = 1; i <= values.length; i++) {

		for (int j = 1; j <= capacity; j++){

			if (weights[i - 1] > j) { // 如果本次挑选的物品重量 > 承重, 则不装入该物品

				dp[i][j] = dp[i - 1][j];

			} else { // 本次挑选的物品重量 <= 承重, 可以选择装入

				// 比较【装】与【不装】分别获得的最大总价值来选择

				dp[i][j] = Math.max(dp[i - 1][j], 

						dp[i - 1][j - weights[i - 1]] + values[i - 1]);

			}

		}

	}

	return dp[values.length][capacity];

} 

public static void main(String[] args) {

int[] values = { 6, 3, 5, 4, 6 };

int[] weights = { 2, 2, 6, 5, 4 };

int capacity = 10;

System.out.println(maxValue1(values, weights, capacity));

// i = 5

// j = 10

// 如果不选择第i个物品, dp(i, j) = dp(i - 1, j)

// 如果选择第i个物品, dp(i, j) = values[i] + dp(i - 1, j - weights[i])

// dp(i, j) = max {dp(i - 1, j), valus[i] + dp(i - 1, j - weights[i])}

}




[](https://gitee.com/vip204888/java-p7)0-1背包 – 一维数组

-------------------------------------------------------------------------------



![在这里插入图片描述](https://img-blog.csdnimg.cn/202004252007283.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzczNDA5NQ==,size_16,color_FFFFFF,t_70)



/**

  • 一维数组

*/

static int maxValue(int[] values, int[] weights, int capacity) {

if (values == null || values.length == 0) return 0;

if (weights == null || weights.length == 0) return 0;

if (weights.length != values.length) return 0;

if (capacity <= 0) return 0;

int[] dp = new int[capacity + 1];



for (int i = 1; i <= values.length; i++) {

	for (int j = capacity; j >= 1; j--) {

		if (j < weights[i - 1]) continue;

		dp[j] = Math.max(dp[j], 

				dp[j - weights[i - 1]] + values[i - 1]);

	}

}

return dp[capacity];

}




[](https://gitee.com/vip204888/java-p7)0-1背包 – 一维数组优化

---------------------------------------------------------------------------------



# 资料分享

> **[领取方式:戳这里即可免费获取](https://gitee.com/vip204888/java-p7),同时还可以“嫖”到一份关于Redis事务源码的详解内容。**

**1、算法大厂——字节跳动面试题**

![](https://img-blog.csdnimg.cn/img_convert/d1fdd8d5f2b15d29231fd7fac0cfbd96.png)

**2、2000页互联网Java面试题大全**

![](https://img-blog.csdnimg.cn/img_convert/39996cfe54f73af899615fe6b522cad4.png)

**3、高阶必备,算法学习**

0,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzczNDA5NQ==,size_16,color_FFFFFF,t_70)



/**

  • 一维数组

*/

static int maxValue(int[] values, int[] weights, int capacity) {

if (values == null || values.length == 0) return 0;

if (weights == null || weights.length == 0) return 0;

if (weights.length != values.length) return 0;

if (capacity <= 0) return 0;

int[] dp = new int[capacity + 1];



for (int i = 1; i <= values.length; i++) {

	for (int j = capacity; j >= 1; j--) {

		if (j < weights[i - 1]) continue;

		dp[j] = Math.max(dp[j], 

				dp[j - weights[i - 1]] + values[i - 1]);

	}

}

return dp[capacity];

}




[](https://gitee.com/vip204888/java-p7)0-1背包 – 一维数组优化

---------------------------------------------------------------------------------



# 资料分享

> **[领取方式:戳这里即可免费获取](https://gitee.com/vip204888/java-p7),同时还可以“嫖”到一份关于Redis事务源码的详解内容。**

**1、算法大厂——字节跳动面试题**

[外链图片转存中...(img-01Dg6tH6-1628602413398)]

**2、2000页互联网Java面试题大全**

[外链图片转存中...(img-QAAZ9CQP-1628602413401)]

**3、高阶必备,算法学习**

![](https://img-blog.csdnimg.cn/img_convert/e16ea65f3ee3fd2bdf17fd1ff1fe6ff3.png)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值