进阶之路

首页 新随笔 管理

问题描述:皇帝决定找出全国中最幸运的一个人,于是从全国选拔出 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 }
Code

 

循环链表模拟:时间复杂度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 }
Code

 

 

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;

 

 

 

posted on 2014-03-24 00:16  进阶之路  阅读(1173)  评论(0编辑  收藏  举报