约瑟夫问题

约瑟夫问题I

已知n个人坐成一圈,按顺时针由1开始给大家编号。然后由第一个人开始顺时针循环报数,数到m的人出局,循环此过程直到最后只剩一个人。给定两个int nm,要求编写函数返回最后一个人的编号。

https://leetcode-cn.com/circle/article/BOoxAL/

【方法1】循环链表(队列):

时间复杂度O(m*n), 空间复杂度O(n)

import java.util.*;

public class Joseph {
    public int getResult(int n, int m) {
        ListNode head = new ListNode();
        ListNode p = head;
        for (int i = 1; i < n; i++) {
            p.val = i;
            p.next = new ListNode();
            p = p.next;
        }
        p.val = n;
        p.next = head;
        while(p.next != p) {
            for (int i = 1; i < m; i++) {
                p = p.next;
            }
            if (p.next == p) {
                return p.val;
            }
            p.next = p.next.next;
        }
        return p.val;
    }
}

class ListNode{
    int val;
    ListNode next;
    public ListNode() {
    }
    public ListNode(int val) {
        this.val = val;
    }
}
【方法2】公式

设当前报号人编号为index,则第m个人的编号为index+m-1,此时设剩余人数为len
下 一 个 淘 汰 的 人 = { i n d e x + m − 1 , m < = l e n ( i n d e x + m − 1 ) % l e n , m > l e n 下一个淘汰的人 = \begin{cases} index+m-1, && m <= len\\ (index+m-1)\%len, && m > len\\ \end{cases} ={index+m1,(index+m1)%len,m<=lenm>len

所以每次从编号为index的人开始报数,则被淘汰的人编号为

index = (index+m-1)\%len
import java.util.*;

public class Joseph {
    public int getResult(int n, int m) {
        List<Integer> list = new ArrayList<>();
        for (int i = 1; i <= n; i++) {
            list.add(i);
        }
        // 开始报数人的编号的索引
        int index = 0;
        while (list.size() > 1) {
            index = (index + m - 1) % list.size();
            list.remove(index);
        }
        return list.get(0);
    }
}
【方法3递归】

递归公式

f(n,m)=(f(n-1,m)+m-1)%n+1
f(n,m)指n个人,报第m个编号淘汰最终编号

推导过程:

  • 当n=1时,最后一个淘汰也是第一个淘汰的为编号1

  • 当n>1时,如下图所示

    可以看出,设原编号为i,新编号为j,则可得到

    i = (j + m - 1) % n + 1
    

这样,我们就不用模拟操作,可以直接从数值的关系找到递推的关系,可以轻轻松松的写下代码:

import java.util.*;

public class Joseph {
    public int getResult(int n, int m) {
        if (n == 1) {
            return 1; 
        }
        return (getResult(n-1, m) + m - 1) % n + 1;
    }
}

迭代方式如下:

public class Joseph {
    public int getResult(int n, int m) {
        int value = 1;
        for (int i = 1; i <= n; i++) {
            value = (value + m - 1) % i + 1;
        }
        return value;
    }
}

约瑟夫问题II

现有n个人围坐一圈,顺时针给大家编号,第一个人编号为1,然后顺时针开始报数。第一轮依次报1,2,1,2…没报1的人出局。接着第二轮再从上一轮最后一个报数的人开始依次报1,2,3,1,2,3…没报1的人都出局。以此类推直到剩下以后一个人。现给定一个int n,要求返回最后一个人的编号。

【方法1】循环队列模拟
public class Joseph {
    public int getResult(int n) {
        ArrayList<Integer> left = new ArrayList<>();
        for (int i = 1; i <= n; i++) {
            left.add(i);
        }
        int m = 2;
        while (left.size() > m) {
            ArrayList<Integer> tmp = new ArrayList<>();
            // 为形成环预先占一个位置
            tmp.add(0);
            for (int i = 0; i < left.size(); i+=m) {
                tmp.add(left.get(i));
            }
            tmp.set(0, tmp.remove(tmp.size() - 1));
            left = tmp;
            m++;
        }
        return left.get(0);
    }
}
【方法2】递归

递归公式
f ( n , m ) = ( f ( l e f t , m + 1 ) − 2 ) ∗ m + 1 , 其 中 l e f t = { n / m , n % m = = 0 n / m + 1 , n % m ! = 0 f(n, m) = (f(left, m + 1) -2) * m + 1, 其中left = \begin{cases} n / m, && n \% m == 0\\ n / m + 1, && n \% m != 0\\ \end{cases} f(n,m)=(f(left,m+1)2)m+1,left={n/m,n/m+1,n%m==0n%m!=0

import java.util.*;

public class Joseph {
    public int getResult(int n) {
        return joseph(n, 2);
    }
    public int joseph(int n, int m) {
        int left = n % m == 0 ? n / m : n / m + 1;
        if (left <= m + 1) {
            return (left - 1) * m + 1;
        }
        int next = joseph(left, m + 1);
        return (next - 2) * m + 1;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值