Showing posts with label DP - Relation. Show all posts
Showing posts with label DP - Relation. Show all posts

LeetCode 813 - Largest Sum of Averages


https://leetcode.com/problems/largest-sum-of-averages/
We partition a row of numbers A into at most K adjacent (non-empty) groups, then our score is the sum of the average of each group. What is the largest score we can achieve?
Note that our partition must use every number in A, and that scores are not necessarily integers.
Example:
Input: 
A = [9,1,2,3,9]
K = 3
Output: 20
Explanation: 
The best choice is to partition A into [9], [1, 2, 3], [9]. The answer is 9 + (1 + 2 + 3) / 3 + 9 = 20.
We could have also partitioned A into [9, 1], [2], [3, 9], for example.
That partition would lead to a score of 5 + 2 + 6 = 13, which is worse.

Note:
  • 1 <= A.length <= 100.
  • 1 <= A[i] <= 10000.
  • 1 <= K <= A.length.
  • Answers within 10^-6 of the correct answer will be accepted as correct.
X.

DP, use dp[k][i] to denote the largest average sum of partitioning first i elements into k groups.

Init

dp[1][i] = sum(a[0] ~ a[i – 1]) / i, for i in 1, 2, … , n.

Transition

dp[k][i] = max(dp[k – 1][j] + sum(a[j] ~ a[i – 1]) / (i – j)) for j in k – 1,…,i-1.
that is find the best j such that maximize dp[k][i]
largest sum of partitioning first j elements (a[0] ~ a[j – 1]) into k – 1 groups (already computed)
+ average of a[j] ~ a[i – 1] (partition a[j] ~ a[i – 1] into 1 group).
这道题给了我们一个数组,说是让我们将数组分成至多K个非空组,然后说需要统计的分数是各组的平均数之和,让我们求一个分割方法,使得这个分数值最大,当然这个分数值不一定是整型数。这道题限制了分割的组必须为非空组,那么就是说K值要小于等于数组的元素个数。但是实际上博主感觉这个必须为非空的限制有没有都一样,因为题目中说至多分成K组,也就是说可以根本不分组,那么比如你输入个A=[9,1], K=3,照样返回一个10,给人的感觉好像是分成了[9], [1], [] 这三组一样,但其实只是分成了两组[9] 和 [1]。但我们不必纠结这些,不是重点。没有啥思路的情况下我们就先想想brute force的解法呗,对于题目中给的那个例子,我们用最暴力的方法就是遍历所有的可能性,即遍历所有分割成三个组的情况,用三个for循环。貌似行的通,但问题来了,如果K大于3呢,每大一个,多加一个for循环么,总共K个for循环?如果K=100呢,100个for循环么?画面太美我不敢看!显然这道题用brute force是行不通的,那么换个方法呗!像这种求极值的题,又是玩数组的题,根据老夫行走江湖多年的经验,十有八九都是用Dynamic Programming来做的。玩子数组且跟极值有关的题天然适合用DP来做,想想为什么?DP的本质是什么,不就是状态转移方程,根据前面的状态来更新当前的状态。而子数组不就是整个数组的前一个状态,不停的更新的使得我们最终能得到极值。
好,下面进入正题。DP走起,首先来考虑dp数组的定义,我们如何定义dp数组有时候很关键,定义的不好,那么就无法写出正确的状态转移方程。对于这道题,我们很容易直接用一个一维数组dp,其中dp[i]表示范围为[0, i]的子数组分成三组能得到的最大分数。用这样定义的dp数组的话,状态转移方程将会非常难写,因为我们忽略了一个重要的信息,即K。dp数组不把K加进去的话就不知道当前要分几组,这个Hidden Information是解题的关键。这是DP中比较难的一类,有些DP题的隐藏信息藏的更深,不挖出来就无法解题。这道题的dp数组应该是个二维数组,其中dp[i][k]表示范围是[i, n-1]的子数组分成k组的最大得分。那么这里你就会纳闷了,为啥范围是[i, n-1]而不是[0, i],为啥要取后半段呢,不着急,听博主慢慢道来。由于把[i, n-1]范围内的子数组分成k组,那么suppose我们已经知道了任意范围内分成k-1组的最大分数,这是此类型题目的破题关键所在,要求状态k,一定要先求出所有的状态k-1,那么问题就转换成了从k-1组变成k组,即多分出一组,那么在范围[i, n-1]多分出一组,实际上就是将其分成两部分,一部分是一组,另一部分是k-1组,怎么分,就用一个变量j,遍历范围(i, n-1)中的每一个位置,那么分成的这两部分的分数如何计算呢?第一部分[i, j),由于是一组,那么直接求出平均值即可,另一部分由于是k-1组,由于我们已经知道了所有k-1的情况,可以直接从cache中读出来dp[j][k-1],二者相加即可 avg(i, j) + dp[j][k-1],所以我们可以得出状态转移方程如下:
dp[i][k] = max(avg(i, n) + max_{j > i} (avg(i, j) + dp[j][k-1]))
这里的avg(i, n)是其可能出现的情况,由于是至多分为k组,所以我们可以不分组,所以直接计算范围[i, n-1]内的平均值,然后用j来遍历区间(i, n-1)中的每一个位置,最终得到的dp[i][k]就即为所求。注意这里我们通过建立累加和数组sums来快速计算某个区间之和。博主觉得这道题十分的经典,考察点非常的多,很具有代表性,标为Hard都不过分,前面提到了dp[i][k]表示的是范围[i, n-1]的子数组分成k组的最大得分,现在想想貌似定义为[0, i]范围内的子数组分成k组的最大得分应该也是可以的,那么此时j就是遍历(0, i)中的每个位置了,好像也没什么不妥的地方,有兴趣的童鞋可以尝试的写一下~
https://leetcode.com/problems/largest-sum-of-averages/discuss/122775/Java-bottom-up-DP-with-Explanation
Let f[i][j]be the largest sum of averages for first i + 1 numbers(A[0], A[1], ... , A[i]) tojgroups. f[i][j] consists of two parts: first j-1 groups' averages and the last group' s average. Considering the last group, its last number must be A[i] and its first number can be from A[0] to A[i]. Suppose the last group starts from A[p+1], we can easily get the average form A[p+1] to A[i]. The sum of first j-1 groups' average is f[p][j-1] which we have got before. So now we can write the DP equation:
f[i][j] = max {f[p][j-1] + (A[p+1] + A[p+2] + ... + A[i]) / (i - p)}, p = 0,1,...,i-1
    public double largestSumOfAverages(int[] A, int K) {
        if (K == 0 || A.length == 0) {
            return 0;
        }
        int l = A.length;
        double[][] f = new double[l][K + 1];
        double[] s = new double[l + 1];
        for (int i = 1; i <= l; i++) {
            s[i] = s[i - 1] + A[i - 1];
            f[i - 1][1] =  s[i] / i;
        }
        for (int j = 2; j <= K; j++) {
            for (int i = 0; i < l; i++) {
                double max = Double.MIN_VALUE;
                for (int p = 0; p < i; p++) {
                    double sum = f[p][j - 1] + (s[i + 1] - s[p + 1]) / (i - p);
                    max = Double.max(sum, max);
                }
                f[i][j] = max;
            }
        }
        return f[l - 1][K];
    }
    double largestSumOfAverages(vector<int>& A, int K) {
        int n = A.size();
        vector<double> sums(n + 1);
        vector<vector<double>> dp(n, vector<double>(K));
        for (int i = 0; i < n; ++i) {
            sums[i + 1] = sums[i] + A[i];
        }
        for (int i = 0; i < n; ++i) {
            dp[i][0] = (sums[n] - sums[i]) / (n - i);
        }    
        for (int k = 1; k < K; ++k) {
            for (int i = 0; i < n - 1; ++i) {
                for (int j = i + 1; j < n; ++j) {
                    dp[i][k] = max(dp[i][k], (sums[j] - sums[i]) / (j - i) + dp[j][k - 1]);
                }
            }
        }
        return dp[0][K - 1];
    }
我们可以对空间进行优化,由于每次的状态k,只跟前一个状态k-1有关,所以我们不需要将所有的状态都保存起来,只需要保存前一个状态的值就行了,那么我们就用一个一维数组就可以了,不断的进行覆盖,从而达到了节省空间的目的
    double largestSumOfAverages(vector<int>& A, int K) {
        int n = A.size();
        vector<double> sums(n + 1);
        vector<double> dp(n);
        for (int i = 0; i < n; ++i) {
            sums[i + 1] = sums[i] + A[i];
        }
        for (int i = 0; i < n; ++i) {
            dp[i] = (sums[n] - sums[i]) / (n - i);
        }    
        for (int k = 1; k < K; ++k) {
            for (int i = 0; i < n - 1; ++i) {
                for (int j = i + 1; j < n; ++j) {
                    dp[i] = max(dp[i], (sums[j] - sums[i]) / (j - i) + dp[j]);
                }
            }
        }
        return dp[0];
    }

https://leetcode.com/articles/largest-sum-of-averages/
The best score partitioning A[i:] into at most K parts depends on answers to paritioning A[j:] (j > i) into less parts. We can use dynamic programming as the states form a directed acyclic graph.
Let dp(i, k) be the best score partioning A[i:] into at most K parts.
If the first group we partition A[i:] into ends before j, then our candidate partition has score average(i, j) + dp(j, k-1)), where average(i, j) = (A[i] + A[i+1] + ... + A[j-1]) / (j - i) (floating point division). We take the highest score of these, keeping in mind we don't necessarily need to partition - dp(i, k) can also be just average(i, N).
In total, our recursion in the general case is dp(i, k) = max(average(i, N), max_{j > i}(average(i, j) + dp(j, k-1))).
We can calculate average a little bit faster by remembering prefix sums. If P[x+1] = A[0] + A[1] + ... + A[x], then average(i, j) = (P[j] - P[i]) / (j - i).
Our implementation showcases a "bottom-up" style of dp. Here at loop number k in our outer-most loop, dp[i] represents dp(i, k) from the discussion above, and we are calculating the next layer dp(i, k+1). The end of our second loop for i = 0..N-1 represents finishing the calculation of the correct value for dp(i, t), and the inner-most loop performs the calculation max_{j > i}(average(i, j) + dp(j, k)).
Time Complexity: O(K * N^2), where N is the length of A.
  public double largestSumOfAverages(int[] A, int K) {
    int N = A.length;
    double[] P = new double[N + 1];
    for (int i = 0; i < N; ++i)
      P[i + 1] = P[i] + A[i];

    double[] dp = new double[N];
    for (int i = 0; i < N; ++i)
      dp[i] = (P[N] - P[i]) / (N - i);

    for (int k = 0; k < K - 1; ++k)
      for (int i = 0; i < N; ++i)
        for (int j = i + 1; j < N; ++j)
          dp[i] = Math.max(dp[i], (P[j] - P[i]) / (j - i) + dp[j]);

    return dp[0];
  }

https://leetcode.com/problems/largest-sum-of-averages/discuss/122739/C%2B%2BJavaPython-Easy-Understood-Solution-with-Explanation


search return the result for n first numbers to k groups.
It's top-down solution and it keeps all process to memory.
So it's like a DP solution while DP is bottom-up.
I took suggestion from @MonnaGotIt and added a prunting: if (n < k) return 0;


    public double largestSumOfAverages(int[] A, int K) {
        int N = A.length;
        double[][] memo = new double[N+1][N+1];
        double cur = 0;
        for (int i = 0; i < N; ++i) {
            cur += A[i];
            memo[i + 1][1] = cur / (i + 1);
        }
        return search(N, K, A, memo);
    }

    public double search(int n, int k, int[] A, double[][] memo) {
        if (memo[n][k] > 0) return memo[n][k];
        if (n < k) return 0;
        double cur = 0;
        for (int i = n - 1; i > 0; --i) {
            cur += A[i];
            memo[n][k] = Math.max(memo[n][k], search(i, k - 1, A, memo) + cur / (n - i));
        }
        return memo[n][k];
    }

https://leetcode.com/problems/largest-sum-of-averages/discuss/126280/Naive-Detailed-Step-by-Step-Approach-from-Recursive-to-DP-O(N)-solution

LeetCode 956 - Tallest Billboard


https://leetcode.com/problems/tallest-billboard/
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:
  1. 0 <= rods.length <= 20
  2. 1 <= rods[i] <= 1000
  3. The sum of rods is at most 5000
X. DP
https://leetcode.com/problems/tallest-billboard/discuss/203181/JavaC%2B%2BPython-DP-min(O(SN2)-O(3N2-*-N)

dp[d]
 mean the maximum pair of sum we can get with pair difference d
For example, if have a pair of sum (a, b) with a > b, then dp[a - b] = b
If we have dp[diff] = a, it means we have a pair of sum (a, a + diff).
And this is the biggest pair with difference = a
dp is initializes with dp[0] = 0;
Assume we have an init state like this
------- y ------|----- d -----|
------- y ------|
case 1
If put x to tall side
------- y ------|----- d -----|----- x -----|
------- y ------|
We update dp[d + x] = max(dp[d + x], y )
case 2.1
Put x to low side and d >= x:
-------y------|----- d -----|
-------y------|--- x ---|
We update dp[d-x] = max( dp[d - x], y + x)
case 2.2
Put x to low side and d < x:
------- y ------|----- d -----|
------- y ------|------- x -------|
We update dp[x - d] = max(dp[x - d], y + d)
case 2.1 and case2.2 can merge into dp[abs(x - d)] = max(dp[abs(x - d)], y + min(d, x))

Time Complexity:

O(NM), where we have
N = rod.length <= 20
S = sum(rods) <= 5000
M = all possible sum = min(3^N, S)
There are 3 ways to arrange a number: in the first group, in the second, not used.
The number of difference will be less than 3^N.
The only case to reach 3^N is when rod = [1,3,9,27,81...]

Java, O(SN) using array, just for better reading:
    public int tallestBillboard(int[] rods) {
        int[] dp = new int[5001];
        for (int d = 1; d < 5001; d++) dp[d] = -10000;
        for (int x : rods) {
            int[] cur = dp.clone();
            for (int d = 0; d + x < 5001; d++) {
                dp[d + x] = Math.max(dp[d + x], cur[d]);
                dp[Math.abs(d - x)] = Math.max(dp[Math.abs(d - x)], cur[d] + Math.min(d, x));
            }
        }
        return dp[0];
    }
Java, using HashMap:
    public int tallestBillboard(int[] rods) {
        Map<Integer, Integer> dp = new HashMap<>(), cur;
        dp.put(0, 0);
        for (int x : rods) {
            cur = new HashMap<>(dp);
            for (int d : cur.keySet()) {
                dp.put(d + x, Math.max(cur.get(d), dp.getOrDefault(x + d, 0)));
                dp.put(Math.abs(d - x), Math.max(cur.get(d) + Math.min(d, x), dp.getOrDefault(Math.abs(d - x), 0)));
            }
        }
        return dp.get(0);
    }

One Optimisation

We do the same thing for both half of rods.
Then we try to find the same difference in both results.
Time Complexity:
O(NM), where we have
N = rod.length <= 20
S = sum(rods) <= 5000
M = all possible sum = min(3^N/2, S)

Python:
    def tallestBillboard(self, rods):
        def helper(A):
            dp = {0: 0}
            for x in A:
                for d, y in dp.items():
                    dp[d + x] = max(dp.get(x + d, 0), y)
                    dp[abs(d - x)] = max(dp.get(abs(d - x), 0), y + min(d, x))
            return dp

        dp, dp2 = helper(rods[:len(rods) / 2]), helper(rods[len(rods) / 2:])
        return max(dp[d] + dp2[d] + d for d in dp if d in dp2)

X.
https://leetcode.com/problems/tallest-billboard/discuss/204232/Simplest-DP-solution-which-is-easy-to-understand
Similar to backpack DP problem as Leetcode 416. But this one is more challenging because some data point may not be chosen. Moreover, the dp definition is different.
In this question, dp[i][j] denotes the largest left sum at the case of after using i-th rod and the difference between left sum and right sum is j - sum of all rods.
Initially, I want to design dp as i-th rod and difference between left sum and right to be j, however, j could be negative, use sum of all rods to offset all negative values.
So the answer should be dp[n][sum of all rods].
Time complexity: O(n * sum)
Space complexity: O(n * sum)
public int tallestBillboard(int[] rods) {
        int sum = 0;
        for (int i : rods){
            sum += i;
        }
        int n = rods.length;
        int[][] dp = new int[n+1][2*sum+1];//largest sum of left at i-th rod and difference between
  //sum of left and sum of right equals to j-sum
        for (int i = 0; i <= n; i++){
            Arrays.fill(dp[i], -1);// -1 means the value could not be reached.
        }
        dp[0][sum] = 0; //it means if there  is no rods, the  largest left sum could be 0, not -1.
        for (int i = 1; i <= n; i++){
            for (int j = 0; j <= 2*sum; j++){
                if (j - rods[i-1] >= 0 && dp[i-1][j-rods[i-1]] != -1){//this means we will add next rod (rods[i-1] to the left, 
    //so the largest left sum should be added by rods[i-1] from previous step
                    dp[i][j] = Math.max(dp[i][j], dp[i-1][j-rods[i-1]] + rods[i-1]);
                }
                if (j + rods[i-1] <= 2*sum && dp[i-1][j+rods[i-1]] != -1){//this means we will add next rod(rods[i-1]) to the right, 
    //so largest left sum at previous step stays at dp[i-1][j+rods[i-1]]
                    dp[i][j] = Math.max(dp[i][j], dp[i-1][j+rods[i-1]]);
                }
                if (dp[i-1][j] != -1){//this means we don't use rods[i-1] but we need ensure 
    //previous step could be reached, so we can compare.
                    dp[i][j] = Math.max(dp[i][j], dp[i-1][j]);
                }
            }
        }
 return dp[n][sum];
}

https://zxi.mytechroad.com/blog/dynamic-programming/leetcode-956-tallest-billboard/
如果直接暴力搜索的话时间复杂度是O(3^N),铁定超时。对于每一根我们可以选择1、放到左边,2、放到右边,3、不使用。最后再看一下左边和右边是否相同。
题目的数据规模中的这句话非常重要:
The sum of rods is at most 5000.
这句话就是告诉你算法的时间复杂度和sum of rods有关系,通常需要使用DP。
由于每根柱子只能使用一次(让我们想到了 回复 01背包),但是我们怎么去描述放到左边还是放到右边呢?
Naive的方法是用 dp[i] 表示使用前i个柱子能够构成的柱子高度的集合。
e.g. dp[i] = {(h1, h2)},  h1 <= h2
和暴力搜索比起来DP已经对状态进行了压缩,因为我并不需要关心h1, h2是通过哪些(在我之前的)柱子构成了,我只关心它们的当前高度。
然后我可以选择
1、不用第i根柱子
2、放到低的那一堆
3、放到高的那一堆
状态转移的伪代码:
for h1, h2 in dp[i – 1]:
dp[i] += (h1, h2)        # not used
dp[i] += (h1, h2 + h)  # put on higher
if h1 + h < h2:
dp[i] += (h1 + h, h2)  # put on lower
else:
dp[i] += (h2, h1 + h)  # put on lower
假设 rods=[1,1,2]
dp[0] = {(0,0)}
dp[1] = {(0,0), (0,1)}
dp[2] = {(0,0), (0,1), (0,2), (1,1)}
dp[3] = {(0,0), (0,1), (0,2), (0,3), (0,4), (1,1), (1,2), (1,3), (2,2)}
但是dp[i]这个集合的大小可能达到sum^2,所以还是会超时…
时间复杂度 O(n*sum^2)
空间复杂度 O(n*sum^2) 可降维至 O(sum^2)
革命尚未成功,同志仍需努力!
all pairs的cost太大,我们还需要继续压缩状态!
重点来了
通过观察发现,若有2个pairs:
(h1, h2), (h3, h4),
h1 <= h2, h3 <= h4, h1 < h3, h2 – h1 = h4 – h3 即 高度差 相同
如果 min(h1, h2) < min(h3, h4) 那么(h1, h2) 不可能产生最优解,直接舍弃。
因为如果后面的柱子可以构成 h4 – h3/h2 – h1 填补高度差,使得两根柱子一样高,那么答案就是 h2 和 h4。但h2 < h4,所以最优解只能来自后者。
举个例子:我有 (1, 3) 和 (2, 4) 两个pairs,它们的高度差都是2,假设我还有一个长度为2的柱子,那么我可以构成(1+2, 3) 以及 (2+2, 4),虽然这两个都是解。但是后者的高度要大于前者,所以前者无法构成最优解,也就没必要存下来。
所以,我们可以把状态压缩到高度差对于相同的高度差,我只存h1最大的
我们用 dp[i][j] 来表示使用前i个柱子,高度差为j的情况下最大的公共高度h1是多少。
状态转移(如下图)
dp[i][j] = max(dp[i][j], dp[i – 1][j])
dp[i][j+h] = max(dp[i][j + h], dp[i – 1][j])
dp[i][|j-h|] = max(dp[i][|j-h|], dp[i – 1][j] + min(j, h))
时间复杂度 O(nsum)
空间复杂度 O(nsum) 可降维至 O(sum)
dp[i] := max common height of two piles of height difference i.
e.g. y1 = 5, y2 = 9 => dp[9 – 5] = min(5, 9) => dp[4] = 5.
answer: dp[0]
Time complexity: O(n*Sum)
Space complexity: O(Sum)
  int tallestBillboard(vector<int>& rods) {
    unordered_map<int, int> dp;
    dp[0] = 0;
    for (int rod : rods) {      
      auto cur = dp;
      for (const auto& kv : cur) {
        const int k = kv.first;
        dp[k + rod] = max(dp[k + rod], cur[k]);
        dp[abs(k - rod)] = max(dp[abs(k - rod)], cur[k] + min(rod, k));
      }    
    }
    return dp[0];
  }

https://www.acwing.com/solution/LeetCode/content/644/
状态 f(i,j)f(i,j) 表示考虑了前 ii 个钢筋,搭建的两个钢制支架差距为 jj 时,较低的支架 的最大高度是多少。
初始化 f(i,j)=−∞f(i,j)=−∞,f(0,0)=0f(0,0)=0。
转移时,如果不用第 ii 个钢筋,则 f(i,j)=max(f(i,j),f(i−1,j))f(i,j)=max(f(i,j),f(i−1,j));如果使用了第 ii 个钢筋,并将它放到了较高的支架上,则差距会扩大 rods[i],即 f(i,j+x)=max(f(i,j+x),f(i−1,j))f(i,j+x)=max(f(i,j+x),f(i−1,j));若放到了较低的支架上,并且差距 jj 小于等于 rods[i],则 f(i,j−x)=max(f(i,j−x),f(i−1,j)+x)f(i,j−x)=max(f(i,j−x),f(i−1,j)+x),否则 f(i,x−j)=max(f(i,x−j),f(i−1,j)+j)f(i,x−j)=max(f(i,x−j),f(i−1,j)+j)。
最终答案为 f(n,0)f(n,0)。
    int tallestBillboard(vector<int>& rods) {
        int n = rods.size(), sum = 0;
        vector<vector<int>> f(n + 1, vector<int>(5010, -5010));

        f[0][0] = 0;
        for (int i = 1; i <= n; i++) {
            sum += rods[i - 1];
            for (int j = 0; j <= sum; j++) {
                f[i][j] = max(f[i][j], f[i - 1][j]);
                int x = rods[i - 1];
                if (j + x <= sum)
                    f[i][j + x] = max(f[i][j + x], f[i - 1][j]);
                if (x <= j)
                    f[i][j - x] = max(f[i][j - x], f[i - 1][j] + x);
                else
                    f[i][x - j] = max(f[i][x - j], f[i - 1][j] + j);
            }
        }

        return f[n][0];
    }

把这个问题看成是扩展版的01背包问题:对于每根棍子,我们可以把它加入背包中,不加入背包中,还可以把它从背包中减去。
f[i][j]表示用前i根棍子能否组成和为j的长度(j有可能是负的)。则f[i+1][j] = f[i][j] || f[i][j-rods[i+1]] || f[i][j+rods[i+1]]。为了找到可能的最大长度,用辅助数组g[i][j]记录f[i][j]为真时,最大可能的正长度之和。算法的复杂度为O(10000*N)
X. DFS + cache
I know it's not as cool as a DP solution, though during the context it may be quicker to do DFS and add memoisation if you get TLE.
So we go through all rods, and either skip the rod, add it to the first support (s1), or to the second support (s2). The result is the maximum of these three operations. When we exhausted all rods (i >= rs.size()), we return the rod size if both rods are the same, or zero. This way, our simple DFS solution can be implemented in just a few lines of code:
int tallestBillboard(vector<int>& rods, int i = 0, int s1 = 0, int s2 = 0) {
  if (i >= rods.size()) return s1 == s2 ? s1 : 0;
  return max({ tallestBillboard(rods, i + 1, s1, s2), 
               tallestBillboard(rods, i + 1, s1 + rods[i], s2), 
               tallestBillboard(rods, i + 1, s1, s2 + rods[i]) });
}
You'll probably get TLE (certainly, in this particular case) for a simple DFS solution, so the next step is to think about memoisation to avoid processing identical conditions over and over again. We could memoise support sizes s1 and s2 for the current rod number i. However, that would require a lot of memory (and we will get MLE or TLE).
The intuition here is that we do not need to memoise the actual support sizes; all is what it's important is the delta: abs(s1 - s2). For example, if for i rod, first support is s1 == 50, the second is s2 == 30, and in the final suport sizes matches and equals 200, we will record m[i][50 - 30] = 200 - 50 or m[i][20] = 150. Next time we process i and support sizes are 100 and 80 (the delta is 20), we know that there are matched size in the end that adds 150 to the larger support: m[i][100 - 80] + max(100, 80) = m[i][20] + 100 = 150 + 100 = 250.
int dfs(vector<int>& rs, int i, int s1, int s2, int m_sum, vector<vector<int>> &m) {
  if (s1 > m_sum || s2 > m_sum) return -1;
  if (i >= rs.size()) return s1 == s2 ? s1 : -1;
  if (m[i][abs(s1 - s2)] == -2) {
    m[i][abs(s1 - s2)] = max(-1, max({ dfs(rs, i + 1, s1, s2, m_sum, m),
      dfs(rs, i + 1, s1 + rs[i], s2, m_sum, m), dfs(rs, i + 1, s1, s2 + rs[i], m_sum, m) }) - max(s1, s2));
  }
  return m[i][abs(s1 - s2)] + (m[i][abs(s1 - s2)] == -1 ? 0 : max(s1, s2));
}
int tallestBillboard(vector<int>& rods) {
  int m_sum = accumulate(begin(rods), end(rods), 0) / 2;
  vector<vector<int>> m(rods.size(), vector<int>(m_sum + 1, -2));
  return max(0, dfs(rods, 0, 0, 0, m_sum, m));
}
As an additinal optimization, I am also calculating maximum possible billboard (sum of all rods divide by 2), and using it for pruning and vector allocation. As the result, this solution runtime beats most of DP solutions other folks posted

X.
https://leetcode.com/articles/tallest-billboard/
Typically, the complexity of brute force can be reduced with a "meet in the middle" technique. As applied to this problem, we have 3^N possible states, from writing either +x-x, or 0 for each rod x, and we want to make this brute force faster.
What we can do is write the first and last 3^{N/2} states separately, and attempt to combine them. For example, if we have rods [1, 3, 5, 7], then the first two rods create up to nine states: [0+0, 0+3, 0-3, 1+0, 1+3, 1-3, -1+0, -1+3, -1-3], and the last two rods also create nine states.
We will store each state as the sum of positive terms, and the sum of absolute values of the negative terms. For example, +1 +2 -3 -4 becomes (3, 7). Let's also call the difference 3 - 7 to be the delta of this state, so this state has a delta of -4.
Our high level goal is to combine states with deltas that sum to 0. The score of a state will be the sum of the positive terms, and we want the highest score. Note that for each delta, we only care about the state that has the highest score.
Algorithm
Split the rods into two halves: left and right.
For each half, use brute force to compute the reachable states as defined above. Then, for each state, record the delta and the maximum score.
Now, we have a left and right halves with [(delta, score)] information. We'll find the largest total score, with total delta 0.

  public int tallestBillboard(int[] rods) {
    int N = rods.length;
    Map<Integer, Integer> Ldelta = make(Arrays.copyOfRange(rods, 0, N / 2));
    Map<Integer, Integer> Rdelta = make(Arrays.copyOfRange(rodsN / 2, N));

    int ans = 0;
    for (int d : Ldelta.keySet())
      if (Rdelta.containsKey(-d))
        ans = Math.max(ansLdelta.get(d) + Rdelta.get(-d));

    return ans;
  }

  public Map<Integer, Integer> make(int[] A) {
    Point[] dp = new Point[60000];
    int t = 0;
    dp[t++] = new Point(0, 0);
    for (int v : A) {
      int stop = t;
      for (int i = 0; i < stop; ++i) {
        Point p = dp[i];
        dp[t++] = new Point(p.x + vp.y);
        dp[t++] = new Point(p.xp.y + v);
      }
    }

    Map<Integer, Integer> ans = new HashMap();
    for (int i = 0; i < t; ++i) {
      int a = dp[i].x;
      int b = dp[i].y;
      ans.put(a - b, Math.max(ans.getOrDefault(a - b, 0), a));
    }

    return ans;

  }


解法2:中间相遇法
rods数组分成大致相等的两半,然后对每一半都枚举每根棍子是+,-还是0。然后对于左边的一半得到的和,在右边寻找这个和的负值是否存在。最后取最大值。算法复杂度为O(3^(N/2))
这个算法也需要记录最大可能的正长度之和
    unordered_map<int, int> results;  // 和 - 最大正值和
    int M, N;
    int ans;
    
    // 枚举棍子状态:sum是和,p是正值和
    void dfs(int x, int sum, int p, vector<int>& rods, bool check) {
        if (x >= rods.size()) {
            if (!check) results[sum] = max(results[sum], p);
            else {
                if (results.find(-sum) != results.end())
                    ans = max(ans, results[-sum] + p);
            }
            return;
        }
        dfs(x+1, sum, p, rods, check);
        dfs(x+1, sum+rods[x], p+rods[x], rods, check);
        dfs(x+1, sum-rods[x], p, rods, check);
    }
    
public:
    int tallestBillboard(vector<int>& rods) {
        N = rods.size();
        if (N == 0) return 0;
        M = N / 2;
        vector<int> rod1, rod2;
        for (int i = 0; i < M; i++)
            rod1.push_back(rods[i]);
        for (int i = M; i < N; i++)
            rod2.push_back(rods[i]);
        ans = 0;
        dfs(0, 0, 0, rod1, false);
        dfs(0, 0, 0, rod2, true);
        return ans;
    }


https://blog.csdn.net/xx_123_1_rj/article/details/86557102

Labels

LeetCode (1432) GeeksforGeeks (1122) LeetCode - Review (1067) Review (882) Algorithm (668) to-do (609) Classic Algorithm (270) Google Interview (237) Classic Interview (222) Dynamic Programming (220) DP (186) Bit Algorithms (145) POJ (141) Math (137) Tree (132) LeetCode - Phone (129) EPI (122) Cracking Coding Interview (119) DFS (115) Difficult Algorithm (115) Lintcode (115) Different Solutions (110) Smart Algorithm (104) Binary Search (96) BFS (91) HackerRank (90) Binary Tree (86) Hard (79) Two Pointers (78) Stack (76) Company-Facebook (75) BST (72) Graph Algorithm (72) Time Complexity (69) Greedy Algorithm (68) Interval (63) Company - Google (62) Geometry Algorithm (61) Interview Corner (61) LeetCode - Extended (61) Union-Find (60) Trie (58) Advanced Data Structure (56) List (56) Priority Queue (53) Codility (52) ComProGuide (50) LeetCode Hard (50) Matrix (50) Bisection (48) Segment Tree (48) Sliding Window (48) USACO (46) Space Optimization (45) Company-Airbnb (41) Greedy (41) Mathematical Algorithm (41) Tree - Post-Order (41) ACM-ICPC (40) Algorithm Interview (40) Data Structure Design (40) Graph (40) Backtracking (39) Data Structure (39) Jobdu (39) Random (39) Codeforces (38) Knapsack (38) LeetCode - DP (38) Recursive Algorithm (38) String Algorithm (38) TopCoder (38) Sort (37) Introduction to Algorithms (36) Pre-Sort (36) Beauty of Programming (35) Must Known (34) Binary Search Tree (33) Follow Up (33) prismoskills (33) Palindrome (32) Permutation (31) Array (30) Google Code Jam (30) HDU (30) Array O(N) (29) Logic Thinking (29) Monotonic Stack (29) Puzzles (29) Code - Detail (27) Company-Zenefits (27) Microsoft 100 - July (27) Queue (27) Binary Indexed Trees (26) TreeMap (26) to-do-must (26) 1point3acres (25) GeeksQuiz (25) Merge Sort (25) Reverse Thinking (25) hihocoder (25) Company - LinkedIn (24) Hash (24) High Frequency (24) Summary (24) Divide and Conquer (23) Proof (23) Game Theory (22) Topological Sort (22) Lintcode - Review (21) Tree - Modification (21) Algorithm Game (20) CareerCup (20) Company - Twitter (20) DFS + Review (20) DP - Relation (20) Brain Teaser (19) DP - Tree (19) Left and Right Array (19) O(N) (19) Sweep Line (19) UVA (19) DP - Bit Masking (18) LeetCode - Thinking (18) KMP (17) LeetCode - TODO (17) Probabilities (17) Simulation (17) String Search (17) Codercareer (16) Company-Uber (16) Iterator (16) Number (16) O(1) Space (16) Shortest Path (16) itint5 (16) DFS+Cache (15) Dijkstra (15) Euclidean GCD (15) Heap (15) LeetCode - Hard (15) Majority (15) Number Theory (15) Rolling Hash (15) Tree Traversal (15) Brute Force (14) Bucket Sort (14) DP - Knapsack (14) DP - Probability (14) Difficult (14) Fast Power Algorithm (14) Pattern (14) Prefix Sum (14) TreeSet (14) Algorithm Videos (13) Amazon Interview (13) Basic Algorithm (13) Codechef (13) Combination (13) Computational Geometry (13) DP - Digit (13) LCA (13) LeetCode - DFS (13) Linked List (13) Long Increasing Sequence(LIS) (13) Math-Divisible (13) Reservoir Sampling (13) mitbbs (13) Algorithm - How To (12) Company - Microsoft (12) DP - Interval (12) DP - Multiple Relation (12) DP - Relation Optimization (12) LeetCode - Classic (12) Level Order Traversal (12) Prime (12) Pruning (12) Reconstruct Tree (12) Thinking (12) X Sum (12) AOJ (11) Bit Mask (11) Company-Snapchat (11) DP - Space Optimization (11) Dequeue (11) Graph DFS (11) MinMax (11) Miscs (11) Princeton (11) Quick Sort (11) Stack - Tree (11) 尺取法 (11) 挑战程序设计竞赛 (11) Coin Change (10) DFS+Backtracking (10) Facebook Hacker Cup (10) Fast Slow Pointers (10) HackerRank Easy (10) Interval Tree (10) Limited Range (10) Matrix - Traverse (10) Monotone Queue (10) SPOJ (10) Starting Point (10) States (10) Stock (10) Theory (10) Tutorialhorizon (10) Kadane - Extended (9) Mathblog (9) Max-Min Flow (9) Maze (9) Median (9) O(32N) (9) Quick Select (9) Stack Overflow (9) System Design (9) Tree - Conversion (9) Use XOR (9) Book Notes (8) Company-Amazon (8) DFS+BFS (8) DP - States (8) Expression (8) Longest Common Subsequence(LCS) (8) One Pass (8) Quadtrees (8) Traversal Once (8) Trie - Suffix (8) 穷竭搜索 (8) Algorithm Problem List (7) All Sub (7) Catalan Number (7) Cycle (7) DP - Cases (7) Facebook Interview (7) Fibonacci Numbers (7) Flood fill (7) Game Nim (7) Graph BFS (7) HackerRank Difficult (7) Hackerearth (7) Inversion (7) Kadane’s Algorithm (7) Manacher (7) Morris Traversal (7) Multiple Data Structures (7) Normalized Key (7) O(XN) (7) Radix Sort (7) Recursion (7) Sampling (7) Suffix Array (7) Tech-Queries (7) Tree - Serialization (7) Tree DP (7) Trie - Bit (7) 蓝桥杯 (7) Algorithm - Brain Teaser (6) BFS - Priority Queue (6) BFS - Unusual (6) Classic Data Structure Impl (6) DP - 2D (6) DP - Monotone Queue (6) DP - Unusual (6) DP-Space Optimization (6) Dutch Flag (6) How To (6) Interviewstreet (6) Knapsack - MultiplePack (6) Local MinMax (6) MST (6) Minimum Spanning Tree (6) Number - Reach (6) Parentheses (6) Pre-Sum (6) Probability (6) Programming Pearls (6) Rabin-Karp (6) Reverse (6) Scan from right (6) Schedule (6) Stream (6) Subset Sum (6) TSP (6) Xpost (6) n00tc0d3r (6) reddit (6) AI (5) Abbreviation (5) Anagram (5) Art Of Programming-July (5) Assumption (5) Bellman Ford (5) Big Data (5) Code - Solid (5) Code Kata (5) Codility-lessons (5) Coding (5) Company - WMware (5) Convex Hull (5) Crazyforcode (5) DFS - Multiple (5) DFS+DP (5) DP - Multi-Dimension (5) DP-Multiple Relation (5) Eulerian Cycle (5) Graph - Unusual (5) Graph Cycle (5) Hash Strategy (5) Immutability (5) Java (5) LogN (5) Manhattan Distance (5) Matrix Chain Multiplication (5) N Queens (5) Pre-Sort: Index (5) Quick Partition (5) Quora (5) Randomized Algorithms (5) Resources (5) Robot (5) SPFA(Shortest Path Faster Algorithm) (5) Shuffle (5) Sieve of Eratosthenes (5) Strongly Connected Components (5) Subarray Sum (5) Sudoku (5) Suffix Tree (5) Swap (5) Threaded (5) Tree - Creation (5) Warshall Floyd (5) Word Search (5) jiuzhang (5)

Popular Posts