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?html
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
.10^-6
of the correct answer will be accepted as correct.
這道題給了咱們一個數組,說是讓咱們將數組分紅至多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的本質是什麼,不就是狀態轉移方程,根據前面的狀態來更新當前的狀態。而子數組不就是整個數組的前一個狀態,不停的更新的使得咱們最終能獲得極值。spa
好,下面進入正題。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],因此咱們能夠得出狀態轉移方程以下:code
dp[i][k] = max(avg(i, n) + max_{j > i} (avg(i, j) + dp[j][k-1]))orm
這裏的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)中的每一個位置了,好像也沒什麼不妥的地方,有興趣的童鞋能夠嘗試的寫一下~htm
解法一:blog
class Solution { public: 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有關,因此咱們不須要將全部的狀態都保存起來,只須要保存前一個狀態的值就好了,那麼咱們就用一個一維數組就能夠了,不斷的進行覆蓋,從而達到了節省空間的目的,參見代碼以下:遞歸
解法二:ip
class Solution { public: 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]; } };
咱們也能夠是用遞歸加記憶數組的方式來實現,記憶數組的運做原理和DP十分相似,也是一種cache,將已經計算過的結果保存起來,用的時候直接取便可,避免了大量的重複計算,參見代碼以下:
解法三:
class Solution { public: double largestSumOfAverages(vector<int>& A, int K) { int n = A.size(); vector<vector<double>> memo(101, vector<double>(101)); double cur = 0; for (int i = 0; i < n; ++i) { cur += A[i]; memo[i + 1][1] = cur / (i + 1); } return helper(A, K, n, memo); } double helper(vector<int>& A, int k, int j, vector<vector<double>>& memo) { if (memo[j][k] > 0) return memo[j][k]; double cur = 0; for (int i = j - 1; i > 0; --i) { cur += A[i]; memo[j][k] = max(memo[j][k], helper(A, k - 1, i, memo) + cur / (j - i)); } return memo[j][k]; } };
參考資料:
https://leetcode.com/problems/largest-sum-of-averages/description/
https://leetcode.com/problems/largest-sum-of-averages/solution/