【算法突击】动态规划系列 (一)| 程序员面试 | 最大子数组和 | 最长递增子序列 | 最长公共子序列
创始人
2024-05-18 19:54:36
0

【算法突击】动态规划系列 (一)| 程序员面试 | 最大子数组和 | 最长递增子序列 | 最长公共子序列

文章目录

  • 【算法突击】动态规划系列 (一)| 程序员面试 | 最大子数组和 | 最长递增子序列 | 最长公共子序列
  • 1. 最大子数组和
    • 1.1 题目描述
    • 1.2 解题思路
    • 1.3 代码实现
  • 2. 最长递增子序列
    • 2.1 题目描述
    • 1.2 解题思路
    • 1.3 代码实现
  • 3. 最长公共子序列
    • 3.1 题目描述
    • 3.2 解题思路
    • 3.3 代码实现

1. 最大子数组和

1.1 题目描述

给你一个整数数组 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 解题思路

(1)穷举法

以数组nums[-2,1,-3,4,-1,2,1,-5,4] 为例,该数组长度为9,设定子数组[i,j](其中j≥i,i≥0, i<9)为最大和的子数组。直接两个嵌套的for循环即可得出答案,时间复杂度是O(n^2)。复杂度太高,是否还有优化空间?

(2)动态规划

以数组nums [-2,1,-3,4,-1,2,1,-5,4] 为例,设定数组f[i]第i位元素结尾时,子数组中最大的元素和(i≥0, i<9),我们接下来寻找一下这个例子中蕴藏的规律。

  • 当指针i=0时

显然f[0] = nums[0],当数组中只有一个元素时,其连续子数组之和就是这个元素;
在这里插入图片描述

  • 当指针i=1时

当前数组nums中只有[-2,1]两个元素,显然-2 + 1 < 1,所以f[1] = nums[1];
在这里插入图片描述

  • 当指针i=2时

nums={-2, 1, -3},由于已经知道了f[1],所以只需比较nums[2] + f[1] 和 当前 nums[2]的大小即可决定在f[2]的大小,故f[2]=nums[2] + f[1] ;
在这里插入图片描述

  • 当指针i=3时

f[3] = nums[3]
在这里插入图片描述

  • 以此类推,当指针i=8时

此时已经计算出数组中全部元素的最大连续和了,发现f[6]是最大的。
在这里插入图片描述

基于上述的推导过程不难发现,f[i]只和f[i-1]和nums[i]的值有关系,且f[0]有初始值,我们将上面分析出的规律总结成数学表达式的形式,如下:

f[i]={nums[0],当i=0时max{nums[i],nums[i]+f[i−1]},当i>0时f[i] = \begin{cases} nums[0], & \text{当i=0时} \\ max\{nums[i], nums[i] + f[i-1]\}, & \text{当i>0时} \\ \end{cases} f[i]={nums[0],max{nums[i],nums[i]+f[i−1]},​当i=0时当i>0时​

这个就是动态规划中最重要的状态转移方程,有了这个东西我们可以根据已有的简单结论推导出复杂场景下的结果。另外可以看到这里只需要一次遍历即可解决问题,时间复杂度降到了O(n)。

1.3 代码实现

	public int maxSubArray(int[] nums) {int[] f = new int[nums.length];  // 以第i个元素结尾的 最大连续子数组之和f[0] = nums[0];int max = nums[0];for (int i = 1; i < nums.length; i++) {f[i] = Math.max(nums[i], nums[i] + f[i - 1]);max = Math.max(f[i], max);}return max;}

2. 最长递增子序列

2.1 题目描述

给你一个整数数组 nums ,找到其中最长严格递增子序列长度

子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。

💡 示例 1:

输入:nums = [10,9,2,5,3,7,101,18]

输出:4

解释:最长递增子序列是 [2,3,7,101],因此长度为 4

示例 2:

输入:nums = [0,1,0,3,2,3]

输出:4

示例 3:

输入:nums = [7,7,7,7,7,7,7]

输出:1

1.2 解题思路

对于这个题,建模的时候我们将f[i]定义成“第i位元素结尾时,当前数组中最大递增序列长度”,其中i为指针。

以输入nums = [10,9,2,5,3,7,101,18]为例,推导一下整个f数组的构建过程:

  • 当指针i=0时

当前数组中只有一个元素,nums[0],即当前递增子序列长度恒定为1;
在这里插入图片描述

  • 当指针i=1时

当前nums[1] < nums[0],f[1] = 1;
在这里插入图片描述

  • 当指针i=2时

同上,f[2]=1
在这里插入图片描述

  • 当指针i=3时

nums[3] > nums[2], f[3] = f[2] + 1;
在这里插入图片描述

  • 当指针i=4时
    这个时候会发现一个问题,因为nums[4] < nums[3],但是由于题目规则允许跳跃元素,且nums[4] > nums[2],此时的f[4] = f[2] + 1 = 2。由于这个特殊的规则,我们必须再加入一个回溯指针j,在nums[i] < nums[i] - 1时,往i - 1之前进行回溯,取最大的长度f[j] + 1,才能得到当前第i个元素结尾时最长的递增序列长度。在i=4时,j=2即可得到f[4]的正确值。
    在这里插入图片描述

  • 以此类推,当指针i=7时

此时的回溯指针j=5时,可以使得f[7]最大,f[7] = f[5] + 1。
在这里插入图片描述

基于上述的推导过程不难发现,f[i]和f[i-1]之间的关系并不固定,我们必须引入i的回溯指针j才能求出正确结果,把上述规律总结成数学表达式为:

f[i]={1,当i=0时max{f[j]+1},当i>0时,j0时,j1,max{f[j]+1},​当i=0时当i>0时,j

1.3 代码实现

	public int lengthOfLIS(int[] nums) {int[] f = new int[nums.length];int max = 1;// 初始化for(int i=0;if[i] = 1;}// 状态转移方程for(int i=1;ifor(int j=i-1;jif(nums[i] > nums[j] && f[i] < f[j] + 1){f[i] = f[j] + 1;}}max = Math.max(max, f[i]);}return max;}

3. 最长公共子序列

3.1 题目描述

给定两个字符串 text1 和 text2,返回这两个字符串的最长 公共子序列长度。如果不存在 公共子序列 ,返回 0 。

一个字符串的 子序列 是指这样一个新的字符串:

它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。

例如,“ace” 是 “abcde” 的子序列,但 “aec” 不是 “abcde” 的子序列。

两个字符串的 公共子序列 是这两个字符串所共同拥有的子序列。

💡 示例 1:

输入:text1 = “abcde”, text2 = “ace”

输出:3

解释:最长公共子序列是 “ace” ,它的长度为 3

示例 2:

输入:text1 = “abc”, text2 = “abc”

输出:3

解释:最长公共子序列是 “abc” ,它的长度为 3

示例 3:

输入:text1 = “abc”, text2 = “def”

输出:0

解释:两个字符串没有公共子序列,返回 0

3.2 解题思路

为了简单起见,我们将字符串text1称为A串,text2称为B串。

这个题,我们需要在两个不同的字符串中来回比较,所以我们的f[i]不能再是一维的,设f[i][j]为A串中以第i个元素结尾,B串中以第j个元素结尾时的最长公共子序列长度。以text1 = “abcde”, text2 = "ace"为例,推导f数组的构建过程:

  • 当指针i=0, j=0时

因为A[i] = B[j],所以f[i][j] = 1
在这里插入图片描述

更进一步,可以推导出所有f[0][j]和f[i][0]的值
在这里插入图片描述

  • 当指针i=1, j=1时

A[1] ≠ B[2],所以f[1][2] = f[1][0]
在这里插入图片描述

  • 当指针i=1, j=2时

A[1] ≠ B[2],所以f[1][2] = f[1][1]
在这里插入图片描述

  • 当指针i=2, j=1时

A[2] = B[1], f[2][1] = f[1][0]+ 1
在这里插入图片描述

  • 当指针i=3, j=1时

A[3] ≠ B[1], f[3][1] = f[2][1]+ 1,要注意的是,这里有两种选择{abcd, a},{abc, ac},选其中最大的即可
在这里插入图片描述

  • 当指针i=4, j=2时

A[4] = B[2], f[4][2] = f[3][1]
在这里插入图片描述

基于上述的推导过程不难发现,初始化过程之外,匹配的时候需要分两种情况讨论,当A[i] = B[j]时,f[i][j]=f[i-1][j-1] + 1;当A[i] ≠ B[j]时,f[i][j]有两种取值情况,f[i-1][j]与f[i][j-1],取两者中的最大值即可。其数学表达式为:

f[i][j]={1,当i=0且j=0时max{f[i−1][j],f[i][j−1]},当A[i] != B[j]时f[i−1][j−1]+1,当A[i]=B[i]时f[i][j] = \begin{cases} 1 , & \text{当i=0且j=0时} \\ max\{f[i-1][j], f[i][j-1]\}, & \text{当A[i] != B[j]时} \\f[i-1][j-1] + 1 , & \text{当A[i]=B[i]时} \\ \end{cases} f[i][j]=⎩⎧​1,max{f[i−1][j],f[i][j−1]},f[i−1][j−1]+1,​当i=0且j=0时当A[i] != B[j]时当A[i]=B[i]时​

3.3 代码实现

public int longestCommonSubsequence(String text1, String text2) {char[] A = text1.toCharArray();char[] B = text2.toCharArray();int[][] f = new int[A.length][B.length];// 初始化f[0][0] = A[0] == B[0] ? 1 : 0;for (int i = 1; i < A.length; i++) {f[i][0] = A[i] == B[0] ? 1 : f[i - 1][0];}for (int j = 1; j < B.length; j++) {f[0][j] = A[0] == B[j] ? 1 : f[0][j - 1];}// 状态转移方程for (int i = 1; i < A.length; i++) {for (int j = 1; j < B.length; j++) {if (A[i] != B[j]) {f[i][j] = Math.max(f[i - 1][j], f[i][j - 1]);} else {f[i][j] = f[i - 1][j - 1] + 1;}}}return f[A.length - 1][B.length - 1];}

相关内容

热门资讯

【NI Multisim 14...   目录 序言 一、工具栏 🍊1.“标准”工具栏 🍊 2.视图工具...
银河麒麟V10SP1高级服务器... 银河麒麟高级服务器操作系统简介: 银河麒麟高级服务器操作系统V10是针对企业级关键业务...
不能访问光猫的的管理页面 光猫是现代家庭宽带网络的重要组成部分,它可以提供高速稳定的网络连接。但是,有时候我们会遇到不能访问光...
AWSECS:访问外部网络时出... 如果您在AWS ECS中部署了应用程序,并且该应用程序需要访问外部网络,但是无法正常访问,可能是因为...
Android|无法访问或保存... 这个问题可能是由于权限设置不正确导致的。您需要在应用程序清单文件中添加以下代码来请求适当的权限:此外...
北信源内网安全管理卸载 北信源内网安全管理是一款网络安全管理软件,主要用于保护内网安全。在日常使用过程中,卸载该软件是一种常...
AWSElasticBeans... 在Dockerfile中手动配置nginx反向代理。例如,在Dockerfile中添加以下代码:FR...
AsusVivobook无法开... 首先,我们可以尝试重置BIOS(Basic Input/Output System)来解决这个问题。...
ASM贪吃蛇游戏-解决错误的问... 要解决ASM贪吃蛇游戏中的错误问题,你可以按照以下步骤进行:首先,确定错误的具体表现和问题所在。在贪...
月入8000+的steam搬砖... 大家好,我是阿阳 今天要给大家介绍的是 steam 游戏搬砖项目,目前...