0956-最高的广告牌
你正在安装一个广告牌,并希望它高度最大。这块广告牌将有两个钢制支架,两边各一个。每个钢支架的高度必须相等。
你有一堆可以焊接在一起的钢筋 rods
。举个例子,如果钢筋的长度为 1
、2
和 3
,则可以将它们焊接在一起形成长度为 6
的支架。
返回 广告牌的最大可能安装高度 。如果没法安装广告牌,请返回 0
。
示例 1:
**输入:** [1,2,3,6]
**输出:** 6
**解释:** 我们有两个不相交的子集 {1,2,3} 和 {6},它们具有相同的和 sum = 6。
示例 2:
**输入:** [1,2,3,4,5,6]
**输出:** 10
**解释:** 我们有两个不相交的子集 {2,3,5} 和 {4,6},它们具有相同的和 sum = 10。
示例 3:
**输入:** [1,2]
**输出:** 0
**解释:** 没法安装广告牌,所以返回 0。
提示:
0 <= rods.length <= 20
1 <= rods[i] <= 1000
sum(rods[i]) <= 5000
方法 1:动态规划
想法
对于每一根钢筋 x
,我们会写下 +x
,-x
或者 0
。我们的目标是最终得到结果 0
并让正数之和最大。我们记所有写下的正数之和为 score。例如,+1 +2 +3 -6
的 score 为 6
。
因为 sum(rods)
的大小限制,就说明可以利用这个性质。事实上,如果之前已经写下了一些数字,那么就不需要考虑这些数字是如何得到的。例如,rods = [1, 2, 2, 3]
,我们可以用 3 种方法得到和为 3
,但只考虑最终的 score 为 3
。数字之和的上界是 10001
,因为只有 [-5000, 5000]
区间内的整数是可能的值。
算法
dp[i][s]
表示当我们可以使用 rods[j]
(j >= i)
时能得到的最大 score
,由于之前写下的数字和为 s
(不统计在 score 内)。例如,rods = [1, 2, 3, 6]
,可以有 dp[1][1] = 5
,在写下 1
之后,可以写下 +2 +3 -6
使得剩下的 rods[i:]
获得 score 为 5
。
边界情况:dp[rods.length][s]
是 0
当 s == 0
,剩余情况为 -infinity
。递推式为 dp[i][s] = max(dp[i+1][s], dp[i+1][s-rods[i]], rods[i] + dp[i+1][s+rods[i]])
。
1 | class Solution { |
1 | from functools import lru_cache |
复杂度分析
- 时间复杂度:O(NS),其中 N 是
rods
的长度,S 是 \sum \text{rods}[i]。 - 空间复杂度:O(NS)。
方法 2:折半搜索
想法
暴力搜索的复杂度可以用“折半搜索”优化。在这个问题中,我们有 3^N 种可行方案,对于每个钢筋 x
可以考虑 +x
,-x
,或者 0
,我们要让暴力的速度更快。
我们可以让前 3^{N/2 和后一半分开来考虑,然后再合并他们。例如,如果有钢筋 [1, 3, 5, 7]
,那么前两根钢筋可以构成九种状态:[0+0, 0+3, 0-3, 1+0, 1+3, 1-3, -1+0, -1+3, -1-3]
,后两根钢筋也可以构成九种状态。
我们对每个状态记录正数之和,以及负数绝对值之和。例如,+1 +2 -3 -4
记为 (3, 7)
。同时记状态的 delta 为两者之差 3-7
,所以这个状态的 delta
为 -4
。
我们的目标是将两个状态合并,使得 delta
之和为 0
。score
是所有正数之和,我们希望获得最高的 score
。对于每个 delta
我们只会记录具有最高 score
的状态。
算法
将钢筋分成左右两半:左侧和右侧。
对于每一半,暴力计算可达的所有状态,如上定义。然后针对所有状态,记录下 delta
和最大的 score
。
然后我们有左右两半的 [(delta, score)]
信息。我们找到 delta
为 0
时最大的 score
和。
1 | import java.awt.Point; |
1 | class Solution(object): |
复杂度分析
- 时间复杂度:O(3^{N/2}),其中 N 是
rods
的长度。 - 空间复杂度:O(3^{N/2})。