1. 程式人生 > >Leetcode 956:最高的廣告牌(超詳細的解法!!!)

Leetcode 956:最高的廣告牌(超詳細的解法!!!)

你正在安裝一個廣告牌,並希望它高度最大。這塊廣告牌將有兩個鋼製支架,兩邊各一個。每個鋼支架的高度必須相等。

你有一堆可以焊接在一起的鋼筋 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。

提示:

  1. 0 <= rods.length <= 20
  2. 1 <= rods[i] <= 1000
  3. 鋼筋的長度總和最多為 5000

解題思路

對於這個問題首先想到的解決思路是通過DFS。我們遍歷rods中的元素,我們對每個元素都有如下三種考量。

  • 放左邊
  • 放右邊
  • 不放

當我們左邊鋼筋的長度left和右邊鋼筋的長度right是一樣的時候,我們就需要記錄這種情況下的最大值res。我們在初始的時候需要記錄一個最大容量maxsumsum(rods)

),我們每次遍歷的過程中需要更新它,也就是我們每使用一個鋼筋i,我們就要將maxsum減去相應的值rods[i]。我們接著思路遞迴到底的情況。

  • 遍歷完rods的最後一個元素
  • a b s ( l e
    f t r i g h t ) &gt; m a x s u m abs(left-right) &gt; maxsum
  • l e f t + r i g h t + m a x s u m &lt; = r e s 2 left+right+maxsum&lt;=res*2

第一種情況自然不用多說。第二種情況,我們最後無法得到left==right的情形,所以也要返回。對於第三種情況,我們此時res就是最大長度了,所以直接返回就可以了。

我們在一開始的時候,也就是放入第一根鋼筋的時候,我們只要考慮放和不放,而不需要考慮放左還是放右。一切安排妥當,我們開始寫程式碼

class Solution:
    def tallestBillboard(self, rods):
        """
        :type rods: List[int]
        :rtype: int
        """
        if len(rods) == 0 or len(rods) == 1:
            return 0
        
        self.res = 0
        
        rods.sort(reverse=True)# add sort
        remain = sum(rods)
        self.dfs(rods, remain - rods[0], 0, rods[0], 0)
        self.dfs(rods, remain - rods[0], 0, 0, 0)
        
        return self.res
    
    def dfs(self, rods, remain, i, left, right):
        if abs(left - right) > remain or\
            (left + right + remain <= self.res*2):
            return
        
        if left == right and self.res < left:
            self.res = left
            
        i += 1
        if i == len(rods):
            return 
        
        remain -= rods[i]
        self.dfs(rods, remain, i, left + rods[i], right)
        self.dfs(rods, remain, i, left, right + rods[i])
        self.dfs(rods, remain, i, left, right)

但是我們這樣做了之後超時了。我們需要一步優化,我們可以在開始的時候對rods按照從大到小排序(貪心策略)。

這個問題有個巧妙的方法,我們首先定義函式 f ( a , b ) f(a,b) 返回距離差是 a b s ( a b ) abs(a-b) 的最大公共長度。例如

我們此時知道兩者長度相差 d d ,那麼此時 f ( d ) = l e n ( r e d ) f(d)=len(red) 。此時如果我們新加入一個鋼筋x,那麼我們有兩種策略,第一種是加在上面

那麼我們知道此時的 f ( d + x ) = m a x ( f ( d + x ) , l e n ( r e d ) ) f(d+x)=max(f(d+x), len(red)) 。我們也可以將它加入到下面

那麼我們知道此時的 f ( d x ) = m a x ( f ( d x ) , l e n ( r e d ) + x ) f(d-x)=max(f(d-x),len(red)+x) ,或者也可以是下面這種

那麼我們知道此時的 f ( x d ) = m a x ( f ( x d ) , l e n ( r e d ) + d ) f(x-d)=max(f(x-d),len(red)+d)

但是這樣做有什麼用呢?我們雖然能保證最大的公有長度,但是這個公有長度不一定可以通過所給的rods中的元素拼湊出來啊?是的,確實是這樣。但是我們可以保證的是 f ( 0 ) f(0) 一定可以通過rods中的元素拼湊出來,而 f ( 0 ) f(0) 恰好就是我們所需要的結果,非常好的思路。

class Solution:
    def tallestBillboard(self, rods):
        """
        :type rods: List[int]
        :rtype: int
        """
        mem = {0: 0}
        
        for i in rods:
            cur = mem.copy()
            for d, val in cur.items():
                mem[d + i] = max(mem.get(i + d, 0), val)
                mem[abs(d - i)] = max(mem.get(abs(d - i), 0), val + min(i, d))
                
        return mem[0]

reference:

https://leetcode.com/problems/tallest-billboard/discuss/203181/JavaC++Python-DP-min(O(SN2)-O(3N2-*-N)

我將該問題的其他語言版本新增到了我的GitHub Leetcode

如有問題,希望大家指出!!!