輸入一個數字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的最大值,也遠遠小於后兩種。

  好了,就先寫到這,人生的第一篇博文就此誕生!慶祝!雖然寫的我自己看了都覺得很爛,但是一步一步來嘛

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM