算法:回溯算法

引言

40. 组合总和 II - 力扣(LeetCode)

93. 复原 IP 地址 - 力扣(LeetCode)

78. 子集 - 力扣(LeetCode)

491. 非递减子序列 - 力扣(LeetCode)

46. 全排列 - 力扣(LeetCode)

47. 全排列 II - 力扣(LeetCode)

51. N 皇后 - 力扣(LeetCode)

代码

第一题

这个题目的最大难题就是去重,所以我们先对于这个数组进行一个排序,那么我们就可以从小到大一个一个的遍历,只要当我们的和大于目标的时候,我们就直接开始回溯。而且这样可以把相同的元素放在一起,便于我们的去重。我们去重的方法用到了used数组,只要这个元素和前面一个元素相同并且前面一个元素没有被使用过了,那么就说明这两个元素已经重复。为什么是没有被使用过呢,因为如果是使用过的,说明这是第一次出现这个组合,比如 {1,2,2},但是如果是没有使用过,那么就说明我们是在回溯的过程之中,那个数因为之前已经被处理过了所以被标记为了false。之所以我们不是直接用当前元素和上一个元素进行比较,是因为我们是在回溯,我们需要确定所有元素的情况,而不是单一的一个相对为止,i和i-1。

cpp 复制代码
class Solution {
public:
    vector<int> path;
    vector<vector<int>> res;
    void traversal(vector<int>& candidates, int target, int sum, int startIndex, vector<bool>& used) {
        if (sum > target) {
            return;
        }
        if (sum == target) {
            res.push_back(path);
            return;
        }
        for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; i++) {
            if (i > 0 && candidates[i] == candidates[i - 1] && used[i - 1] == false) {
                continue;
            }
            used[i] = true;
            sum += candidates[i];
            path.push_back(candidates[i]);
            traversal(candidates, target, sum, i + 1, used);
            path.pop_back();
            used[i] = false;
            sum -= candidates[i];
        }
    }

    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        vector<bool> used(candidates.size(), false);
        sort(candidates.begin(), candidates.end());
        traversal(candidates, target, 0, 0, used);
        return res;
    }
};

第二题

这一题考察的是分割字符串,我们的startIndex不再是数组的字母了,而是字母间的位置。我们每一次分割一段字串之后都会进行判断,而我们的起点一个是startIndex,终点是i,这个点我们可以理解成每一个数组元素后面的那个空格,比如i= 0,那么就对应的是第0个元素后面的那一个空格,所以这也是一个左闭右闭得范围。然后我们每一次插入都要确定这个字串是符合规定得,如果不符合规定那么就结束这个循环,因为再往后遍历肯定也不符合。最后一定要注意,我们插入得那个元素会改变整个数组得下标,所以是i + 2,不再是i + 1。

cpp 复制代码
class Solution {
public:
    vector<string> res;

    bool isValid(const string& s, int start, int end) {
        if (start > end) {
            return false;
        }
        if (s[start] == '0' && start != end) {
            return false;
        }
        int num = 0;
        for (int i = start; i <= end; i++) {
            if (s[i] > '9' || s[i] < '0') {
                return false;
            }
            num = num * 10 + (s[i] - '0');
            if (num > 255) {
                return false;
            }
        }
        return true;
    }

    void traversal(string& s, int startIndex, int pointNum) {
        if (pointNum == 3) {
            if (isValid(s, startIndex, s.size() - 1)) {
                res.push_back(s);
            }
            return;
        }
        for (int i = startIndex; i < s.size(); i++) {
            if (isValid(s, startIndex, i)) {
                s.insert(s.begin() + i + 1, '.');
                pointNum++;
                traversal(s, i + 2, pointNum);
                pointNum--;
                s.erase(s.begin() + i + 1);
            } else {
                break;
            }
        }
    }

    vector<string> restoreIpAddresses(string s) {
        if (s.size() < 4 || s.size() > 12) {
            return res;
        }
        traversal(s, 0, 0);
        return res;
    }
};

第三题

这一题的主要难题就是怎么记录子集,我们一般来说都是判断一个条件然后把结果放进去,但是因为子集不需要任何判断的条件,所以要放到最开始,反而判断的作用仅仅是为了可以回溯。所以我们需要理解我们记录的意义到底是什么

cpp 复制代码
class Solution {
public:
    vector<int> path;
    vector<vector<int>> res;

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

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

第四题

这一题的难点是我们需要对没有排序的数组进行去重,所以我们不可以使用used数组了,我们这里引用uset,但是注意一下,我们uset这个是在函数里面定义的,也就是说每一层的递归都有一个新的uset。因为我们去重的目的就是每一层去重。我们这里深入了两个概念,一个是层,一个是树枝。层代表了这一个循环,也就是取决于开始的位置,也就是startIndex。可是为什么我们之前一直没有关心这个呢,是因为我们之前一直都是处理树枝,就是递归后的结果,而这里需要的是一层一层的结果。

cpp 复制代码
class Solution {
public:
    vector<int> path;
    vector<vector<int>> res;

    void traversal(vector<int>& nums, int startIndex) {
        if (path.size() > 1) {
            res.push_back(path);
        }
        unordered_set<int> uset;
        for (int i = startIndex; i < nums.size(); i++) {
            if ((!path.empty() && nums[i] < path.back()) || uset.find(nums[i]) != uset.end()) {
                continue;
            }
            uset.insert(nums[i]);
            path.push_back(nums[i]);
            traversal(nums, i + 1);
            path.pop_back();
        }
    }

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

第五题

这是一个全排列的问题,也就是说和起点没有什么关系,所以我们这里和startIndex没啥关系,但是因为要记录我们之前遍历了哪一些点,所以我们用另外一个数组used来记录。

cpp 复制代码
class Solution {
public:
    vector<int> path;
    vector<vector<int>> res;

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

    vector<vector<int>> permute(vector<int>& nums) {
        vector<bool> used(nums.size(), false);
        traversal(nums, used);
        return res;
    }
};

第六题

这一题也是全排列,而且还需要去重。所以我们必须要理解我们到底在哪里收集数据。我们肯定是在最后面,也就是树枝的末尾接受数据,但是因为是全排列,所以我们还是不需要startIndex,然后我们依然先排序,把相同的数放在一起,然后我们按照原来的去重逻辑,不过还有一点要注意的是因为这个是全排列,所以我们每一次都是从0开始遍历的,所以不要忘记了在操作的时候要判断这个数是不是已经被记录了哦~~~

cpp 复制代码
class Solution {
public:
    vector<int> path;
    vector<vector<int>> res;

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

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

第七题

首先我们需要一个函数来判断我们这个点是不是符合规矩的。然后我们的落子路线是一行一行的,所以我们不需要判断每一行是不是符合规矩的,因为我们下棋的时候就已经保证了每一行只有一个。然后我们需要在每一行开始遍历每一列,所以for。环是从0开始的,但是我们路线是根据一行一行来的,所以我们传递参数的时候需要记录一下当前是第几行的。这也就是N皇后的解法,其实也不是很难,一层一层的遍历。

cpp 复制代码
class Solution {
public:
    vector<vector<string>> res;

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

    void traversal(vector<string>& chessboard, int row, int n) {
        if (row == n) {
            res.push_back(chessboard);
            return;
        }
        for (int col = 0; col < n; col++) {
            if (isValid(row, col, chessboard, n)) {
                chessboard[row][col] = 'Q';
                traversal(chessboard, row + 1, n);
                chessboard[row][col] = '.';
            }
        }
    }

    vector<vector<string>> solveNQueens(int n) {
        std::vector<std::string> chessboard(n, std::string(n, '.'));
        traversal(chessboard,0 , n);
        return res;
    }
};
相关推荐
数模竞赛Paid answer3 分钟前
2026年华东杯数学建模B题医药物流安排问题解题全过程文档及程序
算法·数学建模·数据分析·华东杯
不会代码的小猴44 分钟前
21. 泛型编程上
开发语言·c++·笔记·算法
营养充电站1 小时前
VS Code Git 工作树:解锁多分支并行开发的高效体验
leetcode·决策树·逻辑回归·散列表·模拟退火算法
青瓦梦滋1 小时前
传输层UDP/TCP协议
linux·网络·c++·网络协议·tcp/ip·udp
AI备案指南-满满1 小时前
大模型与算法备案全流程详解:从零到通过的完整指南
人工智能·算法·备案·大模型备案·算法备案
江畔柳前堤1 小时前
LLM 训练核心机制深度解析:Warmup、Cosine Decay 与 Perplexity 的完整知识体系
网络·人工智能·深度学习·算法·机器学习·语音识别
一只旭宝2 小时前
细讲C加加【9】C++ std::function与std::bind详解|仿函数、绑定器、类成员绑定、占位符、成员偏移指针
开发语言·c++·算法
良木林2 小时前
子串 - LeetCode hot 100
算法·leetcode·职场和发展
陌诺曦.3 小时前
Python扩展小练习
算法