细谈动态规划

  如果一个问题是有好多个重叠子问题,使用动态规划是最有效的。动态规划中每一个状态一定是由上一个状态推导出来的,这一点区别于贪心,贪心没有推导,而是从局部中直接选出最优的。

动态规划步骤:

1. 确定 dp 数组( dp table )以及下标的含义
2. 确定递推公式
3. dp 数组如何初始化
4. 确定遍历顺序

一.基础题目:

(1)斐波那契数列
1. 确定 dp 数组( dp table )以及下标的含义
  dp[i]代表n==i时,斐波那契值;
2. 确定递推公式
  dp[i] = dp[i-1] + dp[i-2];
3. dp 数组如何初始化
dp[0] = 0,dp[1] = 1;
4. 确定遍历顺序
由动态转移方程可知:dp[i]依赖dp[i-1]、dp[i-2]值,可确定遍历顺序为从前往后。
public int Fibonacci(int n) {
        /*
        int[] ret = new int[n+1];
        ret[1] = 1;
        ret[2] = 1;
        for(int i=3;i<n+1;i++){
            ret[i] = ret[i-2] + ret[i-1];
        }
        return ret[n];*/

        //优化:不需要记录整个数组
        int[] dp = new int[2];
        dp[0] = 0;
        dp[1] = 1;
        for(int i=2;i<=n;i++){
            int sum = dp[0]+dp[1];
            dp[0] = dp[1];
            dp[1] = sum;
        }
        return dp[1];
}
(2)不同路径的数目(一)
1. 确定 dp 数组( dp table )以及下标的含义
  dp[i][j] 表示从(0,0)走到(i,j)有dp[i][j]条路径;
2. 确定递推公式
dp[i][j] = dp[i-1][j]+dp[i][j-1];
3. dp 数组如何初始化
由于机器人只能向下或者向右走,那么从(0,0)到(i,0)、(0,j)的方式都为1;即dp[i][0]=1,dp[0][j]=1;
4. 确定遍历顺序
从上到下,从左到右。
public int uniquePaths (int m, int n) {
        // write code here
        int[][] dp = new int[m][n];
        for(int i=0;i<m;i++){
            dp[i][0] = 1;
        }
        for(int i=0;i<n;i++){
            dp[0][i] = 1;
        }
        for(int i=1;i<m;i++){
            for(int j=1;j<n;j++){
                dp[i][j] = dp[i-1][j]+dp[i][j-1];
            }
        }
        return dp[m-1][n-1];
    }
(3)不同的二叉搜索树
1. 确定 dp 数组( dp table )以及下标的含义
  dp[i]表示i个节点的二叉搜索树的个数;
2. 确定递推公式
  dp[i] += dp[j-1]*dp[i-j];
3. dp 数组如何初始化
  dp[0] = 1;
   dp[ j 为头结点左⼦树节点数量 ] * dp[ j 为头结点右⼦树节点数量 ] j 为头结点左⼦树节点数量为 0 ,也需要 dp[ j 为头结点左⼦树节点数量 ] = 1 , 否则乘法的 结果就都变成 0 了;
4. 确定遍历顺序
遍历i中每个元素作为头节点的个数
public int numTrees (int n) {
        // write code here
        int[] dp = new int[n+1];
        dp[0] = 1;
        for(int i=1;i<n+1;i++){
            for(int j=1;j<=i;j++){
                dp[i] += dp[j-1]*dp[i-j];
            }
        }
        return dp[n];
    }

二.背包问题:

(一)01背包
01背包理论基础
N 件物品和⼀个最多能被重量为 W 的背包。第 i 件物品的重量是 weight[i] ,得到的价值是
value[i] 每件物品只能⽤⼀次 ,求解将哪些物品装⼊背包⾥物品价值总和最⼤。
二维数组:
1. 确定 dp 数组( dp table )以及下标的含义
dp[i][j] 表⽰从下标为[0,i] 的物品⾥任意取,放进容量为j 的背包,价值总和最⼤是多少;
2. 确定递推公式
a.当不选取i物品,那dp[i][j]直接可以由dp[i-1][j]得出,即dp[i][j] = dp[i-1][j];
b.当选取i物品时,dp[i-1][j-weight[i]]是不放i物品的最大价值,那么dp[i-1][j-weight[i]]+value[i]就是放i物品的最大价值;
dp[i][j] = Math.max(dp[i-1][j],dp[i-1][j-weight[i]]+value[i]);
3. dp 数组如何初始化
dp[i][0]表示物品(0,i)中放入容量为0的背包里的最大价值,容量为0说明任何物品都不能放到背包中,那么价值都为0;
dp[0][j]说明下标为0的物品放入容量为j的背包中的最大价值,此时就分为两种情况:
        如果j>weight[0],0物品可以放入背包中,那么dp[0][j]=value[0];
        如果j<weight[0],0物品不能放入背包中,那么dp[0][j]=0;
// 正序遍历
for (int j = weight[0]; j <= bagWeight; j++) {
 dp[0][j] = value[0];
}
4. 确定遍历顺序
先遍历物品,再遍历背包容量。
// weight数组的⼤⼩ 就是物品个数
for(int i = 1; i < weight.size(); i++) { // 遍历物品
   for(int j = 0; j <= bagWeight; j++) { // 遍历背包容量
     if (j < weight[i]) {
        dp[i][j] = dp[i - 1][j]; // 这个是为了展现dp数组⾥元素的变化
     } else {
        dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] +value[i]);
     }
   }
}
一维数组:
1. 确定 dp 数组( dp table )以及下标的含义
  dp[j]表示容量为j的物品,所背物品的最大价值为dp[j];
2. 确定递推公式
  a. dp[j] 可以通过 dp[j - weight[j]] 推导出来, dp[j - weight[i]] 表⽰容量为 j - weight[i] 的背包所背 的最⼤价值;
  b. dp[j - weight[i]] + value[i] 表⽰ 容量为 j - 物品 i 重量 的背包 加上 物品 i 的价值。(也就是容 量为 j 的背包,放⼊物品 i 了之后的价值即: dp[j]);
dp[j] = Math.max(dp[j],dp[j-weight[i]]+value[i];
3. dp 数组如何初始化
  dp[j]当背包容量为0时,不能放入任何物品,价值只能为0;
  dp[0] = 0;
4. 确定遍历顺序
  倒序遍历,保证每个物品只被记录一次。
  如果正序遍历,dp[1] = dp[1-weight[0]]+value[0],dp[2] = dp[2-weight[0]]+value[0],那么0物品就被放入了两次。
for(int i = 0; i < weight.size(); i++) { // 遍历物品
 for(int j = bagWeight; j >= weight[i]; j--) { // 遍历背包容量
 dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
 }
}
(1)分割等和子集   https://leetcode-cn.com/problems/partition-equal-subset-sum/
题目:给你一个  只包含正整数 的  非空 数组  nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
1. 确定 dp 数组( dp table )以及下标的含义
dp[j]表示背包容量为j,最大可以凑成j的子集总和为dp[j];
2. 确定递推公式
dp[j] = Math.max(dp[j],dp[j-weight[i]]+value[i]);
本题中weight[i]和value[i]均为nums[i],所以
dp[j] = Math.max(dp[j],dp[j-nums[i]]+nums[i]);
3. dp 数组如何初始化
dp[0] = 0;
4. 确定遍历顺序
倒叙遍历
public boolean canPartition(int[] nums) {
        int sum = 0;
        for(int i=0;i<nums.length;i++){
            sum += nums[i];
        }
        if(sum % 2 == 1){
            return false;
        }
        int target = sum/2;
        int[] dp = new int[target+1];
        dp[0] = 0;
        for(int i=0;i<nums.length;i++){
            for(int j=target;j>=nums[i];j--){
                dp[j] = Math.max(dp[j],dp[j-nums[i]]+nums[i]);
            }
        }
        return dp[target]==target ? true : false;
    }
(2)最后一块石头的重量Ⅱ   https://leetcode-cn.com/problems/last-stone-weight-ii/
题目:有一堆石头,用整数数组  stones 表示。其中  stones[i] 表示第  i 块石头的重量。
每一回合,从中选出 任意两块石头,然后将它们一起粉碎。假设石头的重量分别为  x 和  y,且  x <= y。那么粉碎的可能结果如下:
 如果  x == y,那么两块石头都会被完全粉碎;
 如果  x != y,那么重量为  x 的石头将会完全粉碎,而重量为  y 的石头新重量为  y-x
最后, 最多只会剩下一块 石头。返回此石头  最小的可能重量 。如果没有石头剩下,就返回  0
1. 确定 dp 数组( dp table )以及下标的含义
dp[j]表示背包容量为j,最多可以背dp[j]重的石头;
2. 确定递推公式
dp[j] = Math.max(dp[j],dp[j-weight[i]]+value[i]);
本题中weight[i]和value[i]均为stone,所以
dp[j] = Math.max(dp[j],dp[j-stone[i]]+stone[i]);
3. dp 数组如何初始化
dp[0] = 0;
4. 确定遍历顺序
倒叙遍历
public int lastStoneWeightII(int[] stones) {
        int[] dp = new int[1501];
        int sum = 0;
        for(int i=0;i<stones.length;i++){
            sum += stones[i];
        }
        int target = sum/2;
        for(int i=0;i<stones.length;i++){
            for(int j=target;j>=stones[i];j--){
                dp[j] = Math.max(dp[j],dp[j-stones[i]]+stones[i]);
            }
        }
        return sum - 2*dp[target];
    }
(3)目标和   https://leetcode-cn.com/problems/target-sum/
  题目:给你一个非负整数数组  nums 和一个整数  target 。
向数组中的每个整数前添加  '+' 或  '-' ,然后串联起所有整数,可以构造一个  表达式 :
例如, nums = [2, 1] ,可以在  2 之前添加  '+' ,在  1 之前添加  '-' ,然后串联起来得到表达式  "+2-1" 。
返回可以通过上述方法构造的、运算结果等于  target 的不同  表达式 的数目。
   假设加法的和为x,减法的和应为sum-x,那么有x-(sum-x)=target,->x = (target+sum)/2。问题就转化为填满容量为x的背包,有多少种方法;
1. 确定 dp 数组( dp table )以及下标的含义
  dp[j]表示填满容量为j的背包,有dp[j]种方法;
2. 确定递推公式
  dp[j] += dp[j-nums[i]];
3. dp 数组如何初始化
  dp[0] = 1,填满容量为0的背包,只有一种方法就是放入0件物品;
4. 确定遍历顺序
  倒叙遍历
public int findTargetSumWays(int[] nums, int target) {
        int len = nums.length;
        int sum = 0;
        for(int i = 0; i < len; i++){
            sum += nums[i];
        }
        //修枝 left为小数 而target全为整数 
        //全为正或全为负 都不能达到结果
        if((sum + target) % 2 != 0 || sum < Math.abs(target)){
            return 0;
        }
        int left = (sum + target) / 2;
        int[] dp = new int[left + 1];
        dp[0] = 1;
        for(int i = 0; i < len; i++){
            for(int j = left; j >= nums[i]; j--){
                dp[j] = dp[j] + dp[j - nums[i]];
            }
        }
        return dp[left];
    }

(二)完全背包

  完全背包理论
N 件物品和⼀个最多能背重量为 W 的背包。第 i 件物品的重量是 weight[i] ,得到的价值是
value[i] 每件物品都有⽆限个(也就是可以放⼊背包多次) ,求解将哪些物品装⼊背包⾥
物品价值总和最⼤。
  完全背包较01背包,唯一不同的就是,完全背包物品有无数件。
  01背包 一个物品只能被添加一次,所以要使用逆序从大到小遍历;而完全背包一个物品可以被多次添加,要使用正序从小到大遍历。同样循环嵌套也可以交换顺序。
for(int i = 0; i < weight.size(); i++) { // 遍历物品
 for(int j = weight[i]; j < bagWeight ; j++) { // 遍历背包容量
 dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
 }
}
(1)零钱兑换Ⅱ   https://leetcode-cn.com/problems/coin-change-2/
  题目: 给定不同⾯额的硬币和⼀个总⾦额。写出函数来计算可以凑成总⾦额的硬币组合数。假设每 ⼀种⾯额的硬币有⽆限个。
1. 确定 dp 数组( dp table )以及下标的含义
  dp[j] 表示凑成总金额为j的组合数为dp[j];
2. 确定递推公式
dp[j] += dp[j-num[i]];
3. dp 数组如何初始化
d[0] = 1;
4. 确定遍历顺序
正序遍历,一个面额的硬币可以用多次。
int change(int amount, int[ coins) {
 int[] dp = new int[amount+1];
 dp[0] = 1;
 for (int i = 0; i < coins.size(); i++) { // 遍历物品
   for (int j = coins[i]; j <= amount; j++) { // 遍历背包
      dp[j] += dp[j - coins[i]];
   }
 }
  return dp[amount];
 }
总结(背包递推公式):
1.问能否装满背包(或者做多装多少):dp[j] = math.max(dp[j],dp[j-num[i]]+nums[i]
2.问装满背包有多少种方法:dp[j] += dp[j-nums[i]]
3.问装满背包的最大价值:dp[j] = Math.max(dp[j],dp[j-weight[i]]+value[i])
4.问装满背包所有物品的最小个数:dp[j] = Math.min(dp[j-nums[i]]+1,dp[j])

三.股票问题

(1)买卖股票的最佳时机
题目:假设你有一个数组prices,长度为n,其中prices[i]是股票在第i天的价格,请根据这个价格数组,返回买卖股票能获得的最大收益
1.你可以买入一次股票和卖出一次股票,并非每天都可以买入或卖出一次,总共只能买入和卖出一次,且买入必须在卖出的前面的某一天
2.如果不能获取到任何利润,请返回0
3.假设买入卖出均无手续费
1. 确定 dp 数组( dp table )以及下标的含义
  dp[i][0]表示第i天持有股票所得最多现金;
  dp[i][1]表示第i天不持有股票所得最多现金;
2. 确定递推公式
dp[i][0]:
 a.第i-1天就已经持有股票:dp[i][0] = dp[i-1][0];
 b.第i天买入股票:dp[i][0] = -price[i];
  dp[i][0] = Math.max(dp[i-1][0],-price[i]);
dp[i][1]:
 a.第i-1天不持有股票:dp[i][1] = dp[i-1][1];
 b.第i天卖出股票:dp[i][1] = dp[i-1][0]+price[i];
  dp[i][1] = Math.max(dp[i-1][1],dp[i-1][0]+price[i]);
3. dp 数组如何初始化
dp[0][0],第一天持有股票,那一定是第一天刚买的,一定步数从前一天继承下来的,那么dp[0][0] = -price[0];
dp[0][1],第一天不可能卖出股票,也不可能从前一天继承,那么dp[0][1] = 0;
4. 确定遍历顺序
从前往后正序遍历。
public int maxProfit (int[] prices) {
        // write code here
        int n = prices.length;
        int[][] dp = new int[n][2];
        dp[0][0] = -prices[0];
        dp[0][1] = 0;
        for (int i = 1; i < n; i++) {
            dp[i][0] = Math.max(dp[i - 1][0], -prices[i]);
            dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] + prices[i]);
        }
        return Math.max(dp[n - 1][0], dp[n - 1][1]);
    }
(2)买卖股票的最佳时机Ⅱ
假设你有一个数组prices,长度为n,其中prices[i]是某只股票在第i天的价格,请根据这个价格数组,返回买卖股票能获得的最大收益
1. 你可以多次买卖该只股票,但是再次购买前必须卖出之前的股票
2. 如果不能获取收益,请返回0
3. 假设买入卖出均无手续费
1. 确定dp 数组( dp table )以及下标的含义
  dp[i][0]表示第i天持有股票所得最多现金;
  dp[i][1]表示第i天不持有股票所得最多现金;
2. 确定递推公式
   dp[i][0]:
 a.第i-1天就已经持有股票:dp[i][0] = dp[i-1][0];
 b.第i天买入股票:dp[i][0] = dp[i-1][1]-price[i];
  dp[i][0] = Math.max(dp[i-1][0],dp[i-1][1]-price[i]);
dp[i][1]:
 a.第i-1天不持有股票:dp[i][1] = dp[i-1][1];
 b.第i天卖出股票:dp[i][1] = dp[i-1][0]+price[i];
  dp[i][1] = Math.max(dp[i-1][1],dp[i-1][0]+price[i]);
3. dp 数组如何初始化
dp[0][0] = -price[0];
dp[0][1] = 0;
4. 确定遍历顺序
正序遍历。
public int maxProfit (int[] prices) {
        // write code here
        int n = prices.length;
        int[][] dp = new int[n][2];
        dp[0][0] = -prices[0];
        dp[0][1] = 0;
        for(int i=1;i<n;i++){
            dp[i][0] = Math.max(dp[i-1][0],dp[i-1][1]-prices[i]);
            dp[i][1] = Math.max(dp[i-1][1],dp[i-1][0]+prices[i]);
        }
        return Math.max(dp[n-1][0],dp[n-1][1]);
    }
(3)买卖股票的最佳时机Ⅲ
题目:假设你有一个数组prices,长度为n,其中prices[i]是某只股票在第i天的价格,请根据这个价格数组,返回买卖股票能获得的最大收益
1. 你最多可以对该股票有两笔交易操作,一笔交易代表着一次买入与一次卖出,但是再次购买前必须卖出之前的股票
2. 如果不能获取收益,请返回0
3. 假设买入卖出均无手续费
1. 确定 dp 数组( dp table )以及下标的含义
  一天共个五个状态:0:没有操作,1:第一次买入,2:第一次卖出,3,第二次买入,4,第二次卖出;
  dp[i][j] 表示第i天j状态下所得的最多现金;
2. 确定递推公式
  dp[i][0] = dp[i-1][0];
  dp[i][1] = Math.max(dp[i-1][1],dp[i-1][0]-prices[i]);
  dp[i][2] = Math.max(dp[i-1][2],dp[i-1][1]+prices[i]);
  dp[i][3] = Math.max(dp[i-1][3],dp[i-1][2]-prices[i]);
  dp[i][4] = Math.max(dp[i-1][4],dp[i-1][3]+prices[i]);
3. dp 数组如何初始化
  dp[0][0] = 0;
  dp[0][1] = -prices[0];
  dp[0][2] = 0;
  dp[0][3] = -prices[0];
  dp[0][4] = 0;
4. 确定遍历顺序
从第一支股票到第n支股票,依次遍历。
public int maxProfit (int[] prices) {
        // write code here
        int n = prices.length;
        int[][] dp = new int[n][5];
        //0:无操作 1:第一次买入 2:第一次卖出 3:第二次买入 4:第二次卖出
        dp[0][0] = 0;
        dp[0][1] = -prices[0];
        dp[0][2] = 0;
        dp[0][3] = -prices[0];
        dp[0][4] = 0;
        for (int i = 1; i < n; i++) {
            dp[i][0] = dp[i - 1][0];
            dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] - prices[i]);
            dp[i][2] = Math.max(dp[i - 1][2], dp[i - 1][1] + prices[i]);
            dp[i][3] = Math.max(dp[i - 1][3], dp[i - 1][2] - prices[i]);
            dp[i][4] = Math.max(dp[i - 1][4], dp[i - 1][3] + prices[i]);
        }
        return dp[n - 1][4];
    }
(4)买卖股票的最佳时机Ⅳ
 题目:假设你有一个数组prices,长度为n,其中]prices[i]是某只股票在第i天的价格,请根据这个价格数组,返回买卖股票能获得的最大收益
1. 你最多可以对该股票有k笔交易操作,一笔交易代表着一次买入与一次卖出,但是再次购买前必须卖出之前的股票
2. 如果不能获取收益,请返回0
3. 假设买入卖出均无手续费
1. 确定 dp 数组( dp table )以及下标的含义
  一天共个五个状态:0:没有操作,1:第一次买入,2:第一次卖出,3,第二次买入,4,第二次卖出;
  dp[i][j] 表示第i天j状态下所得的最多现金;
2. 确定递推公式
 由买卖股票的最佳时机Ⅲ初始化结论可知,dp[i][j]递推公式存在规律;
3. dp 数组如何初始化
 由买卖股票的最佳时机Ⅲ初始化结论可知,当j为奇数时,均等于-prices[i];
4. 确定遍历顺序
从第一支股票到第n支股票,依次遍历。
public int maxProfit (int[] prices,int k) {
   int[][] dp = new int[n][2*k+1];
   for(int j=1;j<2*k;j+=2){
       dp[0][j] = -prices[0];
   }
   for(int i=1;i<n;i++){
       for(int j=0;j<2*k;j+=2){
          dp[i][j+1] = Math.max(dp[i-1][j+1],dp[i-1][j]-prices[i]);
          dp[i][j+2] = Math.max(dp[i-1][j+2],dp[i-1][j+1]+prices[i]);
       }
   }
   System.out.println(dp[n-1][2*k]);
}
(5)买卖股票的最佳时机含冷冻期  
https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-with-cool
down/
public int maxProfit(int[] prices) {
        //0:买入 1:卖出已度过冷冻期丹没买入
        //2:卖出 3:冷冻期
        int[][] dp = new int[prices.length][4];
        dp[0][0] = -prices[0];
        for(int i=1;i<prices.length;i++){
            dp[i][0] = Math.max(dp[i-1][0],Math.max(dp[i-1][1],dp[i-1][3])-prices[i]);
            dp[i][1] = Math.max(dp[i-1][1],dp[i-1][3]);
            dp[i][2] = dp[i-1][0]+prices[i];
            dp[i][3] = dp[i-1][2];
        }
        return Math.max(dp[prices.length-1][1],Math.max(dp[prices.length-1][2],dp[prices.length-1][3]));
    }
(6)买卖股票的最佳时机含手续费  
https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-with-tran
saction-fee/
public int maxProfit(int[] prices, int fee) {
        int[][] dp = new int[prices.length][2];
        dp[0][0] = -prices[0];
        dp[0][1] = 0;
        for(int i=1;i<prices.length;i++){
            dp[i][0] = Math.max(dp[i-1][0],dp[i-1][1]-prices[i]);
            //与买卖股票的最佳时机Ⅱ不同就是再卖出时-fee
            dp[i][1] = Math.max(dp[i-1][1],dp[i-1][0]+prices[i]-fee);
        }
        return Math.max(dp[prices.length-1][0],dp[prices.length-1][1]);
    }

四.子序列问题

(一)子序列(不连续)
(1)最长上升子序列一
  题目:给定一个长度为 n 的数组 arr,求它的最长严格上升子序列的长度。
所谓子序列,指一个数组删掉一些数(也可以不删)之后,形成的新数组。
1. 确定 dp 数组( dp table )以及下标的含义
  dp[i]表示从0到i区间内的最长递增子序列;
2. 确定递推公式
  dp[i],位置i的最长子序列为j到0->i-1各位置最长子序列+1
  if(arr[i]>arr[j]){ dp[i]=Math.max(dp[i],dp[j]+1);
3. dp 数组如何初始化
  数组上每个位置的子序列最小值都为它本身;
4. 确定遍历顺序
  因为位置i的最长子序列与0->i-1位置最长子序列的值有关系,所以必须从前往后遍历。
public int LIS (int[] arr) {
        // write code here
        if(arr.length == 0){
            return 0;
        }
        int[] dp = new int[arr.length];
        for(int i=0;i<arr.length;i++){
            dp[i] = 1;
        }
        int ret = 1;
        for(int i=1;i<arr.length;i++){
            for(int j=0;j<i;j++){
                if(arr[i]>arr[j]){
                    dp[i] = Math.max(dp[i],dp[j]+1);
                }
            }
            if(ret < dp[i]){
                ret = dp[i];
            }
        }
        return ret;
    }
(二)子序列(连续)
(1)最长连续递增序列  
https://leetcode-cn.com/problems/longest-continuous-increasing-subsequenc
e/
题目:给定一个未经排序的整数数组,找到最长且  连续递增的子序列,并返回该序列的长度。
1. 确定 dp 数组( dp table )以及下标的含义
  dp[i]表示以i位置结尾的区间内的最长连续递增子序列;
2. 确定递推公式
  dp[i],位置i+1的最长子序列为i位置最长子序列+1;
  if(arr[i+1]>arr[i]){ dp[i+1]=dp[i]+1;
3. dp 数组如何初始化
  数组上每个位置的子序列最小值都为它本身;
  整个区间内都有:dp[i] = 1;
4. 确定遍历顺序
  因为位置i+1的最长子序列与i位置最长子序列的值有关系,所以必须从前往后遍历。
public int findLengthOfLCIS(int[] nums) {
        int[] dp = new int[nums.length];
        for(int i=0;i<nums.length;i++){
            dp[i] = 1;
        }
        int ret = 1;
        for(int i=0;i<nums.length-1;i++){
            if(nums[i+1]>nums[i]){
                dp[i+1] = dp[i]+1;
            }
            if(ret < dp[i+1]){
                ret = dp[i+1];
            }
        }
        return ret;
    }
  不连续递增⼦序列的跟前0-i 个状态有关,连续递增的⼦序列只跟前⼀个状态有关.
(2)最长公共子序列一
题目:给两个整数数组  nums1 和  nums2 ,返回  两个数组中 公共的 、长度最长的子数组的长度 
1. 确定 dp 数组( dp table )以及下标的含义
  dp[i][j]表示以i位置结尾的nums1数组,和以j位置结尾的nums2数组的最长公共子序列;
2. 确定递推公式
  dp[i][j],i位置j位置的最长公共子序列与前一个位置的值是否相同,
  相同时,dp[i][j] = dp[i-1][j-1]+1;
3. dp 数组如何初始化
  dp[0][0] = 0,dp[i][0] = 0,dp[0][j] = 1;
4. 确定遍历顺序
  因为位置i,j的最长子序列与i-1,j-1位置最长子序列的值有关系,所以必须从前往后遍历。
public int findLength(int[] nums1, int[] nums2) {
        int[][] dp = new int[nums1.length+1][nums2.length+1];
        int ret = 0;
        for(int i=1;i<=nums1.length;i++){
            for(int j=1;j<=nums2.length;j++){
                if(nums1[i-1] == nums2[j-1]){
                    dp[i][j] = dp[i-1][j-1]+1;
                }
                if(ret < dp[i][j]){
                    ret = dp[i][j];
                }
            }
        }
        return ret;
    }
(三)编辑距离
(1)编辑距离
  题目:给定两个字符串 str1 和 str2 ,请你算出将 str1 转为 str2 的最少操作数。
你可以对字符串进行3种操作:
1.插入一个字符
2.删除一个字符
3.修改一个字符。
1. 确定 dp 数组( dp table )以及下标的含义
dp[i][j]表示以i-1结尾的str1与以i-1结尾的str2,最小编辑距离为dp[i][j;
2. 确定递推公式
 当str1[i-1] == str2[j-1]时,dp[i][j] = dp[i-1][j-1];
 当str1[i-1] != str2[j-1]时,dp[i][j] = Math.min(dp[i-1][j-1],Math.min(dp[i-1][j],dp[i][j-1]))+1;
3. dp 数组如何初始化
  dp[i][0] = i,dp[0][j] = j;
4. 确定遍历顺序
因为dp[i][j]的值取决于dp[i-1][j],dp[i][j-1],dp[i-1][j-1],所以应该选择从前往后遍历。
public int editDistance (String str1, String str2) {
        // write code here
        int n = str1.length(),m = str2.length();
        int[][] dp = new int[n+1][m+1];
        for(int i=0;i<n+1;i++){
            dp[i][0] = i;
        }
        for(int j=0;j<m+1;j++){
            dp[0][j] = j;
        }
        for(int i=1;i<n+1;i++){
            for(int j=1;j<m+1;j++){
                if(str1.charAt(i-1) == str2.charAt(j-1)){
                    dp[i][j] = dp[i-1][j-1];
                }else{
                    dp[i][j] = Math.min(dp[i-1][j-1],Math.min(dp[i-1][j],dp[i][j-1]))+1;
                }
            }
        }
        return dp[n][m];
    }

五.回文

(1)回文子串   https://leetcode-cn.com/problems/palindromic-substrings/
题目:给你一个字符串  s ,请你统计并返回这个字符串中  回文子串 的数目。
1. 确定 dp 数组( dp table )以及下标的含义
  dp[i][j]表示s从i位置到j位置的字符串是否为回文串;
2. 确定递推公式
  dp[i][j] s.charAt(i)==s.charAt(j)
  如果i-j==1 如aa 一定为回文串 dp[i][j] =true;
  如果i-j>1 如abca 是否为回文串,取决于dp[i+1][j-1]的值;
3. dp 数组如何初始化
  dp[i][j] = false;
4. 确定遍历顺序
  因为要保证dp[i+1][j-1]都是经过计算的,所以要从下到上,从左到右遍历。
public int countSubstrings(String s) {
        boolean[][] dp = new boolean[s.length()][s.length()];
        int ret = 0;
        for(int i=s.length()-1;i>=0;i--){
            for(int j=i;j<=s.length()-1;j++){
                if(s.charAt(i) == s.charAt(j)){
                    if(j-i<=1){
                        dp[i][j] = true;
                        ret++;
                    }else{
                        dp[i][j] = dp[i+1][j-1];
                        if(dp[i][j] == true){
                            ret++;
                        }
                    }
                }
            }
        }
        return ret;
    }
(2)最长回文序列
题目:给定一个字符串,找到其中最长的回文子序列,并返回该序列的长度。
1. 确定 dp 数组( dp table )以及下标的含义
  dp[i][j] 表示str从i到j区间内回文子序列的长度;
2. 确定递推公式
  当str[i] == str[j]时,dp[i][j] = dp[i+1][j-1]+2;
  当str[i] != str[j]时,dp[i][j] = Math.max(dp[i+1][j],dp[i][j-1];
3. dp 数组如何初始化
  str的i位置到它本身就是一个回文子序列;
  dp[i][i] = 1;
4. 确定遍历顺序
  从下到上。
public int getLongestPalindrome (String str) {
  int[][] dp = new int[str.length()][str.length()];
  for(int i=0;i<str.length();i++){
      dp[i][i] = 1;
  }
  for(int i=str.length()-1;i>=0;i--){
      for(int j=i+1;j<str.length();j++){
           if(str.charAt(i) == str.charAt(j)){
              dp[i][j] = dp[i+1][j-1]+2;
           }else{
              dp[i][j] = Math.max(dp[i+1][j],dp[i][j-1]);
           }
      }
  }
  return dp[0][str.length()-1];
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值