博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
53. Maximum Subarray
阅读量:4221 次
发布时间:2019-05-26

本文共 3569 字,大约阅读时间需要 11 分钟。

动态规划

题目描述:
给出一个整数数组,求得一个非空的子数组(连续一段数),使得它的和最大。

1.解题思路一:分治

将数组分成等长的两部分,最后的答案要么在左边存在,要么再右边存在,要么是跨越中心点的某个连续数组,只有这三种情况了

先分:两个基本等长的子数组,分别求解T(n/2)

后合:跨越中心点的最大子数组合,全部枚举出来 O(n)

时间复杂度是:O(n*log n)

//时间复杂度:O(nlogn) int maxSubArray(vector
& nums) { int n = nums.size(); if(n == 1){ return nums[0]; } int mid = n>>1; //除以二 //左边是0~mid-1,右边是mid~n-1 vector
left(mid); vector
right(n-mid);//构造两个左右vector数组 for(int i = 0;i < mid;i++) { left[i] = nums[i]; } for(int i = 0,j = mid;i < right.size();i++,j++) { right[i] = nums[j]; } int answer = max(maxSubArray(left),maxSubArray(right)); //这个answer是左右两部分里面的某一个 //接下来就要假设是不是跨越中心点的子数组,注意这个是假设的,不是最终答案,最终答案是与上面的answer对比,求出三部分中最大的就是真正的答案 //注意下面这个是每次分半都要经过的一步 int now = nums[mid-1];//假设从mid-1开始走,往0(前面)走 int may = now; for(int i = mid-2;i >= 0;--i){ may = max(may,now+=nums[i]); //往左走的最大值,我们将其放在may中 } now = may;//往左走完了,这个值确定了,may for(int i = mid;i < n;++i){ may = max(may,now += nums[i]); //往右走的最大值,我们将其更新到may中 } return max(answer,may); }

总结一下上面代码大致的意思:

我们得到一个数组,对其进行分半,left and right,每次分半都分别对其left 和 right求一个answer = max(maxSubArray(left),maxSubArray(right)),当然这个max开始是求不到的,因为我们没有返回值,于是乎,继续对这个left和right进行分半,直到啥时候呢?到left 和 right 他们都只剩一个一个元素,这时候只能返回这一个元素了,哈哈,终于有返回了,立马比较得出answer,现在就是开始合的过程了
从最下面开始,返回了一个answer之后,执行下面的代码,now = nums[mid-1],显而易见,这个mid从最底端的0开始,左边走完确定一个may=nums[0],再往右边走,确定一个跨越中心点的最大值(这个跨越中心点在左两端),may = max( nums[0] , nums[0]+nums[1] )值,再与两端的max比较,即与nums[0] 和 nums[1]中较大的值比较,之后按照这个顺序继续往上合,每个max值都比较过,就得出了真正最大的子数组的最大值
下面是我写的一个大致的流程:
先从上倒下分开,再从下开始比较往上走,这是给出的测试用例
先从上倒下分开,再从下开始比较往上走,这是给出的测试用例

2.解题思路二:动态规划

我们假设以dp[i]表示以a[i]结尾的最大子数组的和
dp[i] = max(dp[i-1] + a[i] , a[i]);
这个式子怎么理解呢?
对于每个位置的最优解,我们考虑两种情况:
1.包含前一个元素,那这个等式就结果就是dp[i-1]+a[i](既然包含前一个元素dp[i-1],前一个元素也可能包含前一个元素,也可能不包含,这样递推回去就能得到最优解)
2.不包含前一个元素,那就是a[i],将dp[i]设置成a[i]
这里的意思是,加了上一个最优解还不如不加,也就是整个前面的最优解是个负数,拉了后腿,才被舍弃

并且,我们每次都可以使用一个变量answer来保存当前的最大值,先保存a[0],之后遇到新的最优解,刷新answer,之后每次得到新的dp[i],就与之前的最大做对比,不断刷新变量answer,最后返回它即可。

//时间复杂度O(n),空间复杂度O(1)int maxSubArray(vector
& nums) { int m = nums.size(); if(m == 1) { return nums[0]; } vector
dp(m); int answer = nums[0]; dp[0] = nums[0]; for(int i = 1;i < m;i++) { dp[i] = max(dp[i-1]+nums[i],nums[i]); answer = max(dp[i],answer); } return answer;}

下面也有一张自己画的流程图,这个比较简单一点:

这里写图片描述
下面这张表是运行过程中存在的,函数返回之后就销毁了,实际上我们只保存了answer这个一个变量,空间复杂度是O(1)

3.解题思路三:线性枚举

我们假设总数:sum[i] = a[0] + a[1] + a[2] + …… +a[i] i>=0
假设:sum[-1] = 0

则对0 <= i <= j,任意这样的i,j两数

a[i] + a[i+1] + ….. +a[j] = sum[j] - sum[i-1]

根据这个规律,我们想要的不就是这样的一个最大值么?一段区间内的连续值总和最大

So我们可以求得当前的sum[j],取的i-1值一定是使得sum[i-1]最小,然后用一个变量记录sum的最小值,最后用sum[j]减去这个最小值,就得到一个最大值,不断刷新这个最大值,返回

//时间复杂度O(n),空间复杂度O(1)int maxSubArray(vector
& nums) { int sum = nums[0]; int minsum = min(0,sum); //和sum[-1] = 0作比较 int answer = nums[0]; for(int i = 1;i < nums.size();i++){ sum += nums[i]; //这是一直的总数,j值 answer = max(answer,sum - minsum); minsum = min(minsum,sum); } return answer;}

总结一下这个题目,思路并不是很难,但是与上面的都不太相同:

就是相当于轮询一个j,我们从1开始,记录一个sum[j]总数,同时从0开始记录一个minsum(一开始的minsum和0作对比,因为是负数的话就直接取它,是正数的话就取0,也就相当于略过这一位,从下一位开始)。

之后从1开始,刷新 answer = max(answer , sum - minsum),要使得sum - minsum最大,那就minsum一直应该是最小,也就是记录下最小的那个minsum,也就是说有一个位置确定了,除非找到比那个位置更小的。它一直与sum比较,sum也就是指前 i 项,也就是最小的前 i 项数。

一直比较新得到的那个answer,返回。

有什么错误还请指出,谢谢

你可能感兴趣的文章
Oracle wallet 配置 说明
查看>>
Oracle smon_scn_time 表 说明
查看>>
VBox fdisk 不显示 添加的硬盘 解决方法
查看>>
Secure CRT 自动记录日志 配置 小记
查看>>
RMAN RAC 到 单实例 duplicate 自动分配通道 触发 ORA-19505 错误
查看>>
mysql 随机分页的优化
查看>>
DB2快速创建测试库
查看>>
利用db2look查看ddl
查看>>
SD卡驱动分析--基于高通平台
查看>>
[图文] Seata AT 模式分布式事务源码分析
查看>>
pm 源码分析
查看>>
Sending the User to Another App
查看>>
kmsg_dump
查看>>
Getting a Result from an Activity
查看>>
Allowing Other Apps to Start Your Activity
查看>>
dev/mem
查看>>
pfn_valid 源码分析
查看>>
dev/kmem 和dev/mem的区别
查看>>
test-definitions/blob/master/auto-test/bigdata/bigdata.sh
查看>>
/test-definitions/blob/master/auto-test/blktrace/blktrace.sh
查看>>