LeetCode 410 - Split Array Largest Sum

410. Split Array Largest Sum

问题描述

Given an array which consists of non-negative integers and an integer m, you can split the array into m non-empty continuous subarrays. Write an algorithm to minimize the largest sum among these m subarrays.

Note:
Given m satisfies the following constraint: 1 ≤ m ≤ length(nums) ≤ 14,000.

Examples:

Input:
nums = [7,2,5,10,8]
m = 2

Output:
18

Explanation:
There are four ways to split nums into two subarrays.
The best way is to split it into [7,2,5] and [10,8],
where the largest sum among the two subarrays is only 18.

题意大概就是,给定一个序列nums以及一个整数m,要求把序列nums分成m份,并且要让这m个子序列各自的和的最大值最小(minimize the largest sum among these m subarrays)。

一种动态规划的思路

这道题我是在动态规划的分类里面找的,所以一开始也是想用动态规划。

大致思路是考虑一个序列最右边切割的位置。在这里arr[n][m]的意思是对序列arr[1…n]切割m-1次(即划分成m块)的最好结果。

这里有, arr[n][m]=minkmax(arr[k][m1],arr[n][1]arr[k][1]) ,后面这个 arr[n][1]arr[k][1] 就是第k+1个数到第n个数的累加,其实就是第m块的和了。

class Solution {
public:
    int splitArray(vector<int> &nums, int m) {

        //corner cases...


        vector<vector<int>> arr(nums.size(), vector<int>(m + 1, INT_MAX));
        //m == 1
        arr[0][1] = nums[0];
        for (int i = 1; i < nums.size(); i++) {
            arr[i][1] = arr[i - 1][1] + nums[i];
        }


        //m > 1
        for (int m_i = 2; m_i <= m; m_i++) {
            for (int n_j = m_i - 1; n_j < nums.size(); n_j++) {
                arr[n_j][m_i] = INT_MAX;
                for (int k = 0; k < n_j; k++) {
                    arr[n_j][m_i] = min(arr[n_j][m_i], max(arr[k][m_i - 1], arr[n_j][1] - arr[k][1]));
                }
            }
        }
        return arr[nums.size() - 1][m];
    }
};

这个结果应该是对的,我验算过一些测试案例。但很不幸啊,TLE了。我想要不就是我这个DP太挫,还有更好的想法,要不就是这题目分类就是误导人的。

二分法查找

后来我参考了Discuss上的一些帖子,其中这篇我觉得是最浅显易懂的,写得很清楚,目测也是国人。

[C++ / Fast / Very clear explanation / Clean Code] Solution with Greedy Algorithm and Binary Search)

核心思路就是,我们观察到对于一个序列nums分割成m块,无论怎么分割,这个答案(最小值)一定在区间[max(nums), sum(nums)]之中。

比如[1,2,3,4],最好的情况是[[1],[2],[3],[4]] (你可以随意切),最坏的情况是[[1,2,3,4]] (一刀也不能切)。考虑m块,也就是m-1个切割,切割的结果无论好坏总是在上述这个区间里面。

但我们要的是最好的结果。假如可以分成m>=size(nums)块,显而易见,我们就能够取到区间最小值max(nums),但m要是小一些的话就不一定了。比如[1,2,3,4],m=2,我们不能切成两块并使得结果为4,显然这里结果是6。

那我们就只能从区间最小值开始搜索了。比如上面这个例子,我们尝试4,看看能不能找到一种切割,使得每个块的和都不大于4,这做不到吧;于是尝试5,不行;尝试6,可以了,[1,2,3],[4],那么6一定就是最后的结果。

但这样做太慢了,我们可以用二分搜索。假设我们已经构造了一个检测是否存在一个切割使得最大值为k的划分的函数(即下面代码中的doable()),我们可以在区间[max(nums),sum(nums)]中二分查找。因为区间符合的数肯定符合[false, false, …, false, true, … true]这种结构的,我们通过二分查找来找最小的这个true就好了。

因为作者已经提供了一份C++的代码,这里我就改用Python吧。

import math


class Solution(object):
    def doable(self, nums, m, k):
        acc = 0
        for n in nums:
            if acc + n > k:
                if m == 1:
                    return False
                acc = 0
                m -= 1
            acc = acc + n
        return True

    def splitArray(self, nums, m):
        lo = max(nums)
        hi = sum(nums)
        while lo < hi:
            mid = math.floor((lo + hi) / 2);
            if self.doable(nums, m, mid):
                hi = mid - 1
            else:
                lo = mid + 1

        if self.doable(nums, m, lo):
            return int(lo)
        else:
            return int(lo + 1)

你可能感兴趣的:(LeetCode)