53. Maximum Subarray

题意是求最大子序列和
input:[−2,1,−3,4,−1,2,1,−5,4]
ans:6 [4,−1,2,1]
下面给出三种不同时间复杂度的解法。

一. O(N^2)
两个point,一个指向子序列的头,一个指向子序列的尾,两层循环求该子序列的和。

public class Solution {
    public int maxSubArray(int[] nums) {
        int max = nums[0];
        for(int i = 0; i < nums.length; i++) {
            int temp = 0;
            for(int j = i; j < nums.length; j++) {
                temp += nums[j];
                max = temp > max ? temp : max;
            }
        }        
        return max;
    }
}

二. O(N*logN)
步骤如下:
1.取数组的中间元素mid(向下取整),最大子序列无非存在两种情况:
  1.1跨越中间元素
    最大子序列中一定存在mid和mid+1(由于mid向下取整,mid+1<=right),那么就从mid向左计算左最大值,从mid+1向右计算右最大值。这种情况的结果是左最大值加右最大值。
  1.2不跨越中间元素
    将原数组分为两个部分:[left, mid], [mid+1, right],对这两个数组再次使用步骤1。
这种方法是分治法(Divide and Conquer),用递归实现,一般取中间值的算法,复杂度都跟logN有关。

public class Solution {
    public int maxSubArray(int[] nums) {
        return helper(nums, 0, nums.length - 1);
    }

    private int helper(int[] nums, int left, int right) {
        if(left == right) return nums[left];        
        int mid = left + ((right - left) >> 1);
        int leftMax = helper(nums, left, mid);
        int rightMax = helper(nums, mid + 1, right);
        int tempSum = 0;
        int maxL = nums[mid];
        int maxR = nums[mid+1];
        for(int i = mid; i >= left; i--) {
            tempSum += nums[i];
            maxL = Math.max(tempSum, maxL);
        }
        tempSum = 0;
        for(int i = mid + 1; i <= right; i++) {
            tempSum += nums[i];
            maxR = Math.max(tempSum, maxR);
        }
        return Math.max(Math.max(leftMax, rightMax), maxL + maxR);
    }
}

三. O(N)
最大子序列算是动态规划(Dynamic Programming)的一个经典例题。通常我们需要找到一个递推公式。设max(nums, i)是以nums[i]为结尾(该序列中一定包含nums[i],且以nums[i]结尾)的最大子序列和,递推式为:

max(nums, i) = (max(nums, i-1) < 0 ? 0 : max(nums, i-1)) + nums[i];

由于一定要包含nums[i],那么就不必考虑nums[i]是大于0还是小于0。要考虑的是nums[i]前面的序列,如果前面的序列大于0,就要它;如果小于0,就不要它。由于定义了DP数组,空间复杂度为O(N)。

public class Solution {
    public int maxSubArray(int[] nums) {
        int[] DP = new int[nums.length];
        int max = nums[0];
        DP[0] = nums[0];
        for(int i = 1; i < nums.length; i++) {
            DP[i] = (DP[i-1] < 0 ? 0 : DP[i-1]) + nums[i];
            max = Math.max(max, DP[i]);
        }
        return max;
    }
}

一般来说,可以通过优化动态规划中的数组来优化空间复杂度。从for循环中的递推式可以看到:

DP[i] = (DP[i-1] < 0 ? 0 : DP[i-1]) + nums[i];

每次循环只需要上一次的DP值,那么我们就不需要定义DP数组了,空间复杂度可以降低到O(1)。

//空间复杂度O(1)
public class Solution {
    public int maxSubArray(int[] nums) {
        int DP = nums[0];
        int max = nums[0];
        for(int i = 1; i < nums.length; i++) {
            DP = (DP < 0 ? 0 : DP) + nums[i];
            max = Math.max(max, DP);
        }
        return max;
    }
}

四. 注意事项
1.程序中类似于max变量的初始化不能为0,因为序列有可能全是负值,0大于负值,这样最后的结果会是0。
2.对于一种类型的题目,最好能知道不同复杂度的解法,面试也从最简单的解法入手,然后一步一步去优化。

posted @ 2016-10-10 16:31  水煮海鲜  阅读(82)  评论(0编辑  收藏  举报