输入一个数字n 如果n为偶数则除以2,若为奇数则加1或者减1,直到n为1,求最少次数 写出一个函数
题目:
输入一个数字n 如果n为偶数则除以2,若为奇数则加1或者减1,直到n为1,求最少次数 写出一个函数
首先,这道题肯定可以用动态规划来解,
n为整数时,n的解为 n/2 的解加1
n为奇数时,n的解为 (n+1)/2 和 (n-1)/2 的解中较小的解加2
通过这个思路,我们可以自底向上依次计算出n的解,代码如下
public static int getNum(int n) { if(n<=1) { return 0; } int[] res = new int[n+1]; res[0] = 0; res[1] = 0; for(int i=2;i<=n;i++) { if((i&1) == 0) { res[i] = res[i/2] + 1; }else { res[i] = Math.min(res[(i+1)/2], res[(i-1)/2]) + 2; } } return res[n]; }
通过上面的思路可以得到问题的解,但是由于是自底向上依次计算n的解,所以有很多不必要的计算,时间效率和空间效率都不高。
比如,当计算n=100时,如果已经知道n=50的解,那么就可以得出n=100的解,所以n=51到n=99都是没有必要计算的。
如果仍然通过自底向上计算,那么想要忽略51到99这一区间的数字的计算是比较麻烦的,如果是自顶向下计算则容易做到,通过n可以确定只要计算 n/2,(n-1)/2 , (n+1)/2,这三个数就 行,利用递归来做代码如下
public static int getNum2(long n) { if(n<=1) { return 0; } if((n&1) == 0) { return getNum2(n/2) + 1; }else { long a = (n-1) / 2; long b = (n+1) / 2; return Math.min(getNum2(a), getNum2(b)) + 2; } }
递归来做这道题简单明了。
递归也有递归的坏处,首先递归最可能问题就是递归深度的问题,很可能造成栈溢出。虽然对这道题来说,几乎不会出现这个问题,但是在用递归做其他问题的时候一定要考虑到这一点。
至于为什么这道题不会造成栈溢出,自己想吧
所有的递归算法都可以转化成非递归算法,这道题也一样,同样的,递归时还有一个小问题,就是它没有复用子问题的解,对于每个子问题,不管之前是否已经计算过解,都要再重新计算一次,转化成非递归算法时可以一并解决这个问题,代码如下
public static int getNum3(long n) { MyTask task = new MyTask(n); ForkJoinPool pool = new ForkJoinPool(); int res = pool.invoke(task); pool.shutdown(); return res; } static class MyTask extends RecursiveTask<Integer> { private long number; private static final Map<Long,Integer> map = new ConcurrentHashMap<>(); public MyTask(long number) { super(); this.number = number; } private synchronized void put(Long a,Integer b) { if(map.containsKey(a)) { System.out.println("had existed!"); }else { map.put(a, b); } } private Integer get(Long a) { System.out.println("success"); return map.get(a); } @Override protected Integer compute() { if(number<=1) { put(number, 0); return 0; }else if(map.containsKey(number)) { return get(number); } int res = 0; if((number&1) == 0) { MyTask task = new MyTask(number / 2); try { res = task.fork().get() + 1; put(number, res); } catch (Exception e) { e.printStackTrace(); } return res; }else { MyTask task1 = new MyTask((number-1) / 2); MyTask task2 = new MyTask((number+1) / 2); try { int a = task1.fork().get() + 2; int b = task2.fork().get() + 2; res = Math.min(a,b); put(number, res); } catch (Exception e) { e.printStackTrace(); } return res; } } }
这个解法是将第二种解法的递归算法转化成了非递归算法,同时保存了之前已经计算过的子问题的解,并且用到了java中的fork/join框架,至于为什么要用这个框架,原因是,不用它我不知道怎么把这个递归算法转化成非递归算法,望各路大神指点指点
对于这道题来说,第二种方式最快,第三种方式其次,最后是第一种方式,而且第一种方式计算的n的最大值,也远远小于后两种。
好了,就先写到这,人生的第一篇博文就此诞生!庆祝!虽然写的我自己看了都觉得很烂,但是一步一步来嘛