1. 程式人生 > 實用技巧 >leetcode 410 分割陣列的最大值

leetcode 410 分割陣列的最大值

package com.example.lettcode.dailyexercises;

import java.util.Arrays;

/**
 * @Class SplitArray
 * @Description 410 分割陣列的最大值
 * 給定一個非負整數陣列和一個整數 m,你需要將這個陣列分成 m 個非空的連續子陣列。
 * 設計一個演算法使得這 m 個子陣列各自和的最大值最小。
 * 注意:
 * 陣列長度 n 滿足以下條件:
 * 1 ≤ n ≤ 1000
 * 1 ≤ m ≤ min(50, n)
 * 示例:
 * 輸入:
 * nums = [7,2,5,10,8]
 * m = 2
 * 輸出:
 * 18
 * 解釋:
 * 一共有四種方法將nums分割為2個子陣列。
 * 其中最好的方式是將其分為[7,2,5] 和 [10,8],
 * 因為此時這兩個子陣列各自的和的最大值為18,在所有情況中最小。
 * @Author
 * @Date 2020/7/25
 **/
public class SplitArray {
}
/**
 * 方法1:利用動態規劃:
 * 狀態表示 dp[i][j] 表示nums[0..i]劃分成j段時的最大值
 * 狀態轉移:dp[i][j] 為0~i的前k個位置被分成了j-1段,然後最後一個部分的值是sub[i]-sub[j]
 * 其中sub[i] 是前i個nums元素的值
 * 初始條件:dp[0][0]=0
 */
public static int splitArray(int[] nums, int m) {
	if (nums == null || nums.length < m) return -1;
	int n = nums.length;
	// dp[i][j] 表示nums[0..i]劃分成j段時的最小情況
	int[][] dp = new int[n + 1][m + 1];
	for (int i = 0; i <= n; i++) {
		Arrays.fill(dp[i], Integer.MAX_VALUE);
	}

	// sub[i]表示num[0..i]的和
	int[] sub = new int[n + 1];
	for (int i = 0; i < n; i++) {
		sub[i + 1] = sub[i] + nums[i];
	}
	// 初始條件
	dp[0][0] = 0;
	for (int i = 1; i <= n; i++) {
		// 由於我們不能分出空的子陣列,所以必須有i>=j
		for (int j = 1; j <= Math.min(i, m); j++) {
			// nums的前k-1個數被分為j-1段,然後nums(k~i)為第j段
			// k為啥可以從0開始還沒理解 2020/07/25
			for (int k = 0; k < i; k++) {
				// 狀態轉移方程
				dp[i][j] = Math.min(dp[i][j], Math.max(dp[k][j - 1], sub[i] - sub[k]));
			}
		}
	}
	return dp[n][m];
}
/**
 * 解法2:利用二分查詢+貪心
 * 這是哪位大神想出來的方法
 */
public static int splitArray(int[] nums, int m) {
	int left = 0, right = 0;
	for (int i = 0; i < nums.length; i++) {
		right += nums[i];
		if (left < nums[i]) {
			left = nums[i];
		}
	}
	while (left < right) {
		int mid = (right - left) / 2 + left;
		if (check(nums, mid, m)) {
			right = mid;
		} else {
			left = mid + 1;
		}
	}
	return left;
}

// 這個意思是將nums進行劃分時,保證每個連續子陣列之和不超過x ,採用這種方式劃分對應的子陣列個數不是否超過m
// 劃分時,從左到右逐個元素相加,與當前元素之和超過x時,說明需要新劃分一個子陣列,以此類推,判斷最終
// 可以劃分的組數
public static boolean check(int[] nums, int x, int m) {
	int sum = 0;
	int cnt = 1;
	for (int i = 0; i < nums.length; i++) {
		if (sum + nums[i] > x) {
			cnt++;
			sum = nums[i];
		} else {
			sum += nums[i];
		}
	}
	return cnt <= m;
}
// 測試用例
public static void main(String[] args) {
	int[] nums = new int[]{7, 2, 5, 10, 8};
	int m = 2;
	int ans = splitArray(nums, m);
	System.out.println("SplitArray demo01 result:" + ans);
}