力扣完全平方数279和力扣91解码方法的做题笔记

力扣279完全平方数

思路

1.**重述问题:

  • 给你一个数字n,拆分出它的平方数要求和为n,且这些平方数的个数要最少

  • 给定一个整数 n,找到最少数量的完全平方数,使得它们的和等于 n
    2.**找出问题的最后一步:

  • 找到以4为结尾的平方数恰好满足4 + 4 + 4 = 12,且最少

  • 假设最后一步选择了一个完全平方数 ,那么问题转化为:找到和为 n - k² 的最少完全平方数。
    3.**划分子问题:

  • 找到以4为结尾最少的平方数组合,恰好满足12 - 4

  • 对于每个 i(从 1 到 n),我们需要找到所有可能的 k² <= i),然后递归求解 i - k² 的最少完全平方数。
    4.**边界问题:

  • 考虑和不能大于n

  • i = 0 时,不需要任何完全平方数,返回 0。

  • i 本身是一个完全平方数时,直接返回 1。

  • dfs + 记忆化搜索

const int N = 100010;
class Solution
{
  public:
    int mem[N];
    int dfs(int n)
    {
        int res = 1e9;
        if (n == 0)
            return 0;
        if (mem[n])
            return mem[n];
        for (int i = 1; i * i <= n; i++)
        {
            res = min(res, dfs(n - i * i) + 1);
        }
        return mem[n] = res;
    }
    int numSquares(int n)
    {
        if (n == 0)
        {
            return 0;
        }
        int ans = dfs(n);
        return ans;
    }
};
  • dp
class Solution
{
  public:
	int numSquares(int n)
    {
        if (n == 0)
        {
            return 0;
        }
        vector<int> dp(n + 1, INT_MAX);
        dp[0] = 0;
        for (int i = 1; i <= n; i++)
        {
            for (int j = 1; j * j <= i; j++)
            {
                dp[i] = min(dp[i], 1 + dp[i - j * j]);
            }
        }
        return dp[n];
    }
};

力扣91解码方法

思路

1.**重述问题:

  • 给你一个数字字符串,找出它所有能支持的编码,前缀0的是非法
  • 给定一个数字字符串 s,每个数字或两个数字的组合可以解码为字母(1→A,2→B,…,26→Z)。要求计算所有可能的解码方式总数。如果字符串中包含前导零(如 “06”)或无法解码的情况,返回 0。
    2.**找出问题的最后一步:
  • 以226为例子,最后一步为 2 B 2B 6F ,最终问题是每一个字符单独成为编码
  • 对于字符串 s,解码的最后一步有两种可能:
  1. 单独解码最后一个字符:如果最后一个字符是 19,则可以单独解码为一个字母。

  2. 解码最后两个字符:如果最后两个字符组成的数字在 1026 之间,则可以解码为一个字母。

例如,对于 s = "226"

  • 最后一步可以是 6F,此时问题转化为解码 "22"

  • 最后一步也可以是 26Z,此时问题转化为解码 "2"
    3.**划分子问题:

  • 前一个问题就是22V 6F,那么子问题可划分为k个字符为一个编码?

  • 根据最后一步的两种可能,可以将问题划分为两个子问题:

  1. 解码前 n-1 个字符:如果最后一个字符可以单独解码。

  2. 解码前 n-2 个字符:如果最后两个字符可以组合解码。

例如,对于 s = "226"

  • 子问题 1:解码 "22"

  • 子问题 2:解码 "2"
    4.**边界问题:

  • 空字符串:如果字符串为空,只有一种解码方式(即空字符串本身)。

  • 单个字符

    • 如果字符是 0,无法解码,返回 0。

    • 如果字符是 19,可以解码,返回 1。

  • 前导零:如果字符串以 0 开头(如 "06"),无法解码,返回 0。

  • 无效组合:如果两个字符组成的数字不在 1026 之间(如 "27"),则不能组合解码。

const int N = 10010;
class Solution
{
  public:
    int mem[N];
    int dfs(string &s, int x)
    {
        int n = s.size();
        if (n == x)
            return 1;
        if (s[x] == '0')
            return 0;
        if (mem[x])
            return mem[x];
        // 单个解码
        int res = dfs(s, x + 1);
        // 两个解码
        if (x + 1 < n)
        {
            int two = ((s[x] - '0') * 10) + (s[x + 1] - '0');
            if (two >= 10 && two <= 26)
            {
                res += dfs(s, x + 2);
            }
        }
        return mem[x] = res;
    }
    int numDecodings(string s)
    {
        int res = 0;
        int n = s.size();
        if (n == 0 || s[0] == '0')
            return 0;
        // vector<int> dp(n + 1, 0);
        // dp[0] = 1;
        // dp[1] = s[0] != '0' ? 1 : 0;
        int prevPrev = 1;
        int prev = s[0] != '0' ? 1 : 0;
        for (int i = 2; i <= n; i++)
        {
            int cur = 0;
            if (s[i - 1] != '0')
                cur += prev;
            // dp[i] += dp[i - 1];
            int two = ((s[i - 2] - '0') * 10) + (s[i - 1] - '0');
            if (two >= 10 && two <= 26)
            {
                // dp[i] += dp[i - 2];
                cur += prevPrev;
            }
            prevPrev = prev;
            prev = cur;
        }
        return prev;
        // return dp[n];
    }
};

做题中我遇到的问题

为什么是i - 1而不是i呢

1. 动态规划的状态定义

在动态规划中,我们通常定义:

  • dp[i]:表示前 i 个字符的解码方式数。

    • 例如,dp[1] 表示前 1 个字符的解码方式数。

    • dp[2] 表示前 2 个字符的解码方式数。

    • 以此类推。


2. 字符串的索引

  • 在 C++ 中,字符串的索引是从 0 开始的。

    • 例如,字符串 s = "226"

      • s[0] = '2'

      • s[1] = '2'

      • s[2] = '6'


3. 为什么用 s[i - 1] 而不是 s[i]

  • dp[i] 表示前 i 个字符的解码方式数

    • i 个字符对应字符串中的 s[0]s[i - 1]

    • 因此,dp[i] 的解码方式数依赖于 s[i - 1](当前字符)和 s[i - 2](前一个字符)。

  • 举例说明

    • 对于字符串 s = "226"

      • dp[1] 表示前 1 个字符 '2' 的解码方式数。

      • dp[2] 表示前 2 个字符 "22" 的解码方式数。

      • dp[3] 表示前 3 个字符 "226" 的解码方式数。


4. 代码逻辑

在代码中,dp[i] 的解码方式数由两部分组成:

  1. 单独解码当前字符

    • 如果当前字符 s[i - 1] 不是 '0',则可以单独解码。

    • 此时,dp[i] += dp[i - 1],表示前 i - 1 个字符的解码方式数。

  2. 组合解码两个字符

    • 如果当前字符 s[i - 1] 和前一个字符 s[i - 2] 可以组合解码(即组成的数字在 1026 之间),则可以组合解码。

    • 此时,dp[i] += dp[i - 2],表示前 i - 2 个字符的解码方式数。


5. 示例分析

示例:s = "226"
  • 初始化

    • dp[0] = 1(空字符串)。

    • dp[1] = 1'2' 可以单独解码)。

  • 状态转移

is[i-1]单独解码 (dp[i] += dp[i-1])组合解码 (dp[i] += dp[i-2])dp[i]
2'2'dp[2] += dp[1] = 1two = 22dp[2] += dp[0] = 12
3'6'dp[3] += dp[2] = 2two = 26dp[3] += dp[1] = 13
  • 最终结果dp[3] = 3

6. 为什么不用 s[i]

  • 如果使用 s[i],会导致索引越界或逻辑错误:

    • 对于 i = 1s[1] 是第二个字符,而 dp[1] 表示前 1 个字符的解码方式数。

    • 这样会导致状态转移的逻辑混乱,无法正确计算解码方式数。


7. 总结

  • dp[i] 表示前 i 个字符的解码方式数,因此当前字符是 s[i - 1]

  • s[i - 1] 是当前字符s[i - 2] 是前一个字符。

  • 这种设计保证了状态转移的正确性和逻辑清晰性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值