剑指Offer62: 圆圈中最后剩下的数字(约瑟夫环问题)

15 篇文章 0 订阅
9 篇文章 0 订阅
博客详细介绍了约瑟夫环问题,即n个数字排列成圆圈,每次删除第m个数字,求最后剩下的是哪个数字。通过递推公式和重新编号的策略,解析了如何求解这一问题,并提供了证明过程。
摘要由CSDN通过智能技术生成

题目: 0,1,...,n-1等n个数字排成一个圆圈,从数字0开始,每次从这个圆圈里删除第m个数字。求出这个圆圈里剩下的最后一个数字。例如,0、1、2、3、4这5个数字组成一个圆圈,从数字0开始每次删除第3个数字,则删除的前4个数字依次是2、0、4、1,最后剩下的数字是3.

删除过程:

loop 1: 删除从0开始的第三个位置上的数字2

loop 2: 重新编号,从3开始计数,第三个数字为0

依次类推,最后剩余数字3.

解题思路: 令f(n,m)表示从数列中删除第m个数字最后剩余数字的索引,则有递推式:

f(n,m)=\left\{\begin{matrix} 0& n=1\\ mod(f(n-1,m)+m,n)& n>1 \end{matrix}\right.

根据以上公式,可用递归或动态规划求解,代码如下。

/**
    * 剑指offer--环中最后的剩余数字(约瑟夫环)
    * 解法1: 模拟环形链表
    * 解法2: 找规律(推荐)
    * @param n ...... 编号0~n-1
    * @param m ...... 每次删除第m个数字
    * @return last remaining number
    * */
public static int LastRemaining_DP(int n, int m) {
    if (n < 1 || m < 1) {
        return -1;
    }

    int last = 0;

    for(int i = 2; i <= n; i++) {
        last = (last + m) % i;
    }//point: mod i
    return last;
}

public static int LastRemaining_Recur(int n, int m) {
    if (n < 1 || m < 1) {
        return -1;
    }

    if (n == 1) {
        return 0;
    }

    return (LastRemaining_Recur(n-1,m) + m) % n;
}

// 单链表实现
public static int LastRemaining_Solution(int n, int m) {
    if (n < 1 || m < 1) {
        return -1;
    }

    ListNode head = new ListNode(0);
    ListNode tail = head;

    for(int i = 1; i < n; i++) {
        tail.next = new ListNode(i);
        tail = tail.next;
    }

    tail.next = head;// 构造环形链表

    int length = n, index = (m - 2) % length;

    while (length > 1) {
        int i = 0;
        ListNode p = head;

        while (i != index) {
            p = p.next;
            i++;
        }

        head = p.next.next;
        p.next = head; // 删除结点

        length--;
        index = (m - 2) % length;
    }

    return head.val;
}

证明过程: 记方程f(n,m)为从n个数字中删除第m个数字之后最后剩下的数字,从编号为0~n-1的n个数字中删除第m个数字,即索引值为m-1的数字,第一个被删除的数字是(m-1)%n, 记k=(m-1)%n, 删除k之后,序列中剩余的数字为k+1,...,n-1,0,1,2...k-1,该序列的规律与最初序列的规律不同,记为g(n-1,m),表示从新序列中继续删除后剩余的最后一个数字,从而有f(n,m)=g(n-1,m).

对数列进行重新编号之后,对应的索引值为

k+1   ->   0

k+2   ->   1

...

n-1   ->   n-k-2

0   ->   n-k-1

1   ->   n-k

...

k-1   ->   n-2

令重新排列之后的索引值为自变量x,新编号为y

x在[k+1, n-1]范围内时,对应的有x-(k+1) = y-0 => y=x-k-1

x在[0,k-1]范围内时,对应的有x-0=y-(n-k-1) => y=x-k-1+n

综上,y=p(x)=(x-k-1+n)%n, 其反函数为y=(x+k+1)%n,根据新序列最后剩余的数字g(n-1,m)与原序列的对应关系,有:

f(n,m)=g(n-1,m)=p^{-1}(f(n-1,m))=mod(f(n-1,m)+k+1,n)

证明完毕

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值