分治法,动态规划法—最大子数组问题的求解

问题描述:所谓的最大子数组,就是原数组中连续的数组元素之和最大的数组元素的集合。比如购买股票。如果把相邻两天的股价之差作为数组元素,那么求在连续的某个时间段内买入股票的最佳时间和卖出股票的最佳时间就可以抽象为计算最大子数组的问题。

举个例子
某公司股票的价值如下
第一天价值为10元,第二天11元,第三天7元,第四天10,第五天6元
10 11 7 10 6
最大收益的应该是在第3天买入,第五天卖出
这个问题可以简化为
第二天-第一天=1,第三天-第二天= -4,第四天-第三天=3,第五天-第四天= -4
1 -4 3 -4
最大的收益应该是3,为这个数组中的最大子数组。

1. 分治策略

假定我们要找子数组a[low,,,high]的最大子数组,使用分治法技术意味着我们要将子数组划分为两个规模尽量一样的子数组,也就是子数组的中间位置mid,然后考虑求解两个子数组a[low,,,mid],a[mid+1,,,high]。
a[low,,,high]的任何连续子数组a[i,,,j]所处的位置必然是以下三种情况之一

  • 完全位于子数组a[low,mid]中
  • 完全位于子数组a[mid+1,high]中
  • 跨越了中点。
#define min -24551215
#include<stdio.h>
struct tuple{
 int low;
 int high;
 int maxsum;
};
//定义一个结构体存放最大子数组在原数组的左下标右下标以及最大和
tuple findmaxcrossingsubarray(int a[100],int low,int mid,int high)
{
 int leftsum=min;
 int maxleft;
 int rightsum=min;
 int maxright;
 int sum=0;
 for(int i=mid;i>=low;i--)
 {
  sum=sum+a[i];
  if(sum>leftsum)
  {
   leftsum=sum;
   maxleft=i;
  }
 }
 sum=0;
 for(int i=mid+1;i<=high;i++)
 {
  sum=sum+a[i];
  if(sum>rightsum)
  {
   rightsum=sum;
   maxright=i;
  }
 }
 tuple temp;
 temp.low=maxleft;
 temp.high=maxright;
 temp.maxsum=leftsum+rightsum;
 return temp;
} 
tuple findmaximumsubarray(int a[100],int low,int high)
{
 if(high==low)
 {
 tuple tuple0;
 tuple0.low=low;
 tuple0.high=high;
 tuple0.maxsum=a[low];
 return tuple0;
 }
 else
 {
  int mid=(low+high)/2;
  tuple tuple1=findmaximumsubarray(a,low,mid);
  tuple tuple2=findmaximumsubarray(a,mid+1,high);
  tuple tuple3=findmaxcrossingsubarray(a,low,mid,high);
  if((tuple1.maxsum>=tuple2.maxsum)&&(tuple1.maxsum>=tuple3.maxsum))
  return tuple1;
  else if((tuple2.maxsum>=tuple1.maxsum)&&(tuple2.maxsum>=tuple3.maxsum))
  return tuple2;
  else
  return tuple3;
 }
}
int main()
{
  tuple result;
  int x[18]={0,100,113,110,85,105,102,86,63,81,101,94,106,101,79,94,90,97};
 int y[17]={0,13,-3,-25,20,-3,-16,-23,18,20,-7,12,-5,-22,15,-4,7};
 result=findmaximumsubarray(y,1,16);
 printf("%d %d %d ",result.low,result.high,result.maxsum);
 return 0;
}

可以参考下面的分治法解最大子数组问题

2.动态规划法

解析

  • 状态定义:设置动态规划列表dp,dp【i】代表以元素nums【i】为结尾的连续子数组最大和

  • 状态转移方程,如果dp【i-1】<=0,则dp【i-1】对dp【i】产生负影响,就是说dp【i-1】+nums【i】比nums【i】本身要小

    • 当dp【i-1】大于零时,执行dp【i】=dp【i-1】+nums【i】;
    • 当dp【i-1】小于等于零,执行dp【i】=nums【i】;
  • 初始状态dp【0】=nums【0】,以nums【0】结尾的连续子数组最大和就是nums【0】

  • 返回dp动态列表中的最大值。

在这里插入图片描述
空间复杂度降低:
由于 dp[i] 只与 dp[i−1] 和 nums[i]有关系,因此可以将原数组 nums 用作 dp 列表,即直接在 nums 上修改即可。
由于省去 dp 列表使用的额外空间,因此空间复杂度从 O(N) 降至 O(1) 。

java代码实现:

class Solution {
    public int maxSubArray(int[] nums) {
        int res = nums[0];
        for(int i = 1; i < nums.length; i++) {
            nums[i] += Math.max(nums[i - 1], 0);
            res = Math.max(res, nums[i]);
        }
        return res;
    }
}
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
回溯是一种常用于解决组合问题算法,通过不断尝试寻找满足特定条件的解,并记录下已经尝试过的解,从而找到最优解。在求解最大数组和的问题中,可以使用回溯来找到所有可能的数组,然后从中找出和最大数组。 具体实现时,可以使用递归的方式,从数组的第一个元素开始,判断当前元素是否加入数组中。如果加入,则递归考虑下一个元素;如果不加入,则直接递归考虑下一个元素。需要注意的是,每次递归需要记录当前数组的和,以及当前最大数组和。 下面是一个使用回溯求解最大数组和的示例代码: ```c #include <stdio.h> int max_sum = 0; // 记录最大数组和 void backtrack(int array[], int start, int sum, int size) { if (start == size) { // 递归终止条件,到达数组末尾 if (sum > max_sum) { max_sum = sum; // 更新最大数组和 } return; } // 加入当前元素 backtrack(array, start + 1, sum + array[start], size); // 不加入当前元素 backtrack(array, start + 1, sum, size); } int main() { int array[] = {1, -2, 3, 10, -4, 7, 2, -5}; int size = sizeof(array) / sizeof(array[0]); backtrack(array, 0, 0, size); printf("最大数组和为:%d\n", max_sum); return 0; } ``` 以上代码中,通过回溯遍历了数组中的所有数组,并更新最大数组和的值,最终输出结果为最大数组和为18。 需要注意的是,该算法的时间复杂度为O(2^n),因为遍历了所有可能的数组。在实际应用中,可以使用动态规划分治法等更优化的算法来解决最大数组和的问题
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Devin Dever

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值