约瑟夫以及其变异体总结

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

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

目录:

约瑟夫

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里面还有很多变态的解法,这里就不研究了。

猜你喜欢

转载自blog.csdn.net/qq_59414507/article/details/122274410