LeetCode 搜索回溯专题

子集 组合问题

78. 子集

给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。

解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。

示例 1:

输入:nums = [1,2,3]
输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]

示例 2:

输入:nums = [0]
输出:[[],[0]]

class Solution {
public:
    vector<int> sub = {};
    vector<vector<int>> res;
    vector<vector<int>> subsets(vector<int>& nums) {
        sort(nums.begin(), nums.end());
        solve(nums, 0);
        return res;
    }
    void solve(vector<int> &nums, int cur){
        res.push_back(sub);
        if(cur == nums.size()) return;
        for(int i=cur; i<nums.size(); i++){
            sub.push_back(nums[i]);
            solve(nums, i+1);
            sub.pop_back();
        }
    }
};

90. 子集 II

给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。

解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。

示例 1:

输入:nums = [1,2,2]
输出:[[],[1],[1,2],[1,2,2],[2],[2,2]]

示例 2:

输入:nums = [0]
输出:[[],[0]]

class Solution {
public:
    vector<int> sub;
    vector<vector<int>> res;
    vector<vector<int>> subsetsWithDup(vector<int>& nums) {
        sort(nums.begin(), nums.end()); // 先排序
        dfs(nums, 0);
        return res;
    }
    void dfs(vector<int>& nums, int cur){
        res.push_back(sub);
        if(cur == nums.size()) return;
        for(int i=cur; i<nums.size(); ++i){
            if(i > cur && nums[i] == nums[i-1]) continue; // 如果当前有数字和前一个相等则跳过 
            sub.push_back(nums[i]);
            dfs(nums, i+1);
            sub.pop_back();
        }
    }
};

77. 组合

给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。

你可以按 任何顺序 返回答案。

示例 1:

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

示例 2:

输入:n = 1, k = 1
输出:[[1]]

class Solution {
public:
    vector<vector<int>> combine(int n, int k) {
        vector<vector<int>> res;
        vector<int> subRes;
        solve(res, subRes, 0, 1, n, k);
        return res;
    }

    void solve(vector<vector<int>> &res, vector<int> &subRes, int curIndex, int curNum, int n, int k)
    {
        if(curIndex == k)
        {
            res.push_back(subRes);
            return;
        }
        for(int i=curNum; i<=n; i++)
        {
            subRes.push_back(i);
            solve(res, subRes, curIndex+1, i+1, n, k);
            subRes.pop_back();
        }
    }
};

39. 组合总和

给定一个无重复元素的正整数数组 candidates 和一个正整数 target ,找出 candidates 中所有可以使数字和为目标数 target 的唯一组合。

candidates 中的数字可以无限制重复被选取。如果至少一个所选数字数量不同,则两种组合是唯一的。

对于给定的输入,保证和为 target 的唯一组合数少于 150 个。

示例 1:

输入: candidates = [2,3,6,7], target = 7
输出: [[7],[2,2,3]]

class Solution {
public:
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        vector<vector<int>> res;
        vector<int> subRes;
        sort(candidates.begin(), candidates.end());
        solve(res, subRes, candidates, 0, target);
        return res;
    }

    void solve(vector<vector<int>> &res, vector<int> &subRes, vector<int> &candidates, int start, int target)
    {
        if(target == 0)
        {
            res.push_back(subRes);
            return;
        }
        for(int i=start; i<candidates.size(); i++)
        {
            if(target - candidates[i] < 0) break;
            subRes.push_back(candidates[i]);
            solve(res, subRes, candidates, i, target-candidates[i]);
            subRes.pop_back();
        }
    }
};

40. 组合总和 II

给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。

candidates 中的每个数字在每个组合中只能使用一次。

注意:解集不能包含重复的组合。

示例 1:

输入: candidates = [10,1,2,7,6,1,5], target = 8,
输出:
[
[1,1,6],
[1,2,5],
[1,7],
[2,6]
]

关键代码是:

if(i>start && candidates[i] == candidates[i-1]) continue;

class Solution {
public:
    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        vector<vector<int>> res;
        vector<int> subRes;
        sort(candidates.begin(), candidates.end());
        solve(res, subRes, candidates, 0, target);
        return res;
    }

    void solve(vector<vector<int>> &res, vector<int> &subRes, vector<int>& candidates, int start, int target)
    {
        if(target == 0)
        {
            res.push_back(subRes);
            return;
        }
        for(int i=start; i<candidates.size(); i++)
        {
            if(target - candidates[i] < 0) break;
            if(i>start && candidates[i] == candidates[i-1]) continue;
            subRes.push_back(candidates[i]);
            solve(res, subRes, candidates, i+1, target-candidates[i]);
            subRes.pop_back();
        }
    }
};

22. 括号生成

数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。

有效括号组合需满足:左括号必须以正确的顺序闭合。

示例 1:

输入:n = 3
输出:[“((()))”,“(()())”,“(())()”,“()(())”,“()()()”]

class Solution {
public:
    vector<string> res;
    vector<string> generateParenthesis(int n) {
        string str = "";
        dfs(str, 0, 0, n);
        return res;
    }

    void dfs(string &str, int cntL, int cntR, int n){
        if(cntL == n && cntR == n){
            res.push_back(str);
            return;
        }   
        if(cntL < n){
            str += "(";
            dfs(str, cntL+1, cntR, n);
            str.pop_back();
        }
        if(cntL <= n && cntL > cntR){
            str += ')';
            dfs(str, cntL, cntR+1, n);
            str.pop_back();
        }
       
    }
};

全排列问题

46. 全排列

给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。

示例 1:

输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

示例 2:

输入:nums = [0,1]
输出:[[0,1],[1,0]]

示例 3:

输入:nums = [1]
输出:[[1]]

  • 递归解决
// 递归解决
class Solution {
public:
    vector<vector<int>> res;

    vector<vector<int>> permute(vector<int>& nums) {
        //递归解决
        vector<int> cur;
        dfs(cur, nums, 0);
        return res;
    }

    void dfs(vector<int>& cur, vector<int>& nums, int index){
        if(index == nums.size()){
            res.push_back(cur);
            return;
        }
        for(int i=0; i<nums.size(); ++i){
            int ok = 1;
            for(int j=0; j<index; ++j){
                if(cur[j] == nums[i]){
                    ok = 0;
                    break;
                }
            }
            if(ok){
                cur.push_back(nums[i]);
                dfs(cur, nums, index+1);
                cur.pop_back();
            }
        }
    }
};
  • next_permutation(nums.begin(), nums.end())
class Solution {
public:
    vector<vector<int>>res;
    vector<vector<int>> permute(vector<int>& nums) {
        sort(nums.begin(), nums.end());
        do{
            vector<int> v;
            for(int i=0; i<nums.size(); ++i){
                v.push_back(nums[i]);
            }
            res.push_back(v);
        }while(next_permutation(nums.begin(), nums.end()));
        return res;
    }
};

47. 全排列 II

给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列。

示例 1:

输入:nums = [1,1,2]
输出:
[[1,1,2],
[1,2,1],
[2,1,1]]

示例 2:

输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

这里和上一题的不同之处在于它可包含重复数字,因此需要做剪枝操作来排除可能重复的全排列。
在这里插入图片描述

class Solution {
public:
    vector<vector<int>> res;
    vector<vector<int>> permuteUnique(vector<int>& nums) {
        vector<int> cur;
        vector<int> visited(nums.size(), 0);
        sort(nums.begin(), nums.end());
        dfs(cur, nums, visited, 0);
        return res;
    }
    void dfs(vector<int>& cur, vector<int>& nums, vector<int>& visited, int index){
        if(index == nums.size()){
            res.push_back(cur);
            return;
        }
        for(int i=0; i<nums.size(); ++i){
            if(visited[i] || (i>0 && nums[i] == nums[i-1] && visited[i-1]==0)){
                continue; // 如果当前已经使用 或者 当前数字与前一个数字一样且已被撤销,则剪枝跳过
            }
            visited[i] = true;
            cur.push_back(nums[i]);
            dfs(cur, nums, visited, index+1);
            cur.pop_back();
            visited[i] = false;
        }
    }
};

784. 字母大小写全排列

给定一个字符串S,通过将字符串S中的每个字母转变大小写,我们可以获得一个新的字符串。返回所有可能得到的字符串集合。

示例:

输入:S = “a1b2”
输出:[“a1b2”, “a1B2”, “A1b2”, “A1B2”]
输入:S = “3z4”
输出:[“3z4”, “3Z4”]
输入:S = “12345”
输出:[“12345”]

提示:

  • S 的长度不超过12。
  • S 仅由数字和字母组成。
class Solution {
public:
    vector<string> res;
    vector<string> letterCasePermutation(string s) {
        string cur = "";
        dfs(cur, s, 0);
        return res;
    }
    void dfs(string &cur, string s, int index){
        if(index == s.length()){
            res.push_back(cur);
            return;
        }
        if(isdigit(s[index])){
            cur += s[index];
            dfs(cur, s, index+1);
            cur.pop_back();
        }else{
            cur += tolower(s[index]);
            dfs(cur, s, index+1);
            cur.pop_back();
            cur += toupper(s[index]);
            dfs(cur, s, index+1);
            cur.pop_back();
        }
    }
};

剑指 Offer 38. 字符串的排列

输入一个字符串,打印出该字符串中字符的所有排列。

你可以以任意顺序返回这个字符串数组,但里面不能有重复元素。

示例:

输入:s = “abc”
输出:[“abc”,“acb”,“bac”,“bca”,“cab”,“cba”]


搜索问题

37. 解数独

编写一个程序,通过填充空格来解决数独问题。

数独的解法需 遵循如下规则:

数字 1-9 在每一行只能出现一次。
数字 1-9 在每一列只能出现一次。
数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。(请参考示例图)
数独部分空格内已填入了数字,空白格用 ‘.’ 表示。

示例:
在这里插入图片描述
在这里插入图片描述

79. 单词搜索

给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 false 。

单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。

示例 1:

输入:board = [[“A”,“B”,“C”,“E”],[“S”,“F”,“C”,“S”],[“A”,“D”,“E”,“E”]], word = “ABCCED”
输出:true

在这里插入图片描述

class Solution {
public:
    int ok = 0;
    int dir[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
    bool exist(vector<vector<char>>& board, string word) {
        int m = board.size();
        int n = board[0].size();
        vector<vector<int>> visited(m, vector<int>(n));
        for(int i=0; i<m; i++) {
            for(int j=0; j<n; j++) {
                if (board[i][j] == word[0]) {
                    dfs(i, j, visited, board, word, 0);
                    if (ok) return true;
                }
            }
        }
        return false;
    }
    void dfs(int x, int y, vector<vector<int>>& visited, vector<vector<char>>& board, string word, int cur) {
        if (cur == word.length()-1) {
            ok = 1;
            return;
        }
        visited[x][y] = 1;
        for(int i=0; i<4; i++){
            if (ok == 1) break;
            int dx = dir[i][0] + x;
            int dy = dir[i][1] + y;
            if (dx < 0 || dx >= board.size() || dy < 0 || dy >= board[0].size() || visited[dx][dy] || board[dx][dy] != word[cur+1]){
                continue;
            }
            dfs(dx, dy, visited, board, word, cur+1);
        }
        visited[x][y] = 0;
    }
};

面试题 08.12. 八皇后

设计一种算法,打印 N 皇后在 N × N 棋盘上的各种摆法,其中每个皇后都不同行、不同列,也不在对角线上。这里的“对角线”指的是所有的对角线,不只是平分整个棋盘的那两条对角线。

注意:本题相对原题做了扩展

示例:

输入:4
输出:[[“.Q…”,“…Q”,“Q…”,“…Q.”],[“…Q.”,“Q…”,“…Q”,“.Q…”]]
解释: 4 皇后问题存在如下两个不同的解法。
[
[“.Q…”, // 解法 1
“…Q”,
“Q…”,
“…Q.”],
[“…Q.”, // 解法 2
“Q…”,
“…Q”,
“.Q…”]
]

131. 分割回文串

给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是 回文串 。返回 s 所有可能的分割方案。

回文串 是正着读和反着读都一样的字符串。

示例 1:

输入:s = “aab”
输出:[[“a”,“a”,“b”],[“aa”,“b”]]

示例 2:

输入:s = “a”
输出:[[“a”]]

这是整理的LeetCode上的搜索专题。

BFS

这是一种解题框架,使用队列数据结构。

最经典的一类题就是迷宫最短路径问题:

1091. 二进制矩阵中的最短路径

给你一个 n x n 的二进制矩阵 grid 中,返回矩阵中最短 畅通路径 的长度。如果不存在这样的路径,返回 -1 。

二进制矩阵中的 畅通路径 是一条从 左上角 单元格(即,(0, 0))到 右下角 单元格(即,(n - 1, n - 1))的路径,该路径同时满足下述要求:

路径途经的所有单元格都的值都是 0 。
路径中所有相邻的单元格应当在 8 个方向之一 上连通(即,相邻两单元之间彼此不同且共享一条边或者一个角)。
畅通路径的长度 是该路径途经的单元格总数。

示例 1:
在这里插入图片描述

输入:grid = [[0,1],[1,0]]
输出:2

这种题目就是一种典型BFS解决的题型。

首先需要定义一下节点状态结构体,包含位置信息和其所走的单元格总数。

然后需要定义一下队列作BFS的过程。

注意要用一个标记数组来标记已经遍历过的节点,防止再次被遍历。

class Solution
{
public:
    struct Node
    {
        int x;
        int y;
        int cnt;
    };
    const int dir[8][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}, {1, 1}, {1, -1}, {-1, 1}, {-1, -1}};
    int visited[105][105];
    int shortestPathBinaryMatrix(vector<vector<int>> &grid)
    {
        int n = grid.size(), m = grid[0].size();
        if (grid[0][0] == 1 || grid[n - 1][m - 1])
            return -1;
        Node u = {0, 0, 1};
        queue<Node> que;
        que.push(u);
        visited[0][0] = 1;
        while (!que.empty())
        {
            Node v = que.front();
            que.pop();
            if (v.x == n - 1 && v.y == m - 1)
            {
                return v.cnt;
            }
            for (int i = 0; i < 8; ++i)
            {
                int dx = v.x + dir[i][0];
                int dy = v.y + dir[i][1];
                if (dx < 0 || dx >= n || dy < 0 || dy >= m || grid[dx][dy] || visited[dx][dy])
                    continue;
                Node u = {dx, dy, v.cnt+1};
                visited[dx][dy] = 1;
                que.push(u);
            }
        }
        return -1;
    }   
}
;

127. 单词接龙

字典 wordList 中从单词 beginWord 和 endWord 的 转换序列 是一个按下述规格形成的序列:

序列中第一个单词是 beginWord 。
序列中最后一个单词是 endWord 。
每次转换只能改变一个字母。
转换过程中的中间单词必须是字典 wordList 中的单词。
给你两个单词 beginWord 和 endWord 和一个字典 wordList ,找到从 beginWord 到 endWord 的 最短转换序列 中的 单词数目 。如果不存在这样的转换序列,返回 0。

示例 1:

输入:beginWord = “hit”, endWord = “cog”, wordList = [“hot”,“dot”,“dog”,“lot”,“log”,“cog”]
输出:5
解释:一个最短转换序列是 “hit” -> “hot” -> “dot” -> “dog” -> “cog”, 返回它的长度 5。

示例 2:

输入:beginWord = “hit”, endWord = “cog”, wordList = [“hot”,“dot”,“dog”,“lot”,“log”]
输出:0
解释:endWord “cog” 不在字典中,所以无法进行转换。

看到最短转换序列的长度可以想到BFS

class Solution {
public:
    //确定俩个单词能否互相转换
    bool exchange_word(const string& a, const string& b) {
        int time = 0;
        int len = a.size();
        for (int i = 0; i < len; ++i) {
            if (a[i] != b[i]) ++time;
            if (time > 1) return false;
        }
        return time == 1;
    }

    int ladderLength(string beginWord, string endWord, vector<string>& wordList) {
        unordered_set<string> word_set(wordList.begin(), wordList.end());
        if (word_set.count(endWord) == 0) return 0; // 如果endWord不在wordList直接返回0  

        queue<string> q;
        q.push(beginWord);

        //必须消除已经是树节点的单词
        word_set.erase(beginWord);
        int i = 1;

        while (!q.empty()) {
            int size = q.size();                //记住当前队列的长度
            for (int j = 0; j < size; ++j) {    //for循环pop出队列所有元素
                auto str = q.front(); q.pop();  
                vector<string> tmp;             //保存str能够转换的单词
                for (const string& word : word_set) {
                    if (exchange_word(str, word)) {
                        if (word == endWord) return i + 1;
                        tmp.push_back(word);
                        q.push(word);
                    }
                }
                //tmp中的单词已经是树中的节点,我们需要erase它使得后续的树节点不重复
                for (const auto& str : tmp) word_set.erase(str);
            }
            ++i;
        }
        return 0;
    }
};

DFS

695. 岛屿的最大面积

给定一个包含了一些 0 和 1 的非空二维数组 grid 。

一个 岛屿 是由一些相邻的 1 (代表土地) 构成的组合,这里的「相邻」要求两个 1 必须在水平或者竖直方向上相邻。你可以假设 grid 的四个边缘都被 0(代表水)包围着。

找到给定的二维数组中最大的岛屿面积。(如果没有岛屿,则返回面积为 0 。)

示例 1:

[[0,0,1,0,0,0,0,1,0,0,0,0,0],
[0,0,0,0,0,0,0,1,1,1,0,0,0],
[0,1,1,0,1,0,0,0,0,0,0,0,0],
[0,1,0,0,1,1,0,0,1,0,1,0,0],
[0,1,0,0,1,1,0,0,1,1,1,0,0],
[0,0,0,0,0,0,0,0,0,0,1,0,0],
[0,0,0,0,0,0,0,1,1,1,0,0,0],
[0,0,0,0,0,0,0,1,1,0,0,0,0]]

对于上面这个给定矩阵应返回 6。注意答案不应该是 11 ,因为岛屿只能包含水平或垂直的四个方向的 1 。

class Solution {
public:
    int res = 0;
    const int dir[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
    int cur_area = 1;
    int maxAreaOfIsland(vector<vector<int>>& grid) {
        int n = grid.size(), m = grid[0].size();
        vector<vector<int>> visited(n, vector<int>(m));
        for(int i=0; i<n; ++i){
            for(int j=0; j<m; ++j){
                if(grid[i][j] == 1 && visited[i][j] == 0){
                    cur_area = 1;
                    dfs(i, j, grid, visited);
                    res = max(res, cur_area);
                }
            }
        }
        return res;
    }

    void dfs(int x, int y, vector<vector<int>>& grid, vector<vector<int>>& visited){
        visited[x][y] = 1;
        int n = grid.size(), m = grid[0].size();
        for(int i=0; i<4; ++i){
            int dx = x + dir[i][0];
            int dy = y + dir[i][1];
            if(dx < 0 || dx >= n || dy < 0 || dy >= m || grid[dx][dy] == 0 || visited[dx][dy]) continue;
            cur_area++;
            dfs(dx, dy, grid, visited);
        }
    }
};

200. 岛屿数量

给你一个由 ‘1’(陆地)和 ‘0’(水)组成的的二维网格,请你计算网格中岛屿的数量。

岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。

此外,你可以假设该网格的四条边均被水包围。

示例 1:

输入:grid = [
[“1”,“1”,“1”,“1”,“0”],
[“1”,“1”,“0”,“1”,“0”],
[“1”,“1”,“0”,“0”,“0”],
[“0”,“0”,“0”,“0”,“0”]
]
输出:1

示例 2:

输入:grid = [
[“1”,“1”,“0”,“0”,“0”],
[“1”,“1”,“0”,“0”,“0”],
[“0”,“0”,“1”,“0”,“0”],
[“0”,“0”,“0”,“1”,“1”]
]
输出:3

class Solution {
public:
    int res = 0;
    const int dir[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
    int numIslands(vector<vector<char>>& grid) {
        int n = grid.size(), m = grid[0].size();
        vector<vector<int>> visited(n, vector<int>(m, 0));
        for(int i=0; i<n; ++i){
            for(int j=0; j<m; ++j){
                if(grid[i][j] == '1' && visited[i][j] == 0){
                    dfs(i, j, grid, visited, n, m);
                    res++;
                }
            }
        }
        return res;
    }

    void dfs(int x, int y, vector<vector<char>>& grid, vector<vector<int>>& visited, int n, int m){
        visited[x][y] = 1;
        for(int i = 0; i<4; ++i){
            int dx = x + dir[i][0];
            int dy = y + dir[i][1];
            if(dx < 0 || dx >= n || dy < 0 || dy >= m || grid[dx][dy] == '0' || visited[dx][dy]) continue;
            dfs(dx, dy, grid, visited, n, m);
        }
    }
};

547. 省份数量

有 n 个城市,其中一些彼此相连,另一些没有相连。如果城市 a 与城市 b 直接相连,且城市 b 与城市 c 直接相连,那么城市 a 与城市 c 间接相连。

省份 是一组直接或间接相连的城市,组内不含其他没有相连的城市。

给你一个 n x n 的矩阵 isConnected ,其中 isConnected[i][j] = 1 表示第 i 个城市和第 j 个城市直接相连,而 isConnected[i][j] = 0 表示二者不直接相连。

返回矩阵中 省份 的数量。

示例 1:

输入:isConnected = [[1,1,0],[1,1,0],[0,0,1]]
输出:2

示例 2:

输入:isConnected = [[1,0,0],[0,1,0],[0,0,1]]
输出:3

这是一道并查集的题目,也是有框架模板的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值