美文网首页
5.动态规划(五)

5.动态规划(五)

作者: 今天柚稚了么 | 来源:发表于2020-06-20 16:04 被阅读0次

https://leetcode-cn.com/tag/dynamic-programming/

题目汇总

322. 零钱兑换中等[✔]

338. 比特位计数中等

343. 整数拆分中等

354. 俄罗斯套娃信封问题困难(不会做)

357. 计算各个位数不同的数字个数中等

363. 矩形区域不超过 K 的最大数值和困难(没做)

368. 最大整除子集中等(没做)

375. 猜数字大小 II中等(题解能看懂,自己写不明白)

322. 零钱兑换中等

给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1
示例 1:
输入: coins = [1, 2, 5], amount = 11
输出: 3
解释: 11 = 5 + 5 + 1
示例 2:
输入: coins = [2], amount = 3
输出: -1
说明:
你可以认为每种硬币的数量是无限的。

思路一:动态规划
class Solution {
    public int coinChange(int[] coins, int amount) {//执行用时 :14 ms, 在所有 Java 提交中击败了85.58%的用户
        if (coins == null || amount < 0) {
            return -1;
        }
        if (amount == 0){
            return 0;
        }
        int[] dp = new int[amount+1];//dp[i]=j表示凑够i元最少需要j枚硬币。数组长度设为(amount++1)保证可以访问dp[amount]
        dp[0] = 0;
        for(int i=1;i<amount+1;i++){//问题规模从小到大,直到达到目标面值
            dp[i] = Integer.MAX_VALUE/2;//因为出现了Math.min的操作,所以在初始化的时候很重要,初始化为一个很大的数就可以了,但是dp[i] =Integer.MAX_VALUE是不可以的,计算dp[i-coin]+1就会出现溢出问题
            for(int coin:coins){//遍历所有面值的硬币
                if(i-coin >= 0 ){
                    dp[i] = Math.min(dp[i],dp[i-coin]+1);
                }    
            }
        }
    return dp[amount] == Integer.MAX_VALUE/2 ? -1 : dp[amount];
    }
}
思路二:贪心+DFS

这是在题解https://leetcode-cn.com/problems/coin-change/solution/322-by-ikaruga/
中看到的思路,这种思路能提高效率,但是自己想不到,摘抄一下代码。

class Solution {//执行用时 :3 ms, 在所有 Java 提交中击败了97.04%的用户
    int ans = Integer.MAX_VALUE;
    public int coinChange(int[] coins, int amount) {
        Arrays.sort(coins);
        coinChange(coins.length-1, coins, 0, amount);
        return ans == Integer.MAX_VALUE ? -1 : ans;

    }
    private void coinChange(int index, int[] coins, int count, int needAmount) {
        if (needAmount == 0) {
            ans = Math.min(count, ans);
            return;
        }
        if (index < 0) {
            return;
        }

        int i = needAmount / coins[index];
        //count+k<ans关键步骤,一直没怎么理解清楚
        for (int k=i; k>=0 && count+k<ans; k--) {
            coinChange(index-1, coins, count+k, needAmount-k*coins[index]);
        }
    }
}

338. 比特位计数中等

给定一个非负整数 num。对于 **0 ≤ i ≤ num **范围中的每个数字 **i **,计算其二进制数中的 1 的数目并将它们作为数组返回。
示例 :
输入: 5
输出: [0,1,1,2,1,2]
进阶:

  • 给出时间复杂度为O(n×sizeof(integer))的解答非常容易。但你可以在线性时间O(n)内用一趟扫描做到吗?
  • 要求算法的空间复杂度为O(n)
  • 你能进一步完善解法吗?要求在C++或任何其他语言中不使用任何内置函数(如 C++ 中的 __builtin_popcount)来执行此操作。
思路:动态规划

转自https://leetcode-cn.com/problems/counting-bits/solution/hen-qing-xi-de-si-lu-by-duadua/
dp[i]为i的二进制形式的1的个数
分为两种情况:
1.奇数:二进制表示中,奇数一定比前面那个偶数多一个 1,因为多的就是最低位的 1。
举例:
0 = 0 1 = 1
2 = 10 3 = 11
2.偶数:二进制表示中,偶数中 1 的个数一定和除以 2 之后的那个数一样多。因为最低位是 0,除以 2 就是右移一位,也就是把那个 0 抹掉而已,所以 1 的个数是不变的。
举例:
2 = 10 4 = 100 8 = 1000
3 = 11 6 = 110 12 = 1100

class Solution {
    public int[] countBits(int num) {
        if(num == 0) 
            return new int[]{0};
        int[] dp = new int[num + 1];//执行用时 :2 ms, 在所有 Java 提交中击败了81.26%的用户
        dp[0] = 0;
        for(int i = 1; i <= num; i++)
        {
            if(i % 2 == 1)
            {
                dp[i] = dp[i-1] + 1;
            }
            else
            {
                dp[i] = dp[i/2];
            }
        }
        return dp;
    }
}

343. 整数拆分中等

给定一个正整数 n,将其拆分为至少两个正整数的和,并使这些整数的乘积最大化。 返回你可以获得的最大乘积。
示例 1:
输入: 2,输出: 1,解释: 2 = 1 + 1, 1 × 1 = 1。
示例 2:
输入: 10,输出: 36,解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36。
**说明: **你可以假设 *n *不小于 2 且不大于 58。

思路一:动态规划,时间复杂度 O(n2)

定义状态,dp[i] 表示数字 i 拆分为至少两个正整数之和的最大乘积

class Solution {
    public int integerBreak(int n) {//执行用时 :1 ms, 在所有 Java 提交中击败了61.04%的用户
        int[] dp = new int[n + 1];
        dp[2] = 1;
        for(int i=3;i<=n;i++){
            for(int j=1;j<i;j++){//数字 i 可以拆分成 j + (i - j)
                dp[i] = Math.max(dp[i],Math.max(j*(i-j),j*dp[i-j]));
            }
        }
        return dp[n];
    }
}
思路二:贪心算法,时间复杂度 O(1)

题目提示You may check the breaking results of n ranging from 7 to 10 to discover the regularities.
应用数学知识分析题目,我们发现当 n>3 时,求 n 除以 3 的整数部分 a 和余数部分 b ,并分为以下三种情况:
当 b = 0 时,直接返回 3a
当 b = 1 时,要将一个 1 + 3 转换为 2+2,因此返回 3a-1 ×4
当 b = 2 时,返回 3a×2

class Solution {
    public int integerBreak(int n) {//执行用时 :0 ms, 在所有 Java 提交中击败了100.00%的用户
        if(n <= 3)
            return n-1;
        int a = n / 3;  //a是商
        int b = n % 3;  //b是余数
        if(b == 0){
            return (int)(Math.pow(3, a));
        }   
        else if(b == 1){
            return (int)(Math.pow(3, a-1) * 4);
        }  
        else{
            return (int)(Math.pow(3, a) * 2);
        }
          
    }
}

354. 俄罗斯套娃信封问题困难

给定一些标记了宽度和高度的信封,宽度和高度以整数对形式 (w, h) 出现。当另一个信封的宽度和高度都比这个信封大的时候,这个信封就可以放进另一个信封里,如同俄罗斯套娃一样。
请计算最多能有多少个信封能组成一组“俄罗斯套娃”信封(即可以把一个信封放到另一个信封里面)。
说明:不允许旋转信封。
示例:
输入: envelopes = [[5,4],[6,4],[6,7],[2,3]]
输出: 3
解释: 最多信封的个数为 3, 组合为: [2,3] => [5,4] => [6,7]

思路:动态规划+排序

这是LIS问题在二维数组上的扩展,先对宽度 w 进行升序排序,如果遇到 w 相同的情况,则按照高度 h 降序排序。之后把所有的 h 作为一个数组,在这个数组上计算 LIS 的长度。

class Solution {
    public int maxEnvelopes(int[][] envelopes) {//执行用时 :188 ms, 在所有 Java 提交中击败57.25%的用户
        if(envelopes == null || envelopes.length == 0 || envelopes[0].length == 0)
            return 0;
        // 按宽度升序排列,如果宽度一样,则按高度降序排列
        Arrays.sort(envelopes, new Comparator<int[]>() 
        {
            @Override
            public int compare(int[] a, int[] b) {
                if(a[0] == b[0]){
                    return b[1] - a[1];
                }else{
                    return a[0] - b[0];
                }
            }
        });
        // 对高度数组寻找 LIS
        int[] height = new int[envelopes.length];
        for (int i = 0; i < envelopes.length; i++)
            height[i] = envelopes[i][1];

        return lengthOfLIS(height);
    }

    public int lengthOfLIS(int[] nums) {
        int[] dp = new int[nums.length];
        Arrays.fill(dp, 1);//填充dp数组中的每个元素都是1
        for(int i=0;i<nums.length;i++){
            for(int j=0;j<i;j++){
                if(nums[j] < nums[i]){//dp[i]表示以nums[i]结尾的上升子序列的长度
                    dp[i] = Math.max(dp[i], dp[j] + 1);
                }
            }
        }
        int res = 0;
        for(int i=0;i<dp.length;i++){
            res = Math.max(res, dp[i]);// dp[i] 的最大值是最后要输出的值
        }
        return res;
    }
}

357. 计算各个位数不同的数字个数中等

给定一个非负整数 n,计算各位数字都不同的数字 x 的个数,其中 0 ≤ x < 10n
示例:
输入: 2
输出: 91
解释: 答案应为除去 11,22,33,44,55,66,77,88,99 外,在 [0,100) 区间内的所有数字。

思路:动态规划

i=1时,dp[1]=10
i=2时,dp[2]=9×9+dp[1]
i=3时,dp[3]=9×9×8+dp[2]
i=4时,dp[4]=9×9×8×7+dp[3]
dp[i]=(dp[i-1]-dp[i-2])×(10-i+1)+dp[i-1],dp[i-1]-dp[i-2]是i-1次较i-2次多出来的各位不重复的数字。

class Solution {
    public int countNumbersWithUniqueDigits(int n) {//执行用时 :0 ms, 在所有 Java 提交中击败了100.00%的用户
        int[] dp = new int[n+1];
        dp[0] = 1;
        dp[1] = 10;
        for(int i=2;i<=n;i++){
            dp[i] = dp[i-1]+(dp[i-1]-dp[i-2])*(10-i+1);
        }
        return dp[n];
    }
}

363. 矩形区域不超过 K 的最大数值和困难

给定一个非空二维矩阵 matrix和一个整数k,找到这个矩阵内部不大于 k 的最大矩形和。
示例:
输入: matrix = [[1,0,1],[0,-2,3]], k = 2
输出: 2
解释: 矩形区域 [[0, 1], [-2, 3]] 的数值和是 2,且 2 是不超过 k 的最大数字(k = 2)。
说明: 矩阵内的矩形区域面积必须大于 0。如果行数远大于列数,你将如何解答呢?

368. 最大整除子集中等

给出一个由无重复的正整数组成的集合,找出其中最大的整除子集,子集中任意一对 (Si,Sj) 都要满足:Si % Sj = 0 或 Sj % Si = 0。
如果有多个目标子集,返回其中任何一个均可。
示例 1:
输入: [1,2,3]
输出: [1,2] (当然, [1,3] 也正确)
示例 2:
输入: [1,2,4,8]
输出: [1,2,4,8]

375. 猜数字大小 II中等

我们正在玩一个猜数游戏,游戏规则如下:
我从 1n 之间选择一个数字,你来猜我选了哪个数字。
每次你猜错了,我都会告诉你,我选的数字比你的大了或者小了。
然而,当你猜了数字 x 并且猜错了的时候,你需要支付金额为 x 的现金。直到你猜到我选的数字,你才算赢得了这个游戏。
示例:
n = 10, 我选择了8.
第一轮: 你猜我选择的数字是5,我会告诉你,我的数字更大一些,然后你需要支付5块。
第二轮: 你猜是7,我告诉你,我的数字更大一些,你支付7块。
第三轮: 你猜是9,我告诉你,我的数字更小一些,你支付9块。
游戏结束。8 就是我选的数字。
你最终要支付 5 + 7 + 9 = 21 块钱。
给定 n ≥ 1,计算你至少需要拥有多少现金才能确保你能赢得这个游戏。

思路:动态规划

作者:smilyt_
链接:https://leetcode-cn.com/problems/guess-number-higher-or-lower-ii/solution/dong-tai-gui-hua-c-you-tu-jie-by-zhang-xiao-tong-2/
题解看了几遍才能看懂,怀疑自己的脑子已经不转了!!!

class Solution {
    public int getMoneyAmount(int n) {//执行用时 :10 ms, 在所有 Java 提交中击败了35.92%的用户
        if(n==1)
            return 0;
        //定义矩阵
        int[][] dp = new int[n+1][n+1];
        //初始化“\”
        for(int i=0;i<=n;i++){
            for(int j=0;j<=n;j++){
                dp[i][j]=Integer.MAX_VALUE;
            }
        }
        //定义基础值dp[i][i]
        for(int i=0;i<=n;i++){
            dp[i][i]=0;
        }

        //按列来,从第2列开始
        for(int j=2;j<=n;j++){
            //按行来,从下往上
            for(int i=j-1;i>=1;i--){
                //算除了两端的每一个分割点
                for(int k=i+1;k<=j-1;k++){
                    dp[i][j]=Math.min(k+Math.max(dp[i][k-1],dp[k+1][j]),dp[i][j]);
                }
                //算两端
                dp[i][j]=Math.min(dp[i][j],i+dp[i+1][j]);
                dp[i][j]=Math.min(dp[i][j],j+dp[i][j-1]);
            }
        }
        return dp[1][n];


    }
}

相关文章

网友评论

      本文标题:5.动态规划(五)

      本文链接:https://www.haomeiwen.com/subject/iddmghtx.html