一、什么是动态规划
动态规划(Dynamic Programming,简称DP)是一种算法设计技术,它通常用于求解具有重叠子问题和最优子结构特性的问题。动态规划的核心思想是将复杂问题分解成小而简单的子问题来逐个求解,同时保存这些子问题的解以避免重复计算,从而提高效率。
重叠子问题
当一个问题的求解过程中反复出现相同的子问题时,这些子问题就被称为重叠子问题。在没有采用动态规划的情况下,这些子问题会被重复计算多次,导致效率低下。动态规划通过记忆化(通常使用一个数组或哈希表来存储已解决子问题的答案)来避免这种重复计算。
最优子结构
最优子结构意味着一个问题的最优解包含其子问题的最优解。如果一个问题具有最优子结构,那么我们可以通过组合子问题的最优解来构造整个问题的最优解。
二、动态规划的步骤
动态规划通常遵循以下步骤:
- 定义状态:首先定义一个或多个状态,这些状态用来表示问题的部分解。
- 建立状态转移方程:状态转移方程描述了状态之间是如何转移的,即如何从一个或多个较小的子问题的解得到更大问题的解。
- 初始化状态:确定初始条件,即最基本的子问题的解。
- 计算状态值:按照某种顺序(自底向上或自顶向下)计算所有状态的值。自底向上通常使用迭代的方式,自顶向下通常使用递归的方式。
- 构建最终解:根据计算出的状态值,构建问题的最终解。
三、应用场景
动态规划广泛应用于许多领域,包括但不限于:
- 最优路径问题(如最短路径、最长路径问题)
- 资源分配问题(如背包问题)
- 字符串分析(如编辑距离、最长公共子序列)
- 组合数问题(如不同路径、硬币找零问题)
题目:
给你一个整数数组 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
解题思路:
【利用子问题的解来构造原问题的解,这就是动态规划策略的精髓所在。每一步的决策都依赖于之前的计算结果,避免了重复计算,从而提高了效率】
现在的任务是找出其中最长的一段,使得这一段数字从左到右看是逐渐增大的。我们用一个数组 dp
来帮助我们达成这个目标,其中 dp[i]
表示的是以第 i
个数字结尾的最长递增子序列的长度。
我们想计算 dp[i]
时(以第 i
个数字结尾的最长递增子序列的长度),我们就去看前面的每一个数字(也就是第 0
到第 i-1
个数字),如果前面的某个数字比第 i
个数字小,那么它就可以作为递增子序列的一部分,我们可以将这串较小的数字后面接上第 i
个数字,形成一个更长的递增子序列。所以,我们会检查所有可能的这样的较小的数字,看看以哪个数字结尾的递增子序列再加上第 i
个数字后,可以得到最长的递增子序列。
def lengthOfLIS(nums):
# 如果数组为空,直接返回0
if not nums:
return 0
# 初始化dp数组,每个位置初始值为1(因为最短的递增子序列至少包含它自己)
dp = [1] * len(nums)
# 遍历数组,计算每个位置的dp值
for i in range(len(nums)):
for j in range(i):
# 如果nums[i] > nums[j],说明nums[i]可以接在以nums[j]结尾的递增子序列之后
if nums[i] > nums[j]:
# 更新dp[i]为dp[j] + 1和当前dp[i]的较大值
dp[i] = max(dp[i], dp[j] + 1)
# dp数组中的最大值即为最长递增子序列的长度
return max(dp)
# 示例
nums = [10,9,2,5,3,7,101,18]
print(lengthOfLIS(nums)) # 输出:4