leetCode之回溯法

首頁(yè)目錄 點(diǎn)擊查看

第一題

解題思路

  • 這道題也是需要使用回溯法來(lái)解決,窮盡所有可能。借用大佬笨豬爆破組的圖解:


    image.png

我的答案

var letterCombinations = function (digits) {
    if (!digits.length) return []
    let list = {
        "2": "abc",
        "3": "def",
        "4": "ghi",
        "5": "jkl",
        "6": "mno",
        "7": "pqrs",
        "8": "tuv",
        "9": "wxyz"
    }
    let array = [];
    function generate(curStr, i) {
        if (curStr.length === digits.length) {
            array.push(curStr)
            return
        }
        let letters = list[digits[i]];
        if (letters) {
            for (let key of letters) {

                generate(curStr + key, i + 1)
            }
        }

    }
    generate("", 0)
    return array
};

第二題

  • 難度:中等
  • 題目:22. 括號(hào)生成
    數(shù)字 n 代表生成括號(hào)的對(duì)數(shù),請(qǐng)你設(shè)計(jì)一個(gè)函數(shù),用于能夠生成所有可能的并且 有效的 括號(hào)組合。
    示例:
輸入:n = 3
輸出:[
       "((()))",
       "(()())",
       "(())()",
       "()(())",
       "()()()"
     ]

解題思路

  • 判斷回溯很簡(jiǎn)單,拿到一個(gè)問(wèn)題,你感覺(jué)如果不窮舉一下就沒(méi)法知道答案,那就可以開(kāi)始回溯了。
    借用題解中大佬們的圖


    image.png

    右邊的括號(hào)大于左邊括號(hào),就要加入右邊括號(hào),左邊括號(hào)的數(shù)量不能大于n。

我的答案

var generateParenthesis = function (n) {
    let array = [];
    let str = "";

    function generate(str, left, right) {
        if (str.length === 2 * n) {
            array.push(str)
            return
        }
        if (left > 0) {
            generate(str + "(", left - 1, right)
        }
        if (right > left) {
            generate(str + ")", left, right - 1)
        }
    }
    generate(str, n, n)
    return array
};
image.png

第三題

  • 難度:困難
  • 題目: 37. 解數(shù)獨(dú)
    編寫(xiě)一個(gè)程序,通過(guò)填充空格來(lái)解決數(shù)獨(dú)問(wèn)題。
    一個(gè)數(shù)獨(dú)的解法需遵循如下規(guī)則:
  • 數(shù)字 1-9 在每一行只能出現(xiàn)一次。
  • 數(shù)字 1-9 在每一列只能出現(xiàn)一次。
  • 數(shù)字 1-9 在每一個(gè)以粗實(shí)線分隔的 3x3 宮內(nèi)只能出現(xiàn)一次。
    空白格用 '.' 表示。


    image.png

    一個(gè)數(shù)獨(dú)。

示例

image.png
答案被標(biāo)成紅色。
- 給定的數(shù)獨(dú)序列只包含數(shù)字 1-9 和字符 '.' 。
- 你可以假設(shè)給定的數(shù)獨(dú)只有唯一解。
- 給定數(shù)獨(dú)永遠(yuǎn)是 9x9 形式的。

解題思路

  • 這道題是很典型的回溯法的題型,所以也只能用回溯法來(lái)解決,但是我對(duì)于回溯法確實(shí)不是很熟加上這道題本身難度較大,這道題主要參考了大佬的題解。分別創(chuàng)建3個(gè)數(shù)組,用于存放橫向、縱向、單個(gè)9個(gè)小格子的元素,回溯調(diào)用fill方法,如果填寫(xiě)無(wú)沖突返回true,如果沖突則返回false。依次填寫(xiě)每個(gè)格子。

我的答案

/**
 * @param {character[][]} board
 * @return {void} Do not return anything, modify board in-place instead.
 */
var solveSudoku = function (board) {
    const rows = new Array(9);
    const cols = new Array(9);
    const blocks = new Array(9);
    const options = ['1', '2', '3', '4', '5', '6', '7', '8', '9'];
    for (let i = 0; i < 9; i++) {
        rows[i] = new Set(options)
        cols[i] = new Set(options)
        blocks[i] = new Set(options)
    }
    function getBlocksIndex(i, j) {
        return (i / 3 | 0) * 3 + j / 3 | 0        //獲取當(dāng)前處于哪個(gè)小方塊
    }
    for (let i = 0; i < 9; i++) {
        for (let j = 0; j < 9; j++) {
            if (board[i][j] !== '.') {
                rows[i].delete(board[i][j]);
                cols[j].delete(board[i][j]);
                blocks[getBlocksIndex(i, j)].delete(board[i][j])
            }
        }
    }

    function fill(i, j) {
        if (j === 9) {
            i++
            j = 0
            if (i === 9) {
                return true
            }
        }
        if (board[i][j] !== '.') {
            return fill(i, j + 1)
        }
        const blockIndex = getBlocksIndex(i, j);
        for (let num = 0; num < 9; num++) {
            const option = options[num];
            if (!rows[i].has(option) || !cols[j].has(option) || !blocks[blockIndex].has(option)) {
                continue
            }
            board[i][j] = option;
            rows[i].delete(option)
            cols[j].delete(option)
            blocks[blockIndex].delete(option)
            
            if (fill(i, j + 1)) {
                return true
            }
            board[i][j] = ".";
            rows[i].add(option)
            cols[j].add(option)
            blocks[blockIndex].add(option)
        }
        return false
    }

    fill(0, 0)
    return board
};
image.png

第四題

  • 難度:中等
  • 題目:39. 組合總和
    給定一個(gè)無(wú)重復(fù)元素的數(shù)組 candidates 和一個(gè)目標(biāo)數(shù) target ,找出 candidates 中所有可以使數(shù)字和為 target 的組合。
    candidates 中的數(shù)字可以無(wú)限制重復(fù)被選取。
  • 所有數(shù)字(包括 target)都是正整數(shù)。
  • 解集不能包含重復(fù)的組合。

示例

輸入:candidates = [2,3,6,7], target = 7,
所求解集為:
[
  [7],
  [2,2,3]
]
輸入:candidates = [2,3,5], target = 8,
所求解集為:
[
  [2,2,2,2],
  [2,3,3],
  [3,5]
]

解題思路

  • 這道題采取回溯法,選出一個(gè)值后,把目標(biāo)值減去選中值,就得到了新的目標(biāo)值,只要數(shù)組中的值小于或者等于新的目標(biāo)值,就可以繼續(xù)執(zhí)行下去,否則則代表路走不通返回重走。

我的答案

var combinationSum = function (candidates, target) {
    let arr = [];
    function dfs(value, array) {
        if (value === 0) return arr.push(array.slice());
        if (value < 0) return;
        for (let i = 0; i <= candidates.length - 1; i++) {
            if (!array.length || (array[array.length - 1] >= candidates[i])) {
                let temp = value - candidates[i];
                array.push(candidates[i]);
                dfs(temp, array);
                array.pop()
            }
        }
    }
    dfs(target, [])
    return arr
};
image.png

第五題

  • 難度:中等
  • 題目:77. 組合
    給定兩個(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],
]

解題思路

  • 回溯調(diào)用,依次取數(shù)一個(gè)零時(shí)數(shù)組,當(dāng)數(shù)組長(zhǎng)度===k則推入最后的結(jié)果數(shù)組中,下一次取數(shù)和零時(shí)數(shù)組的元素作比較。

我的答案

        var combine = function (n, k) {
            let arr = [];

            function dfs(array) {
                if (array.length === k) return arr.push(array.slice());
                for (let i = 1; i <= n; i++) {
                    if (!array.length || array[array.length - 1] > i) {
                        array.push(i);
                        dfs(array);
                        array.pop()
                    }
                }
            }
            dfs([]);
            return arr
        };
image.png

大神答案

自己寫(xiě)出來(lái)答案后,看了一下結(jié)果實(shí)在是有點(diǎn)慢,所以去看了題解,看了一下別人的答案,還是很有借鑒的地方,因?yàn)槲以趂or循環(huán)這里,每次都需要從1開(kāi)始,其實(shí)很浪費(fèi),像下面的方法就優(yōu)化了for循環(huán),得以提升速度。

var combine = function (n, k) {
    let arr = [];
    function dfs(start, array) {
        if (array.length === k) return arr.push(array.slice());
        for (let i = start; i <= n; i++) {
            array.push(i);
            dfs(i + 1, array);
            array.pop()
        }
    }
    dfs(1, []);
    return arr
};
image.png

第六題

  • 難度:中等
  • 題目:40. 組合總和 II
    給定一個(gè)數(shù)組 candidates 和一個(gè)目標(biāo)數(shù) target ,找出 candidates 中所有可以使數(shù)字和為 target 的組合。
    candidates 中的每個(gè)數(shù)字在每個(gè)組合中只能使用一次。
  • 所有數(shù)字(包括目標(biāo)數(shù))都是正整數(shù)。
  • 解集不能包含重復(fù)的組合。

示例

輸入: candidates = [10,1,2,7,6,1,5], target = 8,
所求解集為:
[
  [1, 7],
  [1, 2, 5],
  [2, 6],
  [1, 1, 6]
]

輸入: candidates = [2,5,2,1,2], target = 5,
所求解集為:
[
  [1,2,2],
  [5]
]

解題思路

  • 這道題和上面的第四題很相似只是說(shuō)每一次每一個(gè)數(shù)只能用一次,我這里有兩種方法,一種是用哈希來(lái)存儲(chǔ)當(dāng)前已經(jīng)達(dá)標(biāo)的數(shù)組字符串,一種是在for循環(huán)遍歷時(shí)跳過(guò)相同的數(shù)字。

我的答案

  • 哈希去重
        var combinationSum2 = function (candidates, target) {
            candidates = candidates.sort((a, b) => {
                return a - b
            })
            let arr = [];
            let obj = {}

            function dfs(target, index, array) {
                if (target === 0 && !obj[array.toString()]) {
                    obj[array.toString()] = true
                    return arr.push(array.slice())
                };
                if (target < 0) return;
                for (let i = index; i <= candidates.length - 1; i++) {
                    let temp = target - candidates[i];
                    array.push(candidates[i]);
                    dfs(temp, i + 1, array);
                    array.pop();
                }
            }
            dfs(target, 0, [])
            return arr
        };
image.png
  • continue跳過(guò)
        var combinationSum2 = function (candidates, target) {
            candidates = candidates.sort((a, b) => {
                return a - b
            })
            let arr = [];

            function dfs(target, index, array) {
                if (target === 0) {
                    return arr.push(array.slice())
                };
                if (target < 0) return;
                for (let i = index; i <= candidates.length - 1; i++) {
                    if (candidates[i - 1] == candidates[i] && i - 1 >= index) continue;
                    let temp = target - candidates[i];
                    array.push(candidates[i]);
                    dfs(temp, i + 1, array);
                    array.pop();
                }
            }
            dfs(target, 0, [])
            return arr
        };
image.png

以上兩者明顯是第二種更好,空間復(fù)雜度也相對(duì)較低

第七題

  • 難度:中等
  • 題目:46. 全排列
    給定一個(gè) 沒(méi)有重復(fù) 數(shù)字的序列,返回其所有可能的全排列。

示例

輸入: [1,2,3]
輸出:
[
  [1,2,3],
  [1,3,2],
  [2,1,3],
  [2,3,1],
  [3,1,2],
  [3,2,1]
]

解題思路

  • 這道題是典型的回溯法,套用公式即可,只是需要處理數(shù)字是否使用過(guò),這里用obj暫存使用過(guò)的數(shù)字

我的答案

var permute = function (nums) {
    let arr = [];
    let obj = {}
    function dfs(array) {
        if (array.length === nums.length) return arr.push(array.slice());
        if (array.length > nums.length) return;
        for (let i = 0; i <= nums.length - 1; i++) {
            if (!obj[i]) {
                obj[i] = true
                array.push(nums[i]);
                dfs(array);
                obj[i] = false
                array.pop()
            }
        }
    }
    dfs([])
    return arr
};
image.png

第八題

  • 難度:中等
  • 題目:47. 全排列 II
    給定一個(gè)可包含重復(fù)數(shù)字的序列,返回所有不重復(fù)的全排列。

示例

輸入: [1,1,2]
輸出:
[
  [1,1,2],
  [1,2,1],
  [2,1,1]
]

解題思路

  • 這道題和上一道題很類似,只是說(shuō)數(shù)字會(huì)出現(xiàn)重復(fù)的,而且是亂序的。所以對(duì)于這兩種情況,需要先排序,排完序過(guò)濾掉相同的數(shù)字和使用過(guò)的數(shù)字即可

我的答案

var permuteUnique = function (nums) {
    let arr = new Set();
    let obj = {}
    nums.sort((a, b) => a - b); // 升序排序
    function dfs(array, index) {
        if (array.length === nums.length) return arr.add(array.slice())
        if (array.length > nums.length) return
        for (let i = 0; i <= nums.length - 1; i++) {
            if (nums[i - 1] === nums[i] && i - 1 >= 0 && !obj[i - 1]) { // 避免產(chǎn)生重復(fù)的排列
                continue;
            }
            if (obj[i]) {      // 這個(gè)數(shù)使用過(guò)了,跳過(guò)。
                continue;
            }
            array.push(nums[i]);
            obj[i] = true
            dfs(array, i + 1);
            array.pop()
            obj[i] = false
        }
    }
    dfs([], 0)
    return [...arr]
};
image.png

第九題

  • 難度:中等
  • 題目:90. 子集 II
    給定一個(gè)可能包含重復(fù)元素的整數(shù)數(shù)組 nums,返回該數(shù)組所有可能的子集(冪集)。
    說(shuō)明:解集不能包含重復(fù)的子集。

示例

輸入: [1,2,2]
輸出:
[
  [2],
  [1],
  [1,2,2],
  [2,2],
  [1,2],
  []
]

解題思路

  • 這道題和78題子集非常相似,只是說(shuō)換成了可能包含重復(fù)元素的nums數(shù)組,這道題解題方法也是一樣的,首先排序,排完序然后按dfs常規(guī)方法來(lái)處理,跳過(guò)相同的數(shù)字即可。

我的答案

var subsetsWithDup = function (nums) {
    let arr = [];
    nums.sort((a, b) => {
        return a - b
    });

    function dfs(index, array) {
        arr.push(array.slice());
        for (let i = index; i <= nums.length - 1; i++) {
            if (nums[i - 1] === nums[i] && i - 1 >= index) continue;
            array.push(nums[i]);
            dfs(i + 1, array);
            array.pop()
        }
    }
    dfs(0, [])
    return arr
};
image.png

第十題

  • 難度:中等
  • 題目:1079. 活字印刷
    你有一套活字字模 tiles,其中每個(gè)字模上都刻有一個(gè)字母 tiles[i]。返回你可以印出的非空字母序列的數(shù)目。
    注意:本題中,每個(gè)活字字模只能使用一次。

示例

輸入:"AAB"
輸出:8
解釋:可能的序列為 "A", "B", "AA", "AB", "BA", "AAB", "ABA", "BAA"。

輸入:"AAABBC"
輸出:188

解題思路

  • 這道題還是套用回溯模板,值得注意的是,去重的方法,用了哈希去重得方法,性能實(shí)在太差,后來(lái)想著用new Set去重,情況稍微好一點(diǎn)。
    但是也不太好,看了題解,看了大神得方法,豁然開(kāi)朗,確實(shí)厲害:
    n長(zhǎng)度的字符可以看做是n-1長(zhǎng)度字符和當(dāng)前剩余字符拼接出來(lái)。所以n長(zhǎng)度字符的可能性為n-1長(zhǎng)度的可能性+剩余字符有多少種
    所以從長(zhǎng)度1開(kāi)始一直到字符長(zhǎng)度,遞歸即可。這樣的方式占用內(nèi)存小,擊敗100%

我的答案

  • new Set去重
        var numTilePossibilities = function (tiles) {
            let obj = {}
            let arr = new Set()

            function dfs(path) {
                if (path.length) {
                    arr.add(path)
                }
                for (let i = 0; i <= tiles.length - 1; i++) {
                    if (!obj[i]) {
                        obj[i] = true
                        path += tiles[i]
                        dfs(path);
                        obj[i] = false
                        path = path.slice(0, path.length - 1)
                    }
                }
            }
            dfs("")
            return arr.size
        };
image.png
  • 內(nèi)存較小遞歸
        var numTilePossibilities = function (tiles) {
            let sum = 0;

            function cal(list, n) {
                let ns = Array.from(new Set(Array.from(list)));
                sum += ns.length;
                for (let i = 0, l = ns.length; i < l; i++) {
                    if (n > 1) { 
                        cal(list.replace(ns[i], ''), n - 1);
                    }
                }
            }
            cal(tiles, tiles.length);
            return sum;
        };
image.png

第十一題

  • 難度:中等
  • 題目:1291. 順次數(shù)
    我們定義「順次數(shù)」為:每一位上的數(shù)字都比前一位上的數(shù)字大 1 的整數(shù)。
    請(qǐng)你返回由 [low, high] 范圍內(nèi)所有順次數(shù)組成的 有序 列表(從小到大排序)。

示例

輸出:low = 100, high = 300
輸出:[123,234]

輸出:low = 1000, high = 13000
輸出:[1234,2345,3456,4567,5678,6789,12345]

解題思路

  • 這道題我確實(shí)想的太麻煩了,處理了很久都沒(méi)搞好,最后看了別人的題解,??,真的菜,大神的題解,后面補(bǔ)上自己思路得版本

大神的答案

var sequentialDigits = function(low, high) {
    let ans = [];
    for (i = 1; i <= 9; i++) {
        backtrack(low, high, i, i);
    }
    ans.sort((a, b) => a-b)
    return ans;
    function backtrack (low, high, curNum, curTail) {
        if(curNum >= low && curNum <= high) {
            ans.push(curNum)
        }
        if(curNum > high) {
            return;
        }
        if(curTail + 1 <= 9) {
            backtrack(low, high, curNum*10 + curTail + 1, curTail + 1);
        }
    }
};

image.png

第十一題

  • 難度:中等
  • 題目:#### 139. 單詞拆分
    給定一個(gè)非空字符串 s 和一個(gè)包含非空單詞的列表 wordDict,判定 s 是否可以被空格拆分為一個(gè)或多個(gè)在字典中出現(xiàn)的單詞。
  • 拆分時(shí)可以重復(fù)使用字典中的單詞。
  • 你可以假設(shè)字典中沒(méi)有重復(fù)的單詞。

示例

輸入: s = "leetcode", wordDict = ["leet", "code"]
輸出: true
解釋: 返回 true 因?yàn)?"leetcode" 可以被拆分成 "leet code"。

輸入: s = "applepenapple", wordDict = ["apple", "pen"]
輸出: true
解釋: 返回 true 因?yàn)?"applepenapple" 可以被拆分成 "apple pen apple"。
     注意你可以重復(fù)使用字典中的單詞。

解題思路

  • 這道題推薦使用動(dòng)態(tài)規(guī)劃去做,性能更好一些,但是我現(xiàn)在對(duì)于動(dòng)態(tài)規(guī)劃還不是非常熟悉,所以先做一個(gè)回溯版本,之后再出動(dòng)態(tài)規(guī)劃版本。首先把單詞用set存儲(chǔ),創(chuàng)建一個(gè)數(shù)組用于存放是否存在某個(gè)單詞,從第一個(gè)字符開(kāi)始拆,截取的字符去尋找是否符合情況,如果符合則將位置存起來(lái),然后繼續(xù)去尋找接下來(lái)的字符。

我的答案

var wordBreak = function (s, wordDict) {
    let wordList = new Set(wordDict);
    let len = s.length;
    let hasWord = new Array(len);
    function dfs(start) {
        if (start == len) return true;
        if (hasWord[start] !== undefined) return hasWord[start];
        for (let i = start + 1; i <= len; i++) {
            let word = s.slice(start, i);
            if (wordList.has(word) && dfs(i)) {
                hasWord[i] = true
                return true
            }

        }
        hasWord[start] = false
        return false
    }
    return dfs(0)
};
image.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容