LeetCode刷题日记之动态规划(二)

目录


前言

在上一篇文章中,我们初步了解了动态规划的基本思路。这次我将继续分享几道经典的动态规划题目,进一步深入这一解题方法的细节与技巧。希望这篇文章能够帮助大家更好地掌握动态规划✍✍✍


完全平方数

LeetCode题目链接

就是给一个整数n,然后让返回和为这个整数n的完全平方数的最少数量,所谓完全平方数就是另一个整数的平方

我们直接来梳理动态规划的五部曲

  • 确定dp数组和下标含义

    • 可以定义dp[i]来表示将整数i拆分为若干个完全平方数和时的最少完全平方数数量
  • 确定递推公式

    其中j*j是一个小于等于i的完全平方数
    dp[i] = min(dp[i], dp[i - j * j] + 1)

  • dp数组如何初始化

    因为i最多完全平方数组合便是由i个1组成,所以可以初始化为i
    dp[i] = i

  • 确定遍历顺序

    • 外层循环遍历 i 从 1 到 n,依次计算每个 i 的最优解。内层循环枚举每个小于等于 i 的完全平方数 j,更新 dp[i] 的值
  • 举例推导dp数组

    n = 12
    初始化 dp[12] = 12(最坏情况,12个1组成)
    dp[12] = min(dp[12], dp[12-11] + 1) = min(12, dp[11] + 1)
    dp[12] = min(dp[12], dp[12-2
    2] + 1) = min(dp[12], dp[8] + 1)
    dp[12] = min(dp[12], dp[12-3*3] + 1) = min(dp[12], dp[3] + 1)
    最终dp[12] 会更新为 3(4 + 4 + 4)

完整代码如下

java 复制代码
class Solution {
    public int numSquares(int n) {
        int[] dp = new int[n + 1];//初始化dp数组

        for(int i = 0; i <= n; i++){//初始化
            dp[i] = i;
        }

        //遍历1到n,计算每个dp[i]的值
        for(int i = 1; i <= n; i++){
            for(int j = 1; j * j <= i; j++){//遍历每个小于等于i的完全平方数
                dp[i] = Math.min(dp[i], dp[i - j * j] + 1);//递推公式
            }
        }

        return dp[n];
    }
}

零钱兑换

LeetCode题目链接

这道题是有一个硬币数组coins,然后的话返回凑成一个所给总金额amount所需的最少硬币数

直接梳理动态规划的五部曲

  • 确定dp数组和下标含义

    • 定义 dp[i] 表示凑成金额 i 所需的最少硬币数
  • 确定递推公式

    对于每一个金额 i,如果选择某个硬币 coin,可以得到凑成金额 i - coin 所需的最少硬币数 dp[i - coin]
    dp[i]=min(dp[i],dp[i−coin]+1)

  • dp数组如果初始化

    • dp[0] = 0,因为凑成金额 0 不需要任何硬币,其他的dp[i]初始化为amount+1,一个大数表示无法凑成也不会影响递推
  • 确定遍历顺序

    • 外层循环遍历金额从 1amount, 内层循环遍历每种硬币,更新每个 dp[i] 的最优解
  • 举例推导dp数组

    coins = [1, 2, 5],amount = 11
    对于 i = 1,用硬币 1,dp[1] = min(dp[1], dp[1-1] + 1) = 1
    对于 i = 2,用硬币 1 或 2,dp[2] = min(dp[2], dp[2-1] + 1) = min(dp[2], dp[2-2] + 1) = 1
    对于 i = 11,尝试用硬币 1、2、5,最终 dp[11] = 3(由硬币 5 + 5 + 1 组成)

完整代码如下

java 复制代码
class Solution {
    public int coinChange(int[] coins, int amount) {
        // 定义dp数组
        int[] dp = new int[amount + 1];

        // 初始化dp数组,dp[0] = 0,其他初始化为一个大数,表示暂时无法凑成
        Arrays.fill(dp, amount + 1);
        dp[0] = 0;

        for(int i = 1; i <= amount; i++){//外层循环遍历金额
            for(int coin : coins){//内层循环遍历每个硬币
                if(i - coin >= 0){//如果可以使用当前硬币
                    dp[i] = Math.min(dp[i], dp[i - coin] + 1);
                }
            }
        }

        // 返回结果,如果dp[amount]还是初始值,说明无法凑成
        return dp[amount] > amount ? -1 : dp[amount];
    }
}

单词拆分

这道题就是给一个字符串s和字符串列表wordDict作为字典,判断字符串能否利用字典里的词来拼接出来

我们来梳理动态规划五部曲

  • 确定dp数组和下标含义

    • 定义布尔型数组 dp,其中 dp[i] 表示字符串 s 的前 i 个字符 s[0:i] 是否可以通过字典中的单词拼接而成
  • 确定递推公式

    对于每一个 i,我们检查从 0 到 i 的每个分割点 j,如果 dp[j] 为 true,且 s[j:i] 在字典 wordDict 中,则 dp[i] = true。
    dp[i]=dp[j] 且 s[j:i] 在字典中

  • dp数组如何初始化

    • dp[0] = true 表示空字符串可以被成功拼接,其他 dp[i] 初始化为 false,表示暂时无法拼接
  • 确定遍历顺序

    • 我们需要遍历从 1s.length() 的每个位置 i,并且对于每个 i 再遍历 j,检查 dp[j]s[j:i] 是否在字典中
  • 举例推导dp数组

    s = "leetcode",wordDict = ["leet", "code"]
    初始化 dp[0] = true,因为空字符串总是可以被拼接
    对于 i = 4,发现 s[0:4] = "leet" 在字典中,且 dp[0] = true,所以 dp[4] = true
    对于 i = 8,发现 s[4:8] = "code" 在字典中,且 dp[4] = true,所以 dp[8] = true
    最终,dp[8] = true,表示整个字符串 s 可以被字典中的单词拼接而成

完整代码如下

java 复制代码
class Solution {
    public boolean wordBreak(String s, List<String> wordDict) {
        // 定义dp数组,dp[i]表示前i个字符是否可以被字典拼接成
        boolean[] dp = new boolean[s.length() + 1];

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

        // 将wordDict转换为HashSet,方便快速查找
        Set<String> wordSet = new HashSet<>(wordDict);

        for(int i = 0; i <= s.length(); i++){
            for(int j = 0; j < i; j++){ // 遍历每一个分割点j
                // 检查dp[j]是否为true,且s[j:i]是否在字典中
                if(dp[j] && wordSet.contains(s.substring(j, i))){
                    dp[i] = true;// 更新dp[i]
                    break;
                }
            }
        }

        return dp[s.length()];
    }
}

这里可能不易理解的点

  • 为什么不用双指针一前一后的方式来解决呢?
    • 这种思路的问题在于,它并不会尝试所有可能的分割方式,只会走一条"贪心"路径。也就是说,如果在某个时刻有多种分割方式,它只能选择当前最匹配的单词,而不会回溯去考虑其他可能的分割方式。这种策略在某些情况下无法找到正确的拼接方式

总结

这里单词拆分的递推公式的逻辑大家可能需要多梳理几遍,弄清楚和贪心策略的区别,大家一起加油✊✊ ✊

相关推荐
LNTON羚通9 分钟前
摄像机视频分析软件下载LiteAIServer视频智能分析平台玩手机打电话检测算法技术的实现
算法·目标检测·音视频·监控·视频监控
弗拉唐41 分钟前
springBoot,mp,ssm整合案例
java·spring boot·mybatis
oi771 小时前
使用itextpdf进行pdf模版填充中文文本时部分字不显示问题
java·服务器
少说多做3432 小时前
Android 不同情况下使用 runOnUiThread
android·java
知兀2 小时前
Java的方法、基本和引用数据类型
java·笔记·黑马程序员
哭泣的眼泪4082 小时前
解析粗糙度仪在工业制造及材料科学和建筑工程领域的重要性
python·算法·django·virtualenv·pygame
清炒孔心菜2 小时前
每日一题 LCR 078. 合并 K 个升序链表
leetcode
蓝黑20202 小时前
IntelliJ IDEA常用快捷键
java·ide·intellij-idea
Ysjt | 深2 小时前
C++多线程编程入门教程(优质版)
java·开发语言·jvm·c++