【Leetcode每日一题】2021-05-03 1473 粉刷房子III - 困难 (Python)
在一个小城市里,有 m
个房子排成一排,你需要给每个房子涂上 n
种颜色之一(颜色编号为 1
到 n
)。有的房子去年夏天已经涂过颜色了,所以这些房子不需要被重新涂色。
我们将连续相同颜色尽可能多的房子称为一个街区。(比方说 houses = [1,2,2,3,3,2,1,1]
,它包含 5
个街区 [{1}, {2,2}, {3,3}, {2}, {1,1}]
。)
给你一个数组 houses
,一个 m * n
的矩阵 cost
和一个整数 target
,其中:houses[i]
:是第 i
个房子的颜色,0
表示这个房子还没有被涂色。cost[i][j]
:是将第 i
个房子涂成颜色 j+1
的花费。
请你返回房子涂色方案的最小总花费,使得每个房子都被涂色后,恰好组成 target
个街区。如果没有可用的涂色方案,请返回 -1
。
示例 1:
输入:houses = [0,0,0,0,0]
, cost = [[1,10],[10,1],[10,1],[1,10],[5,1]]
, m = 5
, n = 2
, target = 3
输出:9
解释:房子涂色方案为 [1,2,2,1,1]
此方案包含 target = 3
个街区,分别是 [{1}, {2,2}, {1,1}]
。
涂色的总花费为 (1 + 1 + 1 + 1 + 5) = 9
。
示例 2:
输入:houses = [0,2,1,2,0]
, cost = [[1,10],[10,1],[10,1],[1,10],[5,1]]
, m = 5
, n = 2
, target = 3
输出:11
解释:有的房子已经被涂色了,在此基础上涂色方案为 [2,2,1,2,2]
此方案包含 target = 3
个街区,分别是 [{2,2}, {1}, {2,2}]
。
给第一个和最后一个房子涂色的花费为 (10 + 1) = 11
。
示例 3:
输入:houses = [0,0,0,0,0]
, cost = [[1,10],[10,1],[1,10],[10,1],[1,10]]
, m = 5
, n = 2
, target = 5
输出:5
示例 4:
输入:houses = [3,1,2,3]
, cost = [[1,1,1],[1,1,1],[1,1,1],[1,1,1]]
, m = 4
, n = 3
, target = 3
输出:-1
解释:房子已经被涂色并组成了 4
个街区,分别是 [{3},{1},{2},{3}]
,无法形成 target = 3
个街区。
提示:m == houses.length == cost.length
n == cost[i].length
1 <= m <= 100
1 <= n <= 20
1 <= target <= m
0 <= houses[i] <= n
1 <= cost[i][j] <= 10^4
标签:动态规划
动态规划
题目中有一点说得不太明确,题目中说已经粉刷过的房子不需要再刷,题目本意应该是去年夏天刷过的房子不准再刷,说不需要感觉还是可以再被刷成其他颜色,所以说有一点不太明确。但是两种意思都挺有意思的,而且代码层面差别不大,这里我就把两种情况的个人解答都写出来了。
其实两种题目的动态规划解题思路大致是一样的:
- 首先我们可以看出房子i
的状态只跟房子i-1
的状态有关,状态可以按target
来分,即每个房子可以有target
种状态,每种状态还可以分为n
种颜色,所以这里要用三维DP$\rightarrow$ dp[m][target][n]
。
- 确定完状态下面就要推状态转移方程了。假设现在状态为dp[i][j][k]
,即房子i
处于j
街区时颜色为k
的最小cost
。首先按街区分为两种,$j = 0$和$j > 0$:
$\rightarrow j = 0$: 说明房子i
还处于第一街区,所以它的颜色必须和前一个房子i-1
相同,所以$$dp[i][j][k] = dp[i-1][j][k] + cost[i][k]$$
$\rightarrow j > 0$: 此时房子已经不属于第一街区了,所以它的上一个房子i-1
可以和i
颜色相同(即i-1
和i
属于同一街区j
),也可以不同(即i-1
属于i
的上一个街区j-1
),所以我们只需从所有可能性中选出cost最小的那一个所以$$dp[i][j][k] = min([dp[i-1][j][k]] + [dp[i-1][j-1][c]\ for\ c\ in\ range(n)\ if\ c\ !=\ k]) + cost[i][k]$$
- 我们下一步再确定一下dp
表的初始值,从题目中我们看到$1 <= cost[i][j] <= 10^4, 1 <= m <= 100$我们可以知道cost
的值最大为$10^4*100=10^6$,所以我们设置一个最大值MAX_COST
为$10^6$,我们直接用MAX_COST
填满dp
表。第一个房子肯定是属于第一个街区的,所以dp[0][0] = cost[0]
,但是这里对于两种不同的题目会有所不同,下面在具体的题目下面会说明。
其实两种题目主要的区别是怎么利用房子已有的颜色来更新cost
表,如果是不准再刷,则我们可以先把当前房子i
的所有颜色的cost
都设为最大值,然后把房子i
已有的颜色k
对应的cost[i][k]
设为0
,即不要改变颜色。如果是可刷可不刷,那么我们直接把房子i
已有的颜色对应的cost[i][k]
设为0
即可。
1. 已经粉刷过的房子可以刷成其他颜色,也可以不刷
时间复杂度为$\mathcal{O}(m\times n^2\times target)$
1 | class Solution: |
2. 已经粉刷过的房子不准再刷成其他颜色
时间复杂度为$\mathcal{O}(m\times n^2\times target)$
1 | class Solution: |
下面我们把上面的代码再优化一下,我们把$j>0$中当前街区j
和上个房子的街区j-1
不同时查找所有颜色不等于k
时最小cost
的复杂度$\mathcal{O}(n)$降到$\mathcal{O}(1)$,大致想法是用数组best
来存储每个房子每个街区所有颜色中cost
的最小值及其颜色值以及第二小的cost
值$\rightarrow$[first, index of first, second]
,best
大小为best[m][target][3]
。之所以只需要这三个值,因为我们只想找出最小值,如果最小值的颜色和当前颜色相同那么直接用第二小的值即可,如果颜色不同就直接用最大值,这样就把查找的时间复杂度从$\mathcal{O}(n)$降为$\mathcal{O}(1)$。
时间复杂度优化为$\mathcal{O}(m\times n\times target)$
1 | class Solution: |