【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.lengthn == cost[i].length1 <= m <= 1001 <= n <= 201 <= target <= m0 <= houses[i] <= n1 <= 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: |