面试题 45,圆圈中最后剩下的数字(学会用已有的stl,比如用List构建环链表)(数学的美丽)

方法一
最直接的方法自然就是构建环链表模拟整个过程。
时间复杂度是O(mn),空间复杂度是O(n)。
我的代码:
#include <stdio.h> struct Node{ int value; Node* next; }; int ReturnLastValue(int n, int m){ if(n <= 0 || m <= 0) return -1; Node* head = NULL; Node* p = new Node(); head = p; p -> value = 0; p -> next = NULL; Node* previous = NULL; int i = 0, j = 0; for(i = 1; i < n; i++){ previous = p; p = new Node(); p -> value = i; p -> next = NULL; previous -> next = p; } p -> next = head; int count = 0; p = head; while(1){ if(p -> next == p) return p -> value; for(count = 0; count < m-1;count++, p = p -> next); //printf("deleted: %d \n", p -> value); p -> value = p -> next -> value; Node* temp = p -> next; p -> next = temp -> next; delete temp; } return NULL; } void Test(char* testName, unsigned int n, unsigned int m, int expected) { if(testName != NULL) printf("%s begins: \n", testName); if(ReturnLastValue(n, m) == expected) printf("Solution passed.\n"); else printf("Solution failed.\n"); printf("\n"); } void Test1() { Test("Test1", 5, 3, 3); } void Test2() { Test("Test2", 5, 2, 2); } void Test3() { Test("Test3", 6, 7, 4); } void Test4() { Test("Test4", 6, 6, 3); } void Test5() { Test("Test5", 0, 0, -1); } void Test6() { Test("Test6", 4000, 997, 1027); } int main() { Test1(); Test2(); Test3(); Test4(); Test5(); Test6(); return 0; }
书中使用了stl的List构建环链表。
学会使用stl是很重要的技能,面试也会考相关的特性和应用。
int LastRemaining_Solution1(unsigned int n, unsigned int m) { if(n < 1 || m < 1) return -1; unsigned int i = 0; list<int> numbers; for(i = 0; i < n; ++ i) numbers.push_back(i); list<int>::iterator current = numbers.begin(); while(numbers.size() > 1) { for(int i = 1; i < m; ++ i) { current ++; if(current == numbers.end()) current = numbers.begin(); } list<int>::iterator next = ++ current; if(next == numbers.end()) next = numbers.begin(); -- current; numbers.erase(current); current = next; } return *(current); }
方法二
这个是看了答案才想到的,自己尝试过使用数学推演来进行计算,脑子里琢磨了洗了个澡的时间,除了想出(m-1)%n的初步表示外,深入就想不出来了。
我们用f(n,m)表示n个数字每次删除m最终剩下的那个数。
我们推演一步,当n个数字第一次删除一个数字后,还剩n-1个数字,假设删除的数字是k,这剩下的n-1个数字中,下一轮第一个要被遍历是k+1,因此可以表示为:
k+1, k+2, ... n, 0, 1, ... k-1。
这里k其实就是(m-1)%n 公式一
我们用f'(n-1, m)表示对于这个循环序列,每次删除m最终剩下的那个数。
自然f(n,m) = f'(n-1, m) 公式二
如果我们可以进一步得到f'(n-1, m) 和 f(n-1, m)的关系,下面就会好办了。那么如何得到这层关系?
f(n-1, m)的意思是:对于序列 0, 1, 2...., n-2,每次删除m后剩下的那个数。
f'(n-1, m)的意思是:对于序列 k+1, k+2, ... n-1, 0, 1, ... k-1,每次删除m后剩下的那个数。
如果我们可以找到一个统一的映射公式,让第一个序列的每一个数字都可以映射到第二序列上的值。那么f(n-1, m)和f'(n-1, m)自然也满足这个映射关系。
这两个序列都是连续序列,不难推出映射公式就是 p(x) = (x-k-1)%n,x是第二序列的值。
我们想用f(n-1, m)来表示f'(n-1, m),这样联系公式二才能得到递推关系。
对于求p-1(x),其实可以分两步做,首先已知 0 <= x <= n,分情况讨论:x > k+1的话,x = y+k+1。
x <= k+1的话,y = x+k+1-n,这个时候因为x <= n, k < n,x+k+1-n和(x+k+1)%n结果一致。
因此p-1(x) = (x+k+1)%n
那么f'(n-1, m) = (f(n-1, m)+k+1)%n,带入公式一,f'(n-1, m) = (f(n-1, m)+m)%n
带入公式二,f(n, m) = (f(n-1, m)+m)%n
如果n=1的话,f(n, m) = 0
这就是数学的美丽之处,它让你穿过纷繁,化繁为简。
代码实现:
int LastRemaining_Solution2(unsigned int n, unsigned int m) { if(n < 1 || m < 1) return -1; int last = 0; for (int i = 2; i <= n; i ++) last = (last + m) % i; return last; }
------------------------------------------------
Felix原创,转载请注明出处,感谢博客园!
posted on 2014-03-09 13:54 Felix Fang 阅读(170) 评论(0) 收藏 举报
浙公网安备 33010602011771号