动态规划
递归:是自上而下的解决问题,假设最基本的问题已经解决了,在递归过程中可能会出现许多重复的子问题的求解过程,可以采用记忆化搜索的方式来优化重复子问题的不足
动态规划:是自底向上的解决一个问题,将原问题拆解成若干子问题,同时保证子问题的答案,使得每个子问题只求解一次,最终获得原问题的答案
动态规划 ==> 出现重叠子问题、最优子结构 ==> 采用记忆化搜索(自顶向下) 或 动态规划(自底向上)
动态规划思路:明确状态(dp数组中存储的值的具体含义)==> 状态初始化 ==> 状态转移方程求解
状态:即函数的定义 (dp数组中存储的值的具体含义);
状态转移:根据对状态的定义,确定状态的转移,dp[i] 如何由 dp[j] (j<i) 获得
70. 爬楼梯
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?注意:给定 n 是一个正整数。
示例 1:
输入: 2
输出: 2
解释: 有两种方法可以爬到楼顶。
1. 1 阶 + 1 阶
2. 2 阶示例 2:
输入: 3
输出: 3
解释: 有三种方法可以爬到楼顶。
1. 1 阶 + 1 阶 + 1 阶
2. 1 阶 + 2 阶
3. 2 阶 + 1 阶
class Solution {
public int climbStairs(int n) {
//爬第k阶台阶,可以是从第k-1阶爬上来,也可以是从第k-2阶爬上来,所以,爬上第k阶楼梯的方法为爬上第k-1阶楼梯的方法+爬上第k-2阶楼梯的方法
//动态规划实现
if(n==1){
return 1;
}
int[] dp = new int[n+1]; //dp[k]为爬上第k阶楼梯的方法数
dp[1] = 1;
dp[2] = 2;
for(int k=3; k<=n; k++){
dp[k] = dp[k-2]+dp[k-1];
}
return dp[n];
}
}
120. 三角形最小路径和
给定一个三角形,找出自顶向下的最小路径和。每一步只能移动到下一行中相邻的结点上。
例如,给定三角形:[
[2],
[3,4],
[6,5,7],
[4,1,8,3]
]
自顶向下的最小路径和为 11(即,2 + 3 + 5 + 1 = 11)。说明:
如果你可以只使用 O(n) 的额外空间(n 为三角形的总行数)来解决这个问题,那么你的算法会很加分。
思路:dp解法,开辟一个int[] dp ,用以记录某一层每个元素到最底层的最短距离,复用这个dp数组。
class Solution {
public int minimumTotal(List<List<Integer>> triangle) {
int n = triangle.size();
//dp[i]表示某一层第i个元素到最底层的最短距离
//从最底层开始,向上推,每一层都要更新dp[i]中的值,直到最顶层只有一个元素,则dp[0]即为所求
int[] dp = new int[triangle.get(n-1).size()];
//初始化,最底层每个元素到最底层的的距离就是最底层每个元素本身的值
for(int i=0; i<triangle.get(n-1).size(); i++){
dp[i] = triangle.get(n-1).get(i);
}
//外层循环负责向上更新层数, 内层循环负责更新当前层每个元素到最底层的最小距离
for(int k=n-2; k>=0; k--){
//获得当前层的list,根据当前层list中的元素值以及下一层dp数组进行更新,使其dp成为当前层dp数组
List<Integer> cur = triangle.get(k);
for(int i=0; i<cur.size(); i++){
dp[i] = cur.get(i)+Math.min(dp[i], dp[i+1]);
}
}
return dp[0];
}
}
64.最小路径和
给定一个包含非负整数的 m x n 网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。
说明:每次只能向下或者向右移动一步。
示例:
输入:
[
[1,3,1],
[1,5,1],
[4,2,1]
]
输出: 7
解释: 因为路径 1→3→1→1→1 的总和最小。
class Solution {
public int minPathSum(int[][] grid) {
if(grid.length == 0 || grid[0].length == 0){
return 0;
}
int m = grid.length, n = grid[0].length;
// 采用二维数组 dp[][], dp[i][k]表示走到grid[i][k]时的最短路径, 则 dp[i][k] = min(dp[i-1][k], dp[i][k-1])+grid[i][k]
// 由于考虑第i行的时候,前i-2行的数据都不需要的,可以复用一个一维数组即可
// dp[k] = min(dp[k-1], dp[k])+grid[i][k], 其中左侧的 dp[k]即dp[i][k], 右侧的 dp[k]即dp[i-1][k], 右侧的dp[k-1]即dp[i][k-1]
int[] dp = new int[n];
for(int i=0; i<m; i++){
for(int k=0; k<n; k++){
if(k==0){
dp[k] = dp[k];
}else if(i==0){
dp[k] = dp[k-1];
}else{
dp[k] = Math.min(dp[k], dp[k-1]);
}
dp[k] += grid[i][k];
}
}
return dp[n-1];
}
}
62.不同路径
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。
问总共有多少条不同的路径?
例如,上图是一个7 x 3 的网格。有多少可能的路径?
说明:m 和 n 的值均不超过 100。
示例 1:
输入: m = 3, n = 2
输出: 3
解释:
从左上角开始,总共有 3 条路径可以到达右下角。
1. 向右 -> 向右 -> 向下
2. 向右 -> 向下 -> 向右
3. 向下 -> 向右 -> 向右
示例 2:输入: m = 7, n = 3
输出: 28
class Solution {
public int uniquePaths(int m, int n) {
if(m==0 || n==0){
return 0;
}
// dp[i][j] 表示 走到(i, j)处的不同路径数, dp[i][j] = dp[i-1][j]+dp[i][j-1]
// 可以复用一个一维数组实现
int[] dp = new int[n];
for(int i=0; i<m; i++){
for(int j=0; j<n; j++){
if(j==0){
dp[j] = 1;
}else if(i==0){
dp[j] = 1;
}else{
dp[j] = dp[j]+dp[j-1];
}
}
}
return dp[n-1];
}
}
53. 最大子序和
给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
示例:
输入: [-2,1,-3,4,-1,2,1,-5,4],
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
class Solution {
//动态规划
public int maxSubArray(int[] nums) {
int n = nums.length;
if(n==0){
return 0;
}
//定义状态,dp[i]表示以第i个元素为结尾的连续子序列的最大和
int[] dp = new int[n];
//状态初始化
for(int i=0; i<n; i++){
dp[i] = nums[i];
}
//以第k个元素结尾的连续子序列的最大和要么是第k个元素本身,要么是第k个元素与以第k-1个元素结尾的子序列和的和
//状态转移方程:dp[k] = Math.max(dp[k], dp[k]+dp[k-1]);
for(int k=1; k<n; k++){
dp[k] = Math.max(dp[k], dp[k]+dp[k-1]);
}
Arrays.sort(dp);
return dp[n-1];
}
}
121. 买卖股票的最佳时机
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
如果你最多只允许完成一笔交易(即买入和卖出一支股票),设计一个算法来计算你所能获取的最大利润。注意你不能在买入股票前卖出股票。
示例 1:
输入: [7,1,5,3,6,4]
输出: 5
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格。示例 2:
输入: [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。
思路1:常规思路, 时间复杂度O(n^2)
class Solution {
public int maxProfit(int[] prices) {
int n = prices.length;
if(n==0){
return 0;
}
int res = 0;
for(int i=0; i<n; i++){
for(int j=i+1; j<n; j++){
if(prices[j] - prices[i]>res){
res = prices[j] - prices[i];
}
}
}
return res;
}
}
思路2:动态规划, 时间复杂度O(n)
class Solution {
//动态规划
public int maxProfit(int[] prices) {
int n = prices.length;
if(n==0){
return 0;
}
//定义状态:dp[i] 为第i天可以获得的最大利润
int[] dp = new int[n];
//状态初始化
dp[0] = 0;
//状态转移方程:第i天可以获得的最大利润 = max(第i-1天可以获得的最大利润, 第i天价格 -前i-1天中价格最小值)
int min = prices[0];
for(int k=1; k<n; k++){
dp[k] = Math.max(dp[k-1], prices[k]-min);
if(prices[k]<min){
min=prices[k];
}
}
int res = 0;
for(int i=0; i<n; i++){
if(dp[i]>res){
res = dp[i];
}
}
return res;
}
}
343. 整数拆分
给定一个正整数 n,将其拆分为至少两个正整数的和,并使这些整数的乘积最大化。 返回你可以获得的最大乘积。
示例 1:
输入: 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1。示例 2:
输入: 10
输出: 36
解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36。
说明: 你可以假设 n 不小于 2 且不大于 58。
class Solution {
public int integerBreak(int n) {
// /*解法1. 递归解法 ,integerBreak(n)的语义为将n分割为两部分的最大积*/
// //提交超时
// if(n == 1){
// return 1;
// }
// //将n拆为i, n-i两部分,f(n) = i*f(n-i)或i*(n-i)
// int res = -1;
// for(int i=1; i<=n-i; i++){
// res = Math.max(Math.max(i*(n-i), i*integerBreak(n-i)), res);
// }
// return res;
// /*解法2. 通过记忆搜索优化的递归解法*/
// int[] memo = new int[n+1]; //int[n] 记录n拆分后的最大积
// memo[1] = 1;
// return intbreak(n, memo);
/*解法3. 动态规划解法*/
int[] dp = new int[n+1]; //dp[i] 记录拆分i后的最大积
dp[1] = 1;
dp[2] = 1;
for(int k=3; k<=n; k++){
for(int i=1; i<k-1; i++){
dp[k] = Math.max(Math.max(i*(k-i), i*dp[k-i]), dp[k]);
}
}
return dp[n];
}
// private int intbreak(int n, int[] memo) {
// if(n == 1){
// return memo[1];
// }
// if(memo[n] == 0){
// int res = -1;
// for(int i=1; i<=n-1; i++){
// memo[n] = Math.max(Math.max(i*(n-i), i*intbreak(n-i,memo)),memo[n]);
// }
// }
// return memo[n];
// }
}
279.完全平方数
给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, ...)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。
示例 1:
输入: n = 12
输出: 3
解释: 12 = 4 + 4 + 4.
示例 2:输入: n = 13
输出: 2
解释: 13 = 4 + 9.
class Solution {
private ArrayList<Integer> squareNums = new ArrayList<>();
public int numSquares(int n) {
generateSquareNums(n);
// dp[k] 表示数字k可以拆分处的最少的完全平方数的个数
int[] dp = new int[n+1];
dp[0] = 0;
dp[1] = 1;
// dp[k] = min(dp[k], 1+dp[k-squareNum[i]]), squareNum[i] 为小于等于K的完全平方数
for(int k=2; k<=n; k++){
dp[k] = Integer.MAX_VALUE;
for(int e: squareNums){
if(e>k){
break;
}
dp[k] = Math.min(dp[k], dp[k-e]+1);
}
}
return dp[n];
}
// 生成小于等于n的完全平方数
private void generateSquareNums(int n){
int squareNum = 1;
int diff = 3;
while(squareNum<=n){
squareNums.add(squareNum);
squareNum += diff;
diff += 2;
}
}
}
91.解码方法
一条包含字母 A-Z 的消息通过以下方式进行了编码:
'A' -> 1
'B' -> 2
...
'Z' -> 26
给定一个只包含数字的非空字符串,请计算解码方法的总数。示例 1:
输入: "12"
输出: 2
解释: 它可以解码为 "AB"(1 2)或者 "L"(12)。
示例 2:输入: "226"
输出: 3
解释: 它可以解码为 "BZ" (2 26), "VF" (22 6), 或者 "BBF" (2 2 6) 。
class Solution {
public int numDecodings(String s) {
int n = s.length();
if(n == 0){
return 0;
}
int[] dp = new int[n];
dp[0] = s.charAt(0) == '0'? 0:1;
for(int k=1; k<n; k++){
char one = s.charAt(k);
if(one != '0'){
dp[k] = dp[k-1];
}
char c = s.charAt(k-1);
if(c == '0'){
continue;
}
int two = Integer.valueOf(s.substring(k-1, k+1));
if(two<=26){
dp[k] += ((k-2)>=0? dp[k-2]:1);
}
}
return dp[n-1];
}
}
62
63
198. 打家劫舍
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额。示例 1:
输入: [1,2,3,1]
输出: 4
解释: 偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。
偷窃到的最高金额 = 1 + 3 = 4 。示例 2:
输入: [2,7,9,3,1]
输出: 12
解释: 偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。
偷窃到的最高金额 = 2 + 9 + 1 = 12 。
思路:动态规划解法
class Solution {
public int rob(int[] nums) {
int n = nums.length;
if(n==0){
return 0;
}
//定义状态,dp[i] 为考虑盗取第[0,i]号房子所取得的最大收益
int[] dp = new int[n];
dp[0] = nums[0];
for(int k=1; k<n; k++){
//状态转移方程,求解dp[k], dp[k] = max( nums[k]+dp[k-2], nums[k-1]+dp[k-3]... )
for(int i=k; i>=0; i--){
dp[k] = Math.max(nums[i]+(i>=2 ? dp[i-2]:0), dp[k]);
}
}
return dp[n-1];
}
}
class Solution {
public int rob(int[] nums) {
int n = nums.length;
if(n==0){
return 0;
}
int[] dp = new int[n]; //dp[k] 表示抢劫第[0, k]家时,可获得的最大财物
dp[0] = nums[0];
// dp[k] = max(nums[k]+dp[k-2], dp[k-1])
for(int k=1; k<n; k++){
int temp = Math.max(nums[k]+((k-2)<0? 0:dp[k-2]), dp[k-1]);
dp[k] = temp;
}
return dp[n-1];
}
}
213. 打家劫舍 II
你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都围成一圈,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额。
示例 1:
输入: [2,3,2]
输出: 3
解释: 你不能先偷窃 1 号房屋(金额 = 2),然后偷窃 3 号房屋(金额 = 2), 因为他们是相邻的。
示例 2:输入: [1,2,3,1]
输出: 4
解释: 你可以先偷窃 1 号房屋(金额 = 1),然后偷窃 3 号房屋(金额 = 3)。
偷窃到的最高金额 = 1 + 3 = 4 。
class Solution {
public int rob(int[] nums) {
int n = nums.length;
if(n == 0){
return 0;
}
if(n==1){
return nums[0];
}
// 由于环形的存在,首尾为相邻两点,分别去首,去尾后做初级版打家劫舍后,取最优值即可,需注意只有一个元素时的情况
int res = 0;
int res1 = subRob(nums, 0, n-2);
int res2 = subRob(nums, 1, n-1);
res = res1>res2? res1:res2;
return res;
}
// 对nums[l, r]做初级版打家劫舍
private int subRob(int[] nums, int l, int r){
if(l>r){
return 0;
}
int[] dp = new int[r-l+1];
dp[0] = nums[l];
for(int k=1; k<=r-l; k++){
dp[k] = Math.max(dp[k-1], nums[k+l]+((k-2)>=0? dp[k-2]:0));
}
return dp[r-l];
}
}
337
309
303. 区域和检索 - 数组不可变
给定一个整数数组 nums,求出数组从索引 i 到 j (i ≤ j) 范围内元素的总和,包含 i, j 两点。
示例:
给定 nums = [-2, 0, 3, -5, 2, -1],求和函数为 sumRange()
sumRange(0, 2) -> 1
sumRange(2, 5) -> -1
sumRange(0, 5) -> -3
说明:你可以假设数组不可变。
会多次调用 sumRange 方法。
class NumArray {
private int[] sums;
public NumArray(int[] nums) {
int n = nums.length;
sums = new int[n+1];
for(int i=1; i<=n; i++){
sums[i] = sums[i-1]+nums[i-1];
}
}
public int sumRange(int i, int j) {
return sums[j+1]-sums[i];
}
}
/**
* Your NumArray object will be instantiated and called as such:
* NumArray obj = new NumArray(nums);
* int param_1 = obj.sumRange(i,j);
*/
413.等差数列划分
如果一个数列至少有三个元素,并且任意两个相邻元素之差相同,则称该数列为等差数列。
例如,以下数列为等差数列:
1, 3, 5, 7, 9
7, 7, 7, 7
3, -1, -5, -9
以下数列不是等差数列。1, 1, 2, 5, 7
数组 A 包含 N 个数,且索引从0开始。数组 A 的一个子数组划分为数组 (P, Q),P 与 Q 是整数且满足 0<=P<Q<N 。
如果满足以下条件,则称子数组(P, Q)为等差数组:
元素 A[P], A[p + 1], ..., A[Q - 1], A[Q] 是等差的。并且 P + 1 < Q 。
函数要返回数组 A 中所有为等差数组的子数组个数。
示例:
A = [1, 2, 3, 4]
返回: 3, A 中有三个子等差数组: [1, 2, 3], [2, 3, 4] 以及自身 [1, 2, 3, 4]。
class Solution {
public int numberOfArithmeticSlices(int[] A) {
int n = A.length;
if(n<3){
return 0;
}
int[] dp = new int[n]; // dp[k] 表示以数组中第k个元素A[K]结尾的等差子数列的数目
// 理解:
// 以3结尾
// [1 2 3] --> 1
// 以4结尾
// [1 2 3 4] --> [1 2 3 4] = [1 2 3]+[4],由上一个状态演变而来
// --> [2 3 4], 相对于上一个状态,新增的1个
// --> 2
// 以5结尾
// [1 2 3 4 5] -->[1 2 3 4 5] = [1 2 3 4]+[5], 由上一个状态演变而来
// -->[2 3 4 5] = [2 3 4]+[5], 由上一个状态演变而来
// -->[3 4 5], 相对于上一个状态,新增的1个
// --> 3
// 即 dp[k] = dp[k-1]+1
// 最终总数目 需要对 dp[2]...dp[n-1] 求和
int res = 0;
for(int k=2; k<n; k++){
if(A[k]-A[k-1] == A[k-1]-A[k-2]){
dp[k] = dp[k-1]+1;
res = res+dp[k];
}
}
return res;
}
}
动态规划经典问题
0-1背包问题
有一个背包它的容量为C,现在有n种不同的物品,编号为0,1,2...n-1,其中每一件物品的重量为w(i), 价值为v(i)。问可以向背包中盛放哪些物品,使得在不超过背包容量的基础上物品的总价值最大。
思路:
从n件物品中选取不同的物品组合,使该组合满足一定的最优条件,属于组合的选择问题。
先考虑一下暴力解法,每件物品都可以选择拿取或者不拿取,组合的总数为2^n, 对于每种组合都要求其总重,判断其是否满足要求,若满足要求则作为一个备用解,总体时间复杂度为O((2^n)*n).
动态规划的思路,需要先明确状态,这里的一个状态受到两个条件的约束,即物品的数目及物品的总重量,确定状态为: dp[i][c] ,表示考虑将i 个物品放入容量为c的背包,并使其价值最大。接下来是状态转移方程:对于新来第i个物品,我们可以选择将其纳入背包,也可以选择不将其纳入背包,分别对应:dp[i][c] = dp[i-1][c-w[i]]+v[i] , dp[i][c] = dp[i-1][c], 然后从这两者中选出最优值(最大),故最终可以确定状态转移方程为 dp[i][c] = max( (dp[i-1][c-w[i]]+v[i]), dp[i-1][c] ). 此时,时间复杂度为O(n*c), 空间复杂度也为O(n*c).
优化思路:实际上第i行元素的值仅仅依赖于第i-1行元素的值,理论上只需要保持两行元素即可,此时空间复杂度可以优化为O(2*c), 也即O(c). 通过这个思路可以进一步优化为只需保持一行元素即可。
0-1背包问题的延伸
- 完全背包问题:每个物品可以无限使用
- 多重背包问题:每个物品不止一个,有num(i)个。
- 多维费用背包问题:要考虑物品的体积和重量两个维度的限制。(三维数组记录)
- 物品间加入更多约束:物品间可以相互排斥也可以相互依赖
416. 分割等和子集
给定一个只包含正整数的非空数组。是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
注意:
每个数组中的元素不会超过 100
数组的大小不会超过 200示例 1:
输入: [1, 5, 11, 5]
输出: true
解释: 数组可以分割成 [1, 5, 5] 和 [11].
示例 2:
输入: [1, 2, 3, 5]
输出: false
解释: 数组不能分割成两个元素和相等的子集.
class Solution {
public boolean canPartition(int[] nums) {
//动态规划,背包问题,从nums中选择一部分数字组合,填满容量为sum/2的背包
int n=nums.length;
if(n == 0){
return false;
}
//确定背包c的大小
int sum = 0;
for(int i=0; i<n; i++){
sum+=nums[i];
}
int c = sum/2;
//两个相等的整数的和一定为偶数
if(sum%2==1){
return false;
}
//动态规划
//明确状态:dp[m][n] 考虑是否将第m个数字放入容量为n的背包
boolean[][] dp = new boolean[n][c+1];
//状态初始化
for(int i=0; i<=c; i++){
if(i!=nums[0]){
dp[0][i] = false;
}else{
dp[0][i] = true;
}
}
//状态转移方程:dp[m][n] = dp[m-1][n] || dp[m-1][n-nums[m]]
for(int i=1; i<n; i++){
for(int j=0; j<=c; j++){
dp[i][j] = dp[i-1][j];
if(nums[i]<=j){
dp[i][j] = dp[i-1][j] || dp[i-1][j-nums[i]];
}
}
}
return dp[n-1][c];
}
}
322
377
474
139
494
300. 最长上升子序列(LIS问题)
给定一个无序的整数数组,找到其中最长上升子序列的长度。
示例:
输入: [10,9,2,5,3,7,101,18]
输出: 4
解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。说明:
可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。
你算法的时间复杂度应该为 O(n2) 。
思路:动态规划
class Solution {
//动态规划思路,时间复杂度为O(n^2)
public int lengthOfLIS(int[] nums) {
int n = nums.length;
if(n==0){
return 0;
}
//定义状态,dp[i] 表示以第i个元素结尾的最长上升子序列的长度,i一定要取到
int[] dp = new int[n];
//初始化状态
for(int i=0; i<n; i++){
dp[i] = 1;
}
//状态转移方程, dp[k] = max( 1+dp[j] if nums[k]>nums[j] )
for(int k=1; k<n; k++){
for(int j=k-1; j>=0; j--){
if(nums[k]>nums[j]){
dp[k] = Math.max(dp[k], 1+dp[j]);
}
}
}
//最大值不一定是dp[n-1]
int res = 1;
for(int i=0; i<n; i++){
res = Math.max(res, dp[i]);
}
return res;
}
}
376
最长公共子序列(LCS问题)
给出两个字符串S1和S2,求这两个字符串的最长公共子序列的长度。该问题有着广泛的应用,如基因工程上用来判断两段基因的相似程度
动态规划思路:
明确状态:dp[m][n] : 表示S1[0...m] 和 S2[0...n]的最长公共子序列的长度
状态转移方程:if s1[m]==s2[n] , dp[m][n] = 1+dp[m-1][n-1]; if s1[m] != s2[n], dp[m][n] = max( dp[m][n-1], dp[m-1][n] ).
dijkstra 单源最短路径算法也是动态规划
动态规划思路:
明确状态:shortestPath(i) : 表示从源点到 i 的最短路径的长度
状态转移方程:shortestPath(x) = min( shortestPath(a) + w(a->x) ).