当前位置: 首页 > article >正文

【算法篇】回溯算法类(1)(笔记)

目录

一、理论基础

1. 相关题目

2. 遍历过程

3. 代码框架 

二、LeetCode 题目

1. 组合

2. 组合总和III

3. 电话号码的字母组合

4. 组合总和

5. 组合总和II

6. 分割回文串

7. 复原IP地址

8. 子集


一、理论基础

1. 相关题目

2. 遍历过程

3. 代码框架 

void backtracking(参数) {
    if (终止条件) {
        存放结果;
        return;
    }

    for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
        处理节点;
        backtracking(路径,选择列表); // 递归
        回溯,撤销处理结果
    }
}

二、LeetCode 题目

1. 组合

https://leetcode.cn/problems/combinations/description/icon-default.png?t=O83Ahttps://leetcode.cn/problems/combinations/description/

        给定两个整数 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>> result;
    vector<int> path;
    void backtracking(int n, int k, int startIndex) {
        // 满足条件插入
        if (path.size() == k) {
            result.push_back(path);
            return;
        }

        // 如果后面的数组满足不了能取出 k - path.size() 个数组,则不用遍历了
        for (int i = startIndex; i <= n - (k - path.size()) + 1; i++) {
            path.push_back(i);
            backtracking(n, k, i + 1);
            path.pop_back();
        }

        return;
    }

    vector<vector<int>> combine(int n, int k) {
        backtracking(n, k, 1);
        return result;
    }
};

2. 组合总和III

https://leetcode.cn/problems/combination-sum-iii/description/icon-default.png?t=O83Ahttps://leetcode.cn/problems/combination-sum-iii/description/

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

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

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

示例 1:
输入: k = 3, n = 7
输出: [[1,2,4]]
解释:
1 + 2 + 4 = 7
没有其他符合的组合了。

示例 2:
输入: k = 3, n = 9
输出: [[1,2,6], [1,3,5], [2,3,4]]
解释:
1 + 2 + 6 = 9
1 + 3 + 5 = 9
2 + 3 + 4 = 9
没有其他符合的组合了。

示例 3:
输入: k = 4, n = 1
输出: []
解释: 不存在有效的组合。
在[1,9]范围内使用4个不同的数字,我们可以得到的最小和是1+2+3+4 = 10,因为10 > 1,没有有效的组合。

class Solution {
public:
    vector<vector<int>> result;
    vector<int> path;
    void backtracking(int targetSum, int k, int sum, int startIndex) {
        if (path.size() == k) {
            if (targetSum == sum) result.push_back(path);
            return;
        }

        // 剪枝
        for (int i = startIndex; i <= 9 - (k - path.size()) + 1; i++) {
            sum += i;
            path.push_back(i);
            backtracking(targetSum, k, sum, i + 1);
            sum -= i;
            path.pop_back();
        }

        return;
    }

    vector<vector<int>> combinationSum3(int k, int n) {
        backtracking(n, k, 0, 1);
        return result;
    }
};

3. 电话号码的字母组合

https://leetcode.cn/problems/letter-combinations-of-a-phone-number/description/icon-default.png?t=O83Ahttps://leetcode.cn/problems/letter-combinations-of-a-phone-number/description/

        给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。

示例 1:
输入:digits = "23"
输出:["ad","ae","af","bd","be","bf","cd","ce","cf"]

示例 2:
输入:digits = ""
输出:[]

示例 3:
输入:digits = "2"
输出:["a","b","c"]

class Solution {
private:
    const string letterMap[10] = {
        "", // 0
        "", // 1
        "abc", // 2
        "def", // 3
        "ghi", // 4
        "jkl", // 5
        "mno", // 6
        "pqrs", // 7
        "tuv", // 8
        "wxyz", // 9
    };
public:
    vector<string> result;
    string s;
    void backtracking(const string& digits, int index) {
        if (index == digits.size()) {
            result.push_back(s);
            return;
        }
        int digit = digits[index] - '0';        // 将index指向的数字转为int
        string letters = letterMap[digit];      // 取数字对应的字符集
        for (int i = 0; i < letters.size(); i++) {
            s.push_back(letters[i]);            // 处理
            backtracking(digits, index + 1);    // 递归,注意index+1,一下层要处理下一个数字了
            s.pop_back();                       // 回溯
        }
    }
    vector<string> letterCombinations(string digits) {
        s.clear();
        result.clear();
        if (digits.size() == 0) {
            return result;
        }
        backtracking(digits, 0);
        return result;
    }
};

4. 组合总和

https://leetcode.cn/problems/combination-sum/description/icon-default.png?t=O83Ahttps://leetcode.cn/problems/combination-sum/description/

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

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

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

示例 1:
输入:candidates = [2,3,6,7], 
target = 7
输出:[[2,2,3],[7]]
解释:
2 和 3 可以形成一组候选,2 + 2 + 3 = 7 。注意 2 可以使用多次。
7 也是一个候选, 7 = 7 。
仅有这两种组合。

示例 2:
输入: candidates = [2,3,5], target = 8
输出: [[2,2,2,2],[2,3,3],[3,5]]

示例 3:
输入: candidates = [2], target = 1
输出: []

class Solution {
public:
    vector<vector<int>> result;
    vector<int> record;
    void backtracking(vector<int>& candidates, int targetnum, int sum, int startIndex) {
        if (sum == targetnum) {
            result.push_back(record);
            return;
        }

        for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= targetnum; i++) {
            record.push_back(candidates[i]);
            backtracking(candidates, targetnum, sum + candidates[i], i);
            record.pop_back();
        }

        return;
    }

    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        sort(candidates.begin(), candidates.end()); // 需要排序
        backtracking(candidates, target, 0, 0);
        return result;
    }
};

5. 组合总和II

https://leetcode.cn/problems/combination-sum-ii/description/icon-default.png?t=O83Ahttps://leetcode.cn/problems/combination-sum-ii/description/

        给定一个候选人编号的集合 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]
]

示例 2:
输入: candidates = [2,5,2,1,2], target = 5,
输出:
[
[1,2,2],
[5]
]

// 方法一:
class Solution {
public:
    vector<vector<int>> result;
    vector<int> path;
    void backtracking(vector<int>& candidates, int startIndex, int target) {
        if (target == 0) {
            result.push_back(path);
            return;
        }
        for (int i = startIndex; i < candidates.size() && target - candidates[i] >= 0; i++) {
            if (i > startIndex && candidates[i] == candidates[i - 1]) {
                continue;
            }
            path.push_back(candidates[i]);
            backtracking(candidates, i + 1, target - candidates[i]);
            path.pop_back();
        }
        return;
    }

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


// 方法二:
class Solution {
public:
    vector<vector<int>> result;
    vector<int> path;
    void backtracking(vector<int>& candidates, int startIndex, int target) {
        if (target == 0) {
            result.push_back(path);
            return;
        }

        unordered_set<int> uset;
        for (int i = startIndex; i < candidates.size() && target - candidates[i] >= 0; i++) {
            if (uset.find(candidates[i]) != uset.end()) {
                continue;
            }
            uset.insert(candidates[i]);
            path.push_back(candidates[i]);
            backtracking(candidates, i + 1, target - candidates[i]);
            path.pop_back();
        }
        return;
    }

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

6. 分割回文串

https://leetcode.cn/problems/palindrome-partitioning/description/icon-default.png?t=O83Ahttps://leetcode.cn/problems/palindrome-partitioning/description/

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

示例 1:
输入:s = "aab"
输出:[["a","a","b"],["aa","b"]]

示例 2:
输入:s = "a"
输出:[["a"]]

class Solution {
public:
    vector<vector<string>> result;
    vector<string> st;
    void backtracking(const string s, int beginIndex) {
        // 找到一种切割方法
        if (beginIndex == s.size()) {
            result.push_back(st);
            return;
        }

        for (int i = beginIndex; i < s.size(); i++) {
            if (isPalindrome(s, beginIndex, i)) {
                // 是回文子串,保存
                st.push_back(s.substr(beginIndex, i - beginIndex + 1));
            }
            else {
                continue;
            }
            backtracking(s, i + 1);
            st.pop_back();
        }

        return;
    }

    bool isPalindrome(const string s, int begin, int end) {
        for (int i = begin, j = end; i < j; i++, j--) {
            if (s[i] != s[j]) return false;
        }
        return true;
    }

    vector<vector<string>> partition(string s) {
        backtracking(s, 0);
        return result;
    }
};

7. 复原IP地址

https://leetcode.cn/problems/restore-ip-addresses/description/icon-default.png?t=O83Ahttps://leetcode.cn/problems/restore-ip-addresses/description/

        有效 IP 地址 正好由四个整数(每个整数位于 0 到 255 之间组成,且不能含有前导 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 中的任何数字。你可以按 任何 顺序返回答案。

示例 1:
输入:s = "25525511135"
输出:["255.255.11.135","255.255.111.35"]

示例 2:
输入:s = "0000"
输出:["0.0.0.0"]

示例 3:
输入:s = "101023"
输出:["1.0.10.23","1.0.102.3","10.1.0.23","10.10.2.3","101.0.2.3"]

// 版本一:
class Solution {
public:
    vector<string> result;
    vector<string> st;
    void backtracking(const string s, int beginIndex) {
        if (beginIndex == s.size()) {
            if (st.size() == 4) {
                string buff = st.front();
                for (int j = 1; j < 4; j++)  buff = buff + "." + st[j];
                result.push_back(buff);
                return;
            }
            else {
                return;
            }
        }

        for (int i = beginIndex; i < s.size(); i++) {
            if (judgeNum(s, beginIndex, i)) {
                // 在范围内
                st.push_back(s.substr(beginIndex, i - beginIndex + 1));
                backtracking(s, i + 1);
                st.pop_back();
            }
            else {
                break;
            }
        }

        return;
    }

    bool judgeNum(const string& s, int start, int end) {
    if (start > end) {
        return false;
    }
    if (s[start] == '0' && start != end) { // 0开头的数字不合法
            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) { // 如果大于255了不合法
            return false;
        }
    }
    return true;
}

    vector<string> restoreIpAddresses(string s) {
        backtracking(s, 0);
        return result;
    }
};


// 版本二:
class Solution {
private:
    vector<string> result;  // 记录结果

    // startIndex: 搜索的起始位置,pointNum:添加逗点的数量
    void backtracking(string& s, int startIndex, int pointNum) {
        if (pointNum == 3) { // 逗点数量为3时,分隔结束
            // 判断第四段子字符串是否合法,如果合法就放进result中
            if (isValid(s, startIndex, s.size() - 1)) {
                result.push_back(s);
            }
            return;
        }

        for (int i = startIndex; i < s.size(); i++) {
            if (isValid(s, startIndex, i)) { // 判断 [startIndex, i] 这个区间的子串是否合法
                s.insert(s.begin() + i + 1 , '.');  // 在i的后面插入一个逗点
                pointNum++;
                backtracking(s, i + 2, pointNum);   // 插入逗点之后下一个子串的起始位置为i+2
                pointNum--;                         // 回溯
                s.erase(s.begin() + i + 1);         // 回溯删掉逗点
            } else {
                break; // 不合法,直接结束本层循环
            }
        }
    }

    // [start, end]
    bool isValid(const string& s, int start, int end) {
        if (start > end) {
            return false;
        }
        if (s[start] == '0' && start != end) { // 0开头的数字不合法
                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) { // 如果大于255了不合法
                return false;
            }
        }
        return true;
    }

public:
    vector<string> restoreIpAddresses(string s) {
        result.clear();
        if (s.size() < 4 || s.size() > 12) return result; // 算是剪枝了
        backtracking(s, 0, 0);
        return result;
    }
};

8. 子集

https://leetcode.cn/problems/subsets/description/icon-default.png?t=O83Ahttps://leetcode.cn/problems/subsets/description/

        给你一个整数数组 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<vector<int>> result;
    vector<int> vec;
    void backtracking(vector<int>& nums, int beginIndex) {
        result.push_back(vec);
        if (beginIndex >= nums.size()) {
            return;
        }

        for (int i = beginIndex; i < nums.size(); i++) {
            vec.push_back(nums[i]);
            backtracking(nums, i + 1);
            vec.pop_back();
        }
        return;
    }

    vector<vector<int>> subsets(vector<int>& nums) {
        if (nums.size() == 0) return result;
        backtracking(nums, 0);
        return result;
    }
};


http://www.kler.cn/news/329801.html

相关文章:

  • 虚拟机U盘启动
  • 使用 Git 帮助文档
  • 养老院管理系统(含源码+sql+视频导入教程+文档)
  • 图解C#高级教程(四):协变、逆变
  • JSR303微服务校验
  • docker-compose 快速部署clickhouse集群
  • Springboot项目在win系统开发部署到linux服务器出现上传文件编码问题
  • 如何改善网站的核心网络生命力
  • TypeScript 封装 Axios 1.7.7
  • 软件测试面试八股文(含答案+文档)
  • 【yolov8】模型导出----pytorch导出为onnx模型
  • windows美化终端
  • go结构体默认值和校验器(go-defaults、go-validator)
  • 【Redis】主从复制(下)--主从复制原理和流程
  • 【数据分享】2001-2023年我国省市县镇四级的逐月平均气温数据(免费获取/Shp/Excel格式)
  • ubuntu 安装kvm 创建windos虚拟机
  • AMD 矩阵核心
  • 搜维尔科技:使用Xsens动作捕捉系统和ai训练人形机器人模仿人类运动,执行复杂任务
  • docker环境下配置cerbot获取免费ssl证书并自动续期
  • java中有两个list列表,尽量少的去循环
  • 模版and初识vector
  • windows系统电脑上scrcpy源码本地调试
  • Java基础——十二、容器
  • 5G NR物理信号
  • git push 远程仓库 linux版
  • 爬虫——爬虫理论+request模块
  • 【Linux】进程周边之优先级
  • 陶建辉先生荣获 2024 年“中国计算机学会(CCF)杰出工程师奖”
  • Harbor系列之12:对接外部redis和pg数据库的harbor容器化部署
  • C++:采用模板封装顺序表,栈,队列