39、組合總和 | 演算法(leetode,附思維導圖 + 全部解法)300題

碼農三少發表於2022-01-15

零 標題:演算法(leetode,附思維導圖 + 全部解法)300題之(39)組合總和

碼農三少 ,一個致力於編寫極簡、但齊全題解(演算法)的博主

一 題目描述

題目描述
題目描述

二 解法總覽(思維導圖)

思維導圖

三 全部解法

1 方案1

1)程式碼:

// 方案1 “回溯(本質:遞迴)法”
// 技巧:說白了,就是通過回溯去窮舉所有的情況,根據當前情況進行不同的處理。

// 思路:
// 1)狀態初始化
// 2)呼叫 - 回溯
// 3)返回結果 resList 
var combinationSum = function(candidates, target) {
    const dfs = (curIndex, l, curSum, target, curArr, resList) => {
        // 1)遞迴出口
        if (curSum === target) {
            // 注:需要使用 slice 獲取其副本值!
            resList.push(curArr.slice());
            return;
        }
        if (curIndex >= l || curSum > target) {
            return;
        }

        // 2)遞迴主體(“核心:回溯 = 選 + 不選”)
        // 2.1)選
        curSum += candidates[curIndex];
        curArr.push(candidates[curIndex]);
        dfs(curIndex, l, curSum, target, curArr,resList);

        // 2.2)不選(“邊界:可能需要恢復環境!”)
        curSum -= candidates[curIndex];
        curArr.pop();
        dfs(curIndex + 1, l, curSum, target, curArr, resList);
    };

    // 1)狀態初始化
    const l = candidates.length;
    let curIndex = 0,
        curSum = 0,
        curArr = [],
        resList = [];

    // 2)呼叫 - 回溯
    dfs(curIndex, l, curSum, target, curArr, resList);

    // 3)返回結果 resList 
    return resList;
};

2 方案2

1)程式碼:

// 方案2 “動態規劃 - 普通版”。
// TODO,注:通過 0 / 170,應該是程式碼哪裡寫錯了!!!

// 思路:
// 1)狀態定義:
// dp[i][j] 前i個物品(使用哨兵從1開始)能組合成j的序列

// 2)初始化:
// dp[0][j] = [], 沒有物品則沒有能組合成j的序列

// 3)轉移方程:
// dp[i][j] 的值由兩個方向遞推得來:
// 當前能選的物品中,不選第i個物品就能組合成目標j的序列,即dp[i - 1][j]
// 當前能選的物品中,選k個第i個物品,即dp[i - 1][j - k * nums[i]]
// 注:動態規劃陣列中儲存的是引用,所以要深拷貝

// 參考:
// 1)https://leetcode-cn.com/problems/combination-sum/solution/dong-tai-gui-hua-bei-bao-wen-ti-by-sjtxw-11yv/
var combinationSum = function(candidates, target) {
    // 1)dp 狀態初始化
    const l = candidates.length;
    const dp = new Array(l + 1);
    for (let i = 0; i <= l; i++) {
        dp[i] = new Array(target + 1);
    };
    for (let i = 0; i <= target; i++) {
        dp[0][i] = [];
    };

    // 2)dp 狀態轉移 並 處理結果
    for (let i = 1; i <= l; i++) {
        dp[i][0] = [];
        for (let j = 1; j <= target; j++) {
            dp[i][j] = [];
            for (const item of dp[i - 1][j]) dp[i][j].push(Array.from(item)); // 不選當前元素
            for (let k = 1; j - k * candidates[i - 1] >= 0; k++) { // 選擇k個當前元素
                const pre = j - k * candidates[i - 1];
                if (pre === 0) {
                    dp[i][j].push(new Array(k).fill(candidates[i - 1])); // 剛好k個當前元素
                } else {
                    for (const item of dp[i - 1][pre]) {
                        dp[i][j].push(item.concat(new Array(k).fill(candidates[i - 1])));
                    }
                }
            }
        }
    }

    // 3)返回結果 dp 陣列
    return dp;
};

3 方案3

1)程式碼:

// 方案3 “動態規劃 - 優化版”。
// 本質:二維儲存空間 壓縮成 一維儲存空間 

// 思路:
// 1)dp 狀態初始化
// 2)dp 狀態轉移 並 處理結果
// 3)返回結果 dp[target] 
var combinationSum = function(candidates, target) {
    // 1)dp 狀態初始化
    const l = candidates.length;
    const dp = new Array(target + 1);
    dp[0] = [];

    // 2)dp 狀態轉移 並 處理結果
    for (let i = 0; i < l; i++) {
        for (let j = 1; j <= target; j++) {
            if (dp[j] === undefined) dp[j] = [];
            const pre = j - candidates[i];
            if (pre < 0) continue;
            if (dp[pre] === undefined) dp[pre] = [];
            if (dp[pre].length === 0 && pre === 0) {
                dp[j].push([candidates[i]]); // target剛好等於當前物品
            } else {
                const t = [];
                for (const item of dp[pre]) {
                    const tt = Array.from(item); // 拷貝
                    tt.push(candidates[i]);
                    t.push(tt);
                }
                dp[j].push(...t);
            }
        }
    }

    // 3)返回結果 dp[target] 
    return dp[target];
};

相關文章