LeetCode【代码随想录】刷题(回溯算法篇)

77.组合

力扣题目链接

题目:给定两个整数 nk,返回范围 [1, n] 中所有可能的 k 个数的组合。你可以按 任何顺序 返回答案。

思路:递归+回溯。有一个地方需要注意,比如第一次取1,第二次取2;那么第二轮的时候就不能第一次取2,第二次取1。所以要有一个start_index标识开始取数的位置。

通过代码:

class Solution {
public:
    vector<vector<int>> res;
    vector<int> once;

    void dfs(int n, int k, int start_index){
        if(once.size() == k)
        {
            res.push_back(once);
            return;
        }
        for(int i = start_index; i <= n; i++)
        {
            if(k - once.size() <= n - start_index + 1)
            {
                once.push_back(i);
                dfs(n, k, i + 1);
                once.pop_back();
            }
        }
    }
    vector<vector<int>> combine(int n, int k) {
        dfs(n, k, 1);
        return res;
    }
};

216.组合总和III

力扣题目链接

题目:找出所有相加之和为 nk 个数的组合,且满足下列条件:

  • 只使用数字1到9
  • 每个数字 最多使用一次

返回 所有可能的有效组合的列表 。该列表不能包含相同的组合两次,组合可以以任何顺序返回。

思路:本题和上一题类似,只需要多维护一个sum即可。

通过代码:

class Solution {
public:
    vector<vector<int>> res;
    vector<int> once;
    int sum = 0;

    void dfs(int k, int n, int start_num){
        if(once.size() == k)
        {
            if(sum == n)
                res.push_back(once);
            return;
        }
        for(int i = start_num; i <= 9; i++)
        {
            if(sum > n)
                return;
            once.push_back(i);
            sum += i;
            dfs(k, n, i + 1);
            once.pop_back();
            sum -= i;
        }
    }

    vector<vector<int>> combinationSum3(int k, int n) {
        dfs(k, n, 1);
        return res;
    }
};

17.电话号码的字母组合

力扣题目链接

题目:给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。

给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。

思路:递归+回溯,没啥好说的

通过代码:

class Solution {
public:
    string map[10]={"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
    vector<string> res;
    string once;

    void dfs(string digits, int depth){
        if(depth == digits.size())
        {
            res.push_back(once);
            return;
        }
        int num = digits[depth] - '0';
        for(char c : map[num])
        {
            once += c;
            dfs(digits, depth + 1);
            once.pop_back();
        }
    }

    vector<string> letterCombinations(string digits) {
        if(digits.empty())
            return res;
        dfs(digits, 0);
        return res;
    }
};

39. 组合总和

力扣题目链接

题目:给你一个无重复元素的整数数组candidates和一个目标整数target,找出candidates中可以使数字和为目标数target的所有不同组合 ,并以列表形式返回。你可以按任意顺序返回这些组合。

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

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

思路:类似第77题,注意修改start_index的位置即可。

通过代码:

class Solution {
public:
    vector<vector<int>> res;
    vector<int> once;
    int sum = 0;

    void dfs(vector<int> &candidates, int target, int start_index){
        if(sum > target)
            return;
        if(sum == target)
        {
            res.push_back(once);
            return;
        }
        for(int i = start_index; i < candidates.size(); i++)
        {
            once.push_back(candidates[i]);
            sum += candidates[i];
            dfs(candidates,  target, i);
            sum -= candidates[i];
            once.pop_back();
        }
    }

    vector<vector<int>> combinationSum(vector<int>& candidates, int target){
        dfs(candidates, target, 0);
        return res;
    }
};

40.组合总和II

力扣题目链接

题目:给定一个候选人编号的集合candidates和一个目标数target,找出candidates中所有可以使数字和为target的组合。

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

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

思路:本题和上一题的区别在于candidates里有重复的元素,这就导致解集里有可能有重复组合。所以重点在于去重。首先对candidates排序,这样相同的元素就在一起了。for循环代表的是横向遍历,递归代表的是纵向。重复组合出现的情况就在于横向的时候,所以当candidates[i] == candidates[i-1]的时候跳过即可。

通过代码:

class Solution {
public:
    vector<vector<int>> res;
    vector<int> once;
    int sum = 0;

    void dfs(vector<int> &candidates, int target, int start_index){
        if(sum == target)
        {
            res.push_back(once);
            return;
        }
        for(int i = start_index; i < candidates.size() && sum + candidates[i] <= target; i++)
        {
            if(i > start_index && candidates[i] == candidates[i - 1])
                continue;
            once.push_back(candidates[i]);
            sum += candidates[i];
            dfs(candidates, target, i + 1);
            sum -= candidates[i];
            once.pop_back();
        }
    }

    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        sort(candidates.begin(), candidates.end());
        dfs(candidates, target, 0);
        return res;
    }
};

131.分割回文串

力扣题目链接

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

思路:类似组合问题。组合问题是先选一个,再在剩下的里面继续选。分割的话就是先切一段,再在剩下的里面继续切。不断递归下去的就是在剩下的字符串里继续切,for循环里枚举单层递归切多长即可。只有切下来的是回文串才会继续往下切,所以最后字符串为空说明切完了,保存一下结果。

通过代码:

class Solution {
public:
    vector<vector<string>> res;
    vector<string> once;

    bool check(string s){
        string tmp = s;
        reverse(tmp.begin(), tmp.end());
        return tmp == s;
    }

    void dfs(string s){
        if(s.empty())
        {
            res.push_back(once);
            return;
        }

        for(int i = 1; i <= s.size(); i++)
        {
            string str = s.substr(0, i);
            string left = s.substr(i, s.size() - str.size());
            if(!check(str))
                continue;
            once.push_back(str);
            dfs(left);
            once.pop_back();
        }
    }

    vector<vector<string>> partition(string s) {
        dfs(s);
        return res;
    }
};

93.复原IP地址

力扣题目链接

题目:有效IP 地址正好由四个整数(每个整数位于0255之间组成,且不能含有前导0),整数之间用'.'分隔。

  • 例如:"0.1.2.201" "192.168.1.1" 是 有效 IP 地址,但是 "0.011.255.245""192.168.1.312""192.168@1.1" 是 无效 IP 地址。

给定一个只包含数字的字符串s,用以表示一个IP地址,返回所有可能的有效IP地址,这些地址可以通过在s中插入'.' 来形成。你 不能 重新排序或删除 s 中的任何数字。你可以按 任何 顺序返回答案。

思路:类似分割回文串,只不过明确有4段,加一些限制条件即可。

通过代码:

class Solution {
public:
    vector<string> res;
    vector<string> once;

    bool check(string s){
        if(s.starts_with('0') && s.size() > 1)
            return false;
        if(s.size() > 3)
            return false;
        int num = stoi(s);
        return num >=0 && num <= 255;
    }

    void dfs(string s){
        if(s.empty() && once.size() == 4)
        {
            string ip;
            for(int i = 0; i < once.size(); i++)
            {
                if(i > 0)
                    ip += ".";
                ip += once[i];
            }
            res.push_back(ip);
            return;
        }
        int max_len = min(3, (int)s.size());
        for(int i = 1; i <= max_len && once.size() < 4; i++)
        {
            string str = s.substr(0, i);
            string left = s.substr(i, s.size() - str.size());
            if(!check(str))
                continue;
            once.push_back(str);
            dfs(left);
            once.pop_back();
        }
    }

    vector<string> restoreIpAddresses(string s) {
        dfs(s);
        return res;
    }
};

78.子集

力扣题目链接

题目:给你一个整数数组nums,数组中的元素互不相同 。返回该数组所有可能的子集(幂集)。解集不能包含重复的子集。你可以按任意顺序返回解集。

思路:

通过代码:

class Solution {
public:
    vector<vector<int>> res = {{}};
    vector<int> once;

    void dfs(vector<int> &nums, int start_index){
        if(start_index == nums.size())
            return;
        for(int i = start_index; i < nums.size(); i++)
        {
            once.push_back(nums[i]);
            res.push_back(once);
            dfs(nums, i + 1);
            once.pop_back();
        }
    }

    vector<vector<int>> subsets(vector<int>& nums) {
        dfs(nums, 0);
        return res;
    }
};

90.子集II

力扣题目链接

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

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

思路:和上一题类似,做好去重即可。去重还是老套路,排完序之后遇到之前出现过的continue即可。

通过代码:

class Solution {
public:
    vector<vector<int>> res = {{}};
    vector<int> once;

    void dfs(vector<int> &nums, int start_index){
        if(start_index == nums.size())
            return;
        for(int i = start_index; i < nums.size(); i++)
        {
            if(i > start_index && nums[i] == nums[i - 1])
                continue;
            once.push_back(nums[i]);
            res.push_back(once);
            dfs(nums, i + 1);
            once.pop_back();
        }
    }

    vector<vector<int>> subsetsWithDup(vector<int>& nums) {
        sort(nums.begin(), nums.end());
        dfs(nums, 0);
        return res;
    }
};

491.非递减子序列

力扣题目链接

题目:给你一个整数数组nums,找出并返回所有该数组中不同的递增子序列,递增子序列中至少有两个元素 。你可以按任意顺序返回答案。

数组中可能含有重复元素,如出现两个整数相等,也可以视作递增序列的一种特殊情况。

思路:和之前回溯题目的套路类似,只不过不能通过排序去重了。为了避免在同一层使用已经遇到过的元素,所以需要设置一个flag数组标记某个值是否出现过。题目里元素的取值范围为[-100, 100],所以用数组就行,用unordered_set速度会慢一些。

通过代码:

class Solution {
public:
    vector<vector<int>> res;
    vector<int> once;

    void dfs(vector<int> &nums, int start_index){
        if(start_index == nums.size())
            return;
        bool flag[201] = {};
        for(int i = start_index; i < nums.size(); i++)
        {
            if(!flag[nums[i] + 100] && (once.empty() || nums[i] >= once.back()))
            {
                once.push_back(nums[i]);
                flag[nums[i] + 100] = true;
            }
            else
                continue;
            if(once.size() > 1)
                res.push_back(once);
            dfs(nums, i + 1);
            once.pop_back();
        }
    }

    vector<vector<int>> findSubsequences(vector<int>& nums) {
        dfs(nums, 0);
        return res;
    }
};

46.全排列

力扣题目链接

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

思路:类似之前的题目,只不过不需要start_index了。但是需要一个used数组,标记已经选择的元素。

通过代码:

class Solution {
public:
    vector<vector<int>> res;
    vector<int> once;
    bool used[21] = {};

    void dfs(vector<int> &nums){
        if(once.size() == nums.size())
        {
            res.push_back(once);
            return;
        }
        for(int i = 0; i < nums.size(); i++)
        {
            if(used[nums[i] + 10])
                continue;
            used[nums[i] + 10] = true;
            once.push_back(nums[i]);
            dfs(nums);
            once.pop_back();
            used[nums[i] + 10] = false;
        }
    }
    vector<vector<int>> permute(vector<int>& nums) {
        dfs(nums);
        return res;
    }
};

47.全排列 II

力扣题目链接

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

思路:本题和上题的区别在于包含重复数字,因此上题当哈希表用的used数组得改一下,改成和nums等长的一对一的used数组,通过下标来判断是否使用过。还有一个需要改的就是去重,和以前的思路一样,先排序,然后判断前一个是否是一样的。

通过代码:

class Solution {
public:
    vector<vector<int>> res;
    vector<int> once;

    void dfs(vector<int> &nums, vector<bool> &used){
        if(once.size() == nums.size())
        {
            res.push_back(once);
            return;
        }
        for(int i = 0; i < nums.size(); i++)
        {
            if(used[i])
                continue;
            if(i > 0 && nums[i] == nums[i - 1] && !used[i - 1])
                continue;
            used[i] = true;
            once.push_back(nums[i]);
            dfs(nums, used);
            once.pop_back();
            used[i] = false;
        }
    }

    vector<vector<int>> permuteUnique(vector<int>& nums) {
        vector<bool> used(nums.size(), false);
        sort(nums.begin(), nums.end());
        dfs(nums, used);
        return res;
    }
};

332.重新安排行程

力扣题目链接

题目:给你一份航线列表tickets,其中tickets[i] = [fromi, toi]表示飞机出发和降落的机场地点。请你对该行程进行重新规划排序。

所有这些机票都属于一个从JFK(肯尼迪国际机场)出发的先生,所以该行程必须从JFK开始。如果存在多种有效的行程,请你按字典排序返回最小的行程组合。

  • 例如,行程["JFK", "LGA"]["JFK", "LGB"]相比就更小,排序更靠前。

假定所有机票至少存在一种合理的行程。且所有的机票必须都用一次且只能用一次。

思路:如果用普通的深搜+回溯的话会超时。即便按照tickets的第二个元素排序之后还是有两个样例过不了。通过分析可知我们需要迅速定位特定出发地的机票,因此可以采用unordered_map。key存出发地,value存目的地。由于可能存在重复的机票,所以最好有一个int来保存机票数量。因此value最好也是一个map。由于题目要求字典序最小,所以采用map还可以保证找到的第一个行程就是字典序最小的(因为map有序)。综上,设置一个unordered_map<string, map<string, int>>的数据结构来重新组织机票信息。由于找到第一个就可以返回了,所以设置一个布尔类型的返回值来表示是否找到。接下来按常规的思路继续做即可。

通过代码:

class Solution {
public:
    vector<string> path = {"JFK"};
    unordered_map<string, map<string, int>> targets;

    bool dfs(int ticket_num){
        if(path.size() == ticket_num + 1)            
            return true;
        for(pair<const string, int> &dest : targets[path.back()])
        {
            if(dest.second < 1)
                continue;
            dest.second--;
            path.push_back(dest.first);
            if(dfs(ticket_num))
                return true;
            path.pop_back();
            dest.second++;
        }
        return false;
    }

    vector<string> findItinerary(vector<vector<string>>& tickets) {
        for(vector<string> ticket : tickets)
            targets[ticket[0]][ticket[1]]++;
        dfs(tickets.size());
        return path;
    }
};

51. N皇后

力扣题目链接

题目:按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。

n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。

给你一个整数 n ,返回所有不同的 n 皇后问题 的解决方案。

每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 'Q''.' 分别代表了皇后和空位。

思路:由于每行只能有一个皇后,所以递归可以按行进行。单次递归里通过for循环遍历该行里的列。

通过代码:

class Solution {
public:
    vector<vector<string>> res;

    bool check(int row, int col, int n, vector<string> &board){
        for(int i = 0; i < row; i++)
            if(board[i][col] == 'Q')
                return false;
        for(int i = row -1, j = col - 1; i >= 0 && j >= 0; i--, j--)
            if(board[i][j] == 'Q')
                return false;
        for(int i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++)
            if(board[i][j] == 'Q')
                return false;
        return true;
    }

    void dfs(int n, int row, vector<string> &board){
        if(row == n)
        {
            res.push_back(board);
            return;
        }
        for(int j = 0; j < n; j++)
        {
            if(check(row, j, n, board))
            {
                board[row][j] = 'Q';
                dfs(n, row + 1, board);
                board[row][j] = '.';
            }
        }
    }

    vector<vector<string>> solveNQueens(int n) {
        vector<string> board(n, string(n, '.'));
        dfs(n, 0, board);
        return res;
    }
};

37. 解数独

力扣题目链接

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

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

  1. 数字 1-9 在每一行只能出现一次。
  2. 数字 1-9 在每一列只能出现一次。
  3. 数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。(请参考示例图)

数独部分空格内已填入了数字,空白格用 '.' 表示。

思路:依次枚举每一个空白格中填的数字,通过递归 + 回溯的方法枚举所有可能的填法。当递归到最后一个空白格后,如果仍然没有冲突,说明我们找到了答案;在递归的过程中,如果当前的空白格不能填下任何一个数字,那么就进行回溯

通过代码:

class Solution {
public:
    bool check(int row, int col, char num, vector<vector<char>> &board){
        for(int j = 0; j < 9; j++)
            if(j != col && board[row][j] == num)
                return false;
        for(int i = 0; i < 9; i++)
            if(i != row && board[i][col] == num)
                return false;
        int si = row / 3, sj = col / 3;
        for(int i = si * 3; i < (si + 1) * 3; i++)
            for(int j = sj * 3; j < (sj + 1) * 3; j++)
                if(i != row && j != col && board[i][j] == num)
                    return false;
        return true;
    }

    bool dfs(vector<vector<char>> &board){
        for(int i = 0; i < 9; i++)
            for(int j = 0; j < 9; j++)
            {
                if(board[i][j] == '.')
                {
                    for(char c = '1'; c <= '9'; c++)
                    {
                        if(check(i, j, c, board))
                        {
                            board[i][j] = c;
                            if(dfs(board))
                                return true;
                            board[i][j] = '.';
                        }
                    }
                    return false;
                }
            }
        return true;
    }

    void solveSudoku(vector<vector<char>>& board) {
        dfs(board);
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

h0l10w

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值