1. 程式人生 > 實用技巧 >2020.7.25 力扣每日

2020.7.25 力扣每日

 1 class Solution {
 2     public int splitArray(int[] nums, int m) {
 3         int len = nums.length;
 4         int[][] dp = new int[len + 1][m + 1];                  //動態規劃陣列,dp[i][j]代表前i個數分為j個連續子陣列時的解
 5         int[] sub = new int[len + 1];                          //字首和
 6         for (int i = 1; i <= len; i++){
7 sub[i] =sub[i - 1] + nums[i - 1]; 8 } 9 for (int i = 0; i <= len; i++) { 10 Arrays.fill(dp[i], Integer.MAX_VALUE); //初始化,避免初始化0造成異常 11 } 12 dp[0][0] = 0; //邊界處理 13 for (int i = 1; i <= len; i++){ //
i為前n個數 14 for (int j = 1; j <= Math.min(i, m); j++){ //j為分為j個數組 15 for (int k = j - 1; k < i; k++ ){ 16 //dp[i][j]的值等於前k個數分為j-1個組與剩下的字首和sub[i]-sub[k]中的最大值 17 //最後取出其最小值的情況,作為最優解dp[i][j] 18 dp[i][j] = Math.min(dp[i][j], Math.max(dp[k][j - 1], sub[i] - sub[k]));
19 } 20 } 21 } 22 return dp[len][m]; 23 } 24 }

解題思路:

對於此類求“分割為m組,求最優解”的題目,一般都可用動態規劃來解決。首先定義一個動態陣列dp[i][j]來表示前i個數,分成j個數組的解。接著分析題目,試圖寫出動態方程,由於題目要求的子陣列為連續的陣列,也就是說對於dp[i][j]來說,最後一組的第j段陣列必定包括陣列中的最後一位數字nums[i],也就是說剩餘部分數字分為了j-1段。我們可以利用k來表示剩餘部分的數字總個數,由於已經使用了動態規劃,我們必定已經求出了dp[k][j-1]的最優解,其中k為小於i的值,而最後一段第k段的陣列則可以用字首和sub[i]-sub[k]來表示,因此此時的dp[i][j]就為兩者中的最大值

此時我們再來考慮k的取值,由於它是分割為j-1段的剩餘部分,那麼其數字就必須大於等於j-1,而最後一段陣列又必須包括第i位sum[i],也就是說k必須小於i,整理可得出k的取值為[j-1,i)

最後,遍歷所有的k值,取出其中對應的dp[i][j]最小值就是前i個數,分為j個數組時的最優解。

注意點:

對於dp[i][j],我們需取出其所有情況中的最小值來作為解,那麼顯然動態陣列的初值不能設定為0,需設定為Integer.MAX_VALUE

dp陣列的邊界情況,題目要求n,m均大於等於1,也就是i,j初值均為1,所以我們只需設定dp[0][0]=0即可。

時間複雜度:O(N*M^2),N為陣列長度,M為子陣列個數

空間複雜度:O(N*M)

優化:

提交後發現這並非最優解,該題可以使用二分查詢+貪心演算法進行優化,以下程式碼及註釋轉載自LeetCode,作者:Adder。

 1 class Solution {
 2     public int splitArray(int[] nums, int m) {
 3         long left ,right;//計算nums的和的時候,可能超過int表示的最大值
 4         left = right = nums[0];//初始化left和right
 5         for (int i = 1; i < nums.length; i++) {//找到left和right
 6             right+=nums[i];//求和
 7             left = Math.max(left,nums[i]);//求最大值
 8         }
 9         while (left<right){//二分查詢開始
10             long mid = (left + right) >>1;//求中間數
11             if (check(nums,mid,m)){//如果這個最大的和滿足,則把這個最大和變小,然後驗證
12                 right = mid;
13             }else left = mid+1;//如果不滿足,那麼這個最大和需要變大
14         }
15         return (int)left;
16     }
17 
18     private boolean check(int[] nums, long x, int m) {//判斷既定的x每個陣列的最大和,是否符合
19         long sum = 0;//陣列的最大和
20         int cnt = 1;//初始化有幾個陣列,剛開始至少有1個
21         for (int i = 0; i < nums.length; i++) {
22             if (sum + nums[i]>x){//子陣列的和比給定的最大的還大
23                 cnt++;//需要開始新的子陣列,個數+1
24                 sum = nums[i];//初始化子陣列和
25             }else {//不超過子陣列和
26                 sum += nums[i];
27             }
28         }
29         return cnt <= m;//如果不超過給定個數,那麼就是符合的,否則不符合
30     }
31 }

題後碎碎念:害,果然困難的題沒那麼簡單,“演算法仍不熟練,程式碼還得多敲”