问题描述:皇帝决定找出全国中最幸运的一个人,于是从全国选拔出 n 个很幸运的人,让这 n 个人围着圆桌进餐,可是怎么选择出其中最幸运的一个人呢?皇帝决定:从其中一个人从 1 开始报数,按顺序数到第 k 个数的人自动出局,然后下一个人从 1 开始报数,数到 k 的人出局……。如此直到最后只剩下约瑟夫一人,然后他就成为全国最幸运的人。请问约瑟夫最初的位置?(注:原问题略显暴力,故自创此趣味题目)
分析:把第一个开始报 1 的人标定为 1,然后按报数顺序依次标定其余的人为:2,3,……,n - 1,n。按规则进行淘汰,直到最后剩一个数字,这个数字就是约瑟夫的位置。
解决方案:
1. 模拟法(simulation)
数组模拟,时间复杂度最高为 O(n3) (当k≈n时 ) ,空间复杂度O(n)
1 /********** 用数组模拟 *************/ 2 void findNext(bool *out, int n, int &curPosition){ 3 if(!out[curPosition]) { 4 int pNext = (curPosition + 1) % n; 5 if(!out[pNext]) 6 curPosition = pNext; 7 else{ 8 curPosition = pNext; 9 while(out[curPosition]) 10 curPosition = (curPosition + 1) % n; 11 } 12 }else 13 { 14 while(out[curPosition]) 15 curPosition = (curPosition + 1) % n; 16 } 17 } 18 int josephus(int n, int k) 19 { 20 if(n < 1 || k < 0) return -1; 21 if(n == 1) return n; 22 bool *out = new bool[n]; /********* 记录是否出局 *********/ 23 for(int i = 0; i < n; ++i) 24 out[i] = false; 25 int current = 0; 26 int n2 = n; 27 while(n2 != 1) 28 { 29 int cnt = k; 30 while(--cnt) 31 findNext(out, n, current); 32 out[current] = true; 33 findNext(out, n, current); 34 --n2; 35 } 36 delete[] out; 37 out = NULL; 38 return (current + 1); 39 }
循环链表模拟:时间复杂度O(n),空间复杂度O(n)
/********** 循环链表 *************/ struct ListNode{ int val; ListNode * next; ListNode(int x):val(x), next(NULL) {} }; int josephus(int n, int k) { if(n < 1 || k < 1) return -1; if(n == 1) return n; ListNode *head = new ListNode(1); ListNode *prior = head; for(int i = 2; i <= n; ++i) { ListNode *tem= new ListNode(i); prior->next = tem; prior = prior->next; } prior->next = head; while(head->next != head) { int cnt = k; while(--cnt) { head = head->next; prior = prior->next; } prior->next = prior->next->next; ListNode *current = head; head = head->next; delete current; /*** 只释放堆内存空间,局部指针自动回收 ***/ } return head->val; }
2.建模法(modeling)
使用队列建模。
1 /********** 用队列(注:使用STL可简单化) *************/ 2 bool ERROR = false; 3 typedef int ELEM; 4 struct Node{ 5 ELEM val; 6 Node *next; 7 Node(ELEM e):val(e), next(NULL){} 8 }; 9 struct queue{ 10 queue():front(NULL), tail(NULL) {} 11 ELEM pop(); 12 void push(ELEM val); 13 bool empty(); 14 private: 15 Node *front; 16 Node *tail; 17 18 }; 19 ELEM queue::pop(){ 20 if(front == NULL){ 21 ERROR = true; 22 return -1; 23 }else{ 24 ELEM v = front->val; 25 front = front->next; 26 return v; 27 } 28 } 29 void queue::push(ELEM val){ 30 Node *p = new Node(val); 31 if(front == NULL) 32 front = tail = p; 33 else 34 { 35 tail->next = p; 36 tail = tail->next; 37 } 38 } 39 bool queue::empty(){ 40 if(front == NULL) 41 return true; 42 else 43 return false; 44 } 45 46 int josephus(int n, int k) 47 { 48 if(n < 1 || k < 1) return -1; 49 if(n == 1) return 1; 50 queue qu; 51 for(int i = 1; i <= n; ++i) 52 qu.push(i); 53 int result = 0; 54 while(!qu.empty()) 55 { 56 for(int i = 1; i <= k-1; ++i) 57 qu.push(qu.pop()); 58 result = qu.pop(); 59 } 60 return result; 61 }
3. 数学推理 && 动态规划
初始:0 1 ... (k-2) (k-1) k ... (N-1)
K 出局: 新的顺序:
k 0
... p ...
N-1 映 N - k - 1 P(x) = (x - k + N) mod N
0 射 N - k 令:y = P(x) = (x - k + N) mod N
1 N - k + 1 则,x = (y + k - N) mod N
... ... = (y + k) mod N
k-2 N - 2 P-1(x) = (x + k) mod N
设 f(N,k) 为最后所得的数字,则:
f(N,k) = P-1( f(N-1,k) ) = (f(N-1,k) + k) mod N
所以有如下递推公式:
int josephus(int n, int k) { if(n < 1 || k < 1) return -1; if(n == 1) return 1; int result = 0; for(int i = 2; i <= n; ++i) result = (result + k) % i; return result+1; }
另外,简洁的递归:(不推荐,递归栈太小,容易溢出)
int josephus(int n, int k) { if(n < 1 || k < 1) return -1; if(n == 1) return 1; else return ((josephus(n - 1, k) + k - 1) % n + 1); }
最后,当 k = 2 时,如下公式可直接求出:
代码为:
int n = 1000; cout<< 2*(n - pow(2.0, int(log((float)n) / log((float)2))))+1 <<endl;