Leetcode Link: 剑指 Offer II 091. 粉刷房子 - 力扣(LeetCode)

题目

假如有一排房子,共 n 个,每个房子可以被粉刷成红色、蓝色或者绿色这三种颜色中的一种,你需要粉刷所有的房子并且使其相邻的两个房子颜色不能相同。

当然,因为市场上不同颜色油漆的价格不同,所以房子粉刷成不同颜色的花费成本也是不同的。每个房子粉刷成不同颜色的花费是以一个 n x 3 的正整数矩阵 costs 来表示的。

例如,costs[0][0] 表示第 0 号房子粉刷成红色的成本花费;costs[1][2] 表示第 1 号房子粉刷成绿色的花费,以此类推。

请计算出粉刷完所有房子最少的花费成本。

解法一

思路:暴力递归

这一题的暴力递归比较简单,是从左到右的尝试模型。

在每个位置,需要知道上一个位置选择了哪一个颜色的涂料,然后在这个位置不选择。

注意 base case

题解

class Solution:
    def minCost(self, costs: List[List[int]]) -> int:
        n = len(costs)
        @functools.lru_cache(None)
        def process(i, color):
            # base case
            if i == n:
                return 0
            
            res = float('inf')
            for c in range(3):
	            # 和上一个位置的颜色一样跳过,不能用
                if c == color:  
                    continue
                res = min(process(i+1, c) + costs[i][c], res)
 
            return res
        
        p1 = process(0, 0)
        p2 = process(0, 1)
        p3 = process(0, 2)
        return min(p1, p2, p3)

解法二

思路:动态规划

这一题的动态规划实际上要比暴力递归更好想

只要能够确定这是一个二维dp,基本上答案就出来了。每一个位置[i][color]受限于当前位置的颜色选择,不能和上一个位置的颜色重复,比较最小值就行

dp五步走:

  1. dp[i][color]表示第i个位置涂color的涂料时,最小的花销
  2. 递推公式:dp[i][0]=min(dp[i-1][1], dp[i-1][2])+costs[i][0],其余两个颜色同理
  3. 初始值:0号位置的三种颜色dp[0][0], dp[0][1], dp[0][2]
  4. 递归方向:正常遍历,for i in range(1, n)即可
  5. 尝试

题解

class Solution:
    def minCost(self, costs: List[List[int]]) -> int:
        n = len(costs)
        # [n ,color]
        dp = [[float('inf') for _ in range(3)] for _ in range(n+1)]
        # 初始化
        dp[0][0] = costs[0][0]
        dp[0][1] = costs[0][1]
        dp[0][2] = costs[0][2]
        for i in range(1, n):
            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]
 
        return min(dp[n-1][0], dp[n-1][1], dp[n-1][2])

启发和联系

开始直接想 dp 的时候没想出来,就是卡在了不知道应该是几维 dp,写完暴力递归后,发现是二维 dp,没参考暴力递归的代码就写出了 dp 方法。

可见,dp维度的确定十分重要。