LeetCode 53. Maximum Subarray

             这道题的题意是给定一个数组,要求求出最大的连续子序列的和。

             这道题有很多的做法,而我做的有四种,下面我按照时间复杂度从高到低说明一下这些算法。

(1) 最简单的做法是直接枚举左右端点,然后计算两个端点之间元素的和,枚举左右端点复杂度为O(n2),计算和的复杂度为O(n),总时间复杂度为O(n3)。实现比较简单,就

         不给出代码。而O(n3)的时间复杂度不足以通过这道题。

(2)通过观察,我们可以发现上一种做法中的计算和的步骤是可以简化的。第二种做法可以先声明一个sum数组。sum[i] 表示原数组的元素0~i的和。计算的过程的时间复杂度

         为O(n),有了这个数组,计算i~j的和只要用sum[j] - sum[i-1]即可,计算和的复杂度为O(1)。总的时间复杂度为O(n2),代码如下:

       

const int maxn = 1000000 + 20 ;
int sum[maxn] ;
# define INF 100000000 

class Solution {
public:
    int maxSubArray(vector<int>& nums) ;
};

int Solution::maxSubArray(vector<int>& nums) {
	memset(sum, 0, sizeof(sum)) ;
	vector<int> a ;
	a.push_back(0) ;
	int n = nums.size() ;
	for (int i=0; i<n; i++) a.push_back(nums[i]) ;
	sum[0] = 0 ;
	
	int ans = -INF ;
	
	for (int i=1; i<=n; i++) sum[i] = sum[i-1] + a[i] ;
	
	for (int i=1; i<=n; i++) {
		for (int j=0; j<i; j++) ans = max(sum[i]- sum[j], ans) ;
	}
	return ans ;
} 

            但O(n2)的时间复杂度依然不足以通过这道题。


(3) 第三种方法用到了分治的思想。我们可以每次把数组分成两半,则有最大连续和的子序列有三种可能:1、左半边数组;2、右半边数组;3、左半边和右半边都有一部分

          (需要连续)。因此我们可以写出一个递归的程序:当前序列的最大连续和等于左半边最大连续和、右半边最大连续和、还有通过中间的最大连续和。计算通过中间的最

           大连续和的时间复杂度为O(n) (只要从中间向两边扫描即可)。则有推导公式,T(n) = T(n/2) + O(n),则时间复杂度O(nlogn),这样就能够通过这道题,代码如下:

const int maxn = 1000000 + 20 ;
int sum[maxn] ;
# define INF 100000000 

class Solution {
public:
    int maxSubArray(vector<int>& nums) ;
    int dfs(vector<int>& nums, int x, int y) ;
};

int Solution::maxSubArray(vector<int>& nums) {
	return dfs(nums, 0, nums.size() ) ;
} 

int Solution::dfs(vector<int>& nums, int x, int y) {
	if (x >= y) return -INF ;
	if (x + 1 == y) return nums[x] ;
	
	int ans = -INF ;
	int m = x + (y-x)/2 ;
	ans = max(max(dfs(nums,x,m), dfs(nums,m,y)), ans) ;
	int L = nums[m-1], R = nums[m] ;
	int lv = 0, rv = 0 ;
	for (int i=m-1; i>=x; i--) {
		lv += nums[i] ; L = max(L, lv) ;
	}
	for (int i=m; i<y; i++) {
		rv += nums[i] ; R = max(R, rv) ;
	}
	ans = max(L+R, ans) ;
//	cout << x << " " << y << " " << ans << endl ;
	return ans ;
}


(4)但O(nlogn)的做法并不是最快的。回到第二种做法,经过思考,我们可以发现第一维的枚举是不必要的,我们不需要枚举左端点,只要维护最小的左端点即可,而维护可

         以在枚举右端点的过程中做到。这样时间复杂度就降到了O(n),代码如下:

const int maxn = 1000000 + 20 ;
int sum[maxn] ;
# define INF 100000000 

class Solution {
public:
    int maxSubArray(vector<int>& nums) ;
};

int Solution::maxSubArray(vector<int>& nums) {
	memset(sum, 0, sizeof(sum)) ;
	vector<int> a ;
	a.push_back(0) ;
	int n = nums.size() ;
	for (int i=0; i<n; i++) a.push_back(nums[i]) ;
	sum[0] = 0 ;
	
	int ans = -INF ;
	
	for (int i=1; i<=n; i++) sum[i] = sum[i-1] + a[i] ;
	
    int minsum = sum[0] ;
    for (int i=1; i<=n; i++) {
    	ans = max(ans, sum[i] - minsum) ;
    	minsum = min(minsum, sum[i]) ;
    }
	return ans ;
} 



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值