阿里巴巴测验题猴子摘桃问题(LIS问题)

来源:互联网 发布:win10国外优化软件 编辑:程序博客网 时间:2024/06/16 06:35

问题描述:小猴子下山,沿着下山的路由一排桃树,每棵树都结了一些桃子。小猴子想摘桃子,但是有一些条件需要遵守,小猴子只能沿着下山的方向走,不能回头,每棵树最多摘一个,而且一旦摘了一棵树的桃子,就不能再摘比这棵树结的桃子少的树上的桃子了,那么小猴子最多能摘几个桃子呢?举例说明,比如有5课树,分别结了10,4,5,12,8颗桃子,那么小猴子最多能摘3颗桃子,来自于结了4,5,8颗桃子的树。
问题解析:如果学过算法的人就会知道这就是一个LIS即最长非递减子序列的求解。
首先我们来看一下这个的动态规划的解法 时间复杂度为O(n^2)
下面我们来一步步分析求得状态转移方程。
树的编号从0开始,我们在每棵树下放一个篮子,用来装如果摘了这棵树,我可以获得的桃子的个数。
篮子就是状态
设每棵树所结桃子的个数集合为V={10,4,5,12,8}
设dp(i)=j 表示为 当摘到第i棵的树时此时可获得桃子的个数为j颗,即树下篮中的桃子的个数。0<=i<=4;
dp(0)=1 代表的是摘第0棵树时此时可获得的桃子为1颗,即第一树树下篮中桃子有1颗。
dp(1)=1代表的是摘第1棵树时此时可获得桃子为1颗 ,即篮中的桃子的个数为1颗。那么这1颗桃子怎么来的了,是这样的,根据题目的要求一旦摘了一棵树的桃子,就不能再摘比这棵树结的桃子少的树上的桃子了,又因为第1棵树上的桃子的个数比第0 棵树上的桃子数小,所以如果我们要摘第1棵树的桃子,我就不能拿第0棵树下篮子内的桃子了,即就是不能依赖第0棵树得到的解,即第0棵不是当前问题的子问题,所以它本身就是一个单独的解。
dp(2)=2 因为在第2棵树时在它之前只有第1棵树的桃子的个数比他小所以第1棵树是当前问题的子问题,可以依赖第1棵树所获的解,即可以拿第1棵树下篮子内的桃子,再加上自身的这一颗桃子所以自身树下篮子内的桃子的个数为dp(2)=dp(1)+1;
dp(3)=3因为在第3棵树时在它之前桃子数比它小的是第1棵和第2棵,即第1棵树和第二棵树的解都是当前问题子问题的解,我们都可以依赖,但是因为我们要取最多的,所以找出这里面的最大的,即所有可依赖的子问题的解中最优的解,通俗的讲就是从那些可以拿桃子的篮子中挑一个桃子数最大的拿,dp(1)=1 ,dp(2)=2,可见是从第2棵树下拿,所以 dp(3)=dp(2)+1=2+1=3;
dp(4)=3 因为在第4棵树时树上桃子数比它小的是第1棵和第2棵,同理得dp(4)=dp(2)+1=2+1=3;
有以上推理我们可以得出状态转移方程为
集合V为每棵树桃子个数的集合
dp(0)=1;
dp(i)=max{1, dp(j)+1} 其中 j 只要 满足V[i]>V[j]且 j在[0,i-1]区间内 就行 所以 dp(j)+1 的个数>=0; max 就是求出 集合内的最大值。
我们用状态转移方程来解上面的状态
dp(0)=1;
dp(1)= max{1}
dp(2)=max{1,dp(1)}=dp(1)+1;
dp(3)=max{1,dp(1),dp(2)}=dp(2)+1
dp(4)=max{1,dp(1),dp(2)}=dp(2)+1
有了状态集合即所有篮子组成的集合
我们就可以知道从第0棵到最后一棵可以获得桃子的个数,即每棵树下篮子内装桃子的个数,我们找出装了桃子数最多的篮子,篮子里面桃子的个数就是我们可以获得最多桃子的个数。
下面我们来看一下代码如何实现的

import java.util.Scanner;public class DPLISTest {       public static int getLIS(int[] a){           //dp用来保存状态值           int[] dp=new int[a.length];           int j=0,max=0;           //dp(0)=1;             dp[0]=1;           for(int i=1;i<a.length;i++){             //先假设dp(i)=1;                 dp[i]=1;              //求出max{1,dp(j)+1}即满足推理的集合内的最大值               for( j=i-1;j>-1;j--){                     if(a[i]>a[j]&&dp[i]<dp[j]+1){                        dp[i]=dp[j]+1;                     }               }           }           //获取状态集合中的最大值就是我们要求得最多可以获得桃子的个数           for(int i=1;i<dp.length;i++){                if(max<dp[i]){                    max=dp[i];                }           }           return max;       }       public static void main(String[] args) {           Scanner sc=new Scanner(System.in);           //输入树的个数           int num=sc.nextInt();           int[] V=new int[num];          //输入每棵树的桃子的个数           for(int i=0;i<num;i++){               V[i]=sc.nextInt();           }                  int len=getLIS(V);        System.out.println(len);    }}

我们来看一下更优的解法时间复杂度只有O(nlongn)
现在我们换个看的更详细的例子来分析这个问题
V={2,1, 5,3, 6 ,4 ,8 ,9, 7}
树的编号从0开始
设dp(i)=j 代表的是最多可获得的桃子的个数为i+1时,第i棵树上桃子的个数最少为j颗
设dp集合的长度为len 也就是我们要求得可以最多获得桃子的个数
我们将V中的第0个元素进行检验可得dp(0)=2; len=1即最多可获取得桃子的个数为1时,第0棵树上的桃子数的最少为2.
我们将V中的第1个元素进行检验可得因为1 比 2小可见当最多可获得桃子的个数为1时,第0棵树上所结的桃子的个数应为1 而不是2;所以得dp(0)=1; len=1;
我们将V中的第2个元素进行检验。因为5比1大说明第0棵树上桃子的个数最小的值可能已经找出,现在我们要找第1棵,即当前可获得最多桃子的个数为2,那么第1棵树上桃子的个数目前最小的为5 所以dp(1)=5; len=2;
我们将V中的第3个元素拿出来进行检验,因为3比5小且比1大,即3在1和5之间说明,第0棵桃子的个数最小的值可能已经找到,而第1棵桃子的个数应该为3而不是5,即dp(1)=3;len=2;
我们将V中的第4个元素拿出来进行检验,因为6比3大说明第0棵,第1棵树桃子的个数最小的值可能已经找到,现在我们要开始找第2棵树,即当前可获得最多桃子的个数为3,那么第2棵桃子的个数目前最小为6所以dp(2)=6;len=3;
我们将V中的第5个元素拿出来进行检验,因为4比6小,比3大,即4在3和6之间说明,第0棵第1棵树桃子的个数最小的值可能已经找到,而第2棵树桃子的个数最小值应该为4而不是6,即dp(2)=4;len=3;
我们将V中的第6个元素拿出来进行检验,因为8比4大,说明第0棵,第1棵,第2棵树桃子的个数最小的值可能已经找到,现在我们要开始找第3棵树,即当前可获得最多桃子的个数为4,那么第3棵桃子的个数目前最小为8所以dp(3)=8;len=4;
我们将V中的第7个元素拿出来进行检验,因为9比8大,说明第0棵,第1棵,第2棵,第3棵树桃子的个数最小的值可能已经找到,现在我们要开始找第4棵树,即当前可获得最多桃子的个数为5,那么第4棵桃子的个数目前最小为9所以dp(4)=9;len=5;
我们将V中的第8个元素拿出来进行检验,因为7比9小比8小,比4大,7在4和8之间说明第0棵,第1棵,第2棵桃子的个数最小的值可能已经找到,而第3棵树桃子的个数最小值应该为7而不是8;即dp(3)=7;len=5;
有以上推理可得每棵树桃子最小值的个数为1,3,4,7,9。树的个数为5,说明最多可以摘5棵桃子。
现在我们可发现,我们每次从V中拿出元素都要找出它在dp集合中要待的位置,如果那位置不存在则直接放入dp集合中的最后面,dp集合的元素个数加一,如果位置存在,这替换掉该位置上的元素,那么这位置怎么找,我们要在dp中从第0个元素开始往后找,找到第一个比它大的数,替换掉它,如果dp集合内不存在比它大的,则dp集合的元素个数需加一,将要检验的元素放到最后,可以看出这样一来dp中的元素肯定是一个非递减的元素集合。那么我们可以用二分查找来查找这个位置,时间复杂度为O(logn),而我们V集合中每个元素都要查找位置,所以最后的时间复杂度为O(nlogn)
下面我们来看一下代码如何实现的

import java.util.Scanner;public class LisTest {    //获取替换位置     private static int getRplaceIndex(int[] dp,int high,int data){         int low=0;         int mid=-1;         //dp合中所有元素都比data小所以插入的位置应该为当前dp中最大元素即最后一个元素所在的位置加1         if(data>dp[high]){             return  high+1;         }         //查找dp集合中从左到右第一个比data大的元素的位置。        while(low<high){            mid=low+(high-low)/2;            //如果集合中间元素小于或等于data说明data要待的位置要在后半段中查找(mid+1,high],否则要在前半段查找[low,mid]           if(dp[mid]<=data){               low=mid+1;           }else{               high=mid;           }        }        //返回我们要查找的位置        return low;     }    public static int getLength(int[] v){        int[] dp=new int[v.length];        //初始化dp集合        int len=1;          dp[0]=v[0];        //将v中剩余的元素拿出来进行检验        for(int i=1;i<dp.length;i++){            //获的替换的位置            int index= getRplaceIndex(dp,len-1,v[i]);            //如果替换的位置大于或等于dp的元素的个数,则dp的当前元素个数加1,将该元素插入到dp集合的最后面            if(index>=len){                len++;            }            //将v中的元素插入到dp集合中的适当位置            dp[index]=v[i];        }        //返回dp集合的元素个数        return len;    }    public static void main(String[] args) {          Scanner sc=new Scanner(System.in);       //输入树的个数       int num=sc.nextInt();       int[] V=new int[num];      //输入每棵树的桃子的个数       for(int i=0;i<num;i++){           V[i]=sc.nextInt();       }          //获得最多桃子的个数        int len=getLength(V);        System.out.println(len);    }}
原创粉丝点击