剑指offer45:约瑟夫环

0,1,,n-1这n个数字排成一个圆圈,从数字0开始,每次从这个圆圈里删除第m个数字。求出这个圆圈里剩下的最后一个数字。

例如,012345个数字组成一个圆圈,从数字0开始每次删除第3个数字,则删除的前4个数字依次是2041,因此最后剩下的数字是3。


示例 1:

输入: n = 5, m = 3
输出: 3

示例 2:

输入: n = 10, m = 17
输出: 2





方法一:数学 + 递归

思路

题目中的要求可以表述为:给定一个长度为 n 的序列,每次向后数 m 个元素并删除,那么最终留下的是第几个元素?

这个问题很难快速给出答案。但是同时也要看到,这个问题似乎有拆分为较小子问题的潜质:如果我们知道对于一个长度 n - 1 的序列,留下的是第几个元素,那么我们就可以由此计算出长度为 n 的序列的答案。

算法

我们将上述问题建模为函数 f(n, m),该函数的返回值为最终留下的元素的序号。

首先,长度为 n 的序列会先删除第 m % n 个元素,然后剩下一个长度为 n - 1 的序列。那么,我们可以递归地求解 f(n - 1, m),就可以知道对于剩下的 n - 1 个元素,最终会留下第几个元素,我们设答案为 x = f(n - 1, m)。

由于我们删除了第 m % n 个元素,将序列的长度变为 n - 1。当我们知道了 f(n - 1, m) 对应的答案 x 之后,我们也就可以知道,长度为 n 的序列最后一个删除的元素,应当是从 m % n 开始数的第 x 个元素。因此有 f(n, m) = (m % n + x) % n = (m + x) % n。

官方解答就扔了个公式,还写错了(划掉,官解没错了,因为。。它改了),然后就得结论了。我们现在来捋一捋这公式到底咋推出来的。

我们有n个数,下标从0到n-1,然后从index=0开始数,每次数m个数,最后看能剩下谁。我们假设能剩下的数的**下标**为y,则我们把这件事表示为

f(n,m) = y

这个y到底表示了啥呢?注意,y是下标,所以就意味着你从index=0开始数,数y+1个数,然后就停,停谁身上谁就是结果。

行了,我们假设f(n-1,m)=x,然后来找一找f(n,m)f(n-1,m)到底啥关系。

f(n-1,m)=x意味着啥呢?意味着有n-1个数的时候从index=0开始数,数x+1个数你就找到这结果了。那我不从index=0开始数呢?比如我从index=i开始数?那很简单,你把上面的答案也往后挪i下,就得到答案了。当然了,你要是挪到末尾了你就取个余,从头接着挪。

于是我们来思考f(n,m)时考虑以下两件事:

    有n个数的时候,要划掉一个数,然后就剩n-1个数了呗,那划掉的这个数,**下标**是多少?
    划完了这个数,往后数,数x+1个数,停在谁身上谁就是我们的答案。当然了,数的过程中你得取余

**问题一**:有n个数的时候,划掉了谁?**下标**是多少?

因为要从0数m个数,那最后肯定落到了下标为m-1的数身上了,但这个下标可能超过我们有的最大下标(n-1)了。所以攒满n个就归零接着数,逢n归零,所以要模n。

所以有n个数的时候,我们划掉了下标为(m-1)%n的数字。

**问题二**:我们划完了这个数,往后数x+1下,能落到谁身上呢,它的下标是几?

你往后数x+1,它下标肯定变成了(m-1)%n +x+1,和第一步的想法一样,你肯定还是得取模,所以答案为[(m-1)%n+x+1]%n,则

f(n,m)=[(m-1)%n+x+1]%n

其中x=f(n-1,m)

我们化简它!

定理一:两个正整数a,b的和,模另外一个数c,就等于它俩分别模c,模完之后加起来再模。

(a+b)%c=((a%c)+(b%c))%c

定理二:一个正整数a,模c,模一遍和模两遍是一样的。

a%c=(a%c)%c

你稍微一琢磨就觉得,嗯,说得对。

所以

f(n,m)=[(m-1)%n+x+1]%n
      =[(m-1)%n%n+(x+1)%n]%n
      =[(m-1)%n+(x+1)%n]%n
      =(m-1+x+1)%n
      =(m+x)%n

剩下的故事你们就都知道了。





package com.atguigu.linkedlist;

import java.util.ArrayList;

public class Josepfu {
    public static void main(String[] args) {
        Josepfu m1=new Josepfu();

        System.out.println(m1.LastRemaining(3,2));


    }


    public int LastRemaining(int n,int m) {
        /**
         * 方法一:
         *
         */
        ArrayList<Integer> kids=new ArrayList<>();
        for (int i = 0; i < n; i++) {
            kids.add(i);
        }

        int index=-1;
        while(kids.size()>1){
            int count=0;
            while(count<m){
                count++;
                index++;
                if(index==kids.size()){
                    index=0;
                }
            }
            kids.remove(index);
            index--;
        }

return kids.get(0);



        //方法二

//        ArrayList<Integer> list = new ArrayList<>(n);
//        for (int i = 0; i < n; i++) {
//            list.add(i);
//        }
//        int idx = 0;
//        while (n > 1) {
//            idx = (idx + m - 1) % n;
//            list.remove(idx);
//            n--;
//        }
//        return list.get(0);
//    }


        //方法3


//        int f = 0;
//        for (int i = 2; i != n + 1; ++i)
//            f = (m + f) % i;
//        return f;


    }
}
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章