常用算法之排列問(wèn)題

遞歸算法

以數(shù)組[1, 2, 3, 4]為例输虱,返回全排列

分析:

問(wèn)題可以拆分為

  • 1 - 2,3,4 (表示提前元素1和剩余元素2,3,4的全排列)
  • 2 - 1,3,4
  • 3 - 1,2,4
  • 4 - 1,2,3

以上四種情況的并集

var permutation = function (nums) {
    let result = []

    function swap(i, j) {
        let t = nums[i]
        nums[i] = nums[j]
        nums[j] = t
    }

    function recall(from, to) {
        if (from === to) {
            result.push([...nums])
            return
        }
        for (let i = from; i <= to; i++) {  // 遍歷所有元素
            swap(from, i)               // 讓數(shù)組中不同元素提前
            recall(from + 1, to)        // 剩下元素做全排列
            swap(from, i)               // 恢復(fù)成原來(lái)的數(shù)組
        }
    }

    recall(0, nums.length - 1)

    return result
}

拓展

如果數(shù)組中元素有重復(fù)的情況,如何處理洲劣?

以[1,2课蔬,2囱稽,3]為例

問(wèn)題拆分

  • 1 - 2,2,3 (表示1和2,3二跋,4的全排列)
  • 2 - 1,2,3
  • 3 - 1,2,2

對(duì)于下標(biāo)為1战惊,2的兩個(gè)元素值都為2,所以只需要進(jìn)行一次求解2和1扎即,2吞获,3全排列的操作

var permutation = function (nums) {
    let result = []

    function swap(i, j) {
        let t = nums[i]
        nums[i] = nums[j]
        nums[j] = t
    }

    function recall(from, to) {
        if (from === to) {
            result.push([...nums])
            return
        }
        let swapped = new Set()             // 提前元素集合
        for (let i = from; i <= to; i++) {  // 遍歷所有元素
            if (swapped.has(nums[i])){      // 判斷當(dāng)前元素是否已經(jīng)存在被提前的情況
                continue
            }
            swapped.add(nums[i])        // 加入提前元素集合
            swap(from, i)               // 讓數(shù)組中不同元素提前
            recall(from + 1, to)        // 剩下元素做全排列
            swap(from, i)               // 恢復(fù)成原來(lái)的數(shù)組
        }
    }

    recall(0, nums.length - 1)

    return result
}

非遞歸算法

以 A[1,2谚鄙,3各拷,4,5]為例

思考

依次從小到大輸出闷营,即為所有全排列

步驟

  • 求出最小數(shù)列(所有元素順序?yàn)檫f增的)
  • 遞增烤黍,求當(dāng)前數(shù)列的下一個(gè)數(shù)列
  • 直到最大數(shù)列(所有元素順序?yàn)檫f減的)結(jié)束

問(wèn)題:如何得到比當(dāng)前數(shù)列大的下一個(gè)數(shù)列呢?

  • 從后往前找傻盟,直到A[j - 1] < A[j]
  • 找到[j, A.length-1] 中 最小的元素Min[j, A.length-1] 速蕊,交換A[j-1]和Min[j, A.length-1]
  • 將[j, A.length - 1]反轉(zhuǎn)

通過(guò)以上三步即可得到比當(dāng)前數(shù)列大的下一個(gè)數(shù)列

/**
 * 全排列
 * 非遞歸算法
 */
var permutation = function (nums) {
    let result = []

    function swap(i, j) {
        let tmp = nums[i]
        nums[i] = nums[j]
        nums[j] = tmp
    }

    function reverse(start, end) {
        if (start >= end) {
            return
        }
        swap(start, end)
        reverse(++start, --end)
    }

    function minIndex(start, end) {
        if (start >= end) {
            return start
        }
        let min = Number.MAX_SAFE_INTEGER, minIndex = start
        for (let i = start; i <= end; i++) {
            if (nums[i] > nums[start - 1]) {
                min = Math.min(nums[i], min)
                minIndex = i
            }
        }
        return minIndex
    }

    function next(nums) {                               // 遞增,求當(dāng)前數(shù)列的下一個(gè)數(shù)列
        hasNext = false
        for (let j = nums.length - 1; j >= 1; j--) {
            if (nums[j - 1] >= nums[j]) {               // 元素遞減,繼續(xù)向隊(duì)列頭部遍歷
                continue
            }
            hasNext = true
            let pos = minIndex(j, nums.length - 1)      // 求[j, length-1]中大于nums[j-1]的最小值下標(biāo)
            swap(pos, j - 1)                            // 交換j-1和Min[j, length-1]
            reverse(j, nums.length - 1)                 // 反轉(zhuǎn) [j, length-1]
            break
        }
        return nums
    }

    nums = nums.sort(function (a, b) {
        return a - b
    })
    let hasNext = true
    while (hasNext) {
        result.push([...nums])
        nums = next(nums)
    }
    return result
}

思考:如果有重復(fù)元素怎么辦娘赴?

解答:因?yàn)槭前催f增順序進(jìn)行排列的规哲,重復(fù)也不會(huì)有影響。

閱讀原文

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末诽表,一起剝皮案震驚了整個(gè)濱河市唉锌,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌竿奏,老刑警劉巖袄简,帶你破解...
    沈念sama閱讀 219,490評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異议双,居然都是意外死亡痘番,警方通過(guò)查閱死者的電腦和手機(jī)捉片,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門平痰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)汞舱,“玉大人,你說(shuō)我怎么就攤上這事宗雇“何撸” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 165,830評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵赔蒲,是天一觀的道長(zhǎng)泌神。 經(jīng)常有香客問(wèn)我,道長(zhǎng)舞虱,這世上最難降的妖魔是什么欢际? 我笑而不...
    開(kāi)封第一講書人閱讀 58,957評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮矾兜,結(jié)果婚禮上损趋,老公的妹妹穿的比我還像新娘。我一直安慰自己椅寺,他們只是感情好浑槽,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,974評(píng)論 6 393
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著返帕,像睡著了一般桐玻。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上荆萤,一...
    開(kāi)封第一講書人閱讀 51,754評(píng)論 1 307
  • 那天镊靴,我揣著相機(jī)與錄音,去河邊找鬼观腊。 笑死邑闲,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的梧油。 我是一名探鬼主播苫耸,決...
    沈念sama閱讀 40,464評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼儡陨!你這毒婦竟也來(lái)了褪子?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤骗村,失蹤者是張志新(化名)和其女友劉穎嫌褪,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體胚股,經(jīng)...
    沈念sama閱讀 45,847評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡笼痛,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,995評(píng)論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片缨伊。...
    茶點(diǎn)故事閱讀 40,137評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡摘刑,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出刻坊,到底是詐尸還是另有隱情枷恕,我是刑警寧澤,帶...
    沈念sama閱讀 35,819評(píng)論 5 346
  • 正文 年R本政府宣布谭胚,位于F島的核電站徐块,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏灾而。R本人自食惡果不足惜胡控,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,482評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望旁趟。 院中可真熱鬧铜犬,春花似錦、人聲如沸轻庆。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 32,023評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)余爆。三九已至纷宇,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間蛾方,已是汗流浹背像捶。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,149評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留桩砰,地道東北人拓春。 一個(gè)月前我還...
    沈念sama閱讀 48,409評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像亚隅,于是被迫代替她去往敵國(guó)和親硼莽。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,086評(píng)論 2 355