lintcode(6)——最大子数组

41. 最大子数组

给定一个整数数组,找到一个具有最大和的子数组,返回其最大和。

样例1:
给出数组[−2,2,−3,4,−1,2,1,−5,3],符合要求的子数组为[4,−1,2,1],其最大和为6
样例2:
给出数组[1,2,3,4],符合要求的子数组为[1,2,3,4],其最大和为10

挑战

要求时间复杂度为O(n)

注意事项

子数组最少包含一个数

方法一: 

class Solution {
public:
    int maxSubArray(vector<int> &nums) {
        // write your code here
        int size = nums.size();
        int max = nums[0];
        int nowM = 0;
        for (int i = 0; i < size; i++) {
            nowM += nums[i];
            if (nowM > max) {
                max = nowM;
            }
            if (nowM < 0) {
                nowM = 0;
            }
        }
        return max;
    }
};

 方法二:

class Solution {
public:    
    int maxSubArray(vector<int> nums) {
        //sum记录前i个数的和,maxSum记录全局最大值,minSum记录前i个数中0-k的最小值
        int sum = 0, minSum = 0, maxSum = INT_MIN;
        for (int i = 0; i < nums.size(); i++) {
            sum += nums[i];
            maxSum = max(maxSum, sum - minSum);
            minSum = min(minSum, sum);
        }
        return maxSum;
    }
};

42. 最大子数组 II

给定一个整数数组,找出两个 不重叠 子数组使得它们的和最大。每个子数组的数字在数组中的位置应该是连续的。返回最大的和。

例1:

输入:
[1, 3, -1, 2, -1, 2]
输出:
7
解释:
最大的子数组为 [1, 3] 和 [2, -1, 2] 或者 [1, 3, -1, 2] 和 [2].

例2:

输入:
[5,4]
输出:
9
解释:
最大的子数组为 [5] 和 [4].

挑战

要求时间复杂度为 O(n)

注意事项

子数组最少包含一个数

思路:从左自右、从右自左分别遍历数组。每次遍历均记录当前最大的单子数组,用2个数组left,right保存。如left[i]的值表示nums从0至i中最大子数组的值,right[i]的值表示nums从i至size-1中最大子数组的值。最后遍历left,right数组,left[i]+right[i+1]表示在第i位拆分数组,得到其子数组的和。

class Solution {
public:
    int maxTwoSubArrays(vector<int> &nums) {
        // write your code here
        int size = nums.size(), i = 0, sum = 0, maxValue = nums[0];
        int *left = new int[size];
        left[0] = nums[0];
        for(i=0; i<size; i++) 
        {
            sum += nums[i];
            if(sum > maxValue) 
            {
                maxValue = sum;
            }
            if(sum < 0) 
            {
                sum =0;
            }
            left[i] = maxValue;
        }
        int *right = new int[size];
        sum = 0;
        maxValue = right[size-1] = nums[size-1];
        for(i=size-1; i>=0; i--) 
        {
            sum += nums[i];
            if(sum > maxValue) {
                maxValue = sum;
            }
            if(sum < 0) {
                sum = 0;
            }
            right[i] = maxValue;
        }

        int result = 0x80000000;
        for(i=0; i<size-1; i++) {
            result = (result > left[i]+right[i+1])?result:(left[i]+right[i+1]); //left[i]+right[i+1]表示在第i位拆分数组,得到其子数组的和
        }

        delete[] left;
        delete[] right;
        return result;
    }
};

 我们来看看0x80000000的输出

0x80000000 的二进制位

原码 1000 0000 0000 0000 0000 0000 0000 0000

若最高位为符号位,则为-0,可是输出int i = 0x80000000 发现i= -(2^31)

原因是在十六进制中负数的二进制原码的最高位是符号位,后面的31位为序号位,不是值位。1后面的000 0000 0000 0000 0000 0000 0000 0000,表示序号1,表示负数中,从小到大的第一位。

由于int的最小值为-2^31,排在负数从小到大的序号1,所以int i = 0x80000000 输出为 -(2^31)
 

43. 最大子数组 III

给定一个整数数组和一个整数 k,找出 k 个不重叠子数组使得它们的和最大。每个子数组的数字在数组中的位置应该是连续的。返回最大的和。

样例1

输入: 
List = [1,2,3]
k = 1
输出: 6
说明: 1 + 2 + 3 = 6

样例2

输入:
List = [-1,4,-2,3,-2,3]
k = 2
输出: 8
说明: 4 + (3 + -2 + 3) = 8

注意事项

子数组最少包含一个数

思路:

 local[i][j]的值表示前j个数字分为i个子数组的最大和,其中确定第j个数包含在第i个子数组中
 global[i][j]的值表示前j个数字分为i个子数组的最大和,其中第j个数未必包含在第i个子数组中

当第j个数到来时,local[i][j]的可能情况为:
1.第j个数单独成为一个子数组,即第i个子数组,意味着之前的j-1个数已经组成了i-1个子数组,且第j-1个数未必是包含在第i-1个子数组中的(或者说我们并不关心它有没有包含)。因此这种情况的表达式为global[i-1][j-1]+nums[j-1]
2.第j个数并入之前的j-1个数中,至少与第j-1个数(或更多前面的数)组成i个子数组,这里的情况便是第j-1个数被包含在第i个子数组内,然后与最新到来的第j个数共同组成第i个子数组。因此这种情况的表达式为local[i][j-1]+nums[j-1]

local[i][j]=max(global[i-1][j-1],local[i][j-1])+nums[j-1];

然后考虑global[i][j]的可能情况:
1.若第j个数到来之后确定被包含在第i个子数组中,即成为了local[i][j]
2.若第j个数到来后可能未被包含在第i个子数组中,此时第j个数对全局最优不一定贡献,因此还是沿用第j-1个数时的全局最优值,通过比较大小来判断该数有没有贡献

global[i][j]=max(local[i][j],global[i][j-1]);

最后考虑如何填表。该表的行代表第i个子数组,列代表第j个数。很显然i<=j,即只需要填右上半部分表。
由于local的计算会用到global的左上角以及自身的左侧,global的计算会用到自身的左侧以及local的当前位置。因此迭代过程中先填local再填global。
应该只需要保证global[0][0]处为0即可进行填表。但由于数可能为负数,比较大小时不能直接跟0比,因此global和local左侧边界应该为负的极小值。

class Solution {
public:
    int maxSubArray(vector<int> &nums, int k) {
        // write your code here
         int n = nums.size();
	    vector<vector<int> > local(k + 1, vector<int>(n + 1));
	    vector<vector<int> > global(k + 1, vector<int>(n + 1));
	    for (int i = 1; i <= k; i++) 
	    {
    		local[i][i] = local[i - 1][i - 1] + nums[i - 1];
    		global[i][i] = local[i][i];
    		for (int j = i + 1; j <= n; j++) 
    		{
    		    local[i][j]=max(global[i-1][j-1],local[i][j-1])+nums[j-1];
    		    global[i][j]=max(local[i][j],global[i][j-1]);
    		}
    	}
    	int result = INT_MIN;
    	for (int i = k; i <= n; i++) 
    	{
    		result = max(result, local[k][i]);
    	}
    	return result;
    }
};

45. 最大子数组差

给定一个整数数组,找出两个不重叠的子数组A和B,使两个子数组和的差的绝对值|SUM(A) - SUM(B)|最大。

返回这个最大的差值。

例1:

输入:[1, 2, -3, 1]
输出:6
解释:
子数组是 [1,2] 和[-3].所以答案是 6.

例2:

输入:[0,-1]
输出:1
解释:
子数组是 [0] 和 [-1].所以答案是 1.

挑战

时间复杂度为O(n),空间复杂度为O(n)

注意事项

子数组最少包含一个数

 思路:维护四个数组,当前位置左边的最大子数组和,最小子数组和。当前位置右边的最大子数组和,最小子数组和。
然后枚举分割线,扫描一下即可。

class Solution {
public:
    /**
     * @param nums: A list of integers
     * @return: An integer indicate the value of maximum difference between two substrings
     */
    int maxDiffSubArrays(vector<int> &nums) {
        // write your code here
        int size = nums.size(), i = 0, sum = 0, maxValue = nums[0],minValue=nums[0];
        int *left_max = new int[size];
        for(i=0; i<size; i++) 
        {
            sum += nums[i];
            if(sum > maxValue) 
            {
                maxValue = sum;
            }
            if(sum < 0) 
            {
                sum =0;
            }
            left_max[i] = maxValue;
        }
        int *right_max = new int[size];
        sum = 0;
        maxValue= nums[size-1];
        for(i=size-1; i>=0; i--) 
        {
            sum += nums[i];
            if(sum > maxValue) {
                maxValue = sum;
            }
            if(sum < 0) {
                sum = 0;
            }
            right_max[i] = maxValue;
        }
        int *left_min=new int[size];
        sum=0;
        for(i=0; i<size; i++) 
        {
            sum += nums[i];
            if(sum < minValue) 
            {
                minValue = sum;
            }
            if(sum > 0) 
            {
                sum =0;
            }
            left_min[i] = minValue;
        }
        int *right_min=new int[size];
        sum=0;
        minValue=nums[size-1];
        for(i=size-1;i>=0;i--)
        {
            sum += nums[i];
            if(sum < minValue) 
            {
                minValue = sum;
            }
            if(sum > 0) 
            {
                sum =0;
            }
            right_min[i]=minValue;
        }
        int result = 0;
        for(i=0; i<size-1; i++) {
            result = max(result,abs(left_max[i]-right_min[i+1]));
            result=max(result,abs(left_min[i]-right_max[i+1]));
        }

        delete[] left_max;
        delete[] left_min;
        delete[] right_max;
        delete[] right_min;
        return result;
    }
};

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值