面试题 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;
}

 

 

posted on 2014-03-09 13:54  Felix Fang  阅读(170)  评论(0)    收藏  举报

导航