<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          ?LeetCode刷題實(shí)戰(zhàn)77:組合

          共 4650字,需瀏覽 10分鐘

           ·

          2020-10-28 08:01

          算法的重要性,我就不多說了吧,想去大廠,就必須要經(jīng)過基礎(chǔ)知識(shí)和業(yè)務(wù)邏輯面試+算法面試。所以,為了提高大家的算法能力,這個(gè)公眾號(hào)后續(xù)每天帶大家做一道算法題,題目就從LeetCode上面選 !

          今天和大家聊的問題叫做?組合,我們先來看題面:

          https://leetcode-cn.com/problems/combinations/

          Given two integers n and k, return all possible combinations of k numbers out of 1 ... n.

          You may return the answer in any order.

          題意

          給定兩個(gè)整數(shù) n 和 k,返回 1 ... n 中所有可能的 k 個(gè)數(shù)的組合。

          樣例

          輸入: n = 4, k = 2
          輸出:
          [
          ??[2,4
          ],
          ??[3,4],
          ??[2,3],
          ??[1,2],
          ??[1,3],
          ??[1,4],
          ]


          解題

          https://www.cnblogs.com/techflow/p/13139689.html

          遞歸

          這是一個(gè)全組合問題,實(shí)際上我們之前做過全排列問題。我們來分析一下排列和組合的區(qū)別,可能很多人知道這兩者的區(qū)別,但是對(duì)于區(qū)別本身的理解和認(rèn)識(shí)不是非常深刻。
          排列和組合有一個(gè)巨大的區(qū)別在于,排列會(huì)考慮物體擺放的順序。也就是說同樣的元素構(gòu)成,只要這些元素一些交換順序,那么就會(huì)被視為是不同的排列。然而對(duì)于組合來說,是不會(huì)考慮物體的擺放順序的。只要是這些元素構(gòu)成,無論它們?cè)趺凑{(diào)換擺放順序,都是同一種組合。
          我們獲取全排列的時(shí)候用的是回溯法,我們當(dāng)然也可以用回溯法來獲取組合。但問題是,我們?cè)趺幢WC獲取到的組合都是元素的組成不同,而不是元素之間的順序不同呢?
          為了保證這一點(diǎn),需要用到一個(gè)慣用的小套路,就是通過下標(biāo)遞增來控制拿取元素的順序。如果我們限定了拿取元素的下標(biāo)是遞增的,那么就可以保證每一次拿取到的組合都是獨(dú)一無二的。所以我們就把這一點(diǎn)加在回溯法上即可,只要理解了,并不難實(shí)現(xiàn)。
          在代碼的實(shí)現(xiàn)當(dāng)中,我們用上了閉包,省略了幾個(gè)參數(shù)的傳遞,整體上來說編碼的難度降低了一些。

          class?Solution:
          ????def?combine(self, n: int, k: int)?-> List[List[int]]:
          ????????def?dfs(start, cur):
          ????????????# 如果當(dāng)前已經(jīng)拿到了K個(gè)數(shù)的組合,直接加入答案
          ????????????# 注意要做深拷貝,否則在之后的回溯過程當(dāng)中變動(dòng)也會(huì)影響結(jié)果
          ????????????if?len(cur) == k:
          ????????????????ret.append(cur[:])
          ????????????????return
          ????????????
          ????????????# 從start+1的位置開始遍歷
          ????????????for?i in?range(start+1, n):
          ????????????????cur.append(i+1)
          ????????????????dfs(i, cur)
          ????????????????# 回溯
          ????????????????cur.pop()
          ????????????????
          ????????ret = []
          ????????dfs(-1, [])
          ????????return?ret

          迭代

          這題并不是只有一種做法,我們也可以不用遞歸實(shí)現(xiàn)算法。不用遞歸意味著沒有系統(tǒng)幫助我們建棧存儲(chǔ)中間信息了,需要我們自己把迭代過程當(dāng)中所有變量的關(guān)系整理清楚。
          我們假設(shè)n=8,k=3,那么在所有合法的組合當(dāng)中,最小的組合一定是[1,2,3],最大的組合一定是[6,7,8]。如果我們保證組合當(dāng)中的元素是有序排列的,那么組合之間的大小關(guān)系也是可以確定的。進(jìn)而我們可以思考設(shè)計(jì)一種方案,使得我們可以從最小的組合[1,2,3]一直迭代到[6,7,8],并且我們還要保證在迭代的過程當(dāng)中,組合當(dāng)中元素的順序不會(huì)被打亂。
          我們可以想象成這n個(gè)數(shù)在一根“直尺”上排成了一行,我們有k個(gè)滑動(dòng)框在上面移動(dòng)。這k個(gè)滑動(dòng)框取值的結(jié)果就是n個(gè)元素中選取k個(gè)的組合,并且由于滑動(dòng)框之間是不能交錯(cuò)的,所以保證了這k個(gè)值是有序的。我們要做的就是設(shè)計(jì)一種移動(dòng)滑動(dòng)框的算法,使得能夠找到所有的組合情況。
          我們可以想象一下,一開始的時(shí)候滑動(dòng)框都聚集在最左邊,我們要移動(dòng)只能移動(dòng)最右側(cè)的滑動(dòng)框。我們把滑動(dòng)框從k移動(dòng)到了k+1,那么這個(gè)時(shí)候它的右側(cè)有k-1個(gè)滑動(dòng)框,一共有k個(gè)位置。
          那么這個(gè)問題其實(shí)轉(zhuǎn)化成了k個(gè)元素當(dāng)中取k-1個(gè)組合的子問題。我們把1-k的這個(gè)部分看成是新的“直尺”,我們要在其中移動(dòng)k-1個(gè)滑動(dòng)框獲取所有的組合。首先,我們需要把這k-1個(gè)滑動(dòng)框全部移動(dòng)到左側(cè),然后再移動(dòng)其中最右側(cè)的滑動(dòng)框。然后循環(huán)往復(fù),直到所有的滑動(dòng)框都往右移動(dòng)了一格為止,這其實(shí)是一個(gè)遞歸的過程。
          我們不去深究這個(gè)遞歸的整個(gè)過程,我們只需要理解清楚其中的幾個(gè)關(guān)鍵點(diǎn)就可以了。首先,對(duì)于每一次遞歸來說,我們只會(huì)移動(dòng)這個(gè)遞歸范圍內(nèi)最右側(cè)的滑動(dòng)框,其次我們清楚每一次遞歸過程中的起始狀態(tài)。開始狀態(tài)就是所有的滑動(dòng)框全部集中在“直尺”的最左側(cè),結(jié)束狀態(tài)就是全部集中在最右側(cè)。
          我們把上面的邏輯整理一下,假設(shè)我們經(jīng)過一系列操作之后,m個(gè)滑動(dòng)框全部移動(dòng)到了長度為n的直尺的最右側(cè)。這就相當(dāng)于的組合都已經(jīng)獲取完了。如果n+1的位置還有滑動(dòng)框,并且它的右側(cè)還可以移動(dòng),那么我們需要將它往右移動(dòng)一個(gè),到n+2的位置。這個(gè)時(shí)候剩下的局面就是,為了獲取這些組合,我們需要把這m個(gè)滑動(dòng)框全部再移動(dòng)到直尺的最左側(cè),重新開始移動(dòng)。
          我們?cè)趯?shí)現(xiàn)的時(shí)候當(dāng)然沒有滑動(dòng)框,我們可以用一個(gè)數(shù)組記錄滑動(dòng)框當(dāng)中的元素。
          我先用遞歸寫一下這段邏輯:

          class Solution:
          ????def combine(self, n: int, k: int) -> List[List[int]]:
          ????????def comb(window, m, ret):
          ????????????ret.append(window[:-1])

          ????????????# 如果第m位的滑動(dòng)框不超過直尺的范圍并且m右側(cè)的滑動(dòng)框
          ????????????while?window[m] < min(n - k?+ m?+ 1, window[m+1] - 1):
          ????????????????# 向右滑動(dòng)一位
          ????????????????window[m] += 1
          ????????????????# 如果m左側(cè)還有滑動(dòng)框,遞歸
          ????????????????if?m?> 0:
          ????????????????????# 把左側(cè)的滑動(dòng)框全部移動(dòng)到最左側(cè)
          ????????????????????window[:m] = range(1, m+1)
          ????????????????????comb(window, m-1, ret)
          ????????????????else:
          ????????????????????# 否則記錄答案
          ????????????????????ret.append(window[:-1])

          ????????????????
          ????????ret?= []
          ????????window = list(range(1, k+1))
          ????????# 額外多放一個(gè)滑動(dòng)框作為標(biāo)兵
          ????????window.append(n+1)
          ????????comb(window, k-1, ret)
          ????????return?ret

          這種解法的速度比上面正規(guī)遞歸的速度快了許多,因?yàn)槲覀冞f歸的過程當(dāng)中做了諸多限制,剪掉了很多無關(guān)的情況,相當(dāng)于做了極致的剪枝。
          最關(guān)鍵的是上面的這段邏輯我們是可以用循環(huán)實(shí)現(xiàn)的,所以我們可以用循環(huán)來將遞歸的邏輯展開,就得到了下面這段代碼。

          class?Solution:
          ????def?combine(self, n:?int, k:?int)?-> List[List[int]]:
          ????????# 構(gòu)造滑動(dòng)框
          ????????window = list(range(1, k + 1)) + [n + 1]
          ????????
          ????????ret, j = [], 0

          ????????while?j < k:
          ????????????# 添加答案
          ????????????ret.append(window[:k])

          ????????????j = 0
          ????????????# 從最左側(cè)的滑動(dòng)框開始判斷
          ????????????# 如果滑動(dòng)框與它右側(cè)滑動(dòng)框挨著,那么就將它移動(dòng)到最左側(cè)
          ????????????# 因?yàn)樗覀?cè)的滑動(dòng)框一定會(huì)向右移動(dòng)
          ????????????while?j < k and?window[j + 1] == window[j] + 1:
          ????????????????window[j] = j + 1
          ????????????????j += 1
          ????????????# 連續(xù)挨著最右側(cè)的滑動(dòng)框向右移動(dòng)一格
          ????????????window[j] += 1
          ????????????
          ????????return?ret

          這段代碼雖然非常精煉,但是很難理解,尤其是你沒能理解上面遞歸實(shí)現(xiàn)的話,會(huì)更難理解。所以我建議,先把遞歸實(shí)現(xiàn)的滑動(dòng)框的方法理解了,再來理解不含遞歸的這段,會(huì)容易一些。

          總結(jié)

          我們通過回溯法求解組合的方法應(yīng)該是最簡單也是最基礎(chǔ)的,難度也不大。相比之下后面一種方法則要困難許多,我們直接去啃,往往不得要領(lǐng)。既會(huì)疑惑為什么這樣可以保證能獲得所有的組合,又會(huì)不明白其中具體的實(shí)現(xiàn)邏輯。所以如果想要弄明白第二種方法,一定要從滑動(dòng)框這個(gè)模型出發(fā)
          從代碼實(shí)現(xiàn)的角度來說,滑動(dòng)框方法的遞歸解法比非遞歸的解法還要困難。因?yàn)檫f歸條件以及邏輯都比較復(fù)雜,還涉及到存儲(chǔ)答案的問題。但是從理解上來說,遞歸的解法更加容易理解一些,非遞歸的算法往往會(huì)疑惑于j這個(gè)指針的取值。所以如果想要理解算法的話,可以從遞歸的代碼入手,想要實(shí)現(xiàn)代碼的話,可以從非遞歸的方法入手。
          這道題目非常有意思,值得大家細(xì)細(xì)思考。
          好了,今天的文章就到這里,如果覺得有所收獲,請(qǐng)順手點(diǎn)個(gè)在看或者轉(zhuǎn)發(fā)吧,你們的支持是我最大的動(dòng)力。


          上期推文:

          LeetCode40-60題匯總,速度收藏!
          LeetCode刷題實(shí)戰(zhàn)61:旋轉(zhuǎn)鏈表
          LeetCode刷題實(shí)戰(zhàn)62:不同路徑
          LeetCode刷題實(shí)戰(zhàn)63:不同路徑 II
          LeetCode刷題實(shí)戰(zhàn)64:最小路徑和
          LeetCode刷題實(shí)戰(zhàn)66:加一
          LeetCode刷題實(shí)戰(zhàn)67:二進(jìn)制求和
          LeetCode刷題實(shí)戰(zhàn)68:文本左右對(duì)齊
          LeetCode刷題實(shí)戰(zhàn)69:x 的平方根
          LeetCode刷題實(shí)戰(zhàn)70:爬樓梯
          LeetCode刷題實(shí)戰(zhàn)71:簡化路徑
          LeetCode刷題實(shí)戰(zhàn)72:編輯距離
          LeetCode刷題實(shí)戰(zhàn)73:矩陣置零
          LeetCode刷題實(shí)戰(zhàn)74:搜索二維矩陣
          LeetCode刷題實(shí)戰(zhàn)75:顏色分類
          LeetCode刷題實(shí)戰(zhàn)76:最小覆蓋子串

          瀏覽 33
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  AV的天堂 | 蜜臀AV午夜| 麻豆美女在线 | 久久精品黄视频 | 国产精彩无码视频 |