线性动态规划详解:从单串到双串问题

线性动态规划详解:从单串到双串问题

LeetCode-Py ⛽️「算法通关手册」:超详细的「算法与数据结构」基础讲解教程,从零基础开始学习算法知识,800+ 道「LeetCode 题目」详细解析,200 道「大厂面试热门题目」。 LeetCode-Py 项目地址: https://gitcode.com/gh_mirrors/le/LeetCode-Py

什么是线性动态规划

线性动态规划(Linear Dynamic Programming)是指具有线性阶段划分的动态规划方法。这类问题的特点是问题的解决过程可以划分为一系列线性排列的阶段,每个阶段的决策只依赖于前一个或前几个阶段的状态。

线性动态规划示意图

线性动态规划问题可以按照不同维度进行分类:

  1. 按状态维度:一维线性DP、二维线性DP、多维线性DP
  2. 按输入格式:单串线性DP、双串线性DP、矩阵线性DP、无串线性DP

单串线性DP问题

单串线性DP问题的输入通常是一个数组或字符串。这类问题的状态定义通常与数组中的某个位置或子数组相关。

状态定义的三种常见形式

  1. dp[i]:以第i个元素为结尾的子数组的相关解
  2. dp[i]:以第i-1个元素为结尾的子数组的相关解
  3. 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的思想和应用。

LeetCode-Py ⛽️「算法通关手册」:超详细的「算法与数据结构」基础讲解教程,从零基础开始学习算法知识,800+ 道「LeetCode 题目」详细解析,200 道「大厂面试热门题目」。 LeetCode-Py 项目地址: https://gitcode.com/gh_mirrors/le/LeetCode-Py

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

娄祺杏Zebediah

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值