二刷 LeetCode:198. 打家劫舍 & 279. 完全平方数 复盘笔记

目录

[一、198. 打家劫舍](#一、198. 打家劫舍)

题目回顾

思路复盘

[基础 DP 思路](#基础 DP 思路)

[空间优化版(O (1) 空间)](#空间优化版(O (1) 空间))

[易错点 & 二刷心得](#易错点 & 二刷心得)

[二、279. 完全平方数](#二、279. 完全平方数)

题目回顾

思路复盘

[方法 1:动态规划](#方法 1:动态规划)

[方法 2:BFS](#方法 2:BFS)

[易错点 & 二刷心得](#易错点 & 二刷心得)

[三、两道题的共性总结 & 二刷收获](#三、两道题的共性总结 & 二刷收获)


这两道题分别是一维动态规划入门动态规划 / 广度优先搜索的经典题,也是面试高频考点。二刷时我们重点拆解思路、优化写法,顺便把易错点和通用模板总结清楚。


一、198. 打家劫舍

题目回顾

你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。

思路复盘

这是一维动态规划的经典题,核心是定义状态和状态转移方程。

基础 DP 思路
  1. 状态定义dp[i] 表示前 i 间房屋能偷窃到的最高金额。
  2. 状态转移
    • 对于第 i 间房屋,有两种选择:
      1. 偷第 i 间:那么不能偷第 i-1 间,最高金额为 dp[i-2] + nums[i]
      2. 不偷第 i 间:最高金额为 dp[i-1]
    • 状态转移方程:dp[i] = max(dp[i-1], dp[i-2] + nums[i])
  3. 初始状态
    • dp[0] = nums[0](只有一间房,偷它)
    • dp[1] = max(nums[0], nums[1])(两间房,偷金额大的)
  4. 结果dp[n-1](n 为房屋数量)

Java 代码实现(基础版)

java

运行

复制代码
public int rob(int[] nums) {
    if (nums == null || nums.length == 0) return 0;
    if (nums.length == 1) return nums[0];
    int n = nums.length;
    int[] dp = new int[n];
    dp[0] = nums[0];
    dp[1] = Math.max(nums[0], nums[1]);
    for (int i = 2; i < n; i++) {
        dp[i] = Math.max(dp[i-1], dp[i-2] + nums[i]);
    }
    return dp[n-1];
}
空间优化版(O (1) 空间)

因为 dp[i] 只依赖 dp[i-1]dp[i-2],所以可以用两个变量滚动更新,空间复杂度从 O (n) 降到 O (1):

java

运行

复制代码
public int rob(int[] nums) {
    if (nums == null || nums.length == 0) return 0;
    if (nums.length == 1) return nums[0];
    int prevPrev = nums[0];
    int prev = Math.max(nums[0], nums[1]);
    for (int i = 2; i < nums.length; i++) {
        int current = Math.max(prev, prevPrev + nums[i]);
        prevPrev = prev;
        prev = current;
    }
    return prev;
}

易错点 & 二刷心得

  1. 边界处理:数组长度为 0 或 1 时要单独判断,避免索引越界。
  2. 状态转移的理解dp[i] 表示前 i 间的最高金额,不是偷第 i 间的最高金额,所以不偷第 i 间时,dp[i] = dp[i-1]
  3. 空间优化技巧:一维 DP 中,如果当前状态只依赖前两个状态,就可以用变量代替数组,大幅降低空间复杂度。

二、279. 完全平方数

题目回顾

给你一个整数 n ,返回和为 n 的完全平方数的最少数量。完全平方数是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,14916 都是完全平方数,而 311 不是。

思路复盘

这道题有两种主流解法:动态规划广度优先搜索(BFS),其中动态规划是面试中最常考的解法。

方法 1:动态规划

核心思路:和「零钱兑换」问题类似,把完全平方数看作硬币,目标是用最少的硬币凑成 n

  1. 状态定义dp[i] 表示和为 i 的完全平方数的最少数量。
  2. 状态转移
    • 对于每个 i,遍历所有小于等于 i 的完全平方数 j*j
      • dp[i] = min(dp[i - j*j] + 1, dp[i])
  3. 初始状态dp[0] = 0,其他 dp[i] 初始化为无穷大
  4. 结果dp[n]

Java 代码实现

java

运行

复制代码
public int numSquares(int n) {
    int[] dp = new int[n + 1];
    Arrays.fill(dp, Integer.MAX_VALUE);
    dp[0] = 0;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j * j <= i; j++) {
            dp[i] = Math.min(dp[i], dp[i - j * j] + 1);
        }
    }
    return dp[n];
}
方法 2:BFS

核心思路:把每个数看作节点,每个数可以减去一个完全平方数得到下一个节点,问题转化为从 n0 的最短路径问题。

java

运行

复制代码
public int numSquares(int n) {
    Queue<Integer> queue = new LinkedList<>();
    Set<Integer> visited = new HashSet<>();
    queue.offer(n);
    visited.add(n);
    int level = 0;
    while (!queue.isEmpty()) {
        int size = queue.size();
        level++;
        for (int i = 0; i < size; i++) {
            int num = queue.poll();
            for (int j = 1; j * j <= num; j++) {
                int next = num - j * j;
                if (next == 0) return level;
                if (!visited.contains(next)) {
                    visited.add(next);
                    queue.offer(next);
                }
            }
        }
    }
    return -1;
}

易错点 & 二刷心得

  1. 动态规划的初始化dp 数组要初始化为无穷大,否则会影响 min 比较的结果。
  2. BFS 的剪枝 :使用 visited 集合避免重复访问节点,提高效率。
  3. 数学优化:根据拉格朗日四平方定理,任何正整数都可以表示为 4 个整数的平方和,所以结果最多为 4,可以作为边界判断。

三、两道题的共性总结 & 二刷收获

  1. 动态规划的核心思路
    • 打家劫舍:一维 DP 的基础,学会定义状态、找到转移方程,理解 "选 / 不选" 两种决策的逻辑。
    • 完全平方数:和零钱兑换问题类似,是 "完全背包" 的变形,核心是遍历所有可能的完全平方数,更新状态。
  2. 优化意识
    • 打家劫舍:从数组 DP 到变量滚动更新,降低空间复杂度。
    • 完全平方数:除了 DP,还可以用 BFS 解决,理解不同算法的适用场景。
  3. 面试重点
    • 打家劫舍:重点是状态转移方程和空间优化,后续的环形打家劫舍、打家劫舍 III 都是它的变形题。
    • 完全平方数:重点是 DP 的实现和 BFS 的思路,以及拉格朗日四平方定理的应用。
相关推荐
承渊政道8 小时前
【动态规划算法】(子序列问题解题框架与典型案例)
数据结构·c++·学习·算法·leetcode·macos·动态规划
阿Y加油吧8 小时前
二刷 LeetCode:215. 数组中的第 K 个最大元素 & 347. 前 K 个高频元素 复盘笔记
笔记·leetcode·排序算法
pop_xiaoli8 小时前
【iOS】KVC与KVO
笔记·macos·ios·objective-c·cocoa
_F_y8 小时前
仿RabbitMQ实现消息队列-服务端核心模块实现(3)
c++·算法·rabbitmq
m0_629494738 小时前
LeetCode 热题 100-----15.轮转数组
数据结构·算法·leetcode
AI科技星8 小时前
从180°旋转定值π、e论证时空宿命与未来可预测性—全域数学视角
人工智能·算法·机器学习·数学建模·数据挖掘
WL_Aurora8 小时前
Python 算法基础篇之栈和队列
python·算法
YJlio8 小时前
Windows Internals 10.5.3:ETW 架构详解,从事件产生到性能分析的完整链路
windows·笔记·python·stm32·嵌入式硬件·学习·架构
艺术电影节8 小时前
惊喜映后 | 伍迪·艾伦经典修复澳门首映
算法·推荐算法·电视