力扣 53. 最大子数组和 --- C语言求解

 题目描述:

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

子数组 是数组中的一个连续部分。

题目链接

题解:

此题可以用动态规划和分治法。

方法1. 动态规划

思路:动态规划就是把一个规模比较大的问题分成几个规模比较小的问题,然后由小的问题推导出大的问题。这个问题的小问题可以是--------让以每个元素为结尾找连续子数组的最大和作为子问题。假如输入数组是 [-2,1,-3,4,-1,2,1,-5,4] ,我们可以求出以下子问题:

子问题 1:以 -2 结尾的连续子数组的最大和是多少;
子问题 2:以 1 结尾的连续子数组的最大和是多少;
子问题 3:以 -3 结尾的连续子数组的最大和是多少;
.............

求出所有子问题后再求所有子问题的最大值即为题解。

子问题 1 以 -2 结尾的连续子数组是[-2],最大和即为 -2

子问题 2 以 1 结尾的连续子数组有 [-2,1] 和 [1] 。其中 [-2,1] 就是在「子问题 1」的后面加上 1 得到。但是前一个子问题小于0,即-2 + 1 = -1 <1 ,故舍去前一个子问题。因此「子问题 2」 的答案是 1。

子问题3 以-3结尾的连续子数组有[-3], [1, -3], [-2, 1, -3]. 其中最大和是 1+(-3). 前一个子问题小于0,故子问题3就是子问题2后面加-3.

根据上面分析可以得到下面的结论:

当 前一个子问题的值大于0时,目前的子问题就是前一个子问题加当前位置的元素值;而  前一个子问题的值小于0时,目前子问题的最大和就是当前位置的元素值。

根据上面分析定义状态方程。

令dp[i]表示以num[i]结尾连续子数组的最大和

方程可以定义如下:

dp[i] = max\left \{ dp[i-1]+nums[i], nums[i] \right \}

可以用一个 dp数组来保存 dp[i] 的值,用一个循环求出最大 dp[i]。如下代码。

int maxSubArray(int* nums, int numsSize){
    int dp[numsSize];
    memset(dp, 0, sizeof(int)*numsSize);
    dp[0] = nums[0];
    int max = nums[0];
    for(int i=1;i<numsSize;++i)
    {
        if(dp[i-1]>0){dp[i] = dp[i-1]+nums[i];}
        else{dp[i] = nums[i];}
        
    }
    for(int i=0; i<numsSize; ++i)
    {
        if(dp[i]>max)
            max = dp[i];
    }
    return max;

}

而dp[i]只和dp[i-1]有关,故定义一个变量记录dp[i-1],并使用一个变量来记录最大的 dp[i] 是最佳的方案,可以让复杂度降为O(1).

int maxSubArray(int* nums, int numsSize){
    int max = nums[0];
    int pre = 0;
    for(int i=0; i<numsSize; ++i)
    {
        if(nums[i]>=pre+nums[i])
            pre = nums[i];
        else
            pre += nums[i];

        if(pre>max)
            max=pre;
    }
    return max;

}

方法2. 分治法

该方法使用递归。

对于一个区间 [l,r],我们取m=(l+r)/2 ,对区间 [l,m]和 [m+1,r]分治求解。当递归逐层深入直到区间长度缩小为 1的时候,递归开始回升。这个时候我们考虑如何通过 [l,m] 区间的信息和 [m+1,r] 区间的信息合并成区间 [l,r]的信息。其实我们要求的就是每个子区间的最大值。

由于最大子区间是连续的,故给每个子区间设置左子区间最大值右子区间最大值,使其在合并的时候能够连接起来。当前区间的最大值就是取【左子区间最大值,右子区间最大值,左子区间的右子区间最大值+右子区间的左子区间最大值】三者最大值。

mSum = fmax(fmax(l.mSum, r.mSum), l.rSum+r.lSum);

左子区间最大值是取其【左子区间的左区间最大值,左子区间的和+右子区间的左子区间最大值】两者最大值

lSum = fmax(l.lSum, l.iSum+r.lSum);

右子区间最大值是取其【右子区间的右区间最大值,右子区间的和+左子区间的右子区间最大值】两者最大值

rSum = fmax(r.rSum, l.rSum+r.iSum);

其中的含义比较绕,需要花时间才能想明白............

lSum 表示[l,r] 内以 l 为左端点的最大子段和
rSum 表示[l,r] 内以 r 为右端点的最大子段和
mSum 表示[l,r] 内的最大子段和
iSum 表示[l,r] 的区间和

分治法的代码如下:

typedef struct status
{
    int lSum;
    int rSum;
    int mSum;
    int iSum;
}Status;

Status get(Status l, Status r)
{
    int lSum = fmax(l.lSum, l.iSum+r.lSum);
    int rSum = fmax(r.rSum, l.rSum+r.iSum);
    int mSum = fmax(fmax(l.mSum, r.mSum), l.rSum+r.lSum);
    int iSum = l.iSum + r.iSum;
    return (Status){lSum, rSum, mSum, iSum};
}
Status part(int* a, int l, int r)
{
    if(l==r)
    {
        return (Status){a[l], a[l], a[l], a[l]};
    }
    int mid = (l+r)>>1;

    Status lSub = part(a, l, mid);
    Status rSub = part(a, mid+1, r);
    return get(lSub, rSub);
}
int maxSubArray(int* nums, int numsSize){
    return part(nums, 0, numsSize-1).mSum;

}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值