LeetCode之旅(C):53.最大子序和

PS:不明之处,请君留言,以期共同进步!

题目描述

给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

示例:

输入: [-2,1,-3,4,-1,2,1,-5,4],
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。

进阶:

如果你已经实现复杂度为 O(n) 的解法,尝试使用更为精妙的分治法求解。

PS:刚好就在前两天学弟问了我一道题目,题意相同,贴图如下:
在这里插入图片描述

1. 思路一(枚举法)

枚举所有可能的结果,并两两比较,得出最大值。
时间复杂度O(n^2),空间复杂度O(1)。

int maxSubArray(int* nums, int numsSize)
{
    int curSum;
    int maxSum = nums[0];
    
    for(int i = 0; i < numsSize; ++i)
    {
        curSum = nums[i];
        if(curSum > maxSum)
            maxSum = curSum;
        
        for(int j = i + 1; j < numsSize; ++j)
        {
            curSum += nums[j];
            if(curSum > maxSum)
                maxSum = curSum;
        }
    }
    
    return maxSum;
}

2. 思路二(动态规划)

我们先看两个引理:

引理1:
以负数开头的子序列不会是最大的子序列。
证明:令子序列为 {ai, …,aj},其中开头的元素 ai < 0,则 ai + … + aj < ai+1 + … + aj 显然成立。

引理2:
对子序列 {ai, …, aj},如果该子序列满足以下两个条件:
① 对 x 取 [i, j) 中的任意整数(包含 i,不包含 j),sum{ai, …, ax} > 0;
② sum{ai, …, aj} < 0,
则以该子序列中的任何元素 ap 开头的以 aj 为终结的任意子序列的和必定小于0,即对 p 取 [i, j) 中的任意整数(包含 i,不包含 j),sum{ap, …, aj} < 0。
证明(反证法):假设 sum{ap, …, aj} > 0,p 取 [i, j) 之间的整数,由引理2条件② sum{ai, …, aj} < 0 得出 sum{ai, …, ap-1} < 0,该结论违反了引理2中的条件① 对 x 取 [i, j) 中的任意整数(包含 i,不包含 j),sum{ai, …, ax} > 0。得证。

结论:
由引理1可知,若 a[i] < 0, 则应跳到 a[i + 1] 作为子序列的开头元素(如果 a[i + 1] > 0)。
由引理2可知,若 a[i] + … + a[j] < 0 且满足引理2的条件①,则应以 a[j + 1] 作为最大连续子序列的开头元素(如果 a[j + 1] > 0)。
根据以上结论写代码即可求解出最大连续子序列。

我们写出如下算法:
时间复杂度O(n),空间复杂度O(1)。

int maxSubArray(int* nums, int numsSize)
{
    int curSum = nums[0];
    int maxSum = nums[0];
    
    for(int i = 1; i < numsSize; ++i)
    {
        if(curSum < 0)
            curSum = 0;
        
        curSum += nums[i];
        if(curSum > maxSum)
            maxSum = curSum;
    }
    
    return maxSum;
}

3. 思路三(分治法)

通过递归分治不断的缩小规模,问题结果就有三种,左边的解,右边的解,以及中间的解(有位置要求,从中介mid向两边延伸寻求最优解),得到三个解通过比较大小,得到最优解。
因为使用了二分,所以时间复杂度为O(nlogn);虽然使用了递归,但是在调用函数maxSubArrayPart和maxSubArrayAll时传递的是C语言指针nums,所以空间复杂度依然是O(1)。

Tip:

这里我还发现了一个问题,使用 #define 宏定义会使执行时间增加很多,应该是宏展开占用了时间。
我们看看下面截图中的时间和空间大小,就可以发现了。
在这里插入图片描述

3.1. 在maxSubArrayPart中使用if-else语句求maxSum
//左右两边合起来求解
int maxSubArrayAll(int* nums, int left, int mid, int right)
{
    int leftCurSum = 0;
    int leftMaxSum = nums[mid];
    for(int i = mid; i >= left; i--)
    {
        leftCurSum += nums[i];
        if(leftCurSum > leftMaxSum)
            leftMaxSum = leftCurSum;
    }
        
    int rightCurSum = 0;
    int rightMaxSum = nums[mid + 1];
    for(int i = mid + 1; i <= right; i++)
    {
        rightCurSum += nums[i];
        if(rightCurSum > rightMaxSum)
            rightMaxSum = rightCurSum;
    }
        
    return leftMaxSum + rightMaxSum;
}

//递归
int maxSubArrayPart(int* nums, int left, int right)
{
    if(left == right)
        return nums[left];
    
    int mid = (left + right) / 2;
    int maxSum;
    
    if(maxSubArrayPart(nums, left, mid) > maxSubArrayPart(nums, mid + 1, right))
       maxSum = maxSubArrayPart(nums, left, mid);
    else
       maxSum = maxSubArrayPart(nums, mid + 1, right);
    
    if(maxSum < maxSubArrayAll(nums, left, mid, right))
        maxSum = maxSubArrayAll(nums, left, mid, right);       
    
    return maxSum;
}

int maxSubArray(int* nums, int numsSize)
{
    return maxSubArrayPart(nums, 0, numsSize - 1);
}
3.2. 在maxSubArrayPart中使用#define宏定义求maxSum
//宏定义,求两数中较大的数
#define max(a,b) (a>b?a:b)

//左右两边合起来求解
int maxSubArrayAll(int* nums, int left, int mid, int right)
{
    int leftCurSum = 0;
    int leftMaxSum = nums[mid];
    for(int i = mid; i >= left; i--)
    {
        leftCurSum += nums[i];
        if(leftCurSum > leftMaxSum)
            leftMaxSum = leftCurSum;
    }
        
    int rightCurSum = 0;
    int rightMaxSum = nums[mid + 1];
    for(int i = mid + 1; i <= right; i++)
    {
        rightCurSum += nums[i];
        if(rightCurSum > rightMaxSum)
            rightMaxSum = rightCurSum;
    }
        
    return leftMaxSum + rightMaxSum;
}

//递归
int maxSubArrayPart(int* nums, int left, int right)
{
    if(left == right)
        return nums[left];
    
    int mid = (left + right) / 2;
    
    return max(maxSubArrayPart(nums, left, mid), 
                    max(maxSubArrayPart(nums, mid + 1, right), 
                    maxSubArrayAll(nums, left, mid, right)));
}

int maxSubArray(int* nums, int numsSize)
{
    return maxSubArrayPart(nums, 0, numsSize - 1);
}
3.3. 在maxSubArrayPart中使用三目运算符求maxSum
//左右两边合起来求解
int maxSubArrayAll(int* nums, int left, int mid, int right)
{
    int leftCurSum = 0;
    int leftMaxSum = nums[mid];
    for(int i = mid; i >= left; i--)
    {
        leftCurSum += nums[i];
        if(leftCurSum > leftMaxSum)
            leftMaxSum = leftCurSum;
    }
        
    int rightCurSum = 0;
    int rightMaxSum = nums[mid + 1];
    for(int i = mid + 1; i <= right; i++)
    {
        rightCurSum += nums[i];
        if(rightCurSum > rightMaxSum)
            rightMaxSum = rightCurSum;
    }
        
    return leftMaxSum + rightMaxSum;
}

//递归
int maxSubArrayPart(int* nums, int left, int right)
{
    if(left == right)
        return nums[left];
    
    int mid = (left + right) / 2;
    int maxSum;
    
    maxSum = maxSubArrayPart(nums, left, mid) > maxSubArrayPart(nums, mid + 1, right) ? 
        maxSubArrayPart(nums, left, mid) : maxSubArrayPart(nums, mid + 1, right);
    maxSum = maxSum > maxSubArrayAll(nums, left, mid, right) ? 
        maxSum : maxSubArrayAll(nums, left, mid, right);
    
    return maxSum;
}

int maxSubArray(int* nums, int numsSize)
{
    return maxSubArrayPart(nums, 0, numsSize - 1);
}

我们使用下面示例,对以上算法进行展开:
输入: [-2,1,-3,4,-1,2,1,-5,4],
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
在这里插入图片描述
到目前为止,我最满意的是思路二。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值