題目:算法
有一個整型數組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 }