Given an array of integers A
, consider all non-empty subsequences of A
.html
For any sequence S, let the width of S be the difference between the maximum and minimum element of S.git
Return the sum of the widths of all subsequences of A. github
As the answer may be very large, return the answer modulo 10^9 + 7.數組
Example 1:ide
Input: [2,1,3] Output: 6 Explanation: Subsequences are [1], [2], [3], [2,1], [2,3], [1,3], [2,1,3]. The corresponding widths are 0, 0, 0, 1, 1, 2, 2. The sum of these widths is 6.
Note:code
1 <= A.length <= 20000
1 <= A[i] <= 20000
這道題給了咱們一個數組,而且定義了一種子序列的寬度,就是非空子序列中最大值和最小值的差值,讓咱們算出全部的子序列的寬度之和,並且提示告終果多是個超大數,要對 1e9+7 取餘。因爲要求是子序列,因此沒必要像子數組那樣必需要連續,而且咱們只關心最大值和最小值,其餘的數字並不 care。因爲子序列並不存在順序之分,因此咱們能夠開始就對輸入數組進行排序,並不會影響最終的結果。想到這裏博主的思路就斷了,難道要生成全部的子序列,而後一個一個的計算差值麼,這種思路對得起 Hard 標籤麼?但一時半會又想不出什麼其餘好思路,其實這道題的最優解法至關的 tricky,基本有點腦筋急轉彎的感受了。在解題以前,咱們首先要知道的是一個長度爲n的數組,共有多少個子序列,若是算上空集的話,共有 2^n 個。那麼在給數組排序以後,對於其中任意一個數字 A[i],其前面共有i個數是小於等於 A[i] 的,這i個數字共有 2^i 個子序列,它們加上 A[i] 均可以組成一個新的非空子序列,而且 A[i] 是這裏面最大的數字,那麼在寬度計算的時候,就要加上 A[i] x (2^i),同理,A[i] 後面還有 n-1-i 個數字是大於等於它的,後面能夠造成 2^(n-1-i) 個子序列,每一個加上 A[i] 就都是一個新的非空子序列,同時 A[i] 是這些子序列中最小的一個,那麼結果中就要減去 A[i] x (2 ^ (n-1-i))。對於每一個數字都這麼計算一下,就是最終要求的全部子序列的寬度之和了。可能你會懷疑雖然加上了 A[i] 前面 2^i 個子序列的最大值,那些子序列的最小值減去了麼?實際上是減去了的,雖然不是在遍歷 A[i] 的時候減去,在遍歷以前的數字時已經將全部該數字是子序列最小值的狀況減去了,同理,A[i] 後面的那些 2^(n-1-i) 個子序列的最大值也是在遍歷到的時候才加上的,因此不會漏掉任何一個數字。在寫代碼的時候有幾點須要注意的地方,首先,結果 res 要定義爲 long 型,由於雖然每次會對 1e9+7 取餘,可是不能保證不會在取餘以前就已經整型溢出,因此要定義爲長整型。其次,不能直接算 2^i 和 2^(n-1-i),很容易溢出,即使是長整型,也有可能溢出。那麼解決方案就是,在累加i的同時,每次都乘以個2,那麼遍歷到i的時候,也就乘到 2^i 了,防止溢出的訣竅就是每次乘以2以後就立馬對 1e9+7 取餘,這樣就避免了指數溢出,同時又不影響結果。最後,因爲這種機制下的 2^i 和 2^(n-1-i) 不方便同時計算,這裏又用了一個 trick,就是將 A[i] x (2^(n-1-i)) 轉換爲了 A[n-1-i] x 2^i,其實兩者最終的累加和是相等的:htm
sum(A[i] * 2^(n-1-i)) = A[0]*2^(n-1) + A[1]*2^(n-2) + A[2]*2^(n-3) + ... + A[n-1]*2^0 sum(A[n-1-i] * 2^i) = A[n-1]*2^0 + A[n-2]*2^1 + ... + A[1]*2^(n-2) + A[0]*2^(n-1)
能夠發現兩個等式的值都是相等的,只不過順序顛倒了一下,參見代碼以下:blog
解法一:排序
class Solution { public: int sumSubseqWidths(vector<int>& A) { long res = 0, n = A.size(), M = 1e9 + 7, c = 1; sort(A.begin(), A.end()); for (int i = 0; i < n; ++i) { res = (res + A[i] * c - A[n - i - 1] * c) % M; c = (c << 1) % M; } return res; } };
咱們也能夠換一種寫法,使用兩個累加和 leftSum 和 rightSum,其中 leftSum[i] 表示數組範圍 [0, i] 內的數字之和,rightSum[i] 表示數組範圍 [i, n-1] 內的數字之和,而後每次在i位置,累加 (rightSum - leftSum) x 2^i 到結果 res,也能獲得一樣正確的結果,這是爲啥呢?咱們只要將 (rightSum - leftSum) x 2^i 展開,就能明白了:element
sum((rightSum - leftSum) * 2^i) = (A[n-1] - A[0]) * 2^0 + (A[n-1] + A[n-2] - A[1] - A[0]) * 2^1 + (A[n-1] + A[n-2] + A[n-3] - A[2] - A[1] - A[0]) * 2^2 + ... + (A[n-1] + A[n-2] - A[1] - A[0]) * 2^(n-3) (A[n-1] - A[0]) * 2^(n-2) = A[n-1] * (2^(n-1) - 2^0) + A[n-2] * (2^(n-2) - 2^1) + ... + A[0] * (2^0 - 2^(n-1)) = sum(A[i] * 2^i - A[i] * 2^(n-1-i))
咱們發現,最終仍是轉化成了跟解法一中同樣的規律,參見代碼以下:
解法二:
class Solution { public: int sumSubseqWidths(vector<int>& A) { long res = 0, n = A.size(), M = 1e9 + 7, c = 1; int leftSum = 0, rightSum = 0, left = 0, right = n - 1; sort(A.begin(), A.end()); while (left < n) { leftSum += A[left++]; rightSum += A[right--]; res = (res + (rightSum - leftSum) * c) % M; c = (c << 1) % M; } return res; } };
討論:作完了這道題以後,博主就在想,若是將子序列變成子數組,其餘不變,該怎麼作?稍稍想了一下,發現是 totally different story,這道題的方法徹底就不能使用了,由於子數組是不能排序的,也不能不連續,雖然只改了幾個字,可是徹底是兩道題。博主能想到的就是創建一個相似分段樹的結構,保存每一個連續區間的最大值和最小值,從而解決問題。各位看官大神有什麼好想法請留言討論哈~
Github 同步地址:
https://github.com/grandyang/leetcode/issues/891
參考資料:
https://leetcode.com/problems/sum-of-subsequence-widths/
https://leetcode.com/problems/sum-of-subsequence-widths/discuss/162318/O(nlogn)-solution