欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

詳解基于C++實(shí)現(xiàn)約瑟夫環(huán)問(wèn)題的三種解法

 更新時(shí)間:2021年06月16日 11:38:38   作者:bigsai  
約瑟夫環(huán)問(wèn)題是算法中相當(dāng)經(jīng)典的一個(gè)問(wèn)題,其問(wèn)題理解是相當(dāng)容易的,并且問(wèn)題描述有非常多的版本,并且約瑟夫環(huán)問(wèn)題還有很多變形,通過(guò)這篇約瑟夫問(wèn)題的講解,一定可以帶你理解透徹

一、前言

什么是約瑟夫環(huán)問(wèn)題?

約瑟夫環(huán)問(wèn)題在不同平臺(tái)被"優(yōu)化"描述的不一樣,例如在??蛣χ竜ffer叫孩子們的游戲,還有叫游戲,點(diǎn)名……最直接的感覺(jué)還是力扣上劍指offer62的描述:圓圈中最后剩下的數(shù)字。

問(wèn)題描述:

0,1,···,n-1這n個(gè)數(shù)字排成一個(gè)圓圈,從數(shù)字0開(kāi)始,每次從這個(gè)圓圈里刪除第m個(gè)數(shù)字(刪除后從下一個(gè)數(shù)字開(kāi)始計(jì)數(shù))。求出這個(gè)圓圈里剩下的最后一個(gè)數(shù)字。

例如,0、1、2、3、4這5個(gè)數(shù)字組成一個(gè)圓圈,從數(shù)字0開(kāi)始每次刪除第3個(gè)數(shù)字,則刪除的前4個(gè)數(shù)字依次是2、0、4、1,因此最后剩下的數(shù)字是3。

當(dāng)然,這里考慮m,n都是正常的數(shù)據(jù)范圍,其中

  • 1 <= n <= 10^5
  • 1 <= m <= 10^6

對(duì)于這個(gè)問(wèn)題,你可能腦海中有了印象,想著小時(shí)候村里一群孩子坐在一起,從某個(gè)開(kāi)始報(bào)數(shù)然后數(shù)到幾出列,下一個(gè)重新開(kāi)始一直到最后一個(gè)。

二、循環(huán)鏈表模擬

這個(gè)問(wèn)題最本質(zhì)其實(shí)就是循環(huán)鏈表的問(wèn)題,圍成一個(gè)圈之后,就沒(méi)有結(jié)尾這就是一個(gè)典型的循環(huán)鏈表嘛!一個(gè)一個(gè)順序報(bào)數(shù),那不就是鏈表的遍歷枚舉嘛!數(shù)到對(duì)應(yīng)數(shù)字的出列,這不就是循環(huán)鏈表的刪除嘛!

并且這里還有非常方便的地方:

  • 循環(huán)鏈表的向下枚舉不需要考慮頭尾問(wèn)題,直接node=node.next向下
  • 循環(huán)聊表的刪除也不需要考慮頭尾問(wèn)題,直接node.next=node.next.next刪除

當(dāng)然也有一些需要注意的地方

  • 形成環(huán)形鏈表很簡(jiǎn)單,只需要將普通鏈表的最后一個(gè)節(jié)點(diǎn)的next指向第一個(gè)節(jié)點(diǎn)即可
  • 循環(huán)鏈表中只有一個(gè)節(jié)點(diǎn)的時(shí)候停止返回,即node.next=node的時(shí)候
  • 刪除,需要找到待刪除的前面節(jié)點(diǎn),所以我們刪除計(jì)數(shù)的時(shí)候要少即一位,利用前面的那個(gè)節(jié)點(diǎn)直接刪除后面節(jié)點(diǎn)即可

這樣,思路明確,直接開(kāi)擼代碼:

class Solution {
    class node//鏈表節(jié)點(diǎn)
    {
        int val;
        public node(int value) {
            this.val=value;
        }
        node next;
    }
    public int lastRemaining(int n, int m) {
        if(m==1)return n-1;//一次一個(gè)直接返回最后一個(gè)即可
        node head=new node(0);
        node team=head;//創(chuàng)建一個(gè)鏈表
        for(int i=1;i<n;i++)
        {
            team.next=new node(i);
            team=team.next;
        }
        team.next=head;//使形成環(huán)
        int index=0;//從0開(kāi)始計(jì)數(shù)
        while (head.next!=head) {//當(dāng)剩余節(jié)點(diǎn)不止一個(gè)的時(shí)候
            //如果index=m-2 那就說(shuō)明下個(gè)節(jié)點(diǎn)(m-1)該刪除了
            if(index==m-2)
            {
                head.next=head.next.next;
                index=0;
            }
            else {
                index++;
            }
            head=head.next;
        }
        return head.val;
    }
}

當(dāng)然,這種算法太復(fù)雜了,大部分的OJ你提交上去是無(wú)法AC的,因?yàn)槌瑫r(shí)太嚴(yán)重了,具體的我們可以下面分析。

三、有序集合模擬

上面使用鏈表直接模擬游戲過(guò)程會(huì)造成非常嚴(yán)重非常嚴(yán)重的超時(shí),n個(gè)數(shù)字,數(shù)到第m個(gè)出列。因?yàn)閙如果非常大遠(yuǎn)遠(yuǎn)大于m,那么將進(jìn)行很多次轉(zhuǎn)圈圈。

所以我們可以利用求余的方法判斷等價(jià)最低的枚舉次數(shù),然后將其刪除即可,在這里你可以繼續(xù)使用自建鏈表去模擬,上面的while循環(huán)以及上面只需添加一個(gè)記錄長(zhǎng)度的每次求余算圈數(shù)即可:

int len=n;
while (head.next!=head) {
  if(index==(m-2)%len)
  {
    head.next=head.next.next;
    index=0;
    len--;
  }
  else {
    index++;
  }
  head=head.next;
}

但我們很多時(shí)候不會(huì)手動(dòng)去寫(xiě)一個(gè)鏈表模擬,我們會(huì)借助ArrayList和LinkedList去模擬,如果使用LinkedList其底層也是鏈表,使用ArrayList的話其底層數(shù)據(jù)結(jié)構(gòu)是數(shù)組。不過(guò)在使用List其代碼方法一致。

List可以直接知道長(zhǎng)度,也可刪除元素,使用List的難點(diǎn)是一個(gè)順序表怎們模擬成循環(huán)鏈表?

咱們仔細(xì)思考:假設(shè)當(dāng)前長(zhǎng)度為n,數(shù)到第m個(gè)(通過(guò)上面分析可以求余讓這個(gè)有效的m不大于n)刪除,在index位置刪除。那么刪除后剩下的就是n-1長(zhǎng)度,index位置就是表示第一個(gè)計(jì)數(shù)的位置,我們可以通過(guò)求余得知走下一個(gè)刪除需要多少步,那么下個(gè)位置怎么確定呢?

你可以分類討論看看走的次數(shù)是否越界,但這里有更巧妙的方法,可以直接求的下一次具體的位置,公式就是為:

index=(index+m-1)%(list.size());

因?yàn)閕ndex是從1計(jì)數(shù),如果是循環(huán)的再往前m-1個(gè)就是真正的位置,但是這里可以先假設(shè)先將這個(gè)有序集合的長(zhǎng)度擴(kuò)大若干倍,然后從index計(jì)數(shù)開(kāi)始找到假設(shè)不循環(huán)的位置index2,最后我們將這個(gè)位置index2%(集合長(zhǎng)度)即為真正的長(zhǎng)度。

使用這個(gè)公式一舉幾得,既能把上面m過(guò)大循環(huán)過(guò)多的情況解決,又能找到真實(shí)的位置,就是將這個(gè)環(huán)先假設(shè)成線性的然后再去找到真的位置,如果不理解的話可以再看看這個(gè)圖:

這種情況的話大部分的OJ是可以勉強(qiáng)過(guò)關(guān)的,面試官的層面也大概率差不多的,具體代碼為:

class Solution {
    public int lastRemaining(int n, int m) {
        if(m==1)
            return n-1;
        List<Integer>list=new ArrayList<>();
        for(int i=0;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);
    }
}

四、遞歸公式解決

我們回顧上面的優(yōu)化過(guò)程,上面用求余可以解決m比n大很多很多的情況(即理論上需要轉(zhuǎn)很多很多圈的情況)。但是還可能存在n本身就很大的情況,無(wú)論是順序表ArrayList還是鏈表LinkedList去頻繁查詢、刪除都是很低效的。

所以聰明的人就開(kāi)始從數(shù)據(jù)找一些規(guī)律或者關(guān)系。

先拋出公式:

f(n,m)=(f(n-1,m)+m)%n

f(n,m)指n個(gè)人,報(bào)第m個(gè)編號(hào)出列最終編號(hào)

下面要認(rèn)真看一下我的分析過(guò)程:

我們舉個(gè)例子,有0 1 2 3 4 5 6 7 8 9十個(gè)數(shù)字,假設(shè)m為3,最后結(jié)果可以先記成f(10,3),即使我們不知道它是多少。

當(dāng)進(jìn)行第一次時(shí)候,找到元素2 刪除,此時(shí)還剩9個(gè)元素,但起始位置已經(jīng)變成元素3。等價(jià)成3 4 5 6 7 8 9 0 1這9個(gè)數(shù)字重寫(xiě)開(kāi)始找。

此時(shí)這個(gè)序列最終剩下的一個(gè)值即為f(10,3),這個(gè)序列的值和f(9,3)不同,但是都是9個(gè)數(shù)且m等于3,所以其刪除位置是相同的,即算法大體流程是一致的,只是各位置上的數(shù)字不一樣。所以我們需要做的事情是找找這個(gè)序列上和f(9,3)值上有沒(méi)有什么聯(lián)系。

尋找過(guò)程中別忘記兩點(diǎn),首先可通過(guò)%符號(hào)對(duì)數(shù)字有效擴(kuò)充,即我們可以將3 4 5 6 7 8 9 0 1這個(gè)序列看成(3,4,5,6,7,8,9,10,11)%10.這里的10即為此時(shí)的n數(shù)值。

另外數(shù)值如果是連續(xù)的,那么最終一個(gè)結(jié)果的話是可以找到聯(lián)系的(差值為一個(gè)定制)。所以我們可以就找到f(10,3)和f(9,3)值之間結(jié)果的關(guān)系,可以看下圖:

所以f(10,3)的結(jié)果就可以轉(zhuǎn)化為f(9,3)的表達(dá),后面也是同理:

f(10,3)=(f(9,3)+3)%10

f(9,3)=(f(8,3)+3)%9

……

f(2,3)=(f(1,3)+3)%2

f(1,3)=0

這樣,我們就不用模擬操作,可以直接從數(shù)值的關(guān)系找到遞推的關(guān)系,可以輕輕松松的寫(xiě)下代碼:

class Solution {
    int index=0;
    public int lastRemaining(int n, int m) {
         if(n==1)
            return 0;      
        return (lastRemaining(n-1,m)+m)%n;
    }
}

但是遞歸效率因?yàn)橛袀€(gè)來(lái)回的規(guī)程,效率相比直接迭代差一些,也可從前往后迭代:

class Solution {
    public int lastRemaining(int n, int m) {
        int value=0;
            for(int i=1;i<=n;i++)
            {
                value=(value+m)%i;
            }
            return  value;
    }
}

五、結(jié)語(yǔ)

我想,通過(guò)本篇文章你應(yīng)該掌握和理解了約瑟夫環(huán)問(wèn)題,這種裸的約瑟夫環(huán)問(wèn)題出現(xiàn)的概率很大,考察很頻繁,鏈表模擬是根本思想,有序集合模擬鏈表是提升,而公式遞推才是最有學(xué)習(xí)價(jià)值的地方,如果你剛開(kāi)始接觸不理解可以多看幾遍。

以上就是詳解基于C++實(shí)現(xiàn)約瑟夫環(huán)問(wèn)題的三種解法的詳細(xì)內(nèi)容,更多關(guān)于C++ 約瑟夫環(huán)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • C++實(shí)現(xiàn)計(jì)算器功能

    C++實(shí)現(xiàn)計(jì)算器功能

    這篇文章主要為大家詳細(xì)介紹了C++實(shí)現(xiàn)計(jì)算器功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-02-02
  • C語(yǔ)言中動(dòng)態(tài)內(nèi)存管理初學(xué)者容易犯的6個(gè)錯(cuò)誤分享

    C語(yǔ)言中動(dòng)態(tài)內(nèi)存管理初學(xué)者容易犯的6個(gè)錯(cuò)誤分享

    本篇文章主要介紹了初學(xué)者使用C語(yǔ)言中動(dòng)態(tài)內(nèi)存管理的4個(gè)函數(shù)時(shí)最容易犯的6個(gè)錯(cuò)誤,以及如何避免這些錯(cuò)誤,文中的示例代碼講解詳細(xì),感興趣的可以了解一下
    2023-04-04
  • C語(yǔ)言實(shí)現(xiàn)圖書(shū)館管理系統(tǒng)

    C語(yǔ)言實(shí)現(xiàn)圖書(shū)館管理系統(tǒng)

    這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言實(shí)現(xiàn)圖書(shū)館管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-07-07
  • 一問(wèn)學(xué)會(huì)QT時(shí)間類

    一問(wèn)學(xué)會(huì)QT時(shí)間類

    QT獲取時(shí)間的類有3個(gè),分別是QDate、QTime、QDateTime,他們屬于QT的network模塊。本文詳細(xì)的介紹了這3個(gè)模塊的使用,感興趣的可以了解一下
    2023-04-04
  • 帶你了解C++的IO流

    帶你了解C++的IO流

    這篇文章主要介紹了C++ IO流的相關(guān)資料,幫助大家更好的理解和學(xué)習(xí)c++,感興趣的朋友可以了解下,希望能夠給你帶來(lái)幫助
    2021-09-09
  • C++標(biāo)準(zhǔn)C函數(shù)在各平臺(tái)編譯結(jié)果都相同

    C++標(biāo)準(zhǔn)C函數(shù)在各平臺(tái)編譯結(jié)果都相同

    今天小編就為大家分享一篇關(guān)于C++標(biāo)準(zhǔn)C函數(shù)在各平臺(tái)編譯結(jié)果都相同,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧
    2018-12-12
  • C語(yǔ)言實(shí)現(xiàn)簡(jiǎn)單圖書(shū)管理系統(tǒng)

    C語(yǔ)言實(shí)現(xiàn)簡(jiǎn)單圖書(shū)管理系統(tǒng)

    這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言實(shí)現(xiàn)圖書(shū)管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-01-01
  • C/C++內(nèi)存管理詳情

    C/C++內(nèi)存管理詳情

    這篇文章主要通過(guò)描述了C/C++內(nèi)存分布、C/C++的一些函數(shù)理方面來(lái)展開(kāi)C/C++內(nèi)存管理的內(nèi)容,需要的朋友請(qǐng)參考下文
    2021-08-08
  • C語(yǔ)言數(shù)據(jù)結(jié)構(gòu)之單向鏈表詳解

    C語(yǔ)言數(shù)據(jù)結(jié)構(gòu)之單向鏈表詳解

    單向鏈表(單鏈表)是鏈表的一種,其特點(diǎn)是鏈表的鏈接方向是單向的,對(duì)鏈表的訪問(wèn)要通過(guò)順序讀取從頭部開(kāi)始。本文將為大家詳細(xì)講講單向鏈表的實(shí)現(xiàn)與使用,需要的可以參考一下
    2022-08-08
  • C語(yǔ)言中花式退出程序的方式總結(jié)

    C語(yǔ)言中花式退出程序的方式總結(jié)

    在本篇文章當(dāng)中主要給大家介紹C語(yǔ)言當(dāng)中一些不常用的特性,比如在main函數(shù)之前和之后設(shè)置我們想要執(zhí)行的函數(shù),以及各種花式退出程序的方式,需要的可以參考一下
    2022-10-10

最新評(píng)論