赞
踩
昨天参加了华为机考,今天将其记录一下,顺便总结一下不足。
第一题:给一个数组,从位置0开始,求第一步可到达跳【1,length/2)步,接着每一步的步数是当前数组元素的的大小,问跳到数组的最后一个元素最少要多少步?
eg
[1,2,3,4,5,6,7,8]
第一步:从1跳3步到arr[3]
第二步:3+arr[3]=3+4=7 到达末尾
结果:最少两步
当时的解题思路:还挺简单,直接用暴力,求第一个位置到终点所需的步数求,再求第二个到终点所需的步数,一直求到length/2-1时,所需的步数,然后比较最小不就好了吗?不对,这可是华为!一定不会这么简单,让我再想一想。嗯,既然求最小,那一定是要考察动态规划了,要用到动态规划,那一定是要求状态转移了,既然状态转移,一定要用集合来存储当前的状态和下一步的状态了,嗯,对,一定是这样。我用record_now集合来存储当前状态,用record_next集合存储下一个状态,这样只要在每次跳之前看看有没有当前的终点就好了?于是我写出了如下代码:
public static int getMinStep(int []arr){ HashSet<Integer> record_now=new HashSet<>(); //用来记录当前到达的状态 HashSet<Integer> record_next=new HashSet<>(); //用来记录下一步的状态 //第一步,跳到i for(int i=1;i<arr.length/2;i++){ record_now.add(i); } int step=1; //当前跳的步数 //接着跳 while(!record_now.isEmpty()){ //location:当前位置,跳到下一次位置 location+arr[location] for(Integer location:record_now){ //如果下一步可达,记录下一步的状态 if(location+arr[location]<arr.length){ record_next.add(location+arr[location]); } } //将下一次的状态设置为当前状态,当前状态清空,步数+1 record_now.clear(); record_now.addAll(record_next); record_next.clear(); step++; //判断是否有直接跳到终点的 if(record_now.contains(arr.length-1)){ return step; } } //当所有状态都结束,但是没有到达终点的,return -1; return -1; }
测试结果:通过60%,部分测试用例因为耗时太长,通不过???
想不通原因,那我还是用暴力法来看看吧:
public static int getMinStep(int []arr){ int min=arr.length; //最大步数 //第一步,跳到i,判断从i要几步到终点 for(int i=1;i<arr.length/2;i++){ int tmp=minSearch(i,arr); if(tmp<min){ min=tmp; } } //如果min值被改变,说明有的点到达了最后,而且此时的min值为最少步数,返回 if(min!=arr.length){ return min; } else{ return -1; } } public static int minSearch(int i,int []arr){ int step=1; int location=i; //当前位置 //还在数组中 while(location<arr.length){ location=location+arr[location]; step++; //如果到达终点 if(location==arr.length-1){ return step; } } //如果没有到达终点,返回原始min值 return arr.length; }
通过了!??
分析:
1.为什么第一次没通过,而用暴力法直接通过了呢?我分析了可能的原因是:第一次我使用的动态规划思想在可能在本题中并不适用,因为动态规划和暴力法在时间复杂度上是一样的O(N^2)。可是至于为什么会这么耗时,动态规划不就是空间换时间吗?时间不应该更低吗?我也弄不清楚了。其实我反而觉得用动态规划是更好的。可能是个玄学问题???
2.使用动态规划的好处是能够提前终止(发现有到达终点的状态就终止),同一个状态不会多次计算(比如arr[3]=2,arr[4]=1都到达5,5在HashSet中只会存储一次),而暴力需要计算所有的结果比较一遍。其实就像图的遍历一样,动态规划用的是BFS,而暴力使用的是DFS,关键是DFS并不能记录重复的路径,它可能一条路走N次;而BFS不会走重复路径,而且有到了终点就停止。
事后反思:由于动态规划没有通过所有的测试用例,所以在这道简单题上花费了一个多小时,直接导致后面的题目都没做出来,唉······
第二题:给两个数M,N,M表示M中元素(1,2,3…),N表示N个成环,元素可重复使用,要求顺序和逆序都算同一种,如{(1,2,3)(2,3,1)(3,1,2)(3,2,1) (2,1,3)(1,3,2)}都算同一种,问有多少种组成的环?
给出样例 M=3 N=3 结果:10
(1,1,1)(1,1,2)(1,1,3)(1,2,2)(1,2,3)(1,3,3)(2,2,2)(2,2,3)(2,3,3)(3,3,3)
当时的解题思路:这既然是一个排列组合问题,那么结果和M,N之间一定有某种数学关系,难道华为要考察我的数学水平?凭借我高中数学的水平,我应该能把这种数学关系求出来,结果,时间结束了,我还是没把这个关系算出来。。。
考试结束,我又想了一下,既然没有把F(M,N)=f(M,N)算出来,要不如,画个表,找找规律,结果,果然发现了一些规律。
于是我大胆猜测:F(M,N)=F(M-1,N)+F(M,N-1)
证明:以F(2,2)为例
分析:首先F(2,2)肯定是包含F(1,2)这种情况的,然后对于F(2,1)->F(2,2)其实是环增加一个元素,然后不要与F(1,2)冲突,不与F(1,2)冲突,环就必须增加一种不与F(1,2)中M种元素相同的元素才可以保证。对于加入的位置,由于成环,并且正逆序算作同一种,其实对于F(2,1)中的某个环,无论加到哪个位置,得到的N种最后都是同一种,所以F(2,1)中全新的(2,2)环,加上之前的(1,2)环,总共可以得到F(2,2)=F(1,2)+F(2,1)种(2,2)环。
同理:可得F(M,N)=F(M-1,N)+F(M,N-1)。(F(M,1)=M;F(1,N)=1)
理清思路,代码就很好写了:
public static int getResult(int M,int N){ int [][]record=new int[M+1][N+1]; //行初始化 for(int j=1;j<N+1;j++){ record[1][j]=1; } //列初始化 for(int i=1;i<M+1;i++){ record[i][1]=i; } //赋值 for(int i=2;i<M+1;i++){ for(int j=2;j<N+1;j++){ record[i][j]=record[i-1][j]+record[i][j-1]; } } //返回结果 return record[M][N]; }
算法时间复杂度(O(M*N))
第三题:我题目都没来得及看(菜的真实),希望有谁能分享一下第三题的问题和解题思路。
总体来说,这次笔试并不是很顺利,主要问题还是在第一题上花费了太多时间,没有将时间分配好;至于为什么在第一题上花费了太多时间,还是经验不够,需要多加练习。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。