约瑟夫以及其变异体总结

Posted yonuyeung

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了约瑟夫以及其变异体总结相关的知识,希望对你有一定的参考价值。

学编程最重要的是学思想!!!

这些题都是尝试去找目标数所在位置,而不是去模拟每一次过程

目录:

约瑟夫

LC 390.消除游戏


约瑟夫

简单约瑟夫

题目链接:OnlineJudge

目录

约瑟夫

    简单约瑟夫

复杂约瑟夫

一、递归写法

二、迭代写法

思路:n 的范围足够小,所以我们直接模拟

记录出圈人数out,记录报数count,通过数组的状态判断是否出圈a[105],走到了哪个位置i

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
using namespace std;

int main()

    int n,m,s;
    cin>>n>>m>>s;
    int out,count,a[105],i;
    memset(a,-1,sizeof(a));
    out=0; count=0; i=s;
    while(out<n)
    
        for(;i<=n;i++)
        
            if(a[i]) count++;
            if(count==m)
                printf("%3d",i); count=0; out++; a[i]=0;
            
        
        i=1;
    
    return 0;

复杂约瑟夫

题目链接:力扣

 思路:n的范围太大了,所以我们要想办法简化算法或者找规律

约瑟夫环——公式法(递推公式)

它的思路相当于,每一次淘汰就把所有人重新编号,胜利者的编号每一次都往前移动m个单位。

 

 拓展:迭代和递归的理解和区别

一、递归写法

class Solution 
public:
    int f(int n,int m)
                if(n==1) return 0;
                else return (f(n-1,m)+m)%n;
            
    int lastRemaining(int n, int m) 
            int x;
            x=f(n,m);
            return x;
    
;

二、迭代写法

class Solution 
public:
    int lastRemaining(int n, int m) 
        int f = 0;
        for (int i = 2; i != n + 1; ++i) 
            f = (m + f) % i;
        
        return f;
    
;

 迭代是建立在递推公式上的,如果递归超时或者是想要求得最优解,可以考虑将递归改成迭代


LC 390.消除游戏

题目链接:​​​​​​https://leetcode-cn.com/problems/elimination-game/

本题与约瑟夫环类似,但又有区别。(疑问:解决约瑟夫环的最优方法是什么?)

不可能把每一个数列出来,然后一次一次地去模拟,所以我们要想办法进行简化

目录

一、自己的思路

二、简化算法

AC代码

三、递归写法


一、自己的思路

通过自己的观察:

1.每执行一次操作后,剩下的数为等差数列

2.不管n为多少,执行3次后,就只剩下一个数了(×)执行次数:⌊log2​n⌋+1

3.n为奇数的时候,会删去末尾的数;n为偶数的时候,不会删去末尾的数

自己有两个思路:

1.草稿纸上推出公式(x)

2.简化算法

二、简化算法

正确的思路:

每次删除后,参数的变化是这样的:

d->2d 

n->n/2

a0=a0或a0=a0+d

程序最优解的时间复杂度可达到O(logn)

我们把它当做等差数列,等差数列的第一项也就是最后一项就是答案

AC代码

class Solution 
public:
    int lastRemaining(int n) 
        int loop_cnt=0; //用来判断是从左到右还是从右到左
        int a0=1,d=1;
        while(n!=1)
        
            if(n%2!=0)
                a0=a0+d;
            else
            
                bool left_to_right = (loop_cnt%2==0);
                if(left_to_right) //左往右
                    a0=a0+d;
                else
                    a0=a0;
            
            n/=2;
            d*=2;
            loop_cnt++;
        
        return a0;
    
;

当然可以写得更简单一点:

class Solution 
public:
    int lastRemaining(int n) 
        int a0 = 1, d = 1;
        bool left_to_right = true;
        while(n > 1)
        
            if(left_to_right || n % 2 == 1)
                a0 += d;
            
            n /= 2;
            d *= 2;
            left_to_right = ! left_to_right;
        
        return a0;
    
;

三、递归写法

作者:daydayUppp

因为每次往下递归 , 数据规模减少 一半 , 所以 时间复杂度 为 : O(logn)

class Solution 
public:
    int help(int n,bool L2R) 
        // L2R = true 从左到右
        if(n == 1) return 1;// 最小子问题
        if(L2R) return 2 * help(n / 2,!L2R);// 左到右
        //  1 2 3 4 5 6 7 8
        //    2   4   6   8
        // 2*(1   2   3   4)  

        // 右到左
        // 奇数长度的情况
        if(n & 1) return 2 * help(n / 2,!L2R);
        //  1 2 3 4 5 6 7
        //    2   4   6  
        // 2*(1   2   3  )  
        // 偶数长度的情况
        return help(n / 2,!L2R) * 2 - 1;
        //   1 2 3 4 5 6 7 8
        //   1   3   5   7
        // 2*(1   2   3   4) - 1  
    
    int lastRemaining(int n) 
        return help(n,true);
    
;

LeetCode里面还有很多变态的解法,这里就不研究了。

以上是关于约瑟夫以及其变异体总结的主要内容,如果未能解决你的问题,请参考以下文章

约瑟夫问题及其变形

分别使用结构体和数组实现约瑟夫环(围圈报数问题之二)

数据结构--线性表及其应用 约瑟夫环

使用μJava进行变异体测试

Unity快速入门教程-详解预制体(Prefab)及其实例化Instantiate

基于全基因组测序数据鉴定结构变异的四大类算法总结