粉刷房子问题:从DP基础到空间极致优化学习笔记

粉刷房子问题:从DP基础到空间极致优化学习笔记

一、问题回顾

n 个房子排成一排,每个房子可刷红、蓝、绿三种颜色,相邻房子颜色不能相同。给定每个房子刷对应颜色的成本矩阵 costs[n][3],求粉刷所有房子的最小总成本。

约束核心:相邻颜色不同 ;目标:全局成本最小。这类递推约束+最优解结构,天然适合动态规划求解。

二、常规动态规划思路

1. 标准状态定义

最直观的方式是定义二维 DP 数组:

  • dp[i][0]:第 i 个房子刷红色时,前 i 个房子的最小成本
  • dp[i][1]:第 i 个房子刷蓝色时,前 i 个房子的最小成本
  • dp[i][2]:第 i 个房子刷绿色时,前 i 个房子的最小成本

2. 状态转移方程

根据颜色互斥约束:

  • dp[i][0] = min(dp[i-1][1], dp[i-1][2]) + costs[i][0]
  • dp[i][1] = min(dp[i-1][0], dp[i-1][2]) + costs[i][1]
  • dp[i][2] = min(dp[i-1][0], dp[i-1][1]) + costs[i][2]

3. 初始与结果

  • 初始:dp[0][0] = costs[0][0], dp[0][1] = costs[0][1], dp[0][2] = costs[0][2]
  • 结果:min(dp[n-1][0], dp[n-1][1], dp[n-1][2])

此时时间复杂度 O(n),空间复杂度 O(n),逻辑清晰但空间存在优化空间。

三、空间优化:从O(n)到O(1)

观察状态转移可以发现:计算第 i 个房子的状态,只依赖第 i-1 个房子的状态 ,与更早历史无关。

因此不需要保存完整 n×3 数组,只需要保存上一轮三个颜色的累计成本即可。

优化后状态表示

用三个变量滚动更新:

  • prev_redprev_blueprev_green:上一个房子对应颜色的最小累计成本
  • curr_redcurr_bluecurr_green:当前房子对应颜色的最小累计成本

转移逻辑不变,只是每次用新值覆盖旧值,遍历结束后取三者最小值。

优化后:

  • 时间复杂度:O(n)(不变)
  • 空间复杂度:O(1)(仅常数变量,无额外动态数组)

在数据规模较大、内存敏感的场景中,这种滚动更新的写法更贴近实际工程实现。

四、实现细节与工程思考

  1. 边界处理

    当房子数量 n = 0 时直接返回 0,避免数组越界,这是代码鲁棒性的基础。

  2. 临时变量的必要性

    更新当前颜色成本时,必须先保存上一轮的三个值,再统一计算新值。如果直接覆盖,会导致后续计算使用已被修改的错误值。

  3. 常数级优化的意义

    本题颜色固定为 3 种,状态数极少,滚动变量的写法不仅节省空间,也更利于编译器做寄存器优化,在高频调用、长序列场景下,实际运行效率更稳定。

  4. 可扩展性思考

    如果颜色种类从 3 种扩展到 k 种,思路依然成立:滚动维护 k 个状态,每次取非自身的 k-1 个历史状态最小值累加,时间复杂度变为 O(n·k),空间仍为 O(k),依旧是较优的解法。

五、最终实现(C++)

cpp 复制代码
class Solution {
public:
    int minCost(vector<vector<int>>& costs) {
        int n = costs.size();
        if (n == 0) return 0;

        int red = costs[0][0];
        int blue = costs[0][1];
        int green = costs[0][2];

        for (int i = 1; i < n; ++i) {
            int pre_r = red;
            int pre_b = blue;
            int pre_g = green;

            red = min(pre_b, pre_g) + costs[i][0];
            blue = min(pre_r, pre_g) + costs[i][1];
            green = min(pre_r, pre_b) + costs[i][2];
        }

        return min({red, blue, green});
    }
};

六、小结

粉刷房子是非常典型的线性递推+有限状态 动态规划问题。

从二维 DP 到滚动变量,本质是只保留对后续有用的历史信息,剔除冗余存储。这种思想在很多空间受限的算法与工程实现中都非常常见,既保证了最优子结构的利用,又在空间与效率上达到了平衡。

相关推荐
czlczl2002092528 分钟前
力扣1911. 最大交替子序列和
算法·leetcode·动态规划
寒秋花开曾相惜1 小时前
(学习笔记)3.8 指针运算(3.8.3 嵌套的数组& 3.8.4 定长数组)
java·开发语言·笔记·学习·算法
是翔仔呐2 小时前
第11章 显示外设驱动:I2C协议OLED屏、SPI协议LCD屏字符/图片/中文显示
c语言·开发语言·stm32·单片机·嵌入式硬件·学习·gitee
_李小白2 小时前
【AI大模型学习笔记之平台篇】第五篇:Trae常用模型介绍与性能对比
人工智能·笔记·学习
承渊政道2 小时前
【优选算法】(实战体会位运算的逻辑思维)
数据结构·c++·笔记·学习·算法·leetcode·visual studio
AI-Ming3 小时前
程序员转行学习 AI 大模型: 踩坑记录:服务器内存不够,程序被killed
服务器·人工智能·python·gpt·深度学习·学习·agi
m0_716765233 小时前
C++提高编程--STL常用容器(set/multiset、map/multimap容器)详解
java·开发语言·c++·经验分享·学习·青少年编程·visual studio
2501_945318493 小时前
零基础学习AI的选型指南:CAIE认证与编程型AI认证如何取舍
人工智能·学习
承渊政道3 小时前
【优选算法】(实战推演模拟算法的蕴含深意)
数据结构·c++·笔记·学习·算法·leetcode·排序算法
Keep learning!3 小时前
PCA主成分分析学习
学习·算法