LeetCode —— 动态规划

这篇具有很好参考价值的文章主要介绍了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];
    }
}

96. 不同的二叉搜索树

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

LeetCode —— 动态规划

示例:输入: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文章来源地址https://www.toymoban.com/news/detail-490829.html

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

到了这里,关于LeetCode —— 动态规划的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • LeetCode练习七:动态规划上:线性动态规划

    参考《OI Wiki动态规划》、《算法通关手册》动态规划篇 1.1 动态规划简介    动态规划(Dynamic Programming) :简称 DP ,是一种通过把原问题分解为相对简单的子问题的方式而求解复杂问题的方法。 动态规划方法与分治算法类似,却又不同于分治算法。 「动态规划的核心思想

    2024年02月12日
    浏览(66)
  • Flink系列Table API和SQL之:动态表、持续查询、将流转换成动态表、更新查询、追加查询、将动态表转换为流、更新插入流(Upsert)

    Flink中使用表和SQL基本上跟其他场景是一样的。不过对于表和流的转换,却稍显复杂。当我们将一个Table转换成DataStream时,有\\\"仅插入流\\\"(Insert-Only Streams)和\\\"更新日志流\\\"(Changelog Streams)两种不同的方式,具体使用哪种方式取决于表中是否存在更新操作。 这种麻烦其实是不可避

    2024年02月03日
    浏览(73)
  • LeetCode —— 动态规划

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

    2024年02月09日
    浏览(34)
  • LeetCode——动态规划

    一、一维数组:斐波那契数列 爬楼梯70简单 dp定义: dp[i]表示爬到第i阶有多少种不同的方式 状态转移方程: dp[i] = dp[i-1] + dp[i-1] (每次可以爬1或2个台阶) 边界条件: dp[0] = 1; dp[1] = 1;(满足dp[2] = 2) 返回值: dp[n],即通过累积,dp[n]为爬到第n阶台阶的方式 改进: 因为dp只

    2024年02月03日
    浏览(45)
  • Leetcode 刷题 动态规划

    309. 最佳买卖股票时机含冷冻期 1. 确定dp数组以及下标的含义 具体可以区分出如下四个状态: 状态一:持有股票状态(今天买入股票,或者是之前就买入了股票然后没有操作,一直持有) 不持有股票状态,这里就有两种卖出股票状态 状态二:保持卖出股票的状态(两天前就

    2024年02月11日
    浏览(44)
  • [LeetCode]-动态规划-4

    记录 LeetCode 刷题时遇到的动态规划相关题目,第四篇 枚举算法:首先对整个矩阵生成一个 row 数组,其中 row[i][j] 表示从 mat[i][j] 开始往左连续的 1 的个数 然后枚举的思路是,枚举所有的 mat[i][j],求以 mat[i][j] 为右下角的子矩形 的个数,然后求和,具体的求法是枚举以 mat[

    2024年01月24日
    浏览(34)
  • 【leetcode】动态规划::前缀和

    标题:【leetcode】前缀和 @水墨不写bug 正文开始: 给定一个长度为n的数组a1​,a2​,....an​. 接下来有q次查询, 每次查询有两个参数l, r. 对于每个询问, 请输出al​+al+1​+....+ar​ 输入描述: 第一行包含两个整数n和q. 第二行包含n个整数, 表示a1​,a2​,....an​. 接下来q行,每行包含

    2024年04月11日
    浏览(39)
  • leetcode 动态规划(单词拆分)

    139.单词拆分 力扣题目链接(opens new window) 给定一个非空字符串 s 和一个包含非空单词的列表 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。 说明: 拆分时可以重复使用字典中的单词。 你可以假设字典中没有重复的单词。 示例 1: 输入: s = “leetcode”

    2024年02月22日
    浏览(40)
  • leetcode-动态规划【背包问题】

    基础背包: 416. 分割等和子集 1049. 最后一块石头的重量ii 494. 目标和 474. 一和零 完全背包: 518. 零钱兑换ii 377. 组合总和iv 70. 爬楼梯 322. 零钱兑换 279. 完全平方数 139. 单词拆分 多重背包: n件物品和最大承受重量为w的背包,其中第i件物品的重量是weight[i],得到的价值是val

    2023年04月08日
    浏览(30)
  • LeetCode——动态规划篇(一)

    刷题顺序及思路来源于代码随想录,网站地址:https://programmercarl.com  目录 509. 斐波那契数 - 力扣(LeetCode) 70. 爬楼梯 - 力扣(LeetCode) 746. 使用最小花费爬楼梯 - 力扣(LeetCode) 62. 不同路径 - 力扣(LeetCode)  63. 不同路径 II - 力扣(LeetCode) 斐波那契数  (通常用  F(n)  表

    2024年02月09日
    浏览(32)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包