alittlepeara / leetcode_notes

0 stars 0 forks source link

动态规划-leetcode #1

Open alittlepeara opened 5 months ago

alittlepeara commented 5 months ago

斐波那契数列

爬楼梯

问题: 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?

思路:(没想出来) 怎么爬到第5层? 第一种:第四层爬1 第二种:第三层爬2 所以 f(5) = f(4)+f(3) 斐波那契数列!

我写的代码

class Solution {
    public int climbStairs(int n) {
        int[]num = new int[n+1];
        for(int i = 1;i <= n;i ++){
            if(i <= 2){
                num[i] = i;
            }
            else{
                num[i] = num[i-1]+num[i-2];
            }
        }
        return num[n];
    }
}

复杂度: 时间复杂度和空间复杂度都是 O(n) 优化: 由于这里的 f(x)只和 f(x−1)与 f(x−2)有关,所以我们可以用「滚动数组思想」把空间复杂度优化成 O(1) 不用数组存储之前的所有结果,只用两个变量临时存储前两个答案 0 1 p q r 0 1 1 1 1 2 1 2 3 2 3 5 优化后的代码

class Solution {
     public int climbStairs(int n) {
        int p = 0,q = 1,r = 1;
        while(n>0){
            r = p+q;
            p = q;
            q = r;
            n--;
        }
        return r;
    }
}

以上方法是动态规划,自底向上,从f(1)计算到f(n)循环迭代 递归是自顶向下,从f(n)推到f(1) 普通的递归 递归树 时间复杂度:子问题个数乘以解决一个子问题需要的时间 子问题个数,即递归树中节点的总数。显然二叉树节点总数为指数级别,所以子问题个数为 O(2^n)。

int fib(int N) {
if (N == 1 || N == 2) return 1;
return fib(N - 1) + fib(N - 2);
}

然后计算解决一个子问题的时间,在本算法中,没有循环,只有 f(n - 1) + f(n - 2) 一个加法操作,时间为 O(1)。

所以,这个算法的时间复杂度为二者相乘,即 O(2^n),指数级别,爆炸。

观察递归树,很明显发现了算法低效的原因:存在大量重复计算

优化:带备忘录的递归

public int fib(int N) {
if (N < 1) return 0;
int[] memo = new int[N + 1];
Arrays.fill(memo, 0);
return helper(memo, N);
}
private int helper(int[] memo, int n) {
if (n == 1 || n == 2) return 1;
if (memo[n] != 0) return memo[n];
memo[n] = helper(memo, n - 1) + helper(memo, n - 2);
return memo[n];
}

复杂度: 子问题个数乘以解决一个子问题需要的时间 子问题个数现在为N,所以时间复杂度优化为O(n)

最小花费爬楼梯

题目: 给你一个整数数组 cost ,其中 cost[i] 是从楼梯第 i 个台阶向上爬需要支付的费用。一旦你支付此费用,即可选择向上爬一个或者两个台阶。 你可以选择从下标为 0 或下标为 1 的台阶开始爬楼梯。 请你计算并返回达到楼梯顶部的最低花费。 

示例 1:输入:cost = [10,15,20] 输出:15 解释:你将从下标为 1 的台阶开始。

  • 支付 15 ,向上爬两个台阶,到达楼梯顶部。 总花费为 15 。

示例 2:输入:cost = [1,100,1,1,1,100,1,1,100,1] 输出:6 解释:你将从下标为 0 的台阶开始。

  • 支付 1 ,向上爬两个台阶,到达下标为 2 的台阶。
  • 支付 1 ,向上爬两个台阶,到达下标为 4 的台阶。
  • 支付 1 ,向上爬两个台阶,到达下标为 6 的台阶。
  • 支付 1 ,向上爬一个台阶,到达下标为 7 的台阶。
  • 支付 1 ,向上爬两个台阶,到达下标为 9 的台阶。
  • 支付 1 ,向上爬一个台阶,到达楼梯顶部。 总花费为 6 。

我的思路: 爬到第n层一共花费f(n) f(n) = min [ f{n-1)+cost(n) , f(n-2)+cost(n) ] f(0) = cost(0) f(1) = cost(1) 要求的是cost(n) ⚠但是题目给出的cost数组下标是0-n-1,一共n个数,没有n这个下标,所以最终的答案要另外计算 我的代码

 class Solution {
    public int minCostClimbingStairs(int[] cost) {
        int n = cost.length;
        int[]f = new int[n+1];
        f[0] = cost[0];
        f[1] = cost[1];
        for(int i = 2;i<n;i++){
            f[i] = Math.min(f[i-1]+cost[i],f[i-2]+cost[i]);
        }
        return Math.min(f[n-1],f[n-2]);
    }
}

通过了!但是依然可以按上面那题的方法优化空间复杂度,不存储过程的变量 进一步:可以直接用题目中的数组cost来存储

 class Solution {
    public int minCostClimbingStairs(int[] cost) {
        int n = cost.length;
        for(int i = 2;i<n;i++){
            cost[i] = Math.min(cost[i-1]+cost[i],cost[i-2]+cost[i]);
        }
        return Math.min(cost[n-1],cost[n-2]);
    }
}

打家劫舍

题目: 你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。

给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。

示例 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 。

我的思路: dp[i] : 到下标i为止,能偷到的最多的钱 dp[i] = max( dp[i-1] , dp[i-2] + nums[i] ) dp[0] = nums[0] dp[1] = max( nums[0] , nums[1])

⚠要注意: 不是每一个数组长度都超过1,所以不是每个都有dp[1],要先判断,不然会越界

优化:用题目给的数组存储dp 空间复杂度为O(1)

我的代码:(优化后)

public class Solution {
    public int rob(int[] nums) {
        int n = nums.length;
        if(n>1){
            nums[1] = Math.max(nums[0],nums[1]);
        }
        for(int i = 2;i < n;i++){
            nums[i] = Math.max(nums[i-1],nums[i-2]+nums[i]);
        }
        return nums[n-1];
    }
}

删除并获得点数

题目:

给你一个整数数组 nums ,你可以对它进行一些操作。 每次操作中,选择任意一个 nums[i] ,删除它并获得 nums[i] 的点数。之后,你必须删除 所有 等于 nums[i] - 1 和 nums[i] + 1 的元素。 开始你拥有 0 个点数。返回你能通过这些操作获得的最大点数。

示例 1:输入:nums = [3,4,2] 输出:6 解释: 删除 4 获得 4 个点数,因此 3 也被删除。 之后,删除 2 获得 2 个点数。总共获得 6 个点数。

示例 2:输入:nums = [2,2,3,3,3,4] 输出:9 解释: 删除 3 获得 3 个点数,接着要删除两个 2 和 4 。 之后,再次删除 3 获得 3 个点数,再次删除 3 获得 3 个点数。 总共获得 9 个点数。

我的思路:(看了一点提示) 转换成打家劫舍 第一次想到的把223334转换成494,但是样例通过了,其他的没通过,因为比如13虽然看上去相邻其实不相邻 所以又想到新开一个数组count[i]存储数字i出现的次数,这样可以看出来是否真的相邻 子问题dp[j] = max( dp[j-1] , dp[j-2] + count[i] * i 所以需要新开两个数组,i和j是分别遍历的

我的代码:(修改了几次之后通过了,但是时间空间复杂度都需要优化)

public class Solution {
    public static void quickSort(int[]arr,int low,int high) {
        //下面这个一定要放在最前面,不然会越界i+1
        if (low > high) {
            return;
        }
        int i = low;
        int j = high;
        int temp = arr[low];
        while (i < j) {
            //一定要先右边再左边
            //可以大于等于
            while ((j > i) && arr[j] >= temp) {
                j--;
            }
            while ((i < j) && arr[i] <= temp) {
                i++;
            }
            if (i < j) {
                int t = arr[i];
                arr[i] = arr[j];
                arr[j] = t;
            }
        }
        arr[low] = arr[i];
        arr[i] = temp;
        quickSort(arr, low, i - 1);
        quickSort(arr, i + 1, high);
    }
    public int deleteAndEarn(int[] nums) {
        int n = nums.length;
        quickSort(nums,0,n-1);
        int[]count = new int[nums[n-1]+1];
        int[]dp = new int[n+1];
        int i,j;
        for(i = 0;i < n; i++){
            count[nums[i]]++;
        }
        i = 0;
        j = 0;
        while(count[i] == 0){
            i++;
        }
        dp[j] = count[i]*i;
        i++;
        j++;
        if(i<=nums[n-1]) {
            if (count[i] != 0) {
                dp[j] = Math.max(dp[j - 1], count[i] * i);
            } else {
                while (count[i] == 0) {
                    i++;
                }
                dp[j] = count[i] * i + dp[j - 1];
            }
            i++;
            j++;
            for (; i < count.length; i++) {
                if (count[i] != 0) {
                    if (count[i - 1] == 0) {
                        dp[j] = dp[j - 1] + count[i] * i;
                    } else {
                        dp[j] = Math.max(dp[j - 1], dp[j - 2] + count[i] * i);
                    }
                    j++;
                }
            }
        }
        return dp[j-1];
    }
}

优化: 需要优化的点:

代码

public class Solution {
    public int deleteAndEarn(int[] nums) {
        if(nums == null || nums.length == 0){
            return 0;
        }
        if(nums.length == 1){
            return nums[0];
        }
        int len = nums.length;
        int max = nums[0];
        for(int i = 0;i < len; i ++){
            max = Math.max(max,nums[i]);
        }
        int[]count = new int[max+1];
        for(int i = 0; i < len; i++){
            count[nums[i]]++;
        }
        int dp[] = new int[max+1];
        dp[1] = count[1]*1;
        for(int i = 2;i <= max; i++){
            dp[i] = Math.max(dp[i-1],dp[i-2]+count[i]*i);
        }
        return dp[max];
    }
}

矩阵类型

不同路径

题目

一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。 问总共有多少条不同的路径?

示例1: 输入:m = 3, n = 7 输出:28 示例 2:输入:m = 3, n = 2 输出:3 解释: 从左上角开始,总共有 3 条路径可以到达右下角。 1 向右 -> 向下 -> 向下 2 向下 -> 向下 -> 向右 3 向下 -> 向右 -> 向下 示例 3:输入:m = 7, n = 3 输出:28 示例 4:输入:m = 3, n = 3 输出:6

我的思路: ①用排列组合 但是通不过!!可能因为数太大了越界?或者是除法精度…… 所以! ②还是用动态规划! dp[i][j] = dp[i-1][j] + dp[i][j-1]

public class Solution {
    public int uniquePaths(int m, int n) {
        int[][]dp = new int[m][n];
        int i,j;
        for(j=0;j<n;j++){
            dp[0][j] = 1;
        }
        for(i = 0;i<m;i++){
            dp[i][0] = 1;
        }
        for(i = 1;i <m ; i ++){
            for(j = 1;j <n;j++){
                dp[i][j] = dp[i-1][j] + dp[i][j-1];
            }
        }
        return dp[m-1][n-1];
    }
}

三角形最小路径和

题目:

给定一个三角形 triangle ,找出自顶向下的最小路径和。每一步只能移动到下一行中相邻的结点上。相邻的结点 在这里指的是 下标 与 上一层结点下标 相同或者等于 上一层结点下标 + 1 的两个结点。也就是说,如果正位于当前行的下标 i ,那么下一步可以移动到下一行的下标 i 或 i + 1 。 

示例 1:输入:triangle = [[2],[3,4],[6,5,7],[4,1,8,3]] 输出:11 解释:如下面简图所示: 2 3 4 6 5 7 4 1 8 3 自顶向下的最小路径和为 11(即,2 + 3 + 5 + 1 = 11)。 示例 2:输入:triangle = [[-10]] 输出:-10

我的思路: 自顶向下,算出走到每一步的最小路径,最后比较最后一行得出最小的 ⚠ List的长度是 List.size() 二维的List取数 List.get(i).get(j)

我的代码

class Solution {
    public int minimumTotal(List<List<Integer>> triangle) {
        if(triangle.size() == 1)return triangle.get(0).get(0);
        int[][]dp = new int[triangle.size()][triangle.size()];
        dp[0][0] = triangle.get(0).get(0);
        int i,j;
        for( i = 1;i<dp.length;i++){
            dp[i][0] = triangle.get(i).get(0)+dp[i-1][0];
        }
        for(j = 1;j<dp.length;j++){
            dp[j][j] = triangle.get(j).get(j)+dp[j-1][j-1];
        }
        if(triangle.size() == 2)return Math.min(dp[1][0],dp[1][1]);
        for(i = 2;i<dp.length;i++){
            for(j = 1;j<i;j++){
                dp[i][j] = Math.min(dp[i-1][j],dp[i-1][j-1])+triangle.get(i).get(j);
            }
        }
        i = dp.length-1;
        int min = dp[i][0];
        for(j = 1;j<=i;j++){
            min = Math.min(min,dp[i][j]);
        }
        return min;
    }
}

优化: 从底下往上推,这样最后不用比较最后一行最小的,直接输出最顶上的一个就可以; 而且不用单独给最旁边的两列赋值,全部都可以用公式 定义dp数组的时候多定义一位,就不用单独给最后一行赋值(因为初值都是0,也可以用公式)

class Solution {
    public int minimumTotal(List<List<Integer>> triangle) {
        if(triangle.size() == 1)return triangle.get(0).get(0);
        int n = triangle.size();
        int[][]dp = new int[n+1][n+1];
        for(int i = n-1;i>=0;i--){
            for(int j = 0;j<=i;j++){
                dp[i][j] = Math.min(dp[i+1][j],dp[i+1][j+1])+triangle.get(i).get(j);
            }
        }
        return dp[0][0];
    }
}

下降路径最小和

题目

给你一个 n x n 的 方形 整数数组 matrix ,请你找出并返回通过 matrix 的下降路径 的 最小和 。 下降路径 可以从第一行中的任何元素开始,并从每一行中选择一个元素。在下一行选择的元素和当前行所选元素最多相隔一列(即位于正下方或者沿对角线向左或者向右的第一个元素)。具体来说,位置 (row, col) 的下一个元素应当是 (row + 1, col - 1)、(row + 1, col) 或者 (row + 1, col + 1) 。

我的代码:(空间复杂度很大)

class Solution {
    public int minFallingPathSum(int[][] matrix) {
        if(matrix[0].length==1){
            int min = 0;
            for(int i = 0;i<matrix.length;i++){
                min +=matrix[i][0];
            }
            return min;
        }
        int[][]dp = new int[matrix.length+1][matrix[0].length+1];
        for(int i = 1;i<=matrix.length;i++){
            for(int j = 1;j<=matrix[0].length;j++){
                if(j == 1){
                    dp[i][j] = Math.min(dp[i-1][j],dp[i-1][j+1])+matrix[i-1][j-1];
                }
                else if(j == matrix[0].length){
                   dp[i][j] = Math.min(dp[i-1][j],dp[i-1][j-1])+matrix[i-1][j-1];
                }
                else{
                    dp[i][j] = Math.min(Math.min(dp[i-1][j],dp[i-1][j+1]),dp[i-1][j-1])+matrix[i-1][j-1];
                }
            }
        }
        int min = dp[matrix.length][1];
        for(int j = 1;j<=matrix[0].length;j++){
            min = Math.min(min,dp[matrix.length][j]);
        }
        return min;
    }
}

优化: 不用新建数组,直接在原来的上面改 (这样还有一个好处就是第一行直接就是原来的,不用特殊处理,遍历从第二行开始) 还有,题目说了n×n!!!!!!!

class Solution {
    public int minFallingPathSum(int[][] matrix) {
        int n = matrix.length;
        if(n==1){
            int min = 0;
            for(int i = 0;i<n;i++){
                min +=matrix[i][0];
            }
            return min;
        }
        for(int i = 1;i<n;i++){
            for(int j = 0;j<n;j++){
                if(j == 0){
                    matrix[i][j] += Math.min(matrix[i-1][j],matrix[i-1][j+1]);
                }
                else if(j == n-1){              
                    matrix[i][j] += Math.min(matrix[i-1][j],matrix[i-1][j-1]);
                }
                else{
                    matrix[i][j] += Math.min(Math.min(matrix[i-1][j-1],matrix[i-1][j]),matrix[i-1][j+1]);
                }
            }
        }
        int min = matrix[n-1][0];
        for(int j = 0;j<n;j++){
            min = Math.min(min,matrix[n-1][j]);
        }
        return min;
    }
}

最大正方形

题目

在一个由 '0' 和 '1' 组成的二维矩阵内,找到只包含 '1' 的最大正方形,并返回其面积。 最大正方形

思路:(没有想到,看了提示才想到)

if(matrix[i][j] == '1'){                     dp[i][j] = min(dp[i-1][j-1],dp[i][j-1],dp[i-1][j])+1;                 }

其中,dp(i, j) 是以 matrix(i, j) 为 右下角 的正方形的最大边长。

代码

public class Solution {
    public int maximalSquare(char[][] matrix) {
        int m = matrix.length;
        int n = matrix[0].length;
        int[][]dp = new int[m][n];
        int i,j;
        int max = Integer.MIN_VALUE;
        for(i = 0;i <m;i++){
            dp[i][0] = (int)(matrix[i][0])-'0';
            max = Math.max(max,dp[i][0]*dp[i][0]);
        }
        for(j = 0;j <n;j++){
            dp[0][j] = (int)(matrix[0][j])-'0';
            max = Math.max(max,dp[0][j]*dp[0][j]);
        }
        for(i = 1;i<m;i++){
            for(j = 1;j<n;j++){
                if(matrix[i][j] == '1'){
                    dp[i][j] = Math.min(Math.min(dp[i-1][j-1],dp[i][j-1]),dp[i-1][j])+1;
                    max = Math.max(max,dp[i][j]*dp[i][j]);
                }
                else{
                    dp[i][j] = 0;
                }
            }
        }
        return max;
    }
}

动态规划在字符串中的应用

最长回文子串

题目: 给你一个字符串 s,找到 s 中最长的回文子串 (有多个就输出第一个)

思路:(没想出来,看了提示dp的递推之后自己写的代码) 状态转移方程, dp[i][j] = dp[i + 1] [j - 1] && s[i] == s[j] 但是ij相邻的情况要特殊考虑 循环怎么写想了好久(不是一般的i循环里面j循环,是01,12,23,34,,,02,13,24,,,03,14,,,04)

代码

public class Solution {
    public static String longestPalindrome(String s) {
        int i = 0,j = 0;
        int n = s.length()-1;
        int max = 1;
        String re = s.substring(0,1);
        int[][]dp = new int[n+1][n+1];
        for(;i<=n;i++){
            dp[i][i]=1;
        }
        for(int num = 1;num<=n;num++){
            for(i = 0;i+num<=n;i++){
                j = i+num;
                    if(num == 1){
                        if (s.charAt(i) == s.charAt(j)){
                            dp[i][j] = 2;
                            if(dp[i][j]>max){
                                re = s.substring(i,j+1);
                            }
                            max = Math.max(dp[i][j],max);
                        }
                        else{
                            dp[i][j] = 0;
                        }
                    }
                    else if(dp[i+1][j-1]!=0&&s.charAt(i)==s.charAt(j)){
                        dp[i][j] = dp[i+1][j-1]+2;
                        if(dp[i][j]>max){
                            re = s.substring(i,j+1);
                        }
                        max = Math.max(dp[i][j],max);
                    }
                    else{
                        dp[i][j] = 0;
                    }
            }
        }
        return re;
    }
}

单词拆分

思路:没想出来…… 主要是没想到用boolean的一维的dp,dp[i]来表示字符串s的前i个字符是否可以被拆分成字典中的单词!!!我一直在想用 dp[i][j] 表示s里的i-j位是否能被表示 状态转移方程: dp[i]=dp[j] && check(s[j..i−1]) 代码

class Solution {
    public boolean wordBreak(String s, List<String> wordDict) {
        int n = s.length();
        boolean dp[] = new boolean[n+1];
        dp[0] = true;
        int i;
        for( i = 1;i<=n;i++){
            for(int j = 0;j<=i;j++){
                if(dp[j]&&wordDict.contains(s.substring(j,i))){
                    dp[i] = true;
                    break;
                }
            }
        }
        return dp[n];
    }
}

编辑距离 (好难的题)

思路:根本没思路

dp[i][j] 代表 word1 中前 i 个字符,变换到 word2 中前 j 个字符,最短需要操作的次数;

dp[i-1][j-1] (表示替换):word1的前i-1个字符已经变换到word2的前j-1个字符的次数,说明word1的前i-1个和word2的前j-1个字符已经完成操作;那么对于word1的第i个怎么变成word2的第j个呢?这两个字符都存在,那么只能是替换了;所以dp[i][j] = dp[i-1][j-1]+1; dp[i][j-1] (表示插入):word1的前i个字符已经变换到word2的前j-1个字符的次数,当前word1的第i步字符已经用了,但是word2还差一个字符(因为当前只是处理了word2的前j-1个字符),那么插入一个字符就好了;所以dp[i][j] = dp[i][j-1]+1; dp[i-1][j] (表示删除):word1的前i-1个字符已经变换到word2的前j个字符的次数,当前word1仅用了前i-1个字符就完成了到word2的前j个字符的操作,所以word1的第i个字符其实没啥用了,那么删除操作就好了;所以dp[i][j] = dp[i-1][j]+1; ⚠我自己写代码的时候漏掉了这个特殊的定义,代表word1为空的时候,需要增加n2次;word2为空的时候,需要删除n1次

for(int j = 1;j<=n2;j++){
            dp[0][j] = j;
        }
        for(int i = 1;i<=n1;i++){
            dp[i][0] = i;
        }

代码

public class Solution {
    public int minDistance(String word1, String word2) {
        int n1 = word1.length();
        int n2 = word2.length();
        int[][]dp = new int[n1+1][n2+1];
        for(int j = 1;j<=n2;j++){
            dp[0][j] = j;
        }
        for(int i = 1;i<=n1;i++){
            dp[i][0] = i;
        }
        for(int i = 1; i <= n1;i++){
            for(int j = 1; j<=n2;j++){
                if(word1.charAt(i-1) == word2.charAt(j-1)){
                    dp[i][j] = dp[i-1][j-1];
                }
                else{
                    dp[i][j] = Math.min(Math.min(dp[i-1][j-1]+1,dp[i-1][j]+1),dp[i][j-1]+1);
                }
            }
        }
        return dp[n1][n2];
    }
}

两个字符串的最小ASCII删除和

题目

给定两个字符串s1 和 s2,返回 使两个字符串相等所需删除字符的 ASCII 值的最小和 。

示例 1:输入: s1 = "sea", s2 = "eat" 输出: 231 解释: 在 "sea" 中删除 "s" 并将 "s" 的值(115)加入总和。 在 "eat" 中删除 "t" 并将 116 加入总和。 结束时,两个字符串相等,115 + 116 = 231 就是符合条件的最小和。

示例 2:输入: s1 = "delete", s2 = "leet" 输出: 403 解释: 在 "delete" 中删除 "dee" 字符串变成 "let", 将 100[d]+101[e]+101[e] 加入总和。在 "leet" 中删除 "e" 将 101[e] 加入总和。 结束时,两个字符串都等于 "let",结果即为 100+101+101+101 = 403 。 如果改为将两个字符串转换为 "lee" 或 "eet",我们会得到 433 或 417 的结果,比答案更大。

思路: 不用关心它内部究竟是怎么实现的,只要知道状态转移方程就好 ⚠对这个else的理解:

if (s1.charAt(i - 1) == s2.charAt(j - 1)) {  
                    dp[i][j] = dp[i - 1][j - 1] + s1.charAt(i - 1);  
                } else {  
                    dp[i][j] = Math.min(dp[i - 1][j] + s1.charAt(i - 1), dp[i][j - 1] + s2.charAt(j - 1));  
                }  

dp[i][j]代表s1前i项和s2前j项的最小ascii删除距离,如果最后两项不相等,一定会至少删除一项,但是这个时候不是直接比较哪个ascii小,因为不是删了一项就结束的,还要加上剩下的i-1项和j项的最小ascii距离。 代码

public class Solution {
    public int minimumDeleteSum(String s1, String s2) {
        int n1 = s1.length();
        int n2 = s2.length();
        int[][]dp = new int[n1+1][n2+1];
        for(int i = 1;i <= n1;i ++){
            dp[i][0]=dp[i-1][0]+s1.charAt(i-1);
        }
        for(int j = 1;j <= n2;j ++){
            dp[0][j]=dp[0][j-1]+s2.charAt(j-1);
        }
        for(int i = 1; i <= n1;i ++){
            for(int j = 1; j <= n2; j ++){
                if(s1.charAt(i-1) == s2.charAt(j-1)){
                    dp[i][j] = dp[i-1][j-1];
                }
                else{
                    dp[i][j] = Math.min(dp[i-1][j]+s1.charAt(i-1),dp[i][j-1]+s2.charAt(j-1));
                }
            }
        }
        return dp[n1][n2];
    }
}

不同的子序列

题目

给你两个字符串 s 和 t ,统计并返回在 s 的 子序列 中 t 出现的个数。

示例 1:输入:s = "rabbbit", t = "rabbit"输出:3 解释: 如下所示, 有 3 种可以从 s 中得到 "rabbit" 的方案。 rabbbit rabbbit rabbbit 示例 2:输入:s = "babgbag", t = "bag" 输出:5 解释: 如下所示, 有 5 种可以从 s 中得到 "bag" 的方案。 babgbag babgbag babgbag babgbag babgbag

思路:(自己想的!困难题!) 秉持着不管过程,只想状态转移方程 画了个图: 画的图 一开始想的是这样的:

for(int j = i; j <= n1; j++){
                if(s.charAt(j-1)==t.charAt(i-1)){
                    if(i==1){
                        dp[i][j]=dp[i][j-1]+1;
                    }
                    else{
                        dp[i][j] = dp[i-1][j]+dp[i][j-1];
                    }
                }
                else{
                    dp[i][j] = dp[i][j-1];
                }
            }

其实知道正经的应该改成dp[i][j] = dp[i-1][j-1]+dp[i][j-1],但是觉得如果最后一位不是那个字母的话上面那一行正上方的数和左边的数应该是一样的,想不通我的答案为什么不对。后来把rabbit的表列出来,发现不一定!有可能两个相同的字母挨着!

代码

public  class Solution {
    public  int numDistinct(String s, String t) {
        int n1 = s.length();
        int n2 = t.length();
        int[][]dp = new int[n2+1][n1+1];
        for(int i = 1;i<=n2;i++){
            for(int j = i; j <= n1; j++){
                if(s.charAt(j-1)==t.charAt(i-1)){
                    if(i==1){
                        dp[i][j]=dp[i][j-1]+1;
                    }
                    else{
                        dp[i][j] = dp[i-1][j-1]+dp[i][j-1];
                    }
                }
                else{
                    dp[i][j] = dp[i][j-1];
                }
            }
        }
        return dp[n2][n1];
    }
}

最长递增子序列

题目

给你一个整数数组 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 。

示例 2:输入:nums = [0,1,0,3,2,3] 输出:4 示例 3:输入:nums = [7,7,7,7,7,7,7] 输出:1

思路:(没想出来,我想得很复杂……还是想得太多了,没有关注状态转移方程

dp[i] 的值代表 nums 以 nums[i]nums[i]nums[i] 结尾的最长子序列长度。(⚠这个很重要!不是前i个的最长子序列,而是以i结尾的!)

代码

public class Solution {
    public int lengthOfLIS(int[] nums) {
        int n = nums.length;
        int[]dp = new int[n];
        for(int i = 0; i < n; i ++){
            dp[i] = 1;
        }
        int res = 1;
        for(int i = 1; i < n; i ++){
            for(int j = 0; j < i;j ++){
                if(nums[j] < nums[i]){
                    dp[i] = Math.max(dp[j]+1,dp[i]);
                }
            }
            res =Math.max(res,dp[i]);
        }
        return res;
        }
}

最长公共子序列

题目

给定两个字符串 text1 和 text2,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 0 。一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。 例如,"ace" 是 "abcde" 的子序列,但 "aec" 不是 "abcde" 的子序列。两个字符串的 公共子序列 是这两个字符串所共同拥有的子序列。

示例 1:输入:text1 = "abcde", text2 = "ace" 输出:3
解释:最长公共子序列是 "ace" ,它的长度为 3 。

思路:(自己想的还是复杂了……) 现在做下来有两类题,一类的dp是从0开始到n,一类从0到n-1,主要是看

没有i-1的部分,(一般应该也是一维的dp吧)用0到n-1

**代码**:

public class Solution {     public int longestCommonSubsequence(String text1, String text2) {         int n1 = text1.length();         int n2 = text2.length();         int [][]dp = new int[n1+1][n2+1];         int re = 0;         for(int i = 1;i <= n1; i++){             for(int j = 1;j<=n2;j++){                     if(text2.charAt(j-1)==text1.charAt(i-1)){                         dp[i][j] = dp[i-1][j-1]+1;                         re = Math.max(re,dp[i][j]);                     }                     else{                         dp[i][j] = Math.max(dp[i-1][j],dp[i][j-1]);                     }             }         }         return dp[n1][n2];     } }


## 背包问题
### 将数表示为完全平方数的和
**题目**:

给你一个整数 n ,返回 和为 n 的完全平方数的最少数量 。完全平方数 是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。
例如,1、4、9 和 16 都是完全平方数,而 3 和 11 不是。 

>示例 1:输入:n = 12输出:3 
>解释:12 = 4 + 4 + 4
>示例 2:输入:n = 13输出:2
>解释:13 = 4 + 9

**思路**:自己想出来大!
状态转移方程:
dp[i] = MIN(dp[i], dp[i - j * j] + 1),i 表示当前数字,j*j 表示平方数

**代码**:

public class Solution {     public static int numSquares(int n) {         int dp[] = new int[n+1];         int k = 1;         for(int i = 1;i <= n;i++){             if(i==kk){                 dp[i] = 1;                 k++;             }             else{                 int k1 = 1;                 dp[i] = dp[i-1]+1;                 while(k1<k){                     int j = i - k1k1;                     dp[i] = Math.min(dp[i],dp[j]+1);                     k1++;                 }             }         }         return dp[n];     } }