线性动态规划详解:从单串到双串问题
什么是线性动态规划
线性动态规划(Linear Dynamic Programming)是指具有线性阶段划分的动态规划方法。这类问题的特点是问题的解决过程可以划分为一系列线性排列的阶段,每个阶段的决策只依赖于前一个或前几个阶段的状态。
线性动态规划问题可以按照不同维度进行分类:
- 按状态维度:一维线性DP、二维线性DP、多维线性DP
- 按输入格式:单串线性DP、双串线性DP、矩阵线性DP、无串线性DP
单串线性DP问题
单串线性DP问题的输入通常是一个数组或字符串。这类问题的状态定义通常与数组中的某个位置或子数组相关。
状态定义的三种常见形式
dp[i]
:以第i个元素为结尾的子数组的相关解dp[i]
:以第i-1个元素为结尾的子数组的相关解dp[i]
:前i个元素组成的子数组的相关解
这三种形式的区别主要在于是否包含当前元素以及子数组是否可以为空。
经典问题1:最长递增子序列(LIS)
问题描述:给定一个整数数组,找到其中最长的严格递增子序列的长度。
状态定义:dp[i]
表示以nums[i]
结尾的最长递增子序列长度。
状态转移方程:
if nums[j] < nums[i]:
dp[i] = max(dp[i], dp[j] + 1) # 0 ≤ j < i
初始条件:每个元素本身构成长度为1的子序列,dp[i] = 1
。
实现代码:
def lengthOfLIS(nums):
size = len(nums)
dp = [1] * size
for i in range(size):
for j in range(i):
if nums[i] > nums[j]:
dp[i] = max(dp[i], dp[j] + 1)
return max(dp)
复杂度分析:
- 时间复杂度:O(n²)
- 空间复杂度:O(n)
经典问题2:最大子数组和
问题描述:找到一个具有最大和的连续子数组。
状态定义:dp[i]
表示以第i个数结尾的连续子数组的最大和。
状态转移方程:
dp[i] = nums[i] if dp[i-1] < 0 else dp[i-1] + nums[i]
优化实现(滚动数组):
def maxSubArray(nums):
subMax = nums[0]
ansMax = nums[0]
for i in range(1, len(nums)):
subMax = nums[i] if subMax < 0 else subMax + nums[i]
ansMax = max(ansMax, subMax)
return ansMax
复杂度分析:
- 时间复杂度:O(n)
- 空间复杂度:O(1)
双串线性DP问题
双串线性DP问题的输入通常是两个数组或字符串。状态定义通常与两个串中的位置相关。
经典问题1:最长公共子序列(LCS)
问题描述:给定两个字符串,找到它们的最长公共子序列长度。
状态定义:dp[i][j]
表示text1
前i个字符和text2
前j个字符的最长公共子序列长度。
状态转移方程:
if text1[i-1] == text2[j-1]:
dp[i][j] = dp[i-1][j-1] + 1
else:
dp[i][j] = max(dp[i-1][j], dp[i][j-1])
实现代码:
def longestCommonSubsequence(text1, text2):
m, n = len(text1), len(text2)
dp = [[0]*(n+1) for _ in range(m+1)]
for i in range(1, m+1):
for j in range(1, n+1):
if text1[i-1] == text2[j-1]:
dp[i][j] = dp[i-1][j-1] + 1
else:
dp[i][j] = max(dp[i-1][j], dp[i][j-1])
return dp[m][n]
复杂度分析:
- 时间复杂度:O(mn)
- 空间复杂度:O(mn)
经典问题2:编辑距离
问题描述:计算将一个单词转换成另一个单词所需的最少操作数(插入、删除、替换)。
状态定义:dp[i][j]
表示word1
前i个字符转换为word2
前j个字符所需的最少操作数。
状态转移方程:
if word1[i-1] == word2[j-1]:
dp[i][j] = dp[i-1][j-1]
else:
dp[i][j] = min(dp[i-1][j-1], dp[i-1][j], dp[i][j-1]) + 1
实现代码:
def minDistance(word1, word2):
m, n = len(word1), len(word2)
dp = [[0]*(n+1) for _ in range(m+1)]
for i in range(m+1):
dp[i][0] = i
for j in range(n+1):
dp[0][j] = j
for i in range(1, m+1):
for j in range(1, n+1):
if word1[i-1] == word2[j-1]:
dp[i][j] = dp[i-1][j-1]
else:
dp[i][j] = min(dp[i-1][j-1], dp[i-1][j], dp[i][j-1]) + 1
return dp[m][n]
复杂度分析:
- 时间复杂度:O(mn)
- 空间复杂度:O(mn)
总结
线性动态规划是解决序列相关问题的重要方法。掌握单串和双串问题的状态定义和转移方程是解决这类问题的关键。通过练习经典问题如最长递增子序列、最大子数组和、最长公共子序列和编辑距离等,可以深入理解线性DP的思想和应用。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考