Leetcode 动态规划 2

题号直达: 198 打家劫舍 | 279 完全平方数 | 322 零钱兑换 | 139 单词拆分

LeetCode 198. 打家劫舍 ↑

题目

你是一个专业的小偷,计划沿着一条街道挨户偷窃。每间房都存放着一定现金;但相邻两间房屋装有相连的防盗系统,若同一晚上偷了两间相邻房屋,系统就会报警。

给定一个非负整数数组 nums,nums[i] 表示第 i 间房屋里的现金。求在这一晚上不触动报警装置的前提下,能偷到的最大金额。

测试样例

  • 示例 1: nums = [1,2,3,1]
    输出:4
    解释:偷窃 1 号屋(金额 1)与 3 号屋(金额 3),共 4。
  • 示例 2: nums = [2,7,9,3,1]
    输出:12
    解释:偷窃 1、3、5 号屋,金额为 2 + 9 + 1 = 12。

提示

  • 1 <= nums.length <= 100
  • 0 <= nums[i] <= 400

代码(Python)

class Solution(object):
    def rob(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        n = len(nums)

        if n == 1:
            return nums[0]

        if n == 2:
            return max(nums[0], nums[1])

        dp = [0] * n

        max_value = 0

        dp[0] = nums[0]

        dp[1] = nums[1]

        for i in range(2, n):

            max_value = max(max_value, dp[i - 2])

            dp[i] = nums[i] + max_value

        return max(dp[n - 1], dp[n - 2])

思路解析

若决定偷第 i 间,则不能偷第 i - 1 间,但可在 0 … i - 2 的最优结果上再累加 nums[i]。用 dp[i] 表示考虑前 i + 1 间房、且必偷第 i 间时能获得的最大金额,则有:

dp[i] = nums[i] + max(dp[0], … , dp[i - 2])

n = 1、n = 2 直接返回。从 i = 2 起遍历时,用变量 max_value 滚动维护 max(dp[0], …, dp[i - 2]),避免在内层再扫一遍。最终答案不一定偷最后一间,因此返回 max(dp[n - 1], dp[n - 2])。

  • 时间复杂度:O(n)
  • 空间复杂度:O(n)(dp 数组)

LeetCode 279. 完全平方数 ↑

题目

给你一个整数 n,返回和为 n 的完全平方数的最少个数。

完全平方数:等于某个整数的平方的数(例如 1、4、9、16 是,3、11 不是)。

测试样例

  • 示例 1: n = 12
    输出:3
    解释:12 = 4 + 4 + 4(三个完全平方数)。
  • 示例 2: n = 13
    输出:2
    解释:13 = 4 + 9。

提示

  • 1 <= n <= 10^4

代码(Python)解法 1:记忆化 DFS

class Solution(object):
    def numSquares(self, n):
        """
        :type n: int
        :rtype: int
        """
        memory = {}
        memory[0] = 0
        memory[1] = 1

        def dfs(k):
            if k in memory:
                return memory[k]
            else:
                max_num = int(k ** 0.5)
                min_count = float('inf')
                for i in range(1, max_num + 1):
                    min_count = min(min_count, dfs(k - i ** 2) + 1)
                memory[k] = min_count
                return min_count

        return dfs(n)

思路解析(解法 1)

把问题看成:最后一次减去的完全平方数是 i²,则 k = i² + (k - i²),最少个数为 1 +(k - i² 的最少个数)。枚举 i = 1 … ⌊√k⌋ 取最小值即可。

memory[k] 记录 dfs(k) 的结果,避免重复子问题;边界 memory[0]=0、memory[1]=1(与递归终止一致)。

  • 时间复杂度:每个 k 最多计算一次,每次 O(√k) 枚举;总体约 O(n√n)。
  • 空间复杂度:记忆化表与递归栈,O(n)。

代码(Python)解法 2:数学(四平方和 / 三平方定理)

class Solution(object):
    def numSquares(self, n):
        """
        :type n: int
        :rtype: int
        """
        m = n
        while m % 4 == 0:
            m //= 4
        if m % 8 == 7:
            return 4
        r = int(n ** 0.5)
        if r * r == n:
            return 1
        for a in range(1, r + 1):
            b = n - a * a
            t = int(b ** 0.5)
            if t * t == b:
                return 2
        return 3

思路解析(解法 2)

方法二:数学(与上图一致)

  1. 四平方和定理(拉格朗日):任意正整数至多可表成四个完全平方数之和,故答案上界为 4。
  2. 三平方和定理(勒让德):正整数 n 能表成至多三个完全平方数之和,当且仅当 n 不能写成 4^k × (8m + 7) 的形式(k,m 为非负整数)。反之,若经不断除以 4 后得到的数满足 ≡ 7 (mod 8),则必须用四个完全平方数,可直接返回 4。
  3. 其余情况答案只能是 1、2 或 3:
    • 1:n 本身就是完全平方数;
    • 2:存在 a 使得 n - a² 仍为完全平方数(枚举 1 ≤ a ≤ √n);
    • 3:若以上及「必为 4」都不成立,由排除法知答案为 3。

实现时先对 n 反复除以 4 做上述 8m+7 判定,再依次判断 1、2,最后返回 3。

  • 时间复杂度:O(√n)(除以 4 与枚举 a 均为 √n 量级)。
  • 空间复杂度:O(1)。

LeetCode 322. 零钱兑换 ↑

题目

给定整数数组 coins 表示不同面额的硬币,每种硬币数量无限。另给定整数 amount 表示要凑出的总金额。求凑成该金额所需的最少硬币个数;若不能凑出,返回 -1。

可以认为 amount == 0 时需要 0 枚硬币。

测试样例

  • 示例 1: coins = [1, 2, 5],amount = 11
    输出:3
    解释:11 = 5 + 5 + 1。
  • 示例 2: coins = [2],amount = 3
    输出:-1
  • 示例 3: coins = [1],amount = 0
    输出:0

提示

  • 1 <= coins.length <= 12
  • 1 <= coins[i] <= 2^31 - 1
  • 0 <= amount <= 10^4

代码(Python):记忆化 DFS

class Solution(object):
    def coinChange(self, coins, amount):
        """
        :type coins: List[int]
        :type amount: int
        :rtype: int
        """
        if amount == 0:
            return 0
        memory = {}
        memory[0] = 0

        def dfs(k):
            if k in memory:
                return memory[k]
            else:
                min_count = 10 ** 4 + 1
                for i in range(len(coins)):
                    if k - coins[i] >= 0:
                        min_count = min(min_count, dfs(k - coins[i]) + 1)
                memory[k] = min_count
                return min_count

        res = dfs(amount)
        if res == 10 ** 4 + 1:
            return -1
        return res

思路解析

dfs(k) 表示凑出面额 k 的最少硬币数,边界 memory[0] = 0。转移时枚举最后一枚硬币 coins[i],则需 1 + dfs(k - coins[i])。题目保证 amount ≤ 10^4,用 10 ** 4 + 1 表示「无解」意义上的无穷大。

边界:必须判断 k - coins[i] >= 0(或先判断 coins[i] <= k)。若不加限制,k - coins[i] 会为负,递归仍可能套用公式得到偏小的错误答案;只有在剩余面额非负时才进入子问题。

最后若 dfs(amount) 仍为哨兵值,说明无法凑出,返回 -1。

  • 时间复杂度:O(amount × len(coins))(每个 k 至多算一次,内层枚举硬币)。
  • 空间复杂度:O(amount)(哈希表与递归栈)。

LeetCode 139. 单词拆分 ↑

题目

给定字符串 s 和字符串数组 wordDict 作为词典。若 s 能拆成一个或多个词典中出现的单词首尾相接(单词之间以隐含的空格概念分界),则返回 true;否则返回 false。

说明:不必用完词典里所有词;同一个词典词可以重复使用。

测试样例

  • 示例 1: s = "leetcode",wordDict = ["leet", "code"]
    输出:true
    解释:"leet" + "code"。
  • 示例 2: s = "applepenapple",wordDict = ["apple", "pen"]
    输出:true
    解释:"apple" + "pen" + "apple"("apple" 可重复用)。
  • 示例 3: s = "catsandog",wordDict = ["cats", "dog", "sand", "and", "cat"]
    输出:false

提示

  • 1 <= s.length <= 300
  • 1 <= wordDict.length <= 1000
  • 1 <= wordDict[i].length <= 20
  • s 与 wordDict[i] 仅由英文小写字母组成

代码(Python)

class Solution(object):
    def wordBreak(self, s, wordDict):
        """
        :type s: str
        :type wordDict: List[str]
        :rtype: bool
        """
        word_set = set(wordDict)
        n = len(s)
        dp = [False] * (n + 1)
        dp[0] = True  # 空串视为可拆分

        for i in range(1, n + 1):
            for j in range(i):
                if dp[j] and s[j:i] in word_set:
                    dp[i] = True
                    break

        return dp[n]

思路解析

dp[i] 表示 s 的前 i 个字符(即 s[0:i])能否被合法拆分。空串 dp[0] = True 作为递推起点。

枚举长度 i 与上一段的切分点 j:若 dp[j] 为真,且 s[j:i] 落在词典里,则 dp[i] = True。一旦发现可行可提前 break。

  • 时间复杂度:O(n³) 量级(n = len(s),双重循环 O(n²),取子串 s[j:i] 并做集合判断与长度线性相关;本题 n ≤ 300 可接受)。
  • 空间复杂度:O(n)(dp 数组)及词典 set 的空间。
posted @ 2026-10-12 00:32  Phier  阅读(2)  评论(0)    收藏  举报