面试_动态规划

目录

  • 代码随想录_动态规划
    • [1. 斐波那契数列](#1. 斐波那契数列)
    • [2. 爬楼梯](#2. 爬楼梯)
    • [3. 使用最小花费爬楼梯](#3. 使用最小花费爬楼梯)
    • [4. 不同路径](#4. 不同路径)
    • [5. 不同路径2](#5. 不同路径2)
    • [6. 整数拆分](#6. 整数拆分)
    • [7. 零一背包问题](#7. 零一背包问题)
    • [8. 分割等和子集](#8. 分割等和子集)
    • [9. 最后一块石头的重量2](#9. 最后一块石头的重量2)
    • [10. 目标和](#10. 目标和)
    • [11. 一和零](#11. 一和零)
    • [12. 完全背包问题](#12. 完全背包问题)
    • [13. 零钱兑换2](#13. 零钱兑换2)
    • [14. 组合总和4](#14. 组合总和4)
    • [15. 零钱兑换](#15. 零钱兑换)
    • [17. 完全平方数](#17. 完全平方数)
    • [18. 单词拆分](#18. 单词拆分)
    • [19. 198 打家劫舍](#19. 198 打家劫舍)
    • [20. 213 打家劫舍2](#20. 213 打家劫舍2)
    • [21. 337 打家劫舍3](#21. 337 打家劫舍3)
    • [22. 121 买卖股票的最佳时机](#22. 121 买卖股票的最佳时机)
    • [23. 122 买卖股票的最佳时机2](#23. 122 买卖股票的最佳时机2)
    • [24. 123 买卖股票的最佳时机3](#24. 123 买卖股票的最佳时机3)
    • [25. 188 买卖股票的最佳时机4](#25. 188 买卖股票的最佳时机4)
    • [26. 714 买卖股票的最佳时机含手续费](#26. 714 买卖股票的最佳时机含手续费)
    • [27. 309 买卖的最佳时机含冷冻期](#27. 309 买卖的最佳时机含冷冻期)
    • [28. 300 最长递增子序列](#28. 300 最长递增子序列)
    • [29. 674 最长连续递增序列](#29. 674 最长连续递增序列)
    • [30. 1143 最长公共子序列](#30. 1143 最长公共子序列)
    • [31. 718 最长重复子数组](#31. 718 最长重复子数组)
    • [34. 1035.不相交的线](#34. 1035.不相交的线)
    • [35. 53. 最大子序和](#35. 53. 最大子序和)
    • [36. 392 判断子序列](#36. 392 判断子序列)
    • [37. 115 不同的子序列](#37. 115 不同的子序列)
    • [38. 583 两个字符串的删除操作](#38. 583 两个字符串的删除操作)
    • [39. 72 编辑距离](#39. 72 编辑距离)
    • [40. 647 回文子串](#40. 647 回文子串)
    • [41. 516 最长回文子序列](#41. 516 最长回文子序列)

代码随想录_动态规划

1. 斐波那契数列

力扣题目509链接

斐波那契数 (通常用 F(n) 表示)形成的序列称为 斐波那契数列 。该数列由 0 和 1 开始,后面的每一项数字都是前面两项数字的和。也就是:
F(0) = 0,F(1) = 1
F(n) = F(n - 1) + F(n - 2),其中 n > 1

给定 n ,请计算 F(n) 。

示例 1:

复制代码
输入:n = 2
输出:1
解释:F(2) = F(1) + F(0) = 1 + 0 = 1

示例2:

复制代码
输入:n = 3
输出:2
解释:F(3) = F(2) + F(1) = 1 + 1 = 2

示例3:

复制代码
输入:n = 4
输出:3
解释:F(4) = F(3) + F(2) = 2 + 1 = 3

提示:

复制代码
0 <= n <= 30

思路

txt 复制代码
斐波那契数列,就是只需要遵守一个原则,每个数都是前两个数的和,如果斐波那契数列小于两个数的话,就直接返回对应的结果,如果大于两个数,那就先初始化前两个数的值,然后再用数组去遍历计算后面每个数的值(每个数都是前两个数的和)。

JAVA代码:

java 复制代码
public class Main {
    public static void main(String[] args) {
        int n = 4;
        System.out.println(fib(n));
    }
    public static int fib(int n) {
        if (n == 0) return 0;
        if (n == 1) return 1;
        int[] arr = new int[n+1];
        arr[0] = 0;
        arr[1] = 1;
        for (int i = 2; i < arr.length; i++) {
            arr[i] = arr[i-1] + arr[i-2];
        }
        return arr[n];
    }
}

2. 爬楼梯

力扣题目70链接

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。

每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?

示例 1:

复制代码
输入:n = 2
输出:2
解释:有两种方法可以爬到楼顶。
1. 1 阶 + 1 阶
2. 2 阶

示例2:

复制代码
输入:n = 3
输出:3
解释:有三种方法可以爬到楼顶。
1. 1 阶 + 1 阶 + 1 阶
2. 1 阶 + 2 阶
3. 2 阶 + 1 阶

思路

txt 复制代码
这道题跟斐波那契数列的思路是一样的,只是初始化需要注意以下,n=1时,有一种情况,n=2时,有两种情况,如果n<2,需要直接return,如果n>2,就需要先初始化,然后再用数组去计算后面每个数的值(每个数都是前两个数的和)。

提示:

复制代码
0 <= n <= 45

JAVA代码:

java 复制代码
public class Main {
    public static void main(String[] args) {
        int n = 4;
        System.out.println(climbStairs(n));
    }
    public static int climbStairs(int n) {
        if (n <= 2) {
            return n;
        }
        int[] dp = new int[n+1];
        dp[1] = 1;
        dp[2] = 2;
        for (int i = 3; i <= n; i++) {
            dp[i] = dp[i-1] + dp[i-2];
        }
        return dp[n];
    }
}

3. 使用最小花费爬楼梯

力扣题目746链接

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

提示:

复制代码
2 <= cost.length <= 1000
0 <= cost[i] <= 999

思路

txt 复制代码
对于这道题,需要先创建一个数组dp[],来存储到达每个台阶所需要的最小花费,然后每个台阶的最小花费都是取(前一个台阶的花费+到达前一个台阶所已经使用的花)和(前两个台阶的花费+到达前两个台阶所已经使用的花费)的最小值。
这里有两个注意点:
1. 因为需要用到前两个数的结果,所以需要先将前两个数进行初始化,然后再使用数组依次遍历后面的值,在初始化的时候,前两个台阶可以直接上去,没有花费,所以一定是0!
2. 在创建数组的时候,要把长度+1,因为是要到顶楼,就是原有数组的最后一位再+1。

JAVA代码:

java 复制代码
public class Main {
    public static void main(String[] args) {
        int[] cost = {10,15,20};
        System.out.println(minCostClimbingStairs(cost));
    }
    public static int minCostClimbingStairs(int[] cost) {
        int[] dp = new int[cost.length+1];
        dp[0] = 0;
        dp[1] = 0;
        for (int i = 2; i <= cost.length; i++) {
            dp[i] = Math.min(dp[i-1]+cost[i-1],dp[i-2]+cost[i-2]);
        }
        return dp[cost.length];
    }
}

4. 不同路径

力扣题目62链接

一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 "Start" )。

机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 "Finish" )。

问总共有多少条不同的路径?。

示例 1:

复制代码
输入:m = 3, n = 7
输出:28
总花费为 15 。

示例2:

复制代码
输入:m = 3, n = 2
输出:3
解释:
从左上角开始,总共有 3 条路径可以到达右下角。
1. 向右 -> 向下 -> 向下
2. 向下 -> 向下 -> 向右
3. 向下 -> 向右 -> 向下

示例3:

复制代码
输入:m = 7, n = 3
输出:28

示例4:

复制代码
输入:m = 3, n = 3
输出:6

提示:

复制代码
1 <= m, n <= 100
题目数据保证答案小于等于 2 * 109

思路

txt 复制代码
对于每一个网格,机器人都可以到达,而且,到每个网格的路径,就是根据到达网格的左网格的路径加上到达上网格的路径的和,而且需要注意的是:
1. 对于第一排和第一列来说,需要先进行初始化,因为对于第一排,没有上网格,而对于第一列,没有左网格,所以需要先初始化初始化第一排和第一列为1,只有一条路径能到。

JAVA代码:

java 复制代码
public class Main {
    public static void main(String[] args) {
        int m = 3;
        int n = 7;
        System.out.println(uniquePaths(3,7));
    }
    public static int uniquePaths(int m, int n) {
        int[][] dp = new int[m+1][n+1];
        for (int i = 1; i <= m; i++) {
            dp[i][1] = 1;
        }
        for (int i = 1; i <= n; i++) {
            dp[1][i] = 1;
        }
        for (int i = 2; i <= m; i++) {
            for (int j = 2; j <= n; j++) {
                dp[i][j] = dp[i-1][j] + dp[i][j-1];
            }
        }
        return dp[m][n];
    }
}

5. 不同路径2

力扣题目63链接

一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 "Start" )。

机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 "Finish")。

现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径?

网格中的障碍物和空位置分别用 1 和 0 来表示。

示例 1:

复制代码
输入:obstacleGrid = [[0,0,0],[0,1,0],[0,0,0]]
输出:2
解释:3x3 网格的正中间有一个障碍物。
从左上角到右下角一共有 2 条不同的路径:
1. 向右 -> 向右 -> 向下 -> 向下
2. 向下 -> 向下 -> 向右 -> 向右

示例2:

复制代码
输入:obstacleGrid = [[0,1],[0,0]]
输出:1

提示:

复制代码
m == obstacleGrid.length
n == obstacleGrid[i].length
1 <= m, n <= 100
obstacleGrid[i][j] 为 0 或 1

思路

txt 复制代码
这道题跟上一题的思路是一样的,只是多了一个限制,再注意点里提到。
对于每一个网格,机器人都可以到达,而且,到每个网格的路径,就是根据到达网格的左网格的路径加上到达上网格的路径的和,而且需要注意的是:
1. 对于第一排和第一列来说,需要先进行初始化,因为对于第一排,没有上网格,而对于第一列,没有左网格,所以需要先初始化初始化第一排和第一列为1,只有一条路径能到。(这里因为有障碍物,所以在初始化的时候,可能遇到第一排和第一列有障碍物的情况,因为数组在创建的时候,数组里的值默认就是0了,所以在遍历第一排和第一列的时候,需要增加一个条件,如果没有遇到障碍物,那就将dp数组的值赋值为1,如果遇到障碍物,就说明后面的路走不通,那就说明后面的数都是0,因为没有路径,直接使用break就可以了)。
2.  因为在其它网格也可能出现障碍物,所以也需要在遍历所有网格的时候去增加一个判断,就是是不是障碍物,如果是障碍物,就赋值为0,如果不是障碍物,那就按照原来的思路计算。

JAVA代码:

java 复制代码
public class Main {
    public static void main(String[] args) {
        int[][] obstacleGrid = {{0,0,0},{0,1,0},{0,0,0}};
        System.out.println(uniquePathsWithObstacles(obstacleGrid));
    }
    public static int uniquePathsWithObstacles(int[][] obstacleGrid) {
        int m = obstacleGrid.length;
        int n = obstacleGrid[0].length;
        int[][] dp = new int[m][n];
        for (int i = 0; i < m; i++) {
            if (obstacleGrid[i][0] == 1) {
                break;
            } else {
                dp[i][0] = 1;
            }
        }
        for (int i = 0; i < n; i++) {
            if (obstacleGrid[0][i] == 1) {
                break;
            } else {
                dp[0][i] = 1;
            }
        }
        for (int i = 1; i < m; i++) {
            for (int j = 1; j < n; j++) {
                if (obstacleGrid[i][j] == 1) {
                    dp[i][j] = 0;
                } else {
                    dp[i][j] = dp[i-1][j] + dp[i][j-1];
                }
            }
        }
        return dp[m-1][n-1];
    }
}

6. 整数拆分

力扣题目343链接

给定一个正整数 n ,将其拆分为 k 个 正整数 的和( k >= 2 ),并使这些整数的乘积最大化。

返回 你可以获得的最大乘积。
示例 1:

复制代码
输入: n = 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1。

示例2:

复制代码
输入: n = 10
输出: 36
解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36。

提示:

复制代码
2 <= n <= 58

JAVA代码:

java 复制代码
public class Main {
    public static void main(String[] args) {
        int n = 10;
        System.out.println(integerBreak(n));
    }
    public static int integerBreak(int n) {
        int[] dp = new int[n+1];
        dp[2] = 1;
        for (int i = 3; i <= n; i++) {
            for (int j = 1; j <= i/2; j++) {
                dp[i] = Math.max(dp[i],Math.max(j*(i-j),j*dp[i-j]));
            }
        }
        return dp[n];
    }
}

7. 零一背包问题

卡码网题目46链接

小明是一位科学家,他需要参加一场重要的国际科学大会,以展示自己的最新研究成果。他需要带一些研究材料,但是他的行李箱空间有限。这些研究材料包括实验设备、文献资料和实验样本等等,它们各自占据不同的空间,并且具有不同的价值。

小明的行李空间为 N,问小明应该如何抉择,才能携带最大价值的研究材料,每种研究材料只能选择一次,并且只有选与不选两种选择,不能进行切割。

第一行包含两个正整数,第一个整数 M 代表研究材料的种类,第二个正整数 N,代表小明的行李空间。

第二行包含 M 个正整数,代表每种研究材料的所占空间。

第三行包含 M 个正整数,代表每种研究材料的价值。

输出一个整数,代表小明能够携带的研究材料的最大价值。

示例 1:

复制代码
输入:6 1      
     2 2 3 1 5 2
     2 3 1 5 4 3
输出:5

提示:

复制代码
小明能够携带 6 种研究材料,但是行李空间只有 1,而占用空间为 1 的研究材料价值为 5,所以最终答案输出 5。 
数据范围:
1 <= N <= 5000
1 <= M <= 5000
研究材料占用空间和价值都小于等于 1000

思路

txt 复制代码
背包问题,其实就是把所有可能出现的情况都遍历了,首先遍历样本个数,就是要确保要不要把这个样本放进去,求放和不放能取到的最大价值,因为在考虑要不要把样本放进去的时候,要考虑能不能背包能不能放得下这个样本,还得考虑要不要放进去,如果放进去之后剩余的空间还能放下东西,前面就已经遍历过了,可以直接取,如果不放的话,就是放上一个样本的时候出现的情况,从而可以得到递推公式。
注意事项:
1. 初始化第一个样本能在哪个背包容量下放下,因为递推需要前面的信息,所以必须先初始化第一个样本的,遍历背包容量,如果背包容量大于样本的重量,就说明能放下。
2. 一定要加上限制条件:背包容量必须大于物品重量。

JAVA代码:

java 复制代码
import java.util.Scanner;
public class FourSix {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int M = sc.nextInt();
        int N = sc.nextInt();
        int[] weight = new int[M];
        int[] value = new int[M];
        for (int i = 0; i < M; i++) weight[i] = sc.nextInt();
        for (int i = 0; i < M; i++) value[i] = sc.nextInt();
        int[][] dp = new int[M][N+1];
        for (int j = 0; j <= N; j++) {
            if (j >= weight[0]) dp[0][j] = value[0];
        }
        for (int i = 1; i < M; i++) {
            for (int j = 0; j <= N; j++) {
                if (j >= weight[i]) {
                	dp[i][j] = Math.max(dp[i-1][j], value[i] + dp[i-1][j-weight[i]]);
                } else {
                    dp[i][j] = dp[i-1][j];
                }
            }
        }
        System.out.println(dp[M-1][N]);
    }
}

public class Main {
    public static void main(String[] args) {
        int[] weight = {1,2,3,4};
        int[] value = {2,4,4,5};
        int bigSize = 5;
        System.out.println(zeroOneBagTwoDimension(weight, value, bigSize));
        System.out.println(zeroOneBagOneDimension(weight, value, bigSize));
    }
    public static int zeroOneBagTwoDimension(int[] weight, int[] value, int bigSize) {
        int N = weight.length;
        int[][] dp = new int[N][bigSize+1];
        for (int j = weight[0]; j <= bigSize; j++) {
            dp[0][j] = value[0];
        }
        for (int i = 1; i < N; i++) {
            for (int j = 1; j <= bigSize; j++) {
                if (weight[i] > j) {
                    dp[i][j] = dp[i-1][j];
                } else {
                    dp[i][j] = Math.max(dp[i-1][j],dp[i-1][j-weight[i]]+value[i]);
                }
            }
        }
        return dp[N-1][bigSize];
    }
    public static int zeroOneBagOneDimension(int[] weight, int[] value, int bigSize) {
        int N = weight.length;
        int[] dp = new int[bigSize+1];
        for (int i = 0; i < N; i++) {
            for (int j = bigSize; j >= weight[i]; j--) {
                dp[j] = Math.max(dp[j],dp[j-weight[i]]+value[i]);
            }
        }
        return dp[bigSize];
    }
}

8. 分割等和子集

力扣题目416链接

给你一个 只包含正整数非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。

示例 1:

复制代码
输入:nums = [1,5,11,5]
输出:true
解释:数组可以分割成 [1, 5, 5] 和 [11] 。

示例2:

复制代码
输入:nums = [1,2,3,5]
输出:false
解释:数组不能分割成两个元素和相等的子集。

提示:

复制代码
1 <= nums.length <= 200
1 <= nums[i] <= 100

思路

txt 复制代码
跟背包问题是一模一样的,唯一不同的点就是需要改造一下让它变成背包问题,注意点:
1. 这道题其实就是把数组分成两部分,让每个部分的和相等,那其实可以把它看成是一个背包,这些值既是重量也是价值,然后总背包容量就是总和的一半,所以如果总和没办法整除2的话,说明一定是不能平分的,所以需要提前剪枝。
2. 把sum/2作为背包容量。

JAVA代码:

java 复制代码
public class Main {
    public static void main(String[] args) {
        int[] nums = {1,5,11,5};
        System.out.println(canPartition(nums));
    }
    public static boolean canPartition(int[] nums) {
        int sum = 0;
        for (int num : nums) sum += num;
        if (sum % 2 == 1) return false;
        int target = sum/2;
        int[][] dp = new int[nums.length][target+1];
        for (int j = 0; j <= target; j++) {
            if (j >= nums[0]) dp[0][j] = nums[0];
        }
        for (int i = 1; i < nums.length; i++) {
            for (int j = 0; j <= target; j++) {
                if (j >= nums[i]) {
                	dp[i][j] = Math.max(dp[i-1][j], dp[i-1][j-nums[i]] + nums[i]);
                } else {
                    dp[i][j] = dp[i-1][j];
                }
            }
        }
        return dp[nums.length-1][target] == target;
    }
}

9. 最后一块石头的重量2

力扣题目1049链接

有一堆石头,用整数数组 stones 表示。其中 stones[i] 表示第 i 块石头的重量。

每一回合,从中选出任意两块石头,然后将它们一起粉碎。假设石头的重量分别为 x 和 y,且 x <= y 。那么粉碎的可能结果如下:

如果 x == y ,那么两块石头都会被完全粉碎;

如果 x != y ,那么重量为 x 的石头将会完全粉碎,而重量为 y 的石头新重量为 y-x。

最后,最多只会剩下一块 石头。返回此石头 最小的可能重量 。如果没有石头剩下,就返回 0。

示例 1:

复制代码
输入:stones = [2,7,4,1,8,1]
输出:1
解释:
组合 2 和 4,得到 2,所以数组转化为 [2,7,1,8,1],
组合 7 和 8,得到 1,所以数组转化为 [2,1,1,1],
组合 2 和 1,得到 1,所以数组转化为 [1,1,1],
组合 1 和 1,得到 0,所以数组转化为 [1],这就是最优值。

示例2:

复制代码
输入:stones = [31,26,33,21,40]
输出:5

提示:

复制代码
1 <= stones.length <= 30
1 <= stones[i] <= 100
txt 复制代码
思路:
跟上一题的思路一样,也是要尽量做到平分成两半,但是可以不用必须严格地分为两半,所以可以直接取背包容量target=sum/2,然后在这个背包容量里取最大价值的样本。

JAVA代码:

java 复制代码
public class Main {
    public static void main(String[] args) {
        int[] stones = {31,26,33,21,40};
        System.out.println(lastStoneWeightII(stones));
    }
    public static int lastStoneWeightII(int[] stones) {
        int sum = 0;
        for (int stone : stones) sum += stone;
        int target = sum/2;
        int[][] dp = new int[stones.length][target+1];
        for (int j = 0; j <= target; j++) {
            if (j >= stones[0]) dp[0][j] = stones[0];
        }
        for (int i = 1; i < stones.length; i++) {
            for (int j = 0; j <= target; j++) {
                if (j >= stones[i]) {
                	dp[i][j] = Math.max(dp[i-1][j], dp[i-1][j-stones[i]] + stones[i]);
                } else {
                    dp[i][j] = dp[i-1][j];
                }
            }
        }
        return sum - dp[stones.length-1][target] * 2;
    }
}

10. 目标和

力扣题目494链接

给你一个非负整数数组 nums 和一个整数 target 。

向数组中的每个整数前添加 '+' 或 '-' ,然后串联起所有整数,可以构造一个 表达式 :

例如,nums = [2, 1] ,可以在 2 之前添加 '+' ,在 1 之前添加 '-' ,然后串联起来得到表达式 "+2-1" 。

返回可以通过上述方法构造的、运算结果等于 target 的不同 表达式 的数目。

示例 1:

复制代码
输入:nums = [1,1,1,1,1], target = 3
输出:5
解释:一共有 5 种方法让最终目标和为 3 。
-1 + 1 + 1 + 1 + 1 = 3
+1 - 1 + 1 + 1 + 1 = 3
+1 + 1 - 1 + 1 + 1 = 3
+1 + 1 + 1 - 1 + 1 = 3
+1 + 1 + 1 + 1 - 1 = 3

示例2:

复制代码
输入:nums = [1], target = 1
输出:1

提示:

复制代码
1 <= nums.length <= 20
0 <= nums[i] <= 1000
0 <= sum(nums[i]) <= 1000
-1000 <= target <= 1000

思路

txt 复制代码
这道题跟上面的题的思路不太一样,因为上面的题是想求在有限背包容量里能装下的最大价值的样品,但是这道题是要求能装满背包容量的最多方法组合数。
有3个注意点:
1. 其实背包容量可以被定义为 bagSize = (sum + target) / 2,因为其实可以把target也想象成是背包里的其中一个样本,只是它可能有正有负,如果它是正的,那就满足跟原来一样,会出现将原来背包里的物品分给它,达到平分的效果,如果是负的,其实也是一样,相当于有样本跟它抵消了,所以可以变相看到是一个背包问题。
2. 但是这道题是要求最大方法数,所以遍历公式肯定是:现在的物品能放到背包下的方法数 + 这个物品还没有放到这个背包下的方法数,这个才是总的方法数量。
3. 还有一个注意点就是,可能会出现0,有0就是至少会出现2的次方,有2个就是2的平方个可能性,就是除了遍历第一行,还要去遍历第一列。

JAVA代码:

java 复制代码
public class Main {
    public static void main(String[] args) {
        int[] nums = {1,1,1,1,1};
        int target = 3;
        System.out.println(findTargetSumWays(nums, target));
    }
    public static int findTargetSumWays(int[] nums, int target) {
        int sum = 0;
        for (int num : nums) sum += num;
        if (sum < Math.abs(target)) return 0;
        if ((sum + target) % 2 != 0) return 0;
        int bagSize = (sum + target)/2;
        int[][] dp = new int[nums.length][bagSize + 1];
        for (int j = 0; j <= bagSize; j++) {
            if (nums[0] == j) dp[0][j] = 1;
        }
        int zeroNum = 0;
        for (int i = 0; i < nums.length; i++) {
            if (nums[i] == 0) zeroNum++;
            dp[i][0] = (int)Math.pow(2, zeroNum);
        }
        for (int i = 1; i < nums.length; i++) {
            for (int j = 1; j <= bagSize; j++) {
                if (j >= nums[i] ) {
                	dp[i][j] = dp[i-1][j] + dp[i-1][j-nums[i]];
                } else {
                	dp[i][j] = dp[i-1][j];
                }
            }
        }
        return dp[nums.length-1][bagSize];
    }
}

11. 一和零

力扣题目474链接

给你一个二进制字符串数组 strs 和两个整数 m 和 n 。

请你找出并返回 strs 的最大子集的长度,该子集中 最多 有 m 个 0 和 n 个 1 。

如果 x 的所有元素也是 y 的元素,集合 x 是集合 y 的 子集 。

示例 1:

复制代码
输入:strs = ["10", "0001", "111001", "1", "0"], m = 5, n = 3
输出:4
解释:最多有 5 个 0 和 3 个 1 的最大子集是 {"10","0001","1","0"} ,因此答案是 4 。
其他满足题意但较小的子集包括 {"0001","1"} 和 {"10","1","0"} 。{"111001"} 不满足题意,因为它含 4 个 1 ,大于 n 的值 3 。

示例2:

复制代码
输入:strs = ["10", "0", "1"], m = 1, n = 1
输出:2
解释:最大的子集是 {"0", "1"} ,所以答案是 2 。

提示:

复制代码
1 <= strs.length <= 600
1 <= strs[i].length <= 100
strs[i] 仅由 '0' 和 '1' 组成
1 <= m, n <= 100

思路

txt 复制代码
这道题是求能放下的样本数量
普通背包问题是求 :放下物品的最大价值(价值)
目标和问题是求   :放下物品的组合搭配种类数量
一和零问题是求   :放下物品的最大样品数量

JAVA代码:

java 复制代码
public class FourSevenFour {
    public static void main(String[] args) {
        String[] strs = new String[]{"10", "0001", "111001", "1", "0"};
        int m = 5;
        int n = 3;
        System.out.println(findMaxForm(strs, m, n));
    }
    public static int findMaxForm(String[] strs, int m, int n) {
        int len = strs.length;
        int[][][] dp = new int[len][m+1][n+1];
        int zeroNums = 0;
        int oneNums = 0;
        char[] arr = strs[0].toCharArray();
        for (char c : arr) {
            if (c == '0') {
                zeroNums++;
            } else {
                oneNums++;
            }
        }
        for (int j = 0; j <= m; j++) {
            for (int k = 0; k <= n; k++) {
                if (j >= zeroNums && k >= oneNums) dp[0][j][k] = 1;
            }
        }
        for (int i = 1; i < len; i++) {
            zeroNums = 0;
            oneNums = 0;
            arr = strs[i].toCharArray();
            for (char ch : arr) {
                if (ch == '0') {
                    zeroNums++;
                } else {
                    oneNums++;
                }
            }
            for (int j = 1; j <= m; j++) {
                for (int k = 1; k <= n; k++) {
                    if (j >= zeroNums && k >= oneNums) {
                        dp[i][j][k] = Math.max(dp[i-1][j][k], dp[i-1][j-zeroNums][k-oneNums] + 1);
                    } else {
                        dp[i][j][k] = dp[i-1][j][k];
                    }
                }
            }
        }
        return dp[len-1][m][n];
    }
}

12. 完全背包问题

卡码网题目52链接

小明是一位科学家,他需要参加一场重要的国际科学大会,以展示自己的最新研究成果。他需要带一些研究材料,但是他的行李箱空间有限。这些研究材料包括实验设备、文献资料和实验样本等等,它们各自占据不同的重量,并且具有不同的价值。

小明的行李箱所能承担的总重量为 N,问小明应该如何抉择,才能携带最大价值的研究材料,每种研究材料可以选择无数次,并且可以重复选择。

第一行包含两个整数,N,V,分别表示研究材料的种类和行李空间

接下来包含 N 行,每行两个整数 wi 和 vi,代表第 i 种研究材料的重量和价值

输出一个整数,表示最大价值。

示例 1:

复制代码
输入:4 5
     1 2
     2 4
     3 4
     4 5
输出:10

提示:

复制代码
第一种材料选择五次,可以达到最大值。
数据范围:
1 <= N <= 10000;
1 <= V <= 10000;
1 <= wi, vi <= 10^9.

思路

txt 复制代码
完全背包问题和零一背包问题最大的区别就是:
零一背包问题中每个物品只能用一次,而完全背包问题中每个物品可以用无限次。
所以只需要注意(修改)以下几点就可以直接AC了:
1. 因为要遍历其它物品的时候,是基于前一个物品的信息来确定的,所以需要先初始化第一个样本在有限容量下的最大价值,在这里,必须注意,dp[0][j]是可以重复放下物品的,只要剩下的容量还能放下这个物品,那就得继续加上对应的价值。
dp[0][j] = value[0] + **dp[0][j-weight[0]]**;
2. 在遍历其它物品的时候,也是需要考虑能不能在剩余的有限的容量里继续用现在的物品。
dp[i][j] = Math.max(dp[i-1][j], **dp[i][j-weight[i]] + value[i]**);

JAVA代码:

java 复制代码
public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int v = sc.nextInt();
        int[] weight = new int[n];
        int[] value = new int[n];
        for (int i = 0; i < n; i++) {
            weight[i] = sc.nextInt();
            value[i] = sc.nextInt();
        }
        int[][] dp = new int[n][v+1];
        for (int j = 0; j <= v; j++) {
            if (j >= weight[0]) dp[0][j] = value[0] + dp[0][j-weight[0]];
        }
        for (int i = 1; i < n; i++) {
            for (int j = 0; j <= v; j++) {
                if (j >= weight[i]) {
                	dp[i][j] = Math.max(dp[i-1][j], dp[i][j-weight[i]] + value[i]);
                } else {
                    dp[i][j] = dp[i-1][j];
                }
            }
        }
        System.out.println(dp[n-1][v]);
    }
}

13. 零钱兑换2

力扣题目518链接

给你一个整数数组 coins 表示不同面额的硬币,另给一个整数 amount 表示总金额。

请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0 。

假设每一种面额的硬币有无限个。

题目数据保证结果符合 32 位带符号整数。

示例 1:

复制代码
输入:amount = 5, coins = [1, 2, 5]
输出:4
解释:有四种方式可以凑成总金额:
5=5
5=2+2+1
5=2+1+1+1
5=1+1+1+1+1

示例2:

复制代码
输入:amount = 3, coins = [2]
输出:0
解释:只用面额 2 的硬币不能凑成总金额 3 。

示例3:

复制代码
输入:amount = 10, coins = [10] 
输出:1

提示:

复制代码
1 <= coins.length <= 300
1 <= coins[i] <= 5000
coins 中的所有值 互不相同
0 <= amount <= 5000

思路

txt 复制代码
这道题和目标和的思路是一样的,只是从零一背包的思想变成了完全背包,有以下注意点:
1. 在为第一行(也就是第一个物品)初始化的时候,零一背包是必须得正正好装满可以是1,但完全背包问题可以重复装,所以只要背包容量能整除物品重量,就可以被赋值为1。
2. 在为第一列(也就是每个物品在容量为0的情况)初始化的时候,因为那道题的0可能是加正号也可以加负号,所以组合数就很多,是2的N次方,而在这道题,只要容量是0,就说明什么都不装肯定可以把这个背包装满,所以就直接赋值为1,这个是1还是2的N次方不是零一背包和完全背包决定的。
3. 在进行dp的填充的时候,也得跟完全背包问题一样,可以在放入物品的时候,加上在剩余容量下的包含该物品的组合数。

JAVA代码:

java 复制代码
public class FiveOneEight {
    public static void main(String[] args) {
        int amount = 5;
        int[] coins = new int[]{1, 2, 5};
        System.out.println(change(amount, coins));
    }
    public static int change(int amount, int[] coins) {
        int[][] dp = new int[coins.length][amount+1];
        for (int j = 0; j <= amount; j++) {
            if (j % coins[0] == 0) dp[0][j] = 1;
        }
        for (int i = 0; i < coins.length; i++) dp[i][0] = 1;
        for (int i = 1; i < coins.length; i++) {
            for (int j = 1; j <= amount; j++) {
                if (j >= coins[i]) {
                    dp[i][j] = dp[i-1][j] + dp[i][j-coins[i]];
                } else {
                    dp[i][j] = dp[i-1][j];
                }
            }
        }
        return dp[coins.length-1][amount];
    }
}

14. 组合总和4

力扣题目377链接

给你一个由 不同 整数组成的数组 nums ,和一个目标整数 target 。请你从 nums 中找出并返回总和为 target 的元素组合的个数。

题目数据保证答案符合 32 位整数范围。

示例 1:

复制代码
输入:nums = [1,2,3], target = 4
输出:7
解释:
所有可能的组合为:
(1, 1, 1, 1)
(1, 1, 2)
(1, 2, 1)
(1, 3)
(2, 1, 1)
(2, 2)
(3, 1)
请注意,顺序不同的序列被视作不同的组合。

示例2:

复制代码
输入:nums = [9], target = 3
输出:0

提示:

复制代码
1 <= nums.length <= 200
1 <= nums[i] <= 1000
nums 中的所有元素 互不相同
1 <= target <= 1000

思路

txt 复制代码
这道题和零钱对换2差不多,但是:
零钱兑换2是组合,而组合总和4是排列!!!!!!
1. 组合是先遍历物品再遍历容量,而排列要先遍历容器再遍历物品。
2. 在填充dp的时候,加上不包含该物品的时候是一样的,但是包含该物品的情况不一样,因为组合只需要考虑当前和前面的物品就可以了,而排列需要考虑所有的样本。所以:
dp[i][j] = dp[i-1][j] + dp[i][j-coins[i]];
dp[i][j] = dp[i-1][j] + dp[nums.length][j-nums[i-1]];

JAVA代码:

java 复制代码
public class ThreeSevenSeven {
    public static void main(String[] args) {
        int[] nums = new int[]{1,2,3};
        int target = 4;
        System.out.println(combinationSum4(nums, target));
    }
    public static int combinationSum4(int[] nums, int target) {
        int[][] dp = new int[nums.length+1][target+1];
        for (int i = 0; i < nums.length; i++) dp[i][0] = 1;
        for (int j = 0; j <= target; j++) {
            for (int i = 1; i <= nums.length; i++) {
                if (j >= nums[i-1]) {
           			dp[i][j] = dp[i-1][j] + dp[nums.length][j-nums[i-1]];
                } else {
                    dp[i][j] = dp[i-1][j];
                }
            }
        }
        return dp[nums.length][target];
    }
}

15. 零钱兑换

力扣题目322链接

给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。

计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。

你可以认为每种硬币的数量是无限的。

示例 1:

复制代码
输入:coins = [1, 2, 5], amount = 11
输出:3 
解释:11 = 5 + 5 + 1

示例2:

复制代码
输入:coins = [2], amount = 3
输出:-1

示例3:

复制代码
输入:coins = [1], amount = 0
输出:0

提示:

复制代码
1 <= coins.length <= 12
1 <= coins[i] <= 231 - 1
0 <= amount <= 10^4

思路

txt 复制代码
这道题和一和零这道题很像,因为一和零是求能放下物品的最大数,要尽可能多放物品,而这道题是要尽可能少放,在满足条件下物品尽可能少一点。
主要就是有几点注意点:
1. 初始化:将第一行中的第零列后的每个数定义成一个很大的值,因为这道题是取最小值,所以定义一个大值取最值就可以保证最小了,但是不能用Integer.MAX_Vaule,因为会让这个值+1,会出现数据溢出。
2. 然后再对第一行来进行真正的初始化,利用第零列来进行求出最小物品。
3. 如果最大容量下放的物品超过一开始设置的最大值,就说明没办法容量没办法放下物品。

JAVA代码:

java 复制代码
public class ThreeTwoTwo {
    public static void main(String[] args) {
        int[] coins = new int[]{1, 2, 5};
        int amount = 11;
        System.out.println(coinChange(coins, amount));
    }
    public static int coinChange(int[] coins, int amount) {
        int[][] dp = new int[coins.length][amount+1];
        for (int j = 1; j <= amount; j++) {
            dp[0][j] = amount+1;
        }
        for (int j = 1; j <= amount; j++) {
            if (j >= coins[0]) {
                dp[0][j] = dp[0][j-coins[0]] + 1;
            }
        }
        for (int i = 1; i < coins.length; i++) {
            for (int j = 1; j <= amount; j++) {
                if (j >= coins[i]) {
                    dp[i][j] = Math.min(dp[i-1][j], dp[i][j-coins[i]] + 1);
                } else {
                    dp[i][j] = dp[i-1][j];
                }
            }
        }
        return dp[coins.length-1][amount] >= amount+1 ? -1 : dp[coins.length-1][amount];
    }
}

17. 完全平方数

力扣题目279链接

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

示例 1:

复制代码
输入:n = 12
输出:3 
解释:12 = 4 + 4 + 4

示例2:

复制代码
输入:n = 13
输出:2
解释:13 = 4 + 9

提示:

复制代码
1 <= n <= 10^4

思路

txt 复制代码
和零钱兑换的思路一样

JAVA代码:

java 复制代码
public class TwoSevenNine {
    public static void main(String[] args) {
        int n = 13;
        System.out.println(numSquares(n));
    }
    public static int numSquares(int n) {
        int m = (int)Math.sqrt(n);
        int[][] dp = new int[m+1][n+1];
        for (int j = 1; j <= n; j++) {
            dp[1][j] = dp[1][j-1] + 1;
        }
        for (int i = 2; i <= m; i++) {
            for (int j = 1; j <= n; j++) {
                if (j >= i * i) {
                    dp[i][j] = Math.min(dp[i-1][j], dp[i][j-i*i] + 1);
                } else {
                    dp[i][j] = dp[i-1][j];
                }
            }
        }
        return dp[m][n];
    }
}

18. 单词拆分

力扣题目139链接

给定一个非空字符串 s 和一个包含非空单词的列表 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。

说明:

复制代码
拆分时可以重复使用字典中的单词。
你可以假设字典中没有重复的单词。

**

示例 1:

**

复制代码
输入: s = "leetcode", wordDict = ["leet", "code"]
输出: true
解释: 返回 true 因为 "leetcode" 可以被拆分成 "leet code"。

示例 2:

复制代码
输入: s = "applepenapple", wordDict = ["apple", "pen"]
输出: true
解释: 返回 true 因为 "applepenapple" 可以被拆分成 "apple pen apple"。
注意你可以重复使用字典中的单词。

示例 3:

复制代码
输入: s = "catsandog", wordDict = ["cats", "dog", "sand", "and", "cat"]
输出: false

思路

txt 复制代码
注意点:
1. substring包头不包尾
2. dp[0]是指当没有单词的时候,肯定为true。
3. 遍历单词的时候,因为substring包头不包尾,所以其实从1开始就是在遍历第0个样本,遍历第n个就是在判断前n-1个,所以i必须得满足i<=s.length。
4. 永远记得j比i少一个身位,i表示的dp的索引,而j表示的是wordDict的索引!!!!!

JAVA代码

java 复制代码
import java.util.*;
public class OneThreeNine {
    public static void main(String[] args) {
        String s = "catsanddog";
        List<String> wordDict = Arrays.asList("cats", "dog", "sand", "and", "cat");
        System.out.println(wordBreak(s, wordDict));
    }
    public static boolean wordBreak(String s, List<String> wordDict) {
        Set<String> set = new HashSet<>(wordDict);
        boolean[] dp = new boolean[s.length()+1];
        dp[0] = true;
        for (int i = 1; i <= s.length(); i++) {
            for (int j = 0; j < i && !dp[i]; j++) {
                if (set.contains(s.substring(j,i)) && dp[j]) {
                    dp[i] = true;
                }
            }
        }
        return dp[s.length()];
    }
}

19. 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 。

提示:

复制代码
0 <= nums.length <= 100
0 <= nums[i] <= 400

思路

txt 复制代码
这道题就是典型的斐波那契数列的变形,同样也是需要根据dp数组的前两个数来求解问题。
注意点:
1. 因为创建dp数组的时候,需要先初始化dp[0]和dp[1],所以如果样例是0或1的时候就会出现异常,必须得单独做一个返回。
2. 在填充dp的时候,核心思想就是,当前房间偷的钱 加上 上上个房间偷的钱 能不能超过上个房间偷的钱,取这两个的最大值,才能偷到最多的钱。
3. dp[i] = Math.max(dp[i-1], dp[i-2] + nums[i]);

JAVA代码

java 复制代码
public class OneNineEight {
    public static void main(String[] args) {
        int[] nums = new int[]{1,2,3,1};
        System.out.println(rob(nums));
    }
    public static int rob(int[] nums) {
        int n = nums.length;
        if (n == 1) return nums[0];
        if (n == 2) return Math.max(nums[0], nums[1]);
        int[] dp = new int[n];
        dp[0] = nums[0];
        dp[1] = Math.max(nums[0], nums[1]);
        for (int i = 2; i < n; i++) {
            dp[i] = Math.max(dp[i-1], dp[i-2] + nums[i]);
        }
        return dp[n-1];
    }
}

20. 213 打家劫舍2

你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都 围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警 。

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

示例 1:

复制代码
输入:nums = [2,3,2]
输出:3
解释:你不能先偷窃 1 号房屋(金额 = 2),然后偷窃 3 号房屋(金额 = 2), 因为他们是相邻的。

示例 2:

复制代码
输入:nums = [1,2,3,1]
输出:4
解释:你可以先偷窃 1 号房屋(金额 = 1),然后偷窃 3 号房屋(金额 = 3)。偷窃到的最高金额 = 1 + 3 = 4 。

示例 3:

复制代码
输入:nums = [0]
输出:0

提示:

复制代码
1 <= nums.length <= 100
0 <= nums[i] <= 1000

思路

txt 复制代码

JAVA代码

java 复制代码
public class TwoOneThree {
    public static void main(String[] args) {
        int[] nums = new int[]{1,2,3,1};
        System.out.println(rob(nums));
    }
    public static int rob(int[] nums) {
        int n = nums.length;
        if (n == 1) return nums[0];
        if (n == 2) return Math.max(nums[0], nums[1]);
        if (n == 3) return Math.max(Math.max(nums[0], nums[1]),nums[2]);
        int[] dp1 = new int[n];
        int[] dp2 = new int[n];
        dp1[0] = nums[0];
        dp1[1] = Math.max(nums[0], nums[1]);
        for (int i = 2; i < n - 1; i++) {
            dp1[i] = Math.max(dp1[i-1], dp1[i-2] + nums[i]);
        }

        dp2[1] = nums[1];
        dp2[2] = Math.max(nums[1], nums[2]);
        for (int i = 3; i < n; i++) {
            dp2[i] = Math.max(dp2[i-1], dp2[i-2] + nums[i]);
        }
        return Math.max(dp1[n-2], dp2[n-1]);
    }
}

21. 337 打家劫舍3

在上次打劫完一条街道之后和一圈房屋后,小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为"根"。 除了"根"之外,每栋房子有且只有一个"父"房子与之相连。一番侦察之后,聪明的小偷意识到"这个地方的所有房屋的排列类似于一棵二叉树"。 如果两个直接相连的房子在同一天晚上被打劫,房屋将自动报警。

计算在不触动警报的情况下,小偷一晚能够盗取的最高金额。

JAVA代码

java 复制代码
class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;
    TreeNode () {}
    TreeNode (int val) {
        this.val = val;
    }
    TreeNode (int val, TreeNode left, TreeNode right) {
        this.val = val;
        this.left = left;
        this.right = right;
    }
}
public class ThreeThreeSeven {
    public int rob(TreeNode root) {
        int[] res = backTracking(root);
        return Math.max(res[0], res[1]);
    }
    public int[] backTracking (TreeNode root) {
        int[] res = new int[2];
        if (root == null) return res;
        int[] left = backTracking(root.left);
        int[] right = backTracking(root.right);
        res[0] = Math.max(left[0], left[1]) + Math.max(right[0], right[1]);
        res[1] = root.val + left[0] + right[0];
        return res;
    }
}

22. 121 买卖股票的最佳时机

给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。

你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。

返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。

示例 1:

复制代码
输入:[7,1,5,3,6,4]
输出:5
解释:在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。

示例 2:

复制代码
输入:prices = [7,6,4,3,1]
输出:0
解释:在这种情况下, 没有交易完成, 所以最大利润为 0。 

思路

txt 复制代码

JAVA代码

java 复制代码
public class OneTwoOne {
    public static void main(String[] args) {
        int[] prices = new int[]{7,1,5,3,6,4};
        System.out.println(maxProfit(prices));
    }
    public static int maxProfit(int[] prices) {
        int n = prices.length;
        if (n == 1) return 0;
        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], prices[i] + dp[i-1][0]);
        }
        return dp[n-1][1];
    }
}

23. 122 买卖股票的最佳时机2

给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。

设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。

注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

示例 1:

复制代码
输入: [7,1,5,3,6,4]
输出: 7
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4。随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。

示例 2:

复制代码
输入: [1,2,3,4,5]
输出: 4
解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。

示例 3:

复制代码
输入: [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。

提示:

复制代码
1 <= prices.length <= 3 * 10 ^ 4
0 <= prices[i] <= 10 ^ 4

思路

txt 复制代码

JAVA代码

java 复制代码
public class OneTwoTwo {
    public static void main(String[] args) {
        int[] prices = new int[]{7,1,5,3,6,4};
        System.out.println(maxProfit(prices));
    }
    public static int maxProfit(int[] prices) {
        if (prices.length == 0) return 0;
        int n = prices.length;
        int[][] dp = new int[n][2];
        dp[0][0] = -prices[0];
        for (int i = 1; i < n; i++) {
            dp[i][0] = Math.max(dp[i-1][0], -prices[i] + dp[i-1][1]);
            dp[i][1] = Math.max(dp[i-1][1], dp[i-1][0] + prices[i]);
        }
        return dp[n-1][1];
    }
}

24. 123 买卖股票的最佳时机3

给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。

设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。

注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

示例 1:

复制代码
输入:prices = [3,3,5,0,0,3,1,4]
输出:6 解释:在第 4 天(股票价格 = 0)的时候买入,在第 6 天(股票价格 = 3)的时候卖出,这笔交易所能获得利润 = 3-0 = 3 。随后,在第 7 天(股票价格 = 1)的时候买入,在第 8 天 (股票价格 = 4)的时候卖出,这笔交易所能获得利润 = 4-1 = 3。

示例 2:

复制代码
输入:prices = [1,2,3,4,5]
输出:4 解释:在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4。注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。

示例 3:

复制代码
输入:prices = [7,6,4,3,1]
输出:0 解释:在这个情况下, 没有交易完成, 所以最大利润为0。

示例 4:

复制代码
输入:prices = [1] 
输出:0

提示:

复制代码
1 <= prices.length <= 10^5
0 <= prices[i] <= 10^5

思路

txt 复制代码

JAVA代码

java 复制代码
public class OneTwoTwo {
    public static void main(String[] args) {
        int[] prices = new int[]{7,1,5,3,6,4};
        System.out.println(maxProfit(prices));
    }
    public static int maxProfit(int[] prices) {
        if (prices.length == 0) return 0;
        int n = prices.length;
        int[][] dp = new int[n][2];
        dp[0][0] = -prices[0];
        for (int i = 1; i < n; i++) {
            dp[i][0] = Math.max(dp[i-1][0], -prices[i] + dp[i-1][1]);
            dp[i][1] = Math.max(dp[i-1][1], dp[i-1][0] + prices[i]);
        }
        return dp[n-1][1];
    }
} 

25. 188 买卖股票的最佳时机4

给定一个整数数组 prices ,它的第 i 个元素 prices[i] 是一支给定的股票在第 i 天的价格。

设计一个算法来计算你所能获取的最大利润。你最多可以完成 k 笔交易。

注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

示例 1:

复制代码
输入:k = 2, prices = [2,4,1]
输出:2 解释:在第 1 天 (股票价格 = 2) 的时候买入,在第 2 天 (股票价格 = 4) 的时候卖出,这笔交易所能获得利润 = 4-2 = 2。

示例 2:

复制代码
输入:k = 2, prices = [3,2,6,5,0,3]
输出:7 解释:在第 2 天 (股票价格 = 2) 的时候买入,在第 3 天 (股票价格 = 6) 的时候卖出, 这笔交易所能获得利润 = 6-2 = 4。随后,在第 5 天 (股票价格 = 0) 的时候买入,在第 6 天 (股票价格 = 3) 的时候卖出, 这笔交易所能获得利润 = 3-0 = 3 。

提示:

复制代码
0 <= k <= 100
0 <= prices.length <= 1000
0 <= prices[i] <= 1000

思路

txt 复制代码

JAVA代码

java 复制代码
public class OneEightEight {
    public static void main(String[] args) {
        int k = 2;
        int[] prices = new int[]{2, 4, 1};
        System.out.println(maxProfit(k, prices));
    }
    public static int maxProfit(int k, int[] prices) {
        int n = prices.length;
        int[][] dp = new int[n][2*k+1];
        for (int j = 1; j < 2*k+1; j += 2) dp[0][j] = -prices[0];
        for (int i = 1; i < n; i++) {
            for (int j = 1; j < 2*k+1; j += 2) {
                dp[i][j] = Math.max(dp[i-1][j], dp[i-1][j-1] - prices[i]);
                dp[i][j+1] = Math.max(dp[i-1][j+1], dp[i-1][j] + prices[i]);
            }
        }
        return dp[n-1][2*k];
    }
}

26. 714 买卖股票的最佳时机含手续费

给定一个整数数组 prices,其中第 i 个元素代表了第 i 天的股票价格 ;非负整数 fee 代表了交易股票的手续费用。

你可以无限次地完成交易,但是你每笔交易都需要付手续费。如果你已经购买了一个股票,在卖出它之前你就不能再继续购买股票了。

返回获得利润的最大值。

注意:这里的一笔交易指买入持有并卖出股票的整个过程,每笔交易你只需要为支付一次手续费。

示例 1:

复制代码
输入: prices = [1, 3, 2, 8, 4, 9], fee = 2
输出: 8
解释: 能够达到的最大利润:
在此处买入 prices[0] = 1
在此处卖出 prices[3] = 8
在此处买入 prices[4] = 4
在此处卖出 prices[5] = 9
总利润: ((8 - 1) - 2) + ((9 - 4) - 2) = 8.

注意:

复制代码
0 < prices.length <= 50000.
0 < prices[i] < 50000.
0 <= fee < 50000.

思路

txt 复制代码

JAVA代码

java 复制代码
import java.util.Arrays;
public class SixSevenFour {
    public static void main(String[] args) {
        int[] nums = new int[]{1,3,5,4,7};
        System.out.println(findLengthOfLCIS(nums));
    }
    public static int findLengthOfLCIS(int[] nums) {
        int[] dp = new int[nums.length];
        Arrays.fill(dp,1);
        int res = 1;
        for (int i = 1; i < nums.length; i++) {
            if (nums[i] > nums[i-1]) {
                dp[i] = dp[i-1] + 1;
            }
            if (res < dp[i]) res = dp[i];
        }
        return res;
    }
}

27. 309 买卖的最佳时机含冷冻期

给定一个整数数组,其中第 i 个元素代表了第 i 天的股票价格 。

设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):

你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。

示例:

复制代码
输入: [1,2,3,0,2]
输出: 3
解释: 对应的交易状态为: [买入, 卖出, 冷冻期, 买入, 卖出]

思路

txt 复制代码

JAVA代码

java 复制代码
public class ThreeZeroNine {
    public static void main(String[] args) {
        int[] prices = new int[]{1,2,3,0,2};
        System.out.println(maxProfit(prices));
    }
    public static int maxProfit(int[] prices) {
        int n = prices.length;
        int[][] dp = new int[n][4];
        // 0 : 当天买入股票
        // 1 : 当天刚卖股票
        // 2 : 当天是冷冻期
        // 3 : 当天是冷冻期过后
        dp[0][0] = -prices[0];
        for (int i = 1; i < n; i++) {
            dp[i][0] = Math.max(dp[i-1][0], Math.max(dp[i-1][2], dp[i-1][3]) - prices[i]);
            dp[i][1] = dp[i-1][0] + prices[i];
            dp[i][2] = dp[i-1][1];
            dp[i][3] = Math.max(dp[i-1][2], dp[i-1][3]);
        }
        return Math.max(dp[n-1][1], Math.max(dp[n-1][2], dp[n-1][3]));
    }
}

28. 300 最长递增子序列

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

提示:

复制代码
1 <= nums.length <= 2500
-10^4 <= nums[i] <= 104

思路

txt 复制代码

Java代码

java 复制代码
import java.util.Arrays;
public class ThreeZeroZero {
    public static void main(String[] args) {
        int[] nums = new int[]{10,9,2,5,3,7,101,18};
        System.out.println(lengthOfLIS(nums));
    }
    public static int lengthOfLIS(int[] nums) {
        int n = nums.length;
        int[] dp = new int[n];
        Arrays.fill(dp, 1);
        int res = 0;
        for (int i = 1; i < n; i++) {
            for (int j = 0; j < n; j++) {
                if (nums[i] > nums[j]) dp[i] = Math.max(dp[i], dp[j] + 1);
            }
            res = Math.max(res, dp[i]);
        }
        return res;
    }
}

29. 674 最长连续递增序列

给定一个未经排序的整数数组,找到最长且 连续递增的子序列,并返回该序列的长度。

连续递增的子序列 可以由两个下标 l 和 r(l < r)确定,如果对于每个 l <= i < r,都有 nums[i] < nums[i + 1] ,那么子序列 [nums[l], nums[l + 1], ..., nums[r - 1], nums[r]] 就是连续递增子序列。

示例 1:

复制代码
输入:nums = [1,3,5,4,7]
输出:3
解释:最长连续递增序列是 [1,3,5], 长度为3。尽管 [1,3,5,7] 也是升序的子序列, 但它不是连续的,因为 5 和 7 在原数组里被 4 隔开。

示例 2:

复制代码
输入:nums = [2,2,2,2,2]
输出:1
解释:最长连续递增序列是 [2], 长度为1。

提示:

复制代码
0 <= nums.length <= 10^4
-10^9 <= nums[i] <= 10^9

思路

txt 复制代码

Java代码

java 复制代码
import java.util.Arrays;
public class SixSevenFour {
    public static void main(String[] args) {
        int[] nums = new int[]{1,3,5,4,7};
        System.out.println(findLengthOfLCIS(nums));
    }

    public static int findLengthOfLCIS(int[] nums) {
        int[] dp = new int[nums.length];
        Arrays.fill(dp,1);
        int res = 1;
        for (int i = 1; i < nums.length; i++) {
            if (nums[i] > nums[i-1]) {
                dp[i] = dp[i-1] + 1;
            }
            if (res < dp[i]) res = dp[i];
        }
        return res;
    }
}

30. 1143 最长公共子序列

给定两个字符串 text1 和 text2,返回这两个字符串的最长公共子序列的长度。

一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。

例如,"ace" 是 "abcde" 的子序列,但 "aec" 不是 "abcde" 的子序列。两个字符串的「公共子序列」是这两个字符串所共同拥有的子序列。

若这两个字符串没有公共子序列,则返回 0。

示例 1:

复制代码
输入:text1 = "abcde", text2 = "ace"
输出:3
解释:最长公共子序列是 "ace",它的长度为 3。

示例 2:

复制代码
输入:text1 = "abc", text2 = "abc"
输出:3
解释:最长公共子序列是 "abc",它的长度为 3。

示例 3:

复制代码
输入:text1 = "abc", text2 = "def"
输出:0
解释:两个字符串没有公共子序列,返回 0。

提示:

复制代码
1 <= text1.length <= 1000
1 <= text2.length <= 1000 输入的字符串只含有小写英文字符。

思路

txt 复制代码

Java代码

java 复制代码
public class OneOneFourThree {
    public static void main(String[] args) {
        String text1 = "abcde";
        String text2 = "ace";
        System.out.println(longestCommonSubsequence(text1, text2));
    }
    public static int longestCommonSubsequence(String text1, String text2) {
        int n1 = text1.length();
        int n2 = text2.length();
        int[][] dp = new int[n1+1][n2+1];
        for (int i = 0; i < text1.length(); i++) {
            for (int j = 0; j < text2.length(); j++) {
                if (text1.charAt(i) == text2.charAt(j)) {
                    dp[i+1][j+1] = dp[i][j] + 1;
                } else {
                    dp[i+1][j+1] = Math.max(dp[i+1][j], dp[i][j+1]);
                }
            }
        }
        return dp[n1][n2];
    }
}

31. 718 最长重复子数组

给两个整数数组 A 和 B ,返回两个数组中公共的、长度最长的子数组的长度。

示例:

复制代码
输入:
A: [1,2,3,2,1]
B: [3,2,1,4,7]
输出:3
解释:长度最长的公共子数组是 [3, 2, 1] 。

提示:

复制代码
1 <= len(A), len(B) <= 1000
0 <= A[i], B[i] < 100

思路

txt 复制代码

Java代码

java 复制代码
public class SevenOneEight {
    public static void main(String[] args) {
        int[] nums1 = new int[]{1,2,3,2,1};
        int[] nums2 = new int[]{3,2,1,4,7};
        System.out.println(findLength(nums1, nums2));
    }
    public static int findLength(int[] nums1, int[] nums2) {
        int n1 = nums1.length;
        int n2 = nums2.length;
        int[][] dp = new int[n1+1][n2+1];
        int res = 0;
        for (int i = 0; i < n1; i++) {
            for (int j = 0; j < n2; j++) {
                if (nums1[i] == nums2[j]) {
                    dp[i+1][j+1] = dp[i][j] + 1;
                }
                if (res < dp[i+1][j+1]) res = dp[i+1][j+1];
            }
        }
        return res;
    }
}

34. 1035.不相交的线

在两条独立的水平线上按给定的顺序写下 nums1 和 nums2 中的整数。

现在,可以绘制一些连接两个数字 nums1[i] 和 nums2[j] 的直线,这些直线需要同时满足:

nums1[i] == nums2[j]

且绘制的直线不与任何其他连线(非水平线)相交。

请注意,连线即使在端点也不能相交:每个数字只能属于一条连线。

以这种方法绘制线条,并返回可以绘制的最大连线数。

思路

txt 复制代码

Java代码

java 复制代码
public class OneZeroThreeFive {
    public static void main(String[] args) {
        int[] nums1 = new int[]{1,4,2};
        int[] nums2 = new int[]{1,2,4};
        System.out.println(maxUncrossedLines(nums1, nums2));
    }
    public static int maxUncrossedLines(int[] nums1, int[] nums2) {
        int n1 = nums1.length;
        int n2 = nums2.length;
        int[][] dp = new int[n1+1][n2+1];
        for (int i = 0; i < n1; i++) {
            for (int j = 0; j < n2; j++) {
                if (nums1[i] == nums2[j]) {
                    dp[i+1][j+1] = dp[i][j] + 1;
                } else {
                    dp[i+1][j+1] = Math.max(dp[i+1][j], dp[i][j+1]);
                }
            }
        }
        return dp[n1][n2];
    }
}

35. 53. 最大子序和

给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

示例:

复制代码
输入: [-2,1,-3,4,-1,2,1,-5,4]
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。

思路

txt 复制代码

Java代码

java 复制代码
public class FiveThree {
    public static void main(String[] args) {
        int[] nums = new int[]{-2,1,-3,4,-1,2,1,-5,4};
        System.out.println(maxSubArray(nums));
    }
    public static int maxSubArray(int[] nums) {
        int[] dp = new int[nums.length];
        dp[0] = nums[0];
        int res = nums[0];
        for (int i = 1; i < nums.length; i++) {
            dp[i] = Math.max(dp[i-1] + nums[i], nums[i]);
            res = Math.max(dp[i], res);
        }
        return res;
    }
}

36. 392 判断子序列

给定字符串 s 和 t ,判断 s 是否为 t 的子序列。

字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。(例如,"ace"是"abcde"的一个子序列,而"aec"不是)。

示例 1:

复制代码
输入:s = "abc", t = "ahbgdc"
输出:true

示例 2:

复制代码
输入:s = "axc", t = "ahbgdc"
输出:false

提示:

复制代码
0 <= s.length <= 100
0 <= t.length <= 10^4
两个字符串都只由小写字符组成。

思路

txt 复制代码

Java代码

java 复制代码
public class ThreeNineTwo {
    public static void main(String[] args) {
        String s = "abc";
        String t = "ahbgdc";
        System.out.println(isSubsequence(s, t));
    }
    public static boolean isSubsequence(String s, String t) {
        int n1 = s.length();
        int n2 = t.length();
        int[][] dp = new int[n1+1][n2+1];
        for (int i = 0; i < n1; i++) {
            for (int j = 0; j < n2; j++) {
                if (s.charAt(i) == t.charAt(j)) {
                    dp[i+1][j+1] = dp[i][j] + 1;
                } else {
                    dp[i+1][j+1] = Math.max(dp[i+1][j], dp[i][j+1]);
                }
            }
        }
        if (dp[n1][n2] == n1) return true;
        return false;
    }
}

37. 115 不同的子序列

给定一个字符串 s 和一个字符串 t ,计算在 s 的子序列中 t 出现的个数。

字符串的一个 子序列 是指,通过删除一些(也可以不删除)字符且不干扰剩余字符相对位置所组成的新字符串。(例如,"ACE" 是 "ABCDE" 的一个子序列,而 "AEC" 不是)

题目数据保证答案符合 32 位带符号整数范围。

提示

复制代码
0 <= s.length, t.length <= 1000
s 和 t 由英文字母组成

思路

txt 复制代码

Java代码

java 复制代码
public class OneOneFive {
    /*
    假设当前遍历到的 t 是 bag,s 是 babga,此时 s 又来了一个 g, 和当前 t 的最后一个元素相同了。
    这个时候新的 babgag 含有 bag 的数量是在 babga 原本包含的 bag 数量(dp【i - 1】【j】)的基础上,增加了使用新来的 g 新组成的 bag 数量。
    新来的 g 能组成多少个bag,其实是原本的 babga 包含多少个 ba。也就是 dp【i - 1】【j - 1】。
    */
    // dp[i][j] 指的是 s字符串中 第 i 个子序列前 包含的 t字符串中 第 j 个子序列前的个数
    public static void main(String[] args) {
        String s = "rabbbit";
        String t = "rabbit";
        System.out.println(numDistinct(s, t));
    }
    public static int numDistinct(String s, String t) {
        int n1 = s.length();
        int n2 = t.length();
        int[][] dp = new int[n1+1][n2+1];
        for (int i = 0; i <= n1; i++) dp[i][0] = 1;
        for (int i = 0; i < n1; i++) {
            for (int j = 0; j < n2; j++) {
                if (s.charAt(i) == t.charAt(j)) {
                    dp[i+1][j+1] = dp[i][j+1] + dp[i][j];
                } else {
                    dp[i+1][j+1] = dp[i][j+1];
                }
            }
        }
        return dp[n1][n2];
    }
}

38. 583 两个字符串的删除操作

给定两个单词 word1 和 word2,找到使得 word1 和 word2 相同所需的最小步数,每步可以删除任意一个字符串中的一个字符。

示例:

复制代码
输入: "sea", "eat"
输出: 2
解释: 第一步将"sea"变为"ea",第二步将"eat"变为"ea"

思路

txt 复制代码

Java代码

java 复制代码
import java.util.Arrays;
public class FiveEightThree {
    public static void main(String[] args) {
        String word1 = "sea";
        String word2 = "eat";
        System.out.println(minDistance(word1, word2));
    }
    public static int minDistance(String word1, String word2) {
        char[] arr1 = word1.toCharArray();
        char[] arr2 = word2.toCharArray();
        int[][] dp = new int[arr1.length+1][arr2.length+1];
        for (int i = 0; i < arr1.length + 1; i++) dp[i][0] = i;
        for (int j = 0; j < arr2.length + 1; j++) dp[0][j] = j;
        for (int i = 0; i < word1.length(); i++) {
            for (int j = 0; j < word2.length(); j++) {
                if (word1.charAt(i) == word2.charAt(j)) {
                    dp[i+1][j+1] = dp[i][j];
                } else {
                    dp[i+1][j+1] = Math.min(dp[i+1][j], dp[i][j+1]) + 1;
                }
            }
        }
        return dp[arr1.length][arr2.length];
    }
}

39. 72 编辑距离

给你两个单词 word1 和 word2,请你计算出将 word1 转换成 word2 所使用的最少操作数 。

你可以对一个单词进行如下三种操作:

插入一个字符

删除一个字符

替换一个字符

示例 1:

复制代码
输入:word1 = "horse", word2 = "ros"
输出:3
解释: horse -> rorse (将 'h' 替换为 'r') rorse -> rose (删除 'r') rose -> ros (删除 'e')

示例 2:

复制代码
输入:word1 = "intention", word2 = "execution"
输出:5
解释: intention -> inention (删除 't') inention -> enention (将 'i' 替换为 'e') enention -> exention (将 'n' 替换为 'x') exention -> exection (将 'n' 替换为 'c') exection -> execution (插入 'u')

提示:

复制代码
0 <= word1.length, word2.length <= 500
word1 和 word2 由小写英文字母组成

思路

txt 复制代码

Java代码

java 复制代码
public class SevenTwo {
    public static void main(String[] args) {
        String word1 = "horse";
        String word2 = "ros";
        System.out.println(minDistance(word1, word2));
    }
    public static int minDistance(String word1, String word2) {
        int n1 = word1.length();
        int n2 = word2.length();
        int[][] dp = new int[n1+1][n2+1];
        for (int i = 0; i < n1 + 1; i++) dp[i][0] = i;
        for (int j = 0; j < n2 + 1; j++) dp[0][j] = j;
        for (int i = 0; i < word1.length(); i++) {
            for (int j = 0; j < word2.length(); j++) {
                if (word1.charAt(i) == word2.charAt(j)) {
                    dp[i+1][j+1] = dp[i][j];
                } else {
                    dp[i+1][j+1] = Math.min(Math.min(dp[i][j],dp[i+1][j]),dp[i][j+1]) + 1;
                }
            }
        }
        return dp[n1][n2];
    }
}

40. 647 回文子串

给定一个字符串,你的任务是计算这个字符串中有多少个回文子串。

具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串。

示例 1:

复制代码
输入:"abc"
输出:3
解释:三个回文子串: "a", "b", "c"。

示例 2:

复制代码
输入:"aaa"
输出:6
解释:6个回文子串: "a", "a", "a", "aa", "aa", "aaa"

提示:输入的字符串长度不会超过 1000 。

思路

txt 复制代码

Java代码

java 复制代码
public class SixFourSeven {
    public static void main(String[] args) {
        String s = "aaa";
        System.out.println(countSubstrings(s));
    }
    public static int countSubstrings(String s) {
        int n = s.length();
        boolean[][] dp = new boolean[n][n];
        int res = 0;
        for (int i = n - 1; i >= 0; i--) {
            for (int j = i; j < n; j++) {
                if (s.charAt(i) == s.charAt(j)) {
                    if (j - i <= 1) {
                        res++;
                        dp[i][j] = true;
                    } else {
                        if (dp[i+1][j-1]) {
                            res++;
                            dp[i][j] = true;
                        }
                    }
                }
            }
        }
        return res;
    }
}

41. 516 最长回文子序列

给定一个字符串 s ,找到其中最长的回文子序列,并返回该序列的长度。可以假设 s 的最大长度为 1000 。

**示例 1: **

复制代码
输入: "bbbab" 
输出: 4 
一个可能的最长回文子序列为 "bbbb"。

示例 2:

复制代码
输入:"cbbd" 
输出: 2 
一个可能的最长回文子序列为 "bb"。

提示:

复制代码
1 <= s.length <= 1000
s 只包含小写英文字母

思路

txt 复制代码

Java代码

java 复制代码
public class FiveOneSix {
    public static void main(String[] args) {
        String s = "bbbab";
        System.out.println(longestPalindromeSubseq(s));
    }
    public static int longestPalindromeSubseq(String s) {
        int n = s.length();
        int[][] dp = new int[n][n];
        for (int i = 0; i < n; i++) dp[i][i] = 1;
        for (int i = n - 2; i >= 0; i--) {
            for (int j = i + 1; j < n; j++) {
                if (s.charAt(i) == s.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][n-1];
    }
}
相关推荐
汤姆Tom2 小时前
我把 Vue Router 搬到了 React —— 从 API 到文件路由、转场动画,一个都不少
前端·react.js·面试
indexsunny2 小时前
互联网大厂Java面试实录:Spring Boot与微服务在电商场景中的应用解析
java·spring boot·面试·kafka·spring security·电商·microservices
robinbird_2 小时前
量化笔试面试概率统计知识要求记录
面试·职场和发展
www_stdio2 小时前
手搓一个 Mini React:从 JSX 到虚拟 DOM 的完整实现
前端·react.js·面试
苏纪云2 小时前
【蓝桥杯】——>进制转换、前缀和、双指针[滑动窗口]
职场和发展·蓝桥杯
I_LPL2 小时前
hot 100 普通数组、矩阵专题
java·数据结构·矩阵·动态规划·贪心·数组·求职面试
wuqingshun3141592 小时前
蓝桥杯 魔法蘑菇
职场和发展·蓝桥杯
茶杯梦轩2 小时前
面试常问:TCP与HTTP的Keep-Alive机制大揭秘
服务器·网络协议·面试
z20348315202 小时前
17届蓝桥杯嵌入式赛道开发板外设使用教程——按键、蜂鸣器、LCD屏幕
mongodb·职场和发展·蓝桥杯