一道阿里筆試題:如何用一行代碼解決約瑟夫環(huán)問題的

約瑟夫環(huán)問題算是很經(jīng)典的題了添吗,估計大家都聽說過,然后我就在一次筆試中遇到了悠汽,下面我就用 3 種方法來詳細(xì)講解一下這道題潜圃,最后一種方法學(xué)了之后保證讓你可以讓你裝逼缸棵。

問題描述:編號為 1-N 的 N 個士兵圍坐在一起形成一個圓圈,從編號為 1 的士兵開始依次報數(shù)(1谭期,2蛉谜,3…這樣依次報),數(shù)到 m 的 士兵會被殺死出列崇堵,之后的士兵再從 1 開始報數(shù)。直到最后剩下一士兵客燕,求這個士兵的編號鸳劳。

1、方法一:數(shù)組

在大一第一次遇到這個題的時候也搓,我是用數(shù)組做的赏廓,我猜絕大多數(shù)人也都知道怎么做涵紊。方法是這樣的:

用一個數(shù)組來存放 1,2幔摸,3 … n 這 n 個編號摸柄,如圖(這里我們假設(shè)n = 6, m = 3)

image.png

然后不停著遍歷數(shù)組,對于被選中的編號既忆,我們就做一個標(biāo)記驱负,例如編號 arr[2] = 3 被選中了,那么我們可以做一個標(biāo)記患雇,例如讓 arr[2] = -1跃脊,來表示 arr[2] 存放的編號已經(jīng)出局的了。


image.png

然后就按照這種方法苛吱,不停著遍歷數(shù)組酪术,不停著做標(biāo)記,直到數(shù)組中只有一個元素是非 -1 的翠储,這樣绘雁,剩下的那個元素就是我們要找的元素了。我演示一下吧:

image.png

這種方法簡單嗎援所?思路簡單庐舟,但是編碼卻沒那么簡單,臨界條件特別多任斋,每次遍歷到數(shù)組最后一個元素的時候继阻,還得重新設(shè)置下標(biāo)為 0,并且遍歷的時候還得判斷該元素時候是否是 -1废酷。感興趣的可以動手寫一下代碼瘟檩,用這種數(shù)組的方式做,千萬不要覺得很簡單澈蟆,編碼這個過程還是挺考驗(yàn)人的墨辛。

這種做法的時間復(fù)雜度是 O(n * m), 空間復(fù)雜度是 O(n);

2、方法二:環(huán)形鏈表

學(xué)過鏈表的人趴俘,估計都會用鏈表來處理約瑟夫環(huán)問題睹簇,用鏈表來處理其實(shí)和上面處理的思路差不多,只是用鏈表來處理的時候寥闪,對于被選中的編號太惠,不再是做標(biāo)記,而是直接移除疲憋,因?yàn)閺逆湵硪瞥粋€元素的時間復(fù)雜度很低凿渊,為 O(1)。當(dāng)然,上面數(shù)組的方法你也可以采用移除的方式埃脏,不過數(shù)組移除的時間復(fù)雜度為 O(n)搪锣。所以采用鏈表的解決方法如下:

1、先創(chuàng)建一個環(huán)形鏈表來存放元素:

image.png

2彩掐、然后一邊遍歷鏈表一遍刪除构舟,直到鏈表只剩下一個節(jié)點(diǎn),我這里就不全部演示了

image.png

代碼如下:

// 定義鏈表節(jié)點(diǎn)
class Node{   
int date;    
Node next;     
public Node(int date) { 
       this.date = date;   
   }
}

核心代碼

    public static int solve(int n, int m) {
        if(m == 1 || n < 2)
            return n;
        // 創(chuàng)建環(huán)形鏈表
        Node head = createLinkedList(n);
        // 遍歷刪除
        int count = 1;
        Node cur = head;
        Node pre = null;//前驅(qū)節(jié)點(diǎn)
        while (head.next != head) {
            // 刪除節(jié)點(diǎn)
            if (count == m) {
                count = 1;
                pre.next = cur.next;
                cur = pre.next;
            } else {
                count++;
                pre = cur;
                cur = cur.next;
            }
        }
        return head.date;
    }
 
    static Node createLinkedList(int n) {
        Node head = new Node(1);
        Node next = head;
        for (int i = 2; i <= n; i++) {
            Node tmp = new Node(i);
            next.next = tmp;
            next = next.next;
        }
        // 頭尾串聯(lián)
        next.next = head;
        return head;
    }

這種方法估計是最多人用的堵幽,時間復(fù)雜度為 O(n * m),空間復(fù)雜度是 O(n)狗超。

還有更好的方法嗎?答有谐檀,請往下看

方法三:遞歸

其實(shí)這道題還可以用遞歸來解決抡谐,遞歸是思路是每次我們刪除了某一個士兵之后,我們就對這些士兵重新編號桐猬,然后我們的難點(diǎn)就是找出刪除前和刪除后士兵編號的映射關(guān)系麦撵。

我們定義遞歸函數(shù) f(n,m) 的返回結(jié)果是存活士兵的編號溃肪,顯然當(dāng) n = 1 時免胃,f(n, m) = 1。假如我們能夠找出 f(n惫撰,m) 和 f(n-1羔沙,m) 之間的關(guān)系的話,我們就可以用遞歸的方式來解決了厨钻。我們假設(shè)人員數(shù)為 n, 報數(shù)到 m 的人就自殺扼雏。則剛開始的編號為

m - 1

m

m + 1

m + 2

進(jìn)行了一次刪除之后,刪除了編號為 m 的節(jié)點(diǎn)夯膀。刪除之后诗充,就只剩下 n - 1 個節(jié)點(diǎn)了,刪除前和刪除之后的編號轉(zhuǎn)換關(guān)系為:

刪除前 --- 刪除后

… --- …

m - 2 --- n - 2

m - 1 --- n - 1

m ---- 無(因?yàn)榫幪柋粍h除了)

m + 1 --- 1(因?yàn)橄麓尉蛷倪@里報數(shù)了)

m + 2 ---- 2

… ---- …

新的環(huán)中只有 n - 1 個節(jié)點(diǎn)诱建。且刪除前編號為 m + 1, m + 2, m + 3 的節(jié)點(diǎn)成了刪除后編號為 1蝴蜓, 2, 3 的節(jié)點(diǎn)俺猿。

假設(shè) old 為刪除之前的節(jié)點(diǎn)編號茎匠, new 為刪除了一個節(jié)點(diǎn)之后的編號,則 old 與 new 之間的關(guān)系為 old = (new + m - 1) % n + 1押袍。

這樣诵冒,我們就得出 f(n, m) 與 f(n - 1, m)之間的關(guān)系了,而 f(1, m) = 1.所以我們可以采用遞歸的方式來做谊惭。代碼如下:

注:有些人可能會疑惑為什么不是 old = (new + m ) % n 呢钙畔?主要是因?yàn)榫幪柺菑?1 開始的,而不是從 0 開始的驮履。如果 new + m == n的話苦锨,會導(dǎo)致最后的計算結(jié)果為 old = 0。所以 old = (new + m - 1) % n + 1.

int f(int n, int m){    if(n == 1)   return n;    return (f(n - 1, m) + m - 1) % n + 1;}

我去药磺,兩行代碼搞定告组,而且時間復(fù)雜度是 O(n),空間復(fù)雜度是O(1)癌佩,牛逼木缝!那如果你想跟別人說,我想一行代碼解決約瑟夫問題呢围辙?答是沒問題的我碟,如下:

int f(int n, int m){    return n == 1 ? n : (f(n - 1, m) + m - 1) % n + 1;}

臥槽,以后面試官讓你手寫約瑟夫問題姚建,你就扔這一行代碼給它矫俺。

總結(jié)

不過那次筆試時,并沒有用遞歸的方法做掸冤,而是用鏈表的方式做厘托,,稿湿,铅匹,,那時饺藤,不知道原來還能用一行代碼搞定的包斑,,涕俗,罗丰,歡迎各位大佬提供半行代碼搞定的方法!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末咽袜,一起剝皮案震驚了整個濱河市丸卷,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌询刹,老刑警劉巖谜嫉,帶你破解...
    沈念sama閱讀 211,194評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異凹联,居然都是意外死亡沐兰,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評論 2 385
  • 文/潘曉璐 我一進(jìn)店門蔽挠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來住闯,“玉大人瓜浸,你說我怎么就攤上這事”仍” “怎么了插佛?”我有些...
    開封第一講書人閱讀 156,780評論 0 346
  • 文/不壞的土叔 我叫張陵,是天一觀的道長量窘。 經(jīng)常有香客問我雇寇,道長,這世上最難降的妖魔是什么蚌铜? 我笑而不...
    開封第一講書人閱讀 56,388評論 1 283
  • 正文 為了忘掉前任锨侯,我火速辦了婚禮,結(jié)果婚禮上冬殃,老公的妹妹穿的比我還像新娘囚痴。我一直安慰自己,他們只是感情好审葬,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,430評論 5 384
  • 文/花漫 我一把揭開白布深滚。 她就那樣靜靜地躺著,像睡著了一般耳璧。 火紅的嫁衣襯著肌膚如雪成箫。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,764評論 1 290
  • 那天旨枯,我揣著相機(jī)與錄音蹬昌,去河邊找鬼。 笑死攀隔,一個胖子當(dāng)著我的面吹牛皂贩,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播昆汹,決...
    沈念sama閱讀 38,907評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼明刷,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了满粗?” 一聲冷哼從身側(cè)響起辈末,我...
    開封第一講書人閱讀 37,679評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎映皆,沒想到半個月后挤聘,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,122評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡捅彻,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,459評論 2 325
  • 正文 我和宋清朗相戀三年组去,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片步淹。...
    茶點(diǎn)故事閱讀 38,605評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡从隆,死狀恐怖诚撵,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情键闺,我是刑警寧澤寿烟,帶...
    沈念sama閱讀 34,270評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站艾杏,受9級特大地震影響韧衣,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜购桑,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,867評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望氏淑。 院中可真熱鬧勃蜘,春花似錦、人聲如沸假残。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽辉懒。三九已至阳惹,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間眶俩,已是汗流浹背莹汤。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留颠印,地道東北人纲岭。 一個月前我還...
    沈念sama閱讀 46,297評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像线罕,于是被迫代替她去往敵國和親止潮。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,472評論 2 348