LeetCode —— 动态规划

持续更新中.....................................

509. 斐波那契数

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

示例:输入:n = 2         输出:1         解释:F(2) = F(1) + F(0) = 1 + 0 = 1

class Solution {
    public int fib(int n) {
        if(n < 2){
            return n;
        }

        int[] dp = new int[n + 1];
        dp[0] = 0;
        dp[1] = 1;
        for(int i = 2; i <= n; i++){
            dp[i] = dp[i - 1] + dp[i - 2];
        }
        return dp[n];
    }
}

70. 爬楼梯

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

示例:输入:n = 2         输出:2

class Solution {
    public 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];
    }
}

746. 使用最小花费爬楼梯

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

示例:输入:cost = [1,100,1,1,1,100,1,1,100,1]         输出:6

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

62. 不同的路径

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

示例:输入:m = 3, n = 7         输出:28

class Solution {
    public int uniquePaths(int m, int n) {
        int[][] dp = new int[m][n];
        //初始化
        for(int i = 0; i < m; i++){
            dp[i][0] = 1;
        }
        for(int j = 0; j < n; j++){
            dp[0][j] = 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];
    }
}

63. 不同的路径II

一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish”)。现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径?网格中的障碍物和空位置分别用 1 和 0 来表示。

示例:输入:obstacleGrid = [[0,0,0],[0,1,0],[0,0,0]]         输出:2

class Solution {
    public 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 && obstacleGrid[i][0] == 0; i++){
            dp[i][0] = 1;
        }
        for(int j = 0; j < n && obstacleGrid[0][j] == 0; j++){
            dp[0][j] = 1;
        }

        for(int i = 1; i < m; i++){
            for(int j = 1; j < n; j++){
                if(obstacleGrid[i][j] == 0){
                    dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
                }else{
                    dp[i][j] = 0;
                }
            }
        }
        return dp[m - 1][n - 1];
    }
}

343. 整数拆分

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

示例:输入: n = 10         输出: 36         解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36。

class Solution {
    public int integerBreak(int n) {
        int[] dp = new int[n + 1];   //dp[i] 为正整数 i 拆分后的结果的最大乘积
        dp[2] = 1;
        for(int i = 3; i <= n; i++){
            for(int j = 1; j <= i - j; j++){
                // (i - j) * j 是单纯的把整数 i 拆分为两个数 也就是 i,i-j ,再相乘
                // dp[i - j] * j 是将 i 拆分成两个以及两个以上的个数,再相乘。
                dp[i] = Math.max(dp[i], Math.max((i - j) * j, dp[i - j] * j));
            }
        }
        return dp[n];
    }
}

 53. 最大子数组和

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

示例:输入:nums = [-2,1,-3,4,-1,2,1,-5,4]        输出:6

class Solution {
    public int maxSubArray(int[] nums) {
        int[] dp = new int[nums.length];
        dp[0] = nums[0];
        for(int i = 1; i < nums.length; i++){
            dp[i] = Math.max(dp[i - 1] + nums[i], nums[i]);
        }
        return Arrays.stream(dp).max().getAsInt();
    }
}

96. 不同的二叉搜索树

给你一个整数 n ,求恰由 n 个节点组成且节点值从 1 到 n 互不相同的 二叉搜索树 有多少种?返回满足题意的二叉搜索树的种数。

示例:输入:n = 3         输出:5

class Solution {
    public int numTrees(int n) {
        int[] dp = new int[n + 1];
        //初始化0个节点和1个节点的情况
        dp[0] = 1;
        dp[1] = 1;
        for(int i = 2; i <= n; i++){
            for(int j = 1; j <= i; j++){
                //对于第i个节点,需要考虑 1 作为根节点直到 i 作为根节点的情况,所以需要累加
                //一共i个节点,对于根节点 j 时,左子树的节点个数为j-1,右子树的节点个数为i-j
                dp[i] += dp[j - 1] * dp[i - j];
            }
        }
        return dp[n];
    }
}

01背包

有n件物品和一个最多能背重量为bagSize的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品只能用一次,问背包能背的物品最大价值是多少?

示例:输入:weight = [1, 3, 4]; value = [15, 20, 30]; bagsize = 4;         输出:35

class Solution{
    public int bagProblem(int[] weight, int[] value, int bagSize) {
        int[][] dp = new int[weight.length + 1][bagSize + 1];
        
        for(int i = 1; i <= weight.length; i++){
            for(int j = 1; j <= bagSize; j++){
                if(j < weight[i - 1]){
                    dp[i][j] = dp[i - 1][j];  //当前背包的容量都没有当前物品i大的时候,则不放物品i
                }else{
                    //比较 不放物品i 和 放物品i 这两种情况下,哪种背包中物品的价值最大
                    dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - weight[i - 1]] + value[i - 1]);
                }
            }
        }
        return dp[weight.length][bagSize];
    }
}
class Solution{
    public int bagProblem(int[] weight, int[] value, int bagSize) {
        int[] dp = new int[bagSize + 1];  //dp[j]表示背包容量为j时,能获得的最大价值
        
        for(int i = 0; i < weight.length; i++){
            for(int j = bagSize; j >= weight[i]; j--){
                dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]);
            }
        }
        return dp[bagSize];
    }
}

416. 分割等和子集

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

示例:输入:nums = [1,5,11,5]         输出:true

class Solution {
    public boolean canPartition(int[] nums) {
        int sum = Arrays.stream(nums).sum();
        if(sum % 2 != 0){
            return false;
        }

        //转化为01背包问题:选取的数字的和恰好等于整个数组的元素和的一半
        int target = sum / 2;
        boolean[][] dp = new boolean[nums.length + 1][target + 1];

        //初始化dp
        dp[0][0] = true;

        for(int i = 1; i <= nums.length; i++){
            for(int j = 1; j <= target; j++){
                if(j == nums[i - 1]){
                    dp[i][j] = true;
                }else if(j < nums[i - 1]){
                    dp[i][j] = dp[i - 1][j];
                }else{
                    dp[i][j] = dp[i - 1][j] || dp[i - 1][j - nums[i - 1]];
                }
            }
        }
        return dp[nums.length][target];
    }
}
class Solution {
    public boolean canPartition(int[] nums) {
        int sum = Arrays.stream(nums).sum();
        if(sum % 2 != 0){
            return false;
        }

        //转化为01背包问题:选取的数字的和恰好等于整个数组的元素和的一半
        int target = sum / 2;
        boolean[] dp = new boolean[target + 1];

        //初始化dp
        if(nums[0] <= target){
            dp[nums[0]] = true;
        }

        for(int i = 1; i < nums.length; i++){
            for(int j = target; j >= nums[i]; j--){
                dp[j] = dp[j] || dp[j - nums[i]];
            }
        }
        return dp[target];
    }
}
class Solution {
    public boolean canPartition(int[] nums) {
        int sum = Arrays.stream(nums).sum();
        if(sum % 2 != 0){
            return false;
        }

        int target = sum / 2;
        int[] dp = new int[target + 1];

        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;
    }
}

1049. 最后一块石头的重量

有一堆石头,用整数数组 stones 表示。其中 stones[i] 表示第 i 块石头的重量。每一回合,从中选出任意两块石头,然后将它们一起粉碎。假设石头的重量分别为 x 和 y,且 x <= y。那么粉碎的可能结果如下:

如果 x == y,那么两块石头都会被完全粉碎;
如果 x != y,那么重量为 x 的石头将会完全粉碎,而重量为 y 的石头新重量为 y-x。
最后,最多只会剩下一块 石头。返回此石头 最小的可能重量 。如果没有石头剩下,就返回 0。

示例:输入:stones = [2,7,4,1,8,1]         输出:1

class Solution {
    public int lastStoneWeightII(int[] stones) {
        //石头的总重量为sum, ki=-1的石头的重量之和为neg,则其余ki=1的石头的重量之和为sum−neg
        //则结果为:ki * stones[i]的累加和 = (sum - neg) - neg = sum - 2 * neg
        //要使最后一块石头的重量尽可能地小, neg需要在不超过 ⌊sum/2⌋ 的前提下尽可能地大
        //本问题可以看作是背包容量为 ⌊sum/2⌋,物品的重量与价值均为stones[i]的0-1背包问题。 
        int sum = Arrays.stream(stones).sum();
        int target = sum / 2;
        int[][] dp = new int[stones.length + 1][target + 1];

        for(int i = 1; i <= stones.length; i++){
            for(int j = 1; j <= target; j++){
                if(j < stones[i - 1]){
                    dp[i][j] = dp[i - 1][j];
                }else{
                    dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - stones[i - 1]] + stones[i - 1]);
                }
            }
        }
        return sum - 2 * dp[stones.length][target];
    }
}
class Solution {
    public int lastStoneWeightII(int[] stones) {
        int sum = Arrays.stream(stones).sum();
        int target = sum / 2;
        int[] dp = new int[target + 1];

        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];
    }
}

494. 目标和

给你一个整数数组 nums 和一个整数 target 。向数组中的每个整数前添加 '+' 或 '-' ,然后串联起所有整数,可以构造一个 表达式 :例如,nums = [2, 1] ,可以在 2 之前添加 '+' ,在 1 之前添加 '-' ,然后串联起来得到表达式 "+2-1" 。返回可以通过上述方法构造的、运算结果等于 target 的不同 表达式 的数目。

示例:输入:nums = [1,1,1,1,1], target = 3         输出:5

class Solution {
    public int findTargetSumWays(int[] nums, int target) {
        int sum = Arrays.stream(nums).sum();
        if(sum < target || (sum - target) % 2 != 0){
            return 0;
        }

        // 数组的元素和为sum,添加-号的元素之和为neg,则其余添加+的元素之和为sum−neg
        // (sum−neg)−neg = sum−2⋅neg = target   则neg = (sum - target) / 2
        // 问题转化成在数组 nums nums 中选取若干元素,使得这些元素之和等于 neg neg,计算选取元素的方案数
        // dp[i][j] 表示在数组nums的前i个数中选取元素,使得这些元素之和等于j 的方案数
        int neg = (sum - target) / 2;
        int[][] dp = new int[nums.length + 1][neg + 1];

        //初始化dp数组
        dp[0][0] = 1;

        for(int i = 1; i <= nums.length; i++){
            for(int j = 0; j <= neg; j++){
                if(j < nums[i - 1]){
                    dp[i][j] = dp[i - 1][j];
                }else{
                    dp[i][j] = dp[i - 1][j] + dp[i - 1][j - nums[i - 1]];
                }
            }
        }
        return dp[nums.length][neg];
    }
}
class Solution {
    public int findTargetSumWays(int[] nums, int target) {
        int sum = Arrays.stream(nums).sum();
        if(sum < target || (sum - target) % 2 != 0){
            return 0;
        }

        int neg = (sum - target) / 2;
        int[] dp = new int[neg + 1];

        dp[0] = 1;
        for(int i = 0; i < nums.length; i++){
            for(int j = neg; j >= nums[i]; j--){
                dp[j] += dp[j - nums[i]];
            }
        }
        return dp[neg];
    }
}

474. 一和零

给你一个二进制字符串数组 strs 和两个整数 m 和 n 。请你找出并返回 strs 的最大子集的长度,该子集中 最多 有 m 个 0 和 n 个 1 。如果 x 的所有元素也是 y 的元素,集合 x 是集合 y 的 子集 。

示例:输入:strs = ["10", "0001", "111001", "1", "0"], m = 5, n = 3         输出:4

class Solution {
    public int findMaxForm(String[] strs, int m, int n) {
        //dp[i][j][k]表示在前i个字符串中,使用j个0和k个1的情况下最多可以得到的字符串数量
        int[][][] dp = new int[strs.length + 1][m + 1][n + 1];
        for(int i = 1; i <= strs.length; i++){
            int[] zeroOne = getZeroOne(strs[i - 1]);
            int zero = zeroOne[0];
            int one = zeroOne[1];
            for(int j = 0; j <= m; j++){
                for(int k = 0; k <= n; k++){
                    if(j < zero || k < one){
                        dp[i][j][k] = dp[i - 1][j][k];
                    }else{
                        dp[i][j][k] = Math.max(dp[i - 1][j][k], dp[i - 1][j - zero][k - one] + 1);
                    }
                }
            }
        }
        return dp[strs.length][m][n];
    }

    public int[] getZeroOne(String str){
        int[] zeroOne = new int[2];
        for (int i = 0; i < str.length(); i++) {
            zeroOne[str.charAt(i) - '0']++;
        }
        return zeroOne;
    }
}
class Solution {
    public int findMaxForm(String[] strs, int m, int n) {
        int[][] dp = new int[m + 1][n + 1];
        for(int i = 0; i < strs.length; i++){
            int[] zeroOne = getZeroOne(strs[i]);;
            int zero = zeroOne[0];
            int one = zeroOne[1];
            for(int j = m; j >= zero; j--){
                for(int k = n; k >= one; k--){
                    dp[j][k] = Math.max(dp[j][k], dp[j - zero][k - one] + 1);
                }
            }
        }
        return dp[m][n];
    }

    public int[] getZeroOne(String str){
        int[] zeroOne = new int[2];
        for (int i = 0; i < str.length(); i++) {
            zeroOne[str.charAt(i) - '0']++;
        }
        return zeroOne;
    }
}

完全背包

有n件物品和一个最多能背重量为bagSize的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品都可以放多次,问背包能背的物品最大价值是多少?

示例:输入:weight = [1, 3, 4]; value = [15, 20, 30]; bagsize = 4;         输出:60

class Solution{
    public int bagProblem(int[] weight, int[] value, int bagSize){
        int[][] dp = new int[weight.length + 1][bagSize + 1];

        for(int i = 1; i <= weight.length; i++){
            for(int j = 1; j <= bagSize; j++){
                if(j < weight[i - 1]){
                    dp[i][j] = dp[i - 1][j];
                }else{
                    dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - weight[i - 1]] + value[i - 1]);    // 注意:完全背包可以多次取同一元素,因此是dp[i]。01背包是dp[i-1]
                }
            }
        }
        return dp[weight.length][bagSize];
    }
}
class Solution{
    public int bagProblem(int[] weight, int[] value, int bagSize){
        int[] dp = new int[bagSize + 1];  //dp[j]表示背包容量为j时,能获得的最大价值

        for(int i = 0; i < weight.length; i++){
            for(int j = weight[i]; j <= bagSize; j++){
                dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]);
            }
        }
        return dp[bagSize];
    }
}

518. 零钱兑换II

给你一个整数数组 coins 表示不同面额的硬币,另给一个整数 amount 表示总金额。请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0 。假设每一种面额的硬币有无限个。 题目数据保证结果符合 32 位带符号整数。

示例:输入:amount = 5, coins = [1, 2, 5]         输出:4

如果求组合数就是外层for循环遍历物品,内层for遍历背包

如果求排列数就是外层for遍历背包,内层for循环遍历物品

class Solution {
    public int change(int amount, int[] coins) {
        int[][] dp = new int[coins.length + 1][amount + 1];

        //初始化dp数组
        dp[0][0] = 1;

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

        //初始化dp数组,表示金额为0时只有一种情况
        dp[0] = 1;

        for(int i = 0; i < coins.length; i++){
            for(int j = coins[i]; j <= amount; j++){
                dp[j] += dp[j - coins[i]];
            }
        }
        return dp[amount];
    }
}

377. 组合总和IV

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

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

如果求组合数就是外层for循环遍历物品,内层for遍历背包

如果求排列数就是外层for遍历背包,内层for循环遍历物品

class Solution {
    public int combinationSum4(int[] nums, int target) {
        int[][] dp = new int[nums.length + 1][target + 1];

        //初始化dp
        for(int i = 0; i <= nums.length; i++){
            dp[i][0] = 1;
        }

        Arrays.sort(nums);
        for(int j = 1; j <= target; j++){
            for(int i = 1; i <= nums.length; i++){
                if(j < nums[i - 1]){
                    dp[i][j] = dp[i - 1][j];
                }else{
                    for(int k = 0; k < i; k++){
                        dp[i][j] += dp[i][j - nums[k]];
                    }
                }
            }
        }
        return dp[nums.length][target];
    }
}
class Solution {
    public int combinationSum4(int[] nums, int target) {
        int[] dp = new int[target + 1];

        dp[0] = 1;
        for(int i = 0; i <= target; i++){
            for(int j = 0; j < nums.length; j++){
                if(i >= nums[j]){
                    dp[i] += dp[i - nums[j]];
                }
            }
        }
        return dp[target];
    }
}

70. 爬楼梯(改编)

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。每次你可以爬 1 或 2 、... 或m个台阶。你有多少种不同的方法可以爬到楼顶呢? (该题目可以看成完全背包的排列问题)

class Solution {
    public int climbStairs(int n) {
        int m = 2;  // 设置成一次可以爬1层或者2层
        int[][] dp = new int[m + 1][n + 1];

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

        for(int j = 1; j <= n; j++){
            for(int i = 1; i <= m; i++){
                if(j < i){
                    dp[i][j] = dp[i - 1][j];
                }else{
                    for(int k = 1; k <= i; k++){
                        dp[i][j] += dp[i][j - k];
                    }
                }
            }
        }
        return dp[m][n];
    }
}
class Solution {
    public int climbStairs(int n) {
        int[] dp = new int[n + 1];
        int m = 2;
        dp[0] = 1;
        
        for(int i = 1; i <= n; i++){
            for(int j = 1; j <= m; j++){
                if(i >= j){
                    dp[i] += dp[i - j];
                }
            }
        }
        return dp[n];
    }
}

322. 零钱兑换

给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。你可以认为每种硬币的数量是无限的。

示例:输入:coins = [1, 2, 5], amount = 11   输出:3

class Solution {
    public int coinChange(int[] coins, int amount) {
        int[][] dp = new int[coins.length + 1][amount + 1];
        for(int i = 0; i <= coins.length; i++){
            Arrays.fill(dp[i], Integer.MAX_VALUE);
        }

        // 初始化dp : 当金额为0时需要的硬币数目为0
        for(int i = 0; i<= coins.length; i++){
            dp[i][0] = 0;   
        }

        for(int i = 1; i <= coins.length; i++){
            for(int j = 1; j <= amount; j++){
                if(j < coins[i - 1] || dp[i][j - coins[i - 1]] == Integer.MAX_VALUE){
                    dp[i][j] = dp[i - 1][j];
                }else{
                    dp[i][j] = Math.min(dp[i - 1][j], dp[i][j - coins[i - 1]] + 1);
                }
            }
        }
        return dp[coins.length][amount] == Integer.MAX_VALUE ? -1 : dp[coins.length][amount];
    }
}
class Solution {
    public int coinChange(int[] coins, int amount) {
        int[] dp = new int[amount + 1];

        Arrays.fill(dp, Integer.MAX_VALUE);
        dp[0] = 0;      //当金额为0时需要的硬币数目为0

        for(int i = 0; i < coins.length; i++){
            for(int j = coins[i]; j <= amount; j++){
                if(dp[j - coins[i]] != Integer.MAX_VALUE){
                    dp[j] = Math.min(dp[j], dp[j - coins[i]] + 1);
                }
            }
        }
        return dp[amount] == Integer.MAX_VALUE ? -1 : dp[amount];
    }
}

279. 完全平方数

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

示例:输入:n = 12         输出:3        解释:12 = 4 + 4 + 4

class Solution {
    public int numSquares(int n) {
        int m = (int) Math.sqrt(n);
        int[][] dp = new int[m + 1][n + 1];
        
        // 初始化dp
        for(int j = 0; j <= n; j++){
            dp[0][j] = Integer.MAX_VALUE;
        }
        for(int i = 0; i <= m; i++){
            dp[i][0] = 0;
        }

        for(int i = 1; i <= m; i++){
            for(int j = 1; j <= n; j++){
                if(j < i * i){
                    dp[i][j] = dp[i - 1][j];
                }else{
                    dp[i][j] = Math.min(dp[i - 1][j], dp[i][j - i * i] + 1);
                }
            }
        }
        return dp[m][n];
    }
}
class Solution {
    public int numSquares(int n) {
        int[] dp = new int[n + 1];
        Arrays.fill(dp, Integer.MAX_VALUE);
        dp[0] = 0;

        int num = (int) Math.sqrt(n);
        for(int i = 1; i <= num; i++){
            for(int j = i * i; j <= n; j++){
                dp[j] = Math.min(dp[j], dp[j - i * i] + 1);
            }
        }
        return dp[n];
    }
}

139. 单词拆分

给你一个字符串 s 和一个字符串列表 wordDict 作为字典。请你判断是否可以利用字典中出现的单词拼接出 s 。注意:不要求字典中出现的单词全部都使用,并且字典中的单词可以重复使用。

示例:输入: s = "leetcode", wordDict = ["leet", "code"]         输出: true

class Solution {
    public 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; j++){
                if(dp[j] && set.contains(s.substring(j, i))){
                    dp[i] = true;
                }
            }
        }
        return dp[s.length()];
    }
}

198. 打家劫舍

你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。

示例:输入:[1,2,3,1]         输出:4

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

213. 打家劫舍II

你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都 围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警 。给定一个代表每个房屋存放金额的非负整数数组,计算你 在不触动警报装置的情况下 ,今晚能够偷窃到的最高金额。

示例:输入:nums = [2,3,2]         输出:3

class Solution {
    public int rob(int[] nums) {
        if(nums.length == 1){
            return nums[0];
        }
        int result1 = robRange(nums, 0, nums.length - 2);  //包含首元素,不包含尾元素
        int result2 = robRange(nums, 1, nums.length - 1);  //包含尾元素,不包含首元素
        return Math.max(result1, result2);
    }

    //打家劫舍I
    public int robRange(int[] nums, int start, int end) {
        if(start == end){
            return nums[start];
        }
        
        int[] dp = new int[nums.length];
        dp[start] = nums[start];
        dp[start + 1] = Math.max(dp[start], nums[start + 1]);
        for(int i = start + 2; i <= end; i++){
            dp[i] = Math.max(dp[i - 1], dp[i - 2] + nums[i]);
        }
        return dp[end];
    }
}

337. 打家劫舍III

小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为 root 。除了 root 之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果 两个直接相连的房子在同一天晚上被打劫 ,房屋将自动报警。给定二叉树的 root 。返回 在不触动警报的情况下 ,小偷能够盗取的最高金额 。

示例:输入: root = [3,2,3,null,3,null,1]         输出: 7

class Solution {
    public int rob(TreeNode root) {
        int[] dp = dfs(root);
        return Math.max(dp[0], dp[1]);
    }

    public int[] dfs(TreeNode root){
        //dp数组:下标为0记录不偷该节点所得到的的最大金钱,下标为1记录偷该节点所得到的的最大金钱。
        int[] dp = new int[2];
        if(root == null){
            return dp;
        }

        //后序遍历
        int[] left = dfs(root.left);     //递归左节点,得到左节点偷与不偷的金钱。
        int[] right = dfs(root.right);   //递归右节点,得到右节点偷与不偷的金钱。

        //不偷当前节点:Max(左孩子不偷,左孩子偷) + Max(右孩子不偷,右孩子偷)
        dp[0] = Math.max(left[0], left[1]) + Math.max(right[0], right[1]);
        //偷当前节点:左孩子不偷+ 右孩子不偷 + 当前节点偷
        dp[1] = left[0] + right[0] + root.val;

        return dp;
    }
}

121. 买股票的最佳时机

给定一个数组 prices,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。

class Solution {
    public int maxProfit(int[] prices) {
        int[][] dp = new int[prices.length][2];
        dp[0][0] = -prices[0];    // dp[i][0]代表第i天持有股票的最大收益
        dp[0][1] = 0;             // dp[i][1]代表第i天不持有股票的最大收益

        for(int i = 1; i < prices.length; 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 dp[prices.length - 1][1];
    }
}
class Solution {
    public int maxProfit(int[] prices) {
        int[] dp = new int[2];
        // 0表示持有,1表示卖出
        dp[0] = -prices[0];
        dp[1] = 0;

        for(int i = 1; i < prices.length; i++){
            dp[0] = Math.max(dp[0], -prices[i]);
            dp[1] = Math.max(dp[1], dp[0] + prices[i]);
        }
        return dp[1];
    }
}

122. 买股票的最佳时机II

给你一个整数数组 prices ,其中 prices[i] 表示某支股票第 i 天的价格。在每一天,你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买,然后在 同一天 出售。返回 你能获得的 最大 利润 。

示例:输入:prices = [7,1,5,3,6,4]         输出:7

class Solution {
    public int maxProfit(int[] prices) {
        int[][] dp = new int[prices.length][2];
        dp[0][0] = -prices[0];   // dp[i][0]代表第i天持有股票的最大收益
        dp[0][1] = 0;            // dp[i][1]代表第i天不持有股票的最大收益
        
        for(int i = 1; i < prices.length; 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 dp[prices.length - 1][1];
    }
}
class Solution {
    public int maxProfit(int[] prices) {
        int[] dp = new int[2];
        // 0表示持有,1表示卖出
        dp[0] = -prices[0];
        dp[1] = 0;

        for(int i = 1; i < prices.length; i++){
            dp[0] = Math.max(dp[0], dp[1] - prices[i]);
            dp[1] = Math.max(dp[1], dp[0] + prices[i]);
        }
        return dp[1];
    }
}

123. 买股票的最佳时机III

给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

示例:输入:prices = [3,3,5,0,0,3,1,4]         输出:6

class Solution {
    public int maxProfit(int[] prices) {
        int[][] dp = new int[prices.length][4];
        dp[0][0] = -prices[0];      // dp[0][0]代表第一次交易的买入
        dp[0][1] = 0;               // dp[0][1]代表第一次交易的卖出
        dp[0][2] = -prices[0];      // dp[0][2]代表第二次交易的买入
        dp[0][3] = 0;               // dp[0][3]代表第二次交易的卖出

        for(int i = 1; i < prices.length; 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]);
            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]);
        }
        return dp[prices.length - 1][3];
    }
}
class Solution {
    public int maxProfit(int[] prices) {
        int[] dp = new int[4];
        dp[0] = -prices[0];     // dp[0]代表第一次交易的买入
        dp[1] = 0;              // dp[1]代表第一次交易的卖出
        dp[2] = -prices[0];     // dp[2]代表第二次交易的买入
        dp[3] = 0;              // dp[3]代表第二次交易的卖出

        for(int i = 1; i < prices.length; i++){
            dp[0] = Math.max(dp[0], -prices[i]);
            dp[1] = Math.max(dp[1], dp[0] + prices[i]);
            dp[2] = Math.max(dp[2], dp[1] - prices[i]);
            dp[3] = Math.max(dp[3], dp[2] + prices[i]);
        }
        return dp[3];
    }
}

188. 买股票的最佳时机IV

给定一个整数数组 prices ,它的第 i 个元素 prices[i] 是一支给定的股票在第 i 天的价格,和一个整型 k 。设计一个算法来计算你所能获取的最大利润。你最多可以完成 k 笔交易。也就是说,你最多可以买 k 次,卖 k 次。注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

示例:输入:k = 2, prices = [3,2,6,5,0,3]         输出:7

class Solution {
    public int maxProfit(int k, int[] prices) {
        int[] dp = new int[2 * k];
        for(int i = 0; i < k; i++){
            dp[i * 2] = -prices[0];
        }

        for(int i = 1; i < prices.length; i++){
            dp[0] = Math.max(dp[0], -prices[i]);
            dp[1] = Math.max(dp[1], dp[0] + prices[i]);
            for(int j = 2; j < 2 * k; j += 2){
                dp[j] = Math.max(dp[j], dp[j - 1] - prices[i]);
                dp[j + 1] = Math.max(dp[j + 1], dp[j] + prices[i]);
            }
        }
        return dp[2 * k - 1];
    }
}

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

给定一个整数数组 prices,其中 prices[i]表示第 i 天的股票价格 ;整数 fee 代表了交易股票的手续费用。你可以无限次地完成交易,但是你每笔交易都需要付手续费。如果你已经购买了一个股票,在卖出它之前你就不能再继续购买股票了。返回获得利润的最大值。

示例:输入:prices = [1, 3, 2, 8, 4, 9], fee = 2         输出:8

class Solution {
    public int maxProfit(int[] prices, int fee) {
        int[] dp = new int[2];
        dp[0] = -prices[0];
        dp[1] = 0;

        for(int i = 1; i < prices.length; i++){
            dp[0] = Math.max(dp[0], dp[1] - prices[i]);
            dp[1] = Math.max(dp[1], dp[0] + prices[i] - fee);
        }
        return dp[1];
    }
}

309. 买股票的最佳时机含冷冻期

给定一个整数数组prices,其中第  prices[i] 表示第 i 天的股票价格 。​卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天),你可以尽可能地完成更多的交易。你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

示例:输入: prices = [1,2,3,0,2]         输出: 3          解释: 对应的交易状态为: [买入, 卖出, 冷冻期, 买入, 卖出]

class Solution {
    public int maxProfit(int[] prices) {
        int[][] dp = new int[prices.length][3];
        dp[0][0] = -prices[0];     // 持有股票
        dp[0][1] = 0;              // 不持有股票,并且处于冷冻期
        dp[0][2] = 0;              // 不持有股票,并且不在冷冻期

        for(int i = 1; i < prices.length; i++){
            // 持有股票:① 第 i- 1 天就已经持有的(dp[i - 1][0]);② 第 i 天买入的,那么第 i-1 天就不能持有股票并且不处于冷冻期中(dp[i - 1][2])
            dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][2] - prices[i]);
            // 不持有股票,并且处于冷冻期:说明在第 i − 1 天时我们必须持有一支股票(dp[i - 1][0]),并且在第 i 天卖出进入冷冻期
            dp[i][1] = dp[i - 1][0] + prices[i];
            // 不持有股票,并且不在冷冻期:说明在第 i 天没有进行任何操作
            dp[i][2] = Math.max(dp[i - 1][1], dp[i - 1][2]);
        }
        return Math.max(dp[prices.length - 1][1], dp[prices.length - 1][2]);
    }
}

300. 最长递增子序列

给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。

示例:输入:nums = [10,9,2,5,3,7,101,18]         输出:4

class Solution {
    public int lengthOfLIS(int[] nums) {
        int[] dp = new int[nums.length];   // dp[i]表示以nums[i]结尾的最长递增子序列的长度
        Arrays.fill(dp, 1);                // dp[i](即最长递增子序列)起始大小至少为 1
        for(int i = 0; i < nums.length; i++){
            for(int j = 0; j < i; j++){
                if(nums[i] > nums[j]){     // 最长递增子序列,需要 i 和小于i的每一个 j 比较
                    dp[i] = Math.max(dp[i], dp[j] + 1); 
                }
            }
        }
        return Arrays.stream(dp).max().getAsInt();
    }
}

674. 最长连续递增序列

给定一个未经排序的整数数组,找到最长且 连续递增的子序列,并返回该序列的长度。连续递增的子序列 可以由两个下标 l 和 rl < r)确定,如果对于每个 l <= i < r,都有 nums[i] < nums[i + 1] ,那么子序列 [nums[l], nums[l + 1], ..., nums[r - 1], nums[r]] 就是连续递增子序列。

示例:输入:nums = [1,3,5,4,7]         输出:3        解释:最长连续递增序列是 [1,3,5]

class Solution {
    public int findLengthOfLCIS(int[] nums) {
        int[] dp = new int[nums.length];        // dp[i]表示以nums[i]结尾的最长连续递增序列的长度
        Arrays.fill(dp, 1);                     // dp[i](即最长连续递增序列)起始大小至少为 1
        for(int i = 1; i < nums.length; i++){
            if(nums[i] > nums[i - 1]){          // 连续递增序列,只需要 i 和 i - 1 比
                dp[i] = dp[i - 1] + 1;
            }
        }
        return Arrays.stream(dp).max().getAsInt();
    }
}

673. 最长递增子序列的个数

给定一个未排序的整数数组 nums , 返回最长递增子序列的个数 。

示例:输入: [1,3,5,4,7]   输出: 2   解释: 有两个最长递增子序列,分别是 [1, 3, 4, 7] 和[1, 3, 5, 7]。

class Solution {
    public int findNumberOfLIS(int[] nums) {
        int[] dp = new int[nums.length];        // dp[i]表示以nums[i]结尾的最长递增子序列的长度
        Arrays.fill(dp, 1);                 // dp[i](即最长递增子序列)起始大小至少为 1
        int[] count = new int[nums.length];     // count[i]表示以nums[i]为结尾的最长递增子序列的个数
        Arrays.fill(count, 1);              // count[i](即最长递增子序列的个数)起始大小至少为 1

        for(int i = 0; i < nums.length; i++){
            for(int j = 0; j < i; j++){
                if(nums[i] > nums[j]){          // 最长递增子序列,需要 i 和小于i的每一个 j 比较
                    if(dp[j] + 1 > dp[i]){
                        dp[i] = dp[j] + 1;
                        count[i] = count[j];
                    }else if(dp[j] + 1 == dp[i]){
                        count[i] += count[j];
                    }
                }
            }
        }

        int max = Arrays.stream(dp).max().getAsInt();
        int result = 0;
        for(int i = 0; i < dp.length; i++){
            if(dp[i] == max){
                result += count[i];
            }
        }
        return result;
    }
}

 718. 最长重复子数组

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

示例:输入:nums1 = [1,2,3,2,1], nums2 = [3,2,1,4,7]         输出:3

class Solution {
    public int findLength(int[] nums1, int[] nums2) {
        int[][] dp = new int[nums1.length + 1][nums2.length + 1];  // dp[i][j]表示以nums1[i-1]和nums1[j-1]结尾的最长重复子数组长度
        int max = 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;
                }
                max = Math.max(max, dp[i][j]);
            }
        }
        return max;
    }
}
class Solution {
    public int findLength(int[] nums1, int[] nums2) {
        int[][] dp = new int[nums1.length][nums2.length];   // dp[i][j]表示以nums1[i]和nums1[j]结尾的最长重复子数组长度
        
        // 初始化
        for(int i = 0; i < nums1.length; i++){
            if(nums1[i] == nums2[0]){
                dp[i][0] = 1;
            }
        }
        for(int j = 0; j < nums2.length; j++){
            if(nums1[0] == nums2[j]){
                dp[0][j] = 1;
            }
        }

        int max = 0;
        for(int i = 0; i < nums1.length; i++){
            for(int j = 0; j < nums2.length; j++){
                if(i > 0 && j > 0 && nums1[i] == nums2[j] ){
                    dp[i][j] = dp[i - 1][j - 1] + 1;
                }
                max = Math.max(max, dp[i][j]);
            }
        }
        return max;
    }
}

1143. 最长公共子序列

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

示例:输入:text1 = "abcde", text2 = "ace"          输出:3

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

1035. 不相交的线

在两条独立的水平线上按给定的顺序写下 nums1 和 nums2 中的整数。现在,可以绘制一些连接两个数字 nums1[i] 和 nums2[j] 的直线,这些直线需要同时满足: nums1[i] == nums2[j]且绘制的直线不与任何其他连线(非水平线)相交。请注意,连线即使在端点也不能相交:每个数字只能属于一条连线。

示例1:输入:nums1 = [1,4,2], nums2 = [1,2,4]         输出:2

示例2:输入:nums1 = [2,5,1,2,5], nums2 = [10,5,2,1,5,2]         输出:3

该题实际上就是求最长公共子序列(与1143题相同)

class Solution {
    public int maxUncrossedLines(int[] nums1, int[] nums2) {
        int[][] dp = new int[nums1.length + 1][nums2.length + 1];
        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;
                }else{
                    dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
                }
            }
        }
        return dp[nums1.length][nums2.length];
    }
}

 583. 两个字符的删除操作

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

示例:输入: word1 = "sea", word2 = "eat"         输出: 2

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

392. 判断子序列

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

示例:输入:s = "abc", t = "ahbgdc"         输出:true

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

115. 不同的子序列

给你两个字符串 s 和 t ,统计并返回在 s 的 子序列 中 t 出现的个数,结果需要对 109 + 7 取模。

示例:输入:s = "rabbbit", t = "rabbit"         输出3

class Solution {
    public int numDistinct(String s, String t) {
        int[][] dp = new int[s.length() + 1][t.length() + 1];
        // 初始化
        for(int i = 0; i <= s.length(); i++){
            dp[i][0] = 1;
        }

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

72. 编辑距离

给你两个单词 word1 和 word2, 请返回将 word1 转换成 word2 所使用的最少操作数  。你可以对一个单词进行如下三种操作:插入一个字符;删除一个字符;替换一个字符。

示例:输入:word1 = "horse", word2 = "ros"         输出:3

class Solution {
    public int minDistance(String word1, String word2) {
        int[][] dp = new int[word1.length() + 1][word2.length() + 1];
        // 初始化
        for(int i = 0; i <= word1.length(); i++){
            dp[i][0] = i;
        }
        for(int j = 0; j <= word2.length(); j++){
            dp[0][j] = j;
        }

        for(int i = 1; i <= word1.length(); i++){
            for(int j = 1; j <= word2.length(); j++){
                if(word1.charAt(i - 1) == word2.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[word1.length()][word2.length()];
    }
}

647. 回文子串

给你一个字符串 s ,请你统计并返回这个字符串中 回文子串 的数目。回文字符串 是正着读和倒过来读一样的字符串。子字符串 是字符串中的由连续字符组成的一个序列。

示例:输入:s = "abc"         输出:3

class Solution {
    public int countSubstrings(String s) {
        boolean[][] dp = new boolean[s.length()][s.length()];
        int count = 0;
        for(int i = s.length() - 1; i >= 0; i--){   // 由于需要判断 dp[i + 1][j - 1] 是不是回文,所以i从后向前遍历,j从前向后遍历
            for(int j = i; j < s.length(); j++){    // i 指向字符串的头,j指向字符串的尾
                if(s.charAt(i) == s.charAt(j)){
                    if(j - i <= 1){                 // i和j相差0(a)  i和j相差1(aa)
                        dp[i][j] = true;
                        count++;
                    }else if(dp[i + 1][j - 1]){     // i和j相差大于1(abba),需要判断 dp[i + 1][j - 1] 是不是回文
                        dp[i][j] = true;
                        count++;
                    }
                }
            }
        }
        return count;
    }
}

5. 最长回文子串

给你一个字符串 s,找到 s 中最长的回文子串。

示例:输入:s = "babad"         输出:"bab"

class Solution {
    public String longestPalindrome(String s) {
        boolean[][] dp = new boolean[s.length()][s.length()];
        int left = 0;
        int right = 0;
        int len = 0;
        for(int i = s.length() - 1; i >= 0; i--){
            for(int j = i; j < s.length(); j++){
                if(s.charAt(i) == s.charAt(j)){     // 判断是否回文
                    if(j - i <= 1){
                        dp[i][j] = true;
                    }else if(dp[i + 1][j - 1]){
                        dp[i][j] = true;
                    }
                }
                if(dp[i][j] && j - i + 1 > len){    // 找到最长回文子串
                    len = j - i + 1;
                    left = i;
                    right = j;
                }
            }
        }
        return s.substring(left, right + 1);
    }
}

516. 最长回文子序列

给你一个字符串 s ,找出其中最长的回文子序列,并返回该序列的长度。

示例:输入:s = "bbbab"         输出:4

class Solution {
    public int longestPalindromeSubseq(String s) {
        int[][] dp = new int[s.length()][s.length()];
        for(int i = s.length() - 1; i >= 0; i--){
            for(int j = i; j < s.length(); j++){
                if(s.charAt(i) == s.charAt(j)){
                    if(j == i){
                        dp[i][j] = 1;
                    }else{
                        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][s.length() - 1];
    }
}

132. 分割回文串

给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是回文串。返回符合要求的 最少分割次数 。

示例:输入:s = "aab"         输出:1

class Solution {
    public int minCut(String s) {
        // 记录是否是回文子串
        boolean[][] isHuiWen = new boolean[s.length()][s.length()];
        for(int i = s.length() - 1; i >= 0; i--){
            for(int j = i; j < s.length(); j++){
                if(s.charAt(i) == s.charAt(j)){
                    if(j - i <= 1){
                        isHuiWen[i][j] = true;
                    }else if(isHuiWen[i + 1][j - 1]){
                        isHuiWen[i][j] = true;
                    }
                }
            }
        }
 
        // 回文子串最小切割次数
        int[] dp = new int[s.length()];
        for(int i = 0; i < s.length(); i++){    // 初始考虑最坏的情况: 1个字符分割0次,len个字符分割len-1次
            dp[i] = i;
        }
        for(int i = 1; i < s.length(); i++){
            if(isHuiWen[0][i]){     // 如果 0 - i 是回文子串,那么不用分割
                dp[i] = 0;
            }else{
                for(int j = 1; j <= i; j++){
                    if(isHuiWen[j][i]){
                        dp[i] = Math.min(dp[i], dp[j - 1] + 1);
                    }
                }
            }
        }
        return dp[s.length() - 1];
    }
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值