leetcode-53- 最大子序和(maximum subarray)-java

题目及用例

package pid053;
/*最大子序和

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

示例:

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

进阶:

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




public class main {
	
	public static void main(String[] args) {
		int[][] testTable = {{-2,1,-3,4,-1,2,1,-5,4},{-1,-2,-3,-4,-1},{7,-6,4,3,1},{-1,6,2,7}};
		for (int[] ito : testTable) {
			test(ito);
		}
	}
		 
	private static void test(int[] ito) {
		Solution solution = new Solution();
		int rtn;
		long begin = System.currentTimeMillis();
		for (int i = 0; i < ito.length; i++) {
		    System.out.print(ito[i]+" ");		    
		}
		System.out.println();
		//开始时打印数组
		
		rtn = solution.maxSubArray(ito);//执行程序
		long end = System.currentTimeMillis();	
		
		//System.out.println(ito + ": rtn=" + rtn);
		System.out.println( " rtn=" +rtn);
//		for (int i = 0; i < ito.length; i++) {
//		    System.out.print(ito[i]+" ");
//		}//打印结果几数组
		
		System.out.println();
		System.out.println("耗时:" + (end - begin) + "ms");
		System.out.println("-------------------");
	}

}

解法1(成功,16ms,较快)
速度o(n)
方法就是不断累加,如果现在的值加到负数,先更新max值
然后加上负数,如果小于0,则初始化为0,否则就不管
如果最后max为0,证明全是负数,则取负数中的最大的那个数

package pid053;


class Solution {
public int maxSubArray(int[] nums) {
    int length=nums.length;
    if(length==0){
    	return 0;
    }
    int totalMax=0;
    int nowMax=0;
    for(int i=0;i<length;i++){
    	int now=nums[i];
    	if(nowMax==0){//新的开始
    		if(now>=0){
    			nowMax=now;
    			continue;
    		}
    		else {
				continue;
			}
    		
    	}
    	else {//之前已经有和了
			if(now>=0){
				nowMax=nowMax+now;
				continue;
			}
			else{//上一个的和暂时结束进入减法模式
				
				if(nowMax>totalMax){//加上负的肯定变小,先更新max值
					totalMax=nowMax;
				}				
				nowMax=nowMax+now;
				if(nowMax<0){
					nowMax=0;
				}
				continue;
			}
    		
		}
    	
    }
	
    if(nowMax>totalMax){
		totalMax=nowMax;
	}
    if(totalMax==0){//全为负的情况
    	totalMax=nums[0];
    	for(int i=0;i<length;i++){
    		int now=nums[i];
    		if(now>totalMax){
    			totalMax=now;
    		}
    	}
    }
	
	return totalMax;
    }
}

解法2(成功,3ms,较慢)
dp算法
i处的max利润 max[i] = max[i-1] / tail[i-1]+nums[i] / nums[i]
包含i的max利润 tail[i] = tail[i-1]+nums[i] / nums[i]
由于每次只需要前一个的变量,只需要设置4个变量即可

    public int maxSubArray(int[] nums) {
        // i处的max利润  max[i]  =  max[i-1]/tail[i-1]+nums[i]/nums[i]
    	// 包含i的max利润   tail[i] = tail[i-1]+nums[i] / nums[i] 
    	int prevMax=nums[0];
    	int prevTail=nums[0];
    	int max=nums[0];
    	int tail=nums[0];
    	for(int i=1;i<nums.length;i++){
    		int now=nums[i];
    		max=Math.max(Math.max(prevMax, prevTail+now), now);
    		tail=Math.max(prevTail+now, now);
    		prevMax=max;
    		prevTail=tail;
    	}   	
    	return max;
    }	

解法3(别人的)
动态规划
这道题用动态规划的思路并不难解决,比较难的是后文提出的用分治法求解,但由于其不是最优解法,所以先不列出来
动态规划的是首先对数组进行遍历,当前最大连续子序列和为 sum,结果为 ans
如果 sum > 0,则说明 sum 对结果有增益效果,则 sum 保留并加上当前遍历数字
如果 sum <= 0,则说明 sum 对结果无增益效果,需要舍弃,则 sum 直接更新为当前遍历数字
每次比较 sum 和 ans的大小,将最大值置为ans,遍历结束返回结果
其实思路和我上面的解法2一样,ans为i处的最大利润,sum为包含i处的最大理论
i处的max利润 max[i] = max[i-1] / tail[i-1]+nums[i] / nums[i]
包含i的max利润 tail[i] = tail[i-1]+nums[i] / nums[i]
变为
tail[i] = tail[i-1]+nums[i] / nums[i]
max[i] = max[i-1] / tail[i] 可以看见原来的公式,后面两个的值就是tail[i]的值,用它替代即可,这样只需2个变量,不用重复计算



class Solution {
    public int maxSubArray(int[] nums) {
        int ans = nums[0];
        int sum = 0;
        for(int num: nums) {
            if(sum > 0) {
                sum += num;
            } else {
                sum = num;
            }
            ans = Math.max(ans, sum);
        }
        return ans;
    }
}

解法4(别人的)
分治法:思路:假设数组下标有效范围是l到r,将数组分为左半部分下标为(l,mid-1)和右半部分下标为(mid+1,r)以及中间元素下标为mid,接下来递归求出左半部分的最大子序和:left=helper(nums,l,mid-1); 右半部分最大子序和right=helper(nums,mid+1,r);接下来再将左半部分右边界,右半部分左边界以及中间元素nums[mid]整合,用了两个循环,先整合左半部分右边界和中间值,再将整合结果与右半部分左边界整合得到整合以后的最大子序和max_num,最后返回max_num,left,right的最大值即是要求的最大子序和。

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        if(nums.size()==0)return 0;
        return helper(nums,0,nums.size()-1);
    }
    int helper(vector<int>& nums,int l,int r){
        if(l>r)return INT_MIN;//注意此处不是返回0,比如{-2,-1},分治以后变为左中右n{},-1,{-2}三部分。左半部分{}应返回INT_MIN,
        //因为还要和右半部分的返回值进行比较,最终正确结果返回-1。若左半部分返回0,0>-2,且大于左中右的最大组合值(-1),最终结果返回0,出错
        if(l==r)return nums[l];
        int mid=(l+r)/2;
        int left=helper(nums,l,mid-1);
        int right=helper(nums,mid+1,r);
        int t=nums[mid];
        int max_num=nums[mid];
        for(int i=mid-1;i>=l;i--){
            t+=nums[i];
            max_num=max(max_num,t);
        }
        t=max_num;
        for(int i=mid+1;i<=r;i++){
            t+=nums[i];
            max_num=max(max_num,t);
        }
        return max(max(left,right),max_num);
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值