刪數字 -- 2016華爲筆試題

題目:算法

有一個整型數組a[n]順序存放0 ~ n-1,要求每隔兩個數刪掉一個數,到末尾時循環至開頭繼續進行,求最後一個被刪掉的數的原始下標位置。數組

以 8 個數(n=8)爲例:{0,1,2,3,4,5,6,7},測試

0->1->2(刪除)->3->4->5(刪除)->6->7->0(刪除),如此循環直到最後一個數被刪除。this

 

思路:spa

這題就是約瑟夫環問題,有3種解法,code

第一種就是構建循環鏈表模擬刪數字的過程,可是時間空間複雜度較高,blog

第二種是用LinkedList模擬刪數字過程,相較於第一種就是代碼量會少一點,get

 

第三種是用動態規劃求解,首先寫出狀態轉移方程,io

設f(n)爲數組長度爲n時按每隔m個數刪除一個數,最後一個剩下的數的數值,for循環

f(n-1)爲數組長度爲n-1時按每隔m個數刪除一個數,最後一個剩下的數的數值,依此類推,

則對於長度爲n的數組,第一次刪除的數爲m,那麼下一輪遍歷是從m+1開始,即剩下要遍歷的元素按順序爲 m+1, m+2, ... n-1, 0, 1, ... m-1  ①,

對於長度爲n-1的數組,一開始要遍歷的元素按順序爲 0, 1 ... n-m-2, n-m-1, n-m,  ... n-2  ②,

由於①和②中元素的個數相等,如今將①②中的元素一一對應,

又由於①②都是每隔m個數刪除一個數,因此①中最後剩下的數f(n)和②中最後剩下的數f(n-1) 必定在同一個位置,

經過找規律能夠發現①和②中對應的數的關係爲 ①中對應的數 = (②中對應的數+m+1) % n,

而剛纔已經推出了f(n)和f(n-1)就是對應的數,因此f(n) = (f(n-1)+m+1) % n, 到此狀態轉移方程就求出來了

邊界的話很明顯是 f(1) = 0,

有了狀態轉移方程和邊界就能夠用動態規劃算法(寫for循環)高效地求出答案. 

 

 

下面給出第一種和第三種解法的完整代碼:

  1 package ustb.huawei;
  2 
  3 
  4 /*
  5     題目:有一個整型數組a[n]順序存放0 ~ n-1,要求每隔兩個數刪掉一個數,到末尾時循環至開頭繼續進行,求最後一個被刪掉的數的原始下標位置。
  6  
  7      以 8 個數(n=8)爲例:{0,1,2,3,4,5,6,7},
  8 
  9    0->1->2(刪除)->3->4->5(刪除)->6->7->0(刪除),如此循環直到最後一個數被刪除。 
 10  
 11 */
 12 public class DeleteNum {
 13     
 14     private static class LinkedNode {
 15         
 16         int value;
 17         LinkedNode next;
 18         
 19         LinkedNode(int value) {
 20             
 21             this.value = value;
 22         }
 23 
 24         public int getValue() {
 25             return value;
 26         }
 27     }
 28     
 29     
 30     
 31     
 32     /**
 33      * 
 34      * 解法1: 使用循環鏈表模擬刪數字的過程,
 35      * @param n  n爲數組的長度
 36      * @param m  每隔m個數刪除一個數字
 37      * @return
 38      */
 39     public static int getLastNum_1(int n, int m) {
 40         
 41         if (n < 1 || m < 1) {
 42             throw new RuntimeException("輸入的參數不合法");
 43         }
 44         
 45         //初始化循環鏈表
 46         LinkedNode head = new LinkedNode(0);
 47         LinkedNode tail = head;
 48         for (int i = 1; i < n; i++) {
 49              tail.next = new LinkedNode(i);
 50              tail = tail.next;
 51         }
 52         
 53         //上面的循環出來後tail指向 new LinkedNode(n-1);因此下面讓tail.next指向head,即完成了循環鏈表的初始化
 54         tail.next = head;
 55         
 56         //模擬刪數字過程
 57         LinkedNode currNode = head;
 58         int count = 1;  
 59         while (currNode != currNode.next) { //當只剩一個節點時就會跳出循環
 60             if (count == m) { //每遍歷m個數刪一個數
 61                 currNode.next = currNode.next.next;
 62                 currNode = currNode.next;
 63                 count = 1;
 64             } else { //還沒遍歷到m個數就繼續遍歷
 65                 currNode = currNode.next;
 66                 count++;
 67             }
 68         }
 69         
 70         return currNode.getValue();    
 71     }
 72     
 73     
 74     
 75     
 76     /*
 77      解法2:找出規律,用公式求解
 78      公式爲:
 79         f(n) = (f(n-1) + m + 1) % n;
 80         f(1) = 0;
 81      其中f(n)爲數組長度爲n時按每隔m個數刪除一個數,最後一個剩下的數的數值,
 82      */
 83     public static int getLastNumByMath(int n, int m) {
 84         
 85         if (n < 1 || m < 1) {
 86             throw new RuntimeException("輸入的參數不合法");
 87         }
 88         
 89         int lastNum = 0; //N爲1時的lastNum
 90         
 91         for (int i = 2; i < n+1; i++) { //求出N爲n時的lastNum
 92             lastNum = (lastNum + m + 1) % i;
 93         }
 94         
 95         return lastNum;
 96     }
 97     
 98     
 99     
100     //測試
101     public static void main(String[] args) {
102         
103         System.out.println(getLastNum_1(8, 2));
104         
105         System.out.println(getLastNumByMath(8, 2));
106 
107 
108     }
109     
110 }
相關文章
相關標籤/搜索