You are installing a billboard and want it to have the largest height. The billboard will have two steel supports, one on each side. Each steel support must be an equal height.
You have a collection of rods
which can be welded together. For example, if you have rods of lengths 1, 2, and 3, you can weld them together to make a support of length 6.
Return the largest possible height of your billboard installation. If you cannot support the billboard, return 0.
Example 1:
Input: [1,2,3,6]
Output: 6
Explanation: We have two disjoint subsets {1,2,3} and {6}, which have the same sum = 6.
Example 2:
Input: [1,2,3,4,5,6]
Output: 10
Explanation: We have two disjoint subsets {2,3,5} and {4,6}, which have the same sum = 10.
Example 3:
Input: [1,2]
Output: 0
Explanation: The billboard cannot be supported, so we return 0.
Note:
0 <= rods.length <= 20
1 <= rods[i] <= 1000
The sum of rods is at most 5000.
這道題說是要裝個很高的廣告牌,有很多長度不同的支撐桿,多個支撐桿可以粘合成一個更長的,廣告牌需要兩個長度相等的支撐桿,問廣告牌最高能放多高。去掉這個具體的場景,其實這道題討論的就是在數組中選取若干個數字分成和相同的兩組,問這個最大和是多少。跟之前那道 Partition Equal Subset Sum 非常的類似,不過那道題是要用到所有的數字,所以一旦可以分割的話,那么最大和就是固定的,為數組所有數字之和的一半。而這道題不強制要用所有的數字,所以最大和是不確定的。所以之前那道題的方法在這里完全不適用,但也不是完全沒有關系,起碼動態規划 Dynamic Programming 的思想還是要用的。博主強調過很多次,像這種數組啊,字符串啊之類的極值問題,DP 解法十有八九繞不開,若還是個 Hard 題,那么基本就是 DP 無疑了。這道題的一大難點就是 dp 不容易定義,即便你知道要用 DP,是否能寫出正確的定義式也是個問題。下面這種解法參考了 zxybazh 大神的帖子,這里我們定一個一個二維 dp 數組,其中 dp[i][j] 表示從前i個數字中選出數字組成兩組(組0和組1,這里假設組0數字之和一定小於組1),此時的二者中的較大長度,其實也就是組1的數字之和,並且j表示二組數字之和的差值。接下來就是要找出狀態轉移方程,如何從之前的狀態推導出 dp[i][j]。當把 rod[i] 加入其中的一組時,此時有三種情況:
-
將 rod[i] 加入組1時,由於組1的數字和大,所以增加新數字會拉大兩組原本的差值,若加入之后的差值為j,則加入之前則為 j-rods[i],所以可以用 dp[i-1][j-rods[i]] + rods[i] 來更新 dp[i][j]。
-
將將 rod[i] 加入組0時,且加入之后組0的數字之和仍小於組1,但此時二者的差距變小了,若加入之后的差值為j,則加入之前則為 j+rods[i],所以可以用 dp[i-1][j+rods[i]] 來更新 dp[i][j]。
-
將將 rod[i] 加入組0時,且加入之后組0的數字之和超過了組1,說明這個新數字要大於原本兩個組之間的差值,若加入之后的差值為j,則加入之前則為 rods[i]-j,所以可以用 dp[i-1][rods[i]-j] + j 來更新 dp[i][j]。
搞清楚了這三種情況,就可以寫出代碼了,最終的結果是存在 dp[n][0] 中的,因為這表示從前n個數字中選出數字組成兩組,且兩組之和差為0,說明可以組成相同和的兩組,參見代碼如下:
解法一:
class Solution {
public:
int tallestBillboard(vector<int>& rods) {
int n = rods.size();
vector<vector<int>> dp(n + 1, vector<int>(5001));
for (int i = 0; i <= 5000; i++) dp[0][i] = -1e9;
dp[0][0] = 0;
for (int i = 1; i <= n; ++i) {
for (int j = 0; j <= 5000; ++j) {
dp[i][j] = dp[i - 1][j];
if (j >= rods[i - 1]) {
dp[i][j] = max(dp[i][j], dp[i - 1][j - rods[i - 1]] + rods[i - 1]);
} else {
dp[i][j] = max(dp[i][j], dp[i - 1][rods[i - 1] - j] + j);
}
if (j + rods[i - 1] <= 5000) {
dp[i][j] = max(dp[i][j], dp[i - 1][j + rods[i - 1]]);
}
}
}
return dp[n][0];
}
};
再來看一種空間復雜度更小的解法,這里就照着 lee215 大神的帖子 來講解吧。定義了一個一維數組 dp,其中 dp[i] 表示兩組中較小的那組的數字之和,且i表示兩組的數字和的差值。其中 dp[0] 要初始化為0。現在假設有兩組數字,其和的差值為d,如下所示:
------- y ------|----- d -----|
------- y ------|
現在要加入一個新的數字x的話,還是有三種情況:
- 加入到長的那組,使得長的更長,此時要用 y 來更新 dp[d+x],如下所示:
------- y ------|----- d -----|----- x -----|
------- y ------|
- 加入到短的那組,加了之后還是沒有超過長的組,此時要用 y+x 來更新 dp[d-x],如下所示:
-------y------|----- d -----|
-------y------|--- x ---|
- 加入到短的那組,加了之后超過了長的組,此時要用 y+d 來更新 dp[x-d],如下所示:
------- y ------|----- d -----|
------- y ------|------- x -------|
可以發現,后兩種情況可以合成為一種,dp[abs(x - d)] = max(dp[abs(x - d)], y + min(d, x))
,最終的結果保存在 dp[0] 中,參見代碼如下:
解法二:
class Solution {
public:
int tallestBillboard(vector<int>& rods) {
vector<int> dp(5001, -1e9);
dp[0] = 0;
for (int x : rods) {
vector<int> cur = dp;
for (int d = 0; d + x <= 5000; ++d) {
dp[d + x] = max(dp[d + x], cur[d]);
dp[abs(d - x)] = max(dp[abs(d - x)], cur[d] + min(d, x));
}
}
return dp[0];
}
};
Github 同步地址:
https://github.com/grandyang/leetcode/issues/956
類似題目:
參考資料:
https://leetcode.com/problems/tallest-billboard/
https://leetcode.com/problems/tallest-billboard/discuss/203274/Simple-C%2B%2B-DP-beating-100-O(NM)