Leetcode Algorithm #53 Maximum Subarray

Leetcode Algorithm #53 Maximum Subarray

题目内容

Given an integer array nums, find the contiguous subarray (containing at least one number) which has the largest sum and return its sum.

Example:

Input: [-2,1,-3,4,-1,2,1,-5,4],
Output: 6
Explanation: [4,-1,2,1] has the largest sum = 6.

Follow up:

If you have figured out the O(n) solution, try coding another solution using the divide and conquer approach, which is more subtle.

解题

很惭愧这样的问题都只想出了一个 O(nlogn) O ( n l o g n ) 的解决方法,感觉脑子好久没有使用了。一开始的确也是首先考虑 O(n) O ( n ) 的方法,利用标志位啥的,但想来想去都发现是遗漏了情况。于是使用了分治递归的方法,可以说是比较偷懒,因为分治的方法比较容易想清楚,虽然在有些情况下分治的是比较快的,但是在这题并不是。

分治方法可以这样考虑,将数组分为两边,最大的区间只会出现在三种情况:

  • 完全在左边
  • 完全在右边
  • 横跨两个区域

对于第三种情况,需要 O(n) O ( n ) 的计算时间,因为要横跨的话这个区域必定是紧挨着边界,所以只要从边界出发,分别求两侧的最大子串再进行加和即可。

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        return divide(0, nums.size() - 1, nums);
    }
private:
    int divide(int head, int rear, vector<int>& nums) {
        if (head == rear)
            return nums[head];
        int mid = (head + rear) / 2;
        int r1 = divide(head, mid, nums);
        int r2 = divide(mid + 1, rear, nums);
        int temp_sum = nums[mid], max1 = nums[mid], max2 = nums[mid+1];
        for (int i = mid - 1; i >= 0; --i)
        {
            temp_sum += nums[i];
            if (temp_sum > max1)
                max1 = temp_sum;
        }
        temp_sum = max2;
        for (int i = mid + 2; i <= rear; ++i)
        {
            temp_sum += nums[i];
            if (temp_sum > max2)
                max2 = temp_sum;
        }
        return max(r1, r2, max1 + max2);
    }

    int max(int a, int b, int c) {
        if (a > b)
        {
            if (a > c)
                return a;
            else
                return c;
        }
        else
        {
            if (b > c)
                return b;
            else
                return c;
        }
    }
};

当然结果就是超级慢,都超过图表中最慢的边界了。

改进

之前考虑不清楚,主要就是总是把当前最大和结尾看作两个部分,考虑如何选择这两个部分,但有可能这个区间出现在两者的中央。实际并不需要这么做。所要考虑的仅仅是一个连续和为正数区间即可,参考GeeksforGeeks。首先有一个ending标记和max_found标记,分别代表在当前元素之前的最长连续元素使得和为正数的值,以及在当前元素之前最大的子串和。若当前位置为i,将ending位置元素加上nums[i]成为新ending,可以考虑如果ending为负,则说明前方元素加该元素没必要和后面的元素进行组合了,而前方最大的子串已经被记录在max_found中,所以舍弃这个值并没有问题,将ending值重新置为0。显而易见,max_found只要每次都和ending做比较,记录其最大值即可。

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int size = nums.size(), found = std::numeric_limits<int>::min(), ending = 0;
        for (int i = 0; i < size; ++i)
        {
            ending += nums[i];
            if (ending > found)
                found = ending;
            if (ending < 0)
                ending = 0;
        }
        return found;
    }
};
点赞