LeetCode 热题 100 之 198.打家劫舍

解题思路:动态规划

这是一道典型的 "不相邻元素最大和" 问题,核心是用动态规划递推:

  • 定义 dp[i] 为前 i 间房屋能偷到的最大金额。
  • 状态转移:
    • 若偷第 i 间,则不能偷第 i-1 间:dp[i] = dp[i-2] + nums[i]
    • 若不偷第 i 间,则最大金额为 dp[i-1]
    • 因此:dp[i] = max(dp[i-1], dp[i-2] + nums[i])
  • 空间优化:只需维护前两个状态,用两个变量滚动更新,空间复杂度从 O (n) 降到 O (1)。

基础动态规划版(未做空间优化)

这个版本会完整维护一个 dp 数组,存储前 i 间房屋的最大金额,逻辑更直观,适合理解动态规划的核心思想:

复制代码
class Solution {
    public int rob(int[] nums) {
        // 边界条件处理
        if (nums == null || nums.length == 0) {
            return 0;
        }
        if (nums.length == 1) {
            return nums[0];
        }

        // 1. 定义dp数组:dp[i]表示前i间房屋能偷到的最大金额
        int[] dp = new int[nums.length];
        
        // 2. 初始化dp数组
        dp[0] = nums[0]; // 只有1间房,偷它
        dp[1] = Math.max(nums[0], nums[1]); // 有2间房,偷金额大的那个
        
        // 3. 状态转移:遍历从第3间房开始(索引2)
        for (int i = 2; i < nums.length; i++) {
            // 两种选择:偷第i间(则不能偷i-1,取dp[i-2]+nums[i]);不偷第i间(取dp[i-1])
            dp[i] = Math.max(dp[i-1], dp[i-2] + nums[i]);
        }
        
        // 4. 最终结果是最后一间房对应的dp值
        return dp[nums.length - 1];
    }
}

代码实现(空间优化版)

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

复杂度分析

  • 时间复杂度:O (n),一次遍历数组。
  • 空间复杂度:O (1),仅用两个变量滚动更新,满足常量空间要求。

示例验证

  • 示例 1nums = [1,2,3,1]

    • i=0: prevPrev=1, prev=max(1,2)=2
    • i=2: curr = max(2, 1+3)=4 → prevPrev=2, prev=4
    • i=3: curr = max(4, 2+1)=4 → prev=4
    • 输出:4
  • 示例 2nums = [2,7,9,3,1]

    • i=0: prevPrev=2, prev=7
    • i=2: curr = max(7, 2+9)=11 → prevPrev=7, prev=11
    • i=3: curr = max(11, 7+3)=11 → prevPrev=11, prev=11
    • i=4: curr = max(11, 11+1)=12 → prev=12
    • 输出:12

扩展

一、核心算法思想回顾

先明确算法的核心适配场景:

  • 存在线性排列的可选项 (如房屋、时间段、资源节点);
  • 有 "相邻项互斥" 的约束(选了 A 就不能选 A 的直接邻居);
  • 目标是最大化收益 / 最小化成本

这三个特征是判断能否用该算法的关键,也是它能落地到生产的核心原因。

二、实际生产场景落地(附代码示例)

场景 1:广告位投放优化(电商 / 信息流平台)

业务背景

APP 首页有一排广告位(位置 0→1→2→...→n-1),每个广告位投放后能获得的收益为 profit[i],但平台规则要求相邻广告位不能投放同一品牌(避免用户视觉疲劳),需要计算 "投放哪些广告位能获得最大总收益"。

算法适配性

  • 广告位 = 房屋,投放收益 = 房屋金额;
  • 相邻广告位互斥 = 相邻房屋不能偷;
  • 目标:最大化总收益 = 最大化偷到的金额。

生产级代码实现(Java)

复制代码
/**
 * 广告位投放最大收益计算(基于打家劫舍DP算法)
 */
public class AdSlotOptimizer {
    // 核心算法:计算最大投放收益
    public static int calculateMaxAdProfit(int[] slotProfits) {
        if (slotProfits == null || slotProfits.length == 0) return 0;
        if (slotProfits.length == 1) return slotProfits[0];
        
        // 基础DP版(便于生产环境调试,可记录中间状态)
        int[] dp = new int[slotProfits.length];
        dp[0] = slotProfits[0];
        dp[1] = Math.max(slotProfits[0], slotProfits[1]);
        
        for (int i = 2; i < slotProfits.length; i++) {
            dp[i] = Math.max(dp[i-1], dp[i-2] + slotProfits[i]);
            // 生产环境可打印中间状态,用于监控/调试
            System.out.printf("广告位%d:不投放收益=%d,投放收益=%d,最优=%d%n",
                    i, dp[i-1], dp[i-2]+slotProfits[i], dp[i]);
        }
        return dp[slotProfits.length - 1];
    }

    public static void main(String[] args) {
        // 模拟广告位收益:位置0=500元,位置1=300元,位置2=800元,位置3=400元
        int[] slotProfits = {500, 300, 800, 400};
        int maxProfit = calculateMaxAdProfit(slotProfits);
        System.out.println("广告投放最大收益:" + maxProfit); // 结果:500+800=1300元
    }
}

生产价值

  • 电商大促(618 / 双 11)时,广告位收益实时变化,该算法能快速计算最优投放方案;
  • 支持动态调整:若某广告位临时不可用(收益设为 0),算法可实时重新计算,适配业务变化。

场景 2:服务器资源调度(云服务平台)

业务背景

云服务器集群按机架线性排列(机架 0→1→2→...→n-1),每个机架部署任务的收益为 reward[i],但相邻机架不能同时部署高负载任务(避免电力过载),需要计算 "部署哪些机架能获得最大总收益"。

算法适配性

  • 机架 = 房屋,部署收益 = 房屋金额;
  • 相邻机架互斥 = 相邻房屋不能偷;
  • 目标:最大化集群收益 = 最大化偷到的金额。

核心代码(空间优化版,适配高并发)

复制代码
/**
 * 服务器机架资源调度(空间优化版,适合大规模集群)
 */
public class ServerResourceScheduler {
    public static int maxClusterReward(int[] rackRewards) {
        if (rackRewards == null || rackRewards.length == 0) return 0;
        if (rackRewards.length == 1) return rackRewards[0];
        
        // 空间优化版:适合机架数上千的大规模集群,节省内存
        int prevPrev = rackRewards[0];
        int prev = Math.max(rackRewards[0], rackRewards[1]);
        
        for (int i = 2; i < rackRewards.length; i++) {
            int curr = Math.max(prev, prevPrev + rackRewards[i]);
            prevPrev = prev;
            prev = curr;
        }
        return prev;
    }

    public static void main(String[] args) {
        // 模拟1000个机架的收益(随机生成,仅示例)
        int[] rackRewards = new int[1000];
        for (int i = 0; i < 1000; i++) {
            rackRewards[i] = (int) (Math.random() * 1000);
        }
        long startTime = System.currentTimeMillis();
        int maxReward = maxClusterReward(rackRewards);
        long endTime = System.currentTimeMillis();
        
        System.out.println("集群最大收益:" + maxReward);
        System.out.println("计算耗时:" + (endTime - startTime) + "ms"); // 毫秒级,满足实时调度
    }
}

生产价值

  • 云平台机架数可达上千 / 上万,空间优化版仅用 2 个变量,内存占用可忽略;
  • 时间复杂度 O (n),毫秒级完成计算,满足资源调度的实时性要求(调度决策需在秒级内完成)。

场景 3:电网负荷分配(能源行业)

业务背景

电网的输电节点按线路线性排列,每个节点的供电收益为 profit[i],但相邻节点不能同时满负荷供电(避免线路过载),需要计算 "哪些节点供电能获得最大总收益"。

算法适配性

  • 输电节点 = 房屋,供电收益 = 房屋金额;
  • 相邻节点互斥 = 相邻房屋不能偷;
  • 目标:最大化供电收益 = 最大化偷到的金额。

扩展:最小成本版本(反向应用)

生产中也常遇到 "最小成本" 场景,比如 "相邻节点不能同时断电,求最小断电损失",只需把 max 换成 min:

复制代码
/**
 * 电网节点断电最小损失计算(反向应用打家劫舍算法)
 */
public class PowerGridOptimizer {
    public static int minPowerLoss(int[] loss) {
        if (loss == null || loss.length == 0) return 0;
        if (loss.length == 1) return loss[0];
        
        int[] dp = new int[loss.length];
        dp[0] = loss[0];
        dp[1] = Math.min(loss[0], loss[1]); // 换成min求最小损失
        
        for (int i = 2; i < loss.length; i++) {
            dp[i] = Math.min(dp[i-1], dp[i-2] + loss[i]);
        }
        return dp[loss.length - 1];
    }
}

三、算法扩展:生产环境的进阶适配

在实际生产中,基础算法会根据业务需求扩展,核心思路不变:

  1. 环形约束(打家劫舍 II):比如广告位围成一圈(首尾也互斥),只需拆分为 "不选第一个" 和 "不选最后一个" 两个子问题,分别计算取最大值;
  2. 多维度约束 :比如广告位不仅相邻互斥,还受 "每日投放次数限制",可扩展为二维 DP 数组 dp[i][k](前 i 个广告位,投放 k 次的最大收益);
  3. 实时更新:生产中收益 / 成本会动态变化,可结合滑动窗口,只重新计算变化的区间,提升效率。

总结

  1. 核心应用场景:所有 "线性排列 + 相邻互斥 + 最优选择" 的场景,如广告投放、资源调度、能源分配、任务规划等;
  2. 生产选型建议
    • 小规模场景 / 需要调试:用基础 DP 版(便于记录中间状态);
    • 大规模场景 / 高并发:用空间优化版(节省内存,提升速度);
  3. 扩展价值:不仅能求 "最大值",也能反向求 "最小值",适配收益 / 成本两类核心业务目标。
相关推荐
Anastasiozzzz2 小时前
深入研究Redis的ZSet底层数据结构:从 Ziplist 的级联更新到 Listpack 的完美救场
数据结构·数据库·redis
Eward-an2 小时前
LeetCode 1980 题通关指南|3种解法拆解“找唯一未出现二进制串”问题,附Python最优解实现
python·算法·leetcode
程序员酥皮蛋2 小时前
hot 100 第四十题 40.二叉树的层序遍历
数据结构·算法·leetcode
※DX3906※3 小时前
Java排序算法--全面详解面试中涉及的排序
java·开发语言·数据结构·面试·排序算法
木斯佳3 小时前
HarmonyOS 6实战:从爆款vlog探究鸿蒙智能体提取关键帧算法
算法·华为·harmonyos
Mr.朱鹏4 小时前
JVM-GC垃圾回收案例
java·jvm·spring boot·算法·spring·spring cloud·java-ee
WJSKad12354 小时前
【DepthPro】实战教程:单目深度估计算法详解与应用
算法
wzqllwy4 小时前
8 大经典排序算法(Java 实现):原理 + Demo + 核心分析
java·算法·排序算法
We་ct4 小时前
LeetCode 77. 组合:DFS回溯+剪枝,高效求解组合问题
开发语言·前端·算法·leetcode·typescript·深度优先·剪枝
重生之我是Java开发战士4 小时前
【递归、搜索与回溯】二叉树中的深度优先搜索:布尔二叉树,求根节点到叶节点数字之和,二叉树剪枝,验证二叉搜索树,第K小的元素,二叉树的所有路径
算法·深度优先·剪枝