最大连续子序列——动态规划经典问题

前几天在牛客网上看到一道关于动态规划的题目,完全不知如何着手。所以就去学习了一下动态规划,参考网上的解析,跌跌撞撞把一道杭电上的最大连续子序列敲了出来。

题目来源: http://acm.hdu.edu.cn/showproblem.php?pid=1231

给定K个整数的序列{ N1, N2, ..., NK },其任意连续子序列可表示为{ Ni, Ni+1, ..., Nj },其中 1 <= i <= j <= K。最大连续子序列是所有连续子序列中元素和最大的一个, 例如给定序列{ -2, 11, -4, 13, -5, -2 },其最大连续子序列为{ 11, -4, 13 },最大和 
为20。 在今年的数据结构考卷中,要求编写程序得到最大和,现在增加一个要求,即还需要输出该 子序列的第一个和最后一个元素。

 分析:

1.我第一次采用的是暴力循环的方法,利用两层for循环,遍历数组以每一个元素开头的所有子序列。虽然可以算出结果,但是超时了,时间复杂度为O(n^2)。

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        int n;
        Scanner scanner=new Scanner(System.in);
        while ((n=scanner.nextInt())!=0){
            int[] a=new int[n];
            for(int i=0;i<n;i++){
                a[i]=scanner.nextInt();
            }
            int x=0,y=0,sum,max=Integer.MIN_VALUE;
            for (int i=0;i<n;i++){
                sum=0;
                for (int j=i;j<n;j++){
                    sum+=a[j];
                    if (sum>max){
                        x=i;
                        y=j;
                        max=sum;
                    }
                }
            }
            if (max<0){
                x=0;
                y=n-1;
                max=0;
            }
            System.out.println(max+" "+a[x]+" "+a[y]);
        }
    }
}

2.第二次采用的是分治法,结果还是超时了。

算法的主要思想如下:

首先,我们可以把整个序列平均分成左右两部分,答案则会在以下三种情况中:
1、所求序列完全包含在左半部分的序列中。
2、所求序列完全包含在右半部分的序列中。
3、所求序列刚好横跨分割点,即左右序列各占一部分。

import java.util.Scanner;

public class Main {
    static int x,y,sum=0,max=Integer.MIN_VALUE;
    public static void main(String[] args) {
        int n;
        Scanner scanner=new Scanner(System.in);
        while ((n=scanner.nextInt())!=0){
            x=y=0;
            int[] a=new int[n];
            for(int i=0;i<n;i++){
                a[i]=scanner.nextInt();
            }
            max=a[0];
            System.out.println(findMax(a,0,n-1)+" "+a[x]+" "+a[y]);
        }
    }

    //递归方法
    public static int findMax(int a[],int left,int right){
        //递归边界值
        if(left==right){
            int sum=a[left];
            if (sum>max){
                x=y=left;
                max=sum;
            }else if(sum==max&&left<x){
                    x=y=left;
            }
            return sum;
        }


        int mid=(left+right)/2;
        //找出左边部分的最大值
        int leftMax=findMax(a,left,mid);
        //找出右边部分的最大值
        int rightMax=findMax(a,mid+1,right);

        //找出中间部分的最大值,不需要递归
        int mLeftMax=Integer.MAX_VALUE;int mLeftSum=0;
        int start=mid;
        for(int i=mid;i>=0;i--){
            mLeftSum+=a[i];
            if(mLeftSum>mLeftMax){
                mLeftMax=mLeftSum;
                start=i;
            }
        }
        int mRightMax=Integer.MAX_VALUE;int mRightSum=0;
        int end=mid+1;
        for(int i=mid+1;i<=right;i++){
            mRightSum+=a[i];
            if(mRightSum>mRightMax){
                mRightMax=mRightSum;
                end=i;
            }
        }

        int mMax=mLeftMax+mRightMax;
        if (mMax>max){
            max=mMax;
            x=start;
            y=end;
        }else if(mMax==max&&start<x){
                x=start;
                y=end;
        }
        if(max<0){
            x=0;y=a.length-1;
            max=0;
        }

        return max;


    }
}

这里又牵扯到递归了,递归一时半会儿我也没法弄的很清晰。可以参考一文读懂递归算法,这篇博客还挺简洁通俗的。

回到上面的分治法,它的时间复杂度是多少呢?它的效率有比第一种方法高么?

假设数据数量为n,它的时间复杂度假设为T(n),O(n)为遍历中间部分的时间复杂度,那么:

T(n)=2T(n/2)+O(n)

      =4T(n/4)+2O(n)

      =8T(n/8)+3O(n)

      ...

      =nT(n/n)+log(n)O(n)

      =n+O(nlog(n))

省略n,最终T(n)=O(nlog(n))。所以,它的时间效率稍微高于第一种方法。

3.第三次采用的是动态规划,时间复杂度为O(n)。

算法主要思想:遍历该序列每一个元素,计算以它结尾的子序列的最大值,然后找出最大的那个最大值。

import java.util.Scanner;
public class Main {
    public static void main(String[] args) {
        Scanner scanner=new Scanner(System.in);
        int n;
        while ((n = scanner.nextInt())!=0) {
            int[] a=new int[n];
          
            int s=0,L=0,R=0,l=0;int max=Integer.MIN_VALUE;
            for(int i=0;i<n;i++){
                a[i]=scanner.nextInt();
                //以a[i]元素结尾的子序列必须包含a[i]
                s+=a[i];
                if(s>max){
                    max=s;L=l;R=i;
                }
                if(s<0){
                    //在这一轮循环中,l表示的是以a[i+1]开始的子序列的左下标
                    s=0;l=i+1;
                }
            }
            if(max<0){max=0;L=0;R=n-1;}
            System.out.println(max+" "+a[L]+" "+a[R]);
        }
    }
}

参考博客:

经典算法问题 - 最大连续子数列和

几个经典的动态规划问题

猜你喜欢

转载自blog.csdn.net/linghuainian/article/details/85792433