LeetCode题解之回溯算法

回溯算法要点

回溯法,⼀般可以解决如下⼏种问题:
组合问题: N个数⾥⾯按⼀定规则找出k个数的集合
切割问题:⼀个字符串按⼀定规则有⼏种切割⽅式
⼦集问题:⼀个N个数的集合⾥有多少符合条件的⼦集
排列问题: N个数按⼀定规则全排列,有⼏种排列⽅式
棋盘问题: N皇后,解数独等等

回溯模板三部曲:
1. 回溯函数模板返回值以及参数,返回值一般为void
2. 回溯函数终止条件
3. 回溯搜索的遍历过程
回溯算法模板框架:

void backtracking(参数) {
	if (终⽌条件) {
		存放结果;
		return;
	}
	for (选择:本层集合中元素(树中节点孩⼦的数量就是集合的⼤⼩) ) {
		处理节点;
		backtracking(路径,选择列表); // 递归
		回溯,撤销处理结果
	}
} 

组合问题

77. 组合

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

216. 组合总和 III

找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。

说明:

所有数字都是正整数。
解集不能包含重复的组合

第一就是递归的for循环开始的位置要注意,如果只能用一次就是i+1,如果不是就是i
第二注意点就是对性能进行剪枝优化

class Solution {
public:
    vector<vector<int>> result;
    vector<int> path;
    int sum = 0;
    vector<vector<int>> combinationSum3(int k, int n) {
        result.clear();
        path.clear();
        backtracking(k, n, 1);
        return result;
    }
    void backtracking(int k, int n, int startIndex) {
        if (sum > n) return;
        if (path.size() == k && sum == n) {
            result.push_back(path);
            return;
        }
        for (int i = startIndex; i <=  9 - (k - path.size()) + 1; i++){//剪枝
            path.push_back(i);
            sum += i;
            backtracking(k, n, i+1);//这里是i+1
            sum -= i;
            path.pop_back();
        }
    }
};

39. 组合总和

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

candidates 中的数字可以无限制重复被选取。

说明:

所有数字(包括 target)都是正整数。
解集不能包含重复的组合。

这里去重的目的是说,同一层之间不能被选取在一个集合,不同层之间是可以的,也就是递归是选择i而不是i+1
无限制重复被选取:backtracking(candidates, target, i);让递归函数开始的时候从i处开始而不是i+1

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

    void backtracking(vector<int>& candidates, int target, int sum, int startIndex)
    {
        if(sum > target) return;
        if(sum == target) {
            res.push_back(path);
            return;
        }
        for(int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; i++)
        {
            //sum += candidates[i];
            path.push_back(candidates[i]);
            backtracking(candidates, target, sum + candidates[i], i);
            path.pop_back();

        }
    }
};

40. 组合总和 II(☆)

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

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

说明:

所有数字(包括目标数)都是正整数。
解集不能包含重复的组合。

在这里插入图片描述

used[i - 1] == true,说明同⼀树⽀candidates[i - 1]使⽤过
used[i - 1] == false,说明同⼀树层candidates[i - 1]使⽤过

代码:

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

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

    void backtracking(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++)
        {
            // used[i - 1] == true,说明同⼀树⽀candidates[i - 1]使⽤过
            // used[i - 1] == false,说明同⼀树层candidates[i - 1]使⽤过
            // 要对同⼀树层使⽤过的元素进⾏跳过
            if(i > startIndex && candidates[i] == candidates[i-1] && used[i] == false) continue;
            used[i] = true;
            path.push_back(candidates[i]);
            backtracking(candidates, target, sum + candidates[i], i+1, used);// 和39.组合总和的区别1,这⾥是i+1,每个数字在每个组合中只能使⽤⼀次

            path.pop_back();
            used[i] = false;

        }

    }
};

对于本题还有另外一种写法:

去重(保证本层不重复,但是不同层(递归深度)重复):首选必须排序:
sort(candidates.begin(), candidates.end());
其次进行判断
if (i > startIndex && candidates[i] == candidates[i-1]) continue;

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

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

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

        }

    }
};

17. 电话号码的字母组合

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

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

针对每个数字对应的字符可以构建一个数组:
const string letterMap[10] = {"","", “abc”, “def”, “ghi”, “jkl”, “mon”, “pqrs”, “tuv”, “wxyz”};
在遍历的时候,注意因为每一个数字都是一个string所以需要双重循环

class Solution {
public:
    vector<string> res;
    string str;
    vector<string> letterCombinations(string digits) {
        res.clear();
        if(digits.size() == 0) return res;
        backtracking(digits, 0);
        return res;
    }

    void backtracking (string digits, int startIndex)
    {
        if(str.size() > digits.size()) return;
        if(str.size() == digits.size())
        {
            res.push_back(str);
            return;
        }

        for(int i = startIndex; i < digits.size(); i++)
        {
            int k = digits[i] - '0';
            string letter = letterMap[k];

            for(int j = 0; j <letter.size(); j++)
            {
                str += letter[j];
                backtracking(digits, i+1);
                str.pop_back();
            }
        } 
    }

private:
    const string letterMap[10] = {"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
};

分割问题:

对于字符串abcdef:
组合问题:选取⼀个a之后,在bcdef中再去选取第⼆个,选取b之后在cdef中在选组第三个…
切割问题:切割⼀个a之后,在bcdef中再去切割第⼆段,切割b之后在cdef中在切割第三段…

在这里插入图片描述
那么在代码⾥什么是切割线呢?
在处理组合问题的时候,递归参数需要传⼊startIndex,表示下⼀轮递归遍历的起始位置,这个startIndex就是切割线

切割问题的几个难点:
切割问题可以抽象为组合问题
如何模拟哪些切割线
切割问题中递归如何种植
在递归循环中如何截取子串
如何判断回文

131. 分割回文串

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

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

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

代码:

class Solution {
public:
    vector<vector<string>> result;
    vector<string> path; // 放已经回⽂的⼦串
    vector<vector<string>> partition(string s) {
        result.clear();
        path.clear();
        backtracking(s, 0);
        return result;   
    }

    void backtracking (const string& s, int startIndex)
    {
        // 如果起始位置已经⼤于s的⼤⼩,说明已经找到了⼀组分割⽅案了
        if (startIndex >= s.size()) {
            result.push_back(path);
           
            return;
        }
        for(int i = startIndex; i < s.size(); i++)
        {
            if(isPalindrome(s, startIndex, i))
            {
                // 获取[startIndex,i]在s中的⼦串
                string str = s.substr(startIndex, i - startIndex + 1);
                path.push_back(str);
            }
            else continue;

            backtracking(s, i+1);// 寻找i+1为起始位置的⼦串
            path.pop_back();
        }
    }
    
    bool isPalindrome(const string& s, int start, int end) {
        for (int i = start, j = end; i < j; i++, j--) {
            if (s[i] != s[j]) {
                return false;
            }
        }
        return true;
    }
};

93. 复原 IP 地址(☆☆)

给定一个只包含数字的字符串,用以表示一个 IP 地址,返回所有可能从 s 获得的 有效 IP 地址 。你可以按任何顺序返回答案。

有效 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 地址。

难点主要在于如何进行分割成四份,利用什么标志位来实现
第二就是对每一份进行判断是否合理

class Solution {
public:
    vector<string> res;
    vector<string> restoreIpAddresses(string &s) {
        res.clear();
        if(s.size() > 12) return res;
        backtracking(s, 0, 0);
        return res;
    }

    void backtracking(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)) { // 判断 [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; // 不合法,直接结束本层循环
        }
    }

    // 判断字符串s在左闭⼜闭区间[start, end]所组成的数字是否合法
     /*
    段位以0为开头的数字不合法
    段位里有非正整数字符不合法
    段位如果大于255了不合法
    */

    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;
    }
};

1593. 拆分字符串使唯一子字符串的数目最大

给你一个字符串 s ,请你拆分该字符串,并返回拆分后唯一子字符串的最大数目。

字符串 s 拆分后可以得到若干 非空子字符串 ,这些子字符串连接后应当能够还原为原字符串。但是拆分出来的每个子字符串都必须是 唯一的 。

注意:子字符串 是字符串中的一个连续字符序列。

class Solution {
public:
    int res = 0;
    unordered_map<string, int> map;
    int maxUniqueSplit(string s) {
        //分割字符串
        if(s.size() < 2) return s.size();
        dfs(s, 0, 0);
        return res;

    }
    void dfs(string s, int startIndex, int sz) {
        //终止条件的判断
        if(startIndex >= s.size()) {
            res = max(sz, res);
            return;
        }

        for(int i = startIndex; i < s.size(); i++) {
            // 获取[startIndex,i]在s中的⼦串
            string str = s.substr(startIndex, i - startIndex + 1);
            if(map.find(str) != map.end()) continue;
            //如果在map中没有找到说明是新的,可以插入
            map[str]++;
            sz++;
            dfs(s, i+1, sz);
            sz--;
            map.erase(str);
        }
    }
};

140. 单词拆分 II

给定一个非空字符串 s 和一个包含非空单词列表的字典 wordDict,在字符串中增加空格来构建一个句子,使得句子中所有的单词都在词典中。返回所有这些可能的句子。

说明:

分隔时可以重复使用字典中的单词。
你可以假设字典中没有重复的单词。

class Solution {
public:
    vector<string> res;
    vector<string> path;
    vector<string> wordBreak(string s, vector<string>& wordDict) {
        unordered_set<string> set(wordDict.begin(), wordDict.end());
        backtracking(s, 0, set);
        return res;
    }

    void backtracking(string s, int startIndex, unordered_set<string>& set) {
        if(startIndex >= s.size()) {
            string str;
            for(int i = 0; i < path.size()-1; i++) {
                str += path[i];
                str += " ";
            }
            str += path.back();
            res.push_back(str);
            return;
        }

        for(int i = startIndex; i < s.size(); i++) {
            string substr = s.substr(startIndex, i - startIndex+1);
            if(set.find(substr) != set.end()) {
                path.push_back(substr);
            } else continue;

            backtracking(s, i+1, set);

           path.pop_back();
        }
    }
};

子集问题

子集问题:在树形结构中⼦集问题是要收集所有节点的结果,⽽组合问题是收集叶⼦节点的结果。
子集去重问题主要是要对每一层的去重

78. 子集

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

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

class Solution {
public:
    vector<vector<int>> res;
    vector<int> path;
    vector<vector<int>> subsets(vector<int>& nums) {
        res.clear();
        path.clear();
        if(nums.size() == 0) return res;
        backtracking(nums, 0);
        return res;
    }

    void backtracking(vector<int>& nums, int startIndex)
    {
        res.push_back(path);

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

90. 子集 II

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

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

去重的思路跟40题组合的思路一样

class Solution {
public:
    vector<vector<int>> res;
    vector<int> path;
    vector<vector<int>> subsetsWithDup(vector<int>& nums) {
        res.clear();
        path.clear();
        if(nums.size() == 0) return res;
        sort(nums.begin(), nums.end());
        backtracking(nums, 0);
        return res;
    }
    
    void backtracking(vector<int>& nums, int startIndex)
    {
        res.push_back(path);

        for(int i = startIndex; i < nums.size(); i++)
        {
            if(i > startIndex && nums[i] == nums[i-1]) continue;//去重
            path.push_back(nums[i]);
            backtracking(nums, i+1);
            path.pop_back();
        }
    }
};

491. 递增子序列(☆☆☆)

给定一个整型数组, 你的任务是找到所有该数组的递增子序列,递增子序列的长度至少是 2 。

输入:[4, 6, 7, 7]
输出:[[4, 6], [4, 7], [4, 6, 7], [4, 6, 7, 7], [6, 7], [6, 7, 7], [7,7], [4,7,7]]

子集去重跟组合不同,统一父节点的同层使用过的本层不能使用了,以往我们是通过排序然后标记一个数组来达到去重的目的
但是这个不能排序,因此不能使用以前的去重逻辑,我们可以在每一层记录一个集合或者哈希数组来进行标记
代码:

class Solution {
public:
    vector<vector<int>> res;
    vector<int> path;
    vector<vector<int>> findSubsequences(vector<int>& nums) {
        res.clear();
        path.clear();
        if(nums.size() == 0) return res;
        backtracking(nums, 0);
        return res;
    }

    void backtracking(vector<int>& nums, int startIndex)
    {
        if(path.size() > 1)
        {
            res.push_back(path);
        }
        

        unordered_set<int> uset;//这里完全用一个哈希数组
        //int used[201] = {0}; // 这⾥使⽤数组来进⾏去重操作,题⽬说数值范围[-100, 100]
        for(int i = startIndex; i < nums.size(); i++)
        {
            if((path.size() > 0 && path.back() > nums[i]) || uset.find(nums[i]) != uset.end()) continue;
                uset.insert(nums[i]);
                path.push_back(nums[i]);
                backtracking(nums, i+1);
                path.pop_back();
        }
    }
};

排列问题:

⾸先排列是有序的,也就是说[1,2] 和[2,1] 是两个集合,这和之前分析的⼦集以及组合所不同的地⽅
和组合问题、 切割问题和⼦集问题最⼤的不同就是for循环⾥不⽤startIndex了

46 全排列(☆)

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

输入: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<int> path;
    vector<vector<int>> permute(vector<int>& nums) {
        res.clear();
        path.clear();
        if(nums.size() == 0) return res;
        vector<bool> used(nums.size(), false);
        backtracking(nums, used);
        return res;
    }

    void backtracking(vector<int>& nums, vector<bool> used)
    {
        if(path.size() > nums.size()) return;
        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]);
                backtracking(nums, used);
                path.pop_back();
                used[i] = false;
            }
        }
    }
};

47. 全排列 II(☆☆)

给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列。
在这里插入图片描述
代码:

class Solution {
public:
    vector<vector<int>> permuteUnique(vector<int>& nums) {
        result.clear();
        path.clear();
        sort(nums.begin(), nums.end());
        vector<bool>used(nums.size(), false);//必须首先设置为false
        backtracking(nums, used);
        return result;
    }
private:
    vector<vector<int>> result;
    vector<int> path;
    void backtracking (vector<int>& nums, vector<bool>& used) {
        if (path.size() == nums.size()) {
            result.push_back(path);
            return;
        }
        for (int i = 0; i < nums.size(); i++) {
            // used[i - 1] == true,说明同一树支candidates[i - 1]使用过
            // used[i - 1] == false,说明同一树层candidates[i - 1]使用过
            // 要对同一树层使用过的元素进行跳过
            if (i > 0 && nums[i] == nums[i-1] && used[i-1] == false) continue;
            if (used[i] == false) {
                used[i] = true;
                path.push_back(nums[i]);
                backtracking(nums, used);
                path.pop_back();
                used[i] = false;
            }
        }
    }
};

剑指 Offer 38. 字符串的排列

输入一个字符串,打印出该字符串中字符的所有排列。
你可以以任意顺序返回这个字符串数组,但里面不能有重复元素。

思路:跟40相同

class Solution {
public:
    vector<string> res;
    string path;
    vector<string> permutation(string s) {
        res.clear();
        if(s.size() == 0) return res;
        sort(s.begin(), s.end());
        vector<bool> used(s.size(), false);
        backtracking(s, used);
        return res;
    }

    void backtracking(string &s, vector<bool> &used)
    {
        if(path.size() > s.size()) return;
        if(path.size() == s.size()){
            res.push_back(path);
            return;
        }

        for(int i = 0; i < s.size(); i++)
        {
            if(i > 0 && s[i] == s[i-1] && used[i-1] == false) continue;
            if(used[i] == false)
            {
                used[i] = true;
                path += s[i];
                backtracking(s, used);
                path.pop_back();
                used[i] = false;
            }
        }
    }
};

526. 优美的排列

假设有从 1 到 N 的 N 个整数,如果从这 N 个数字中成功构造出一个数组,使得数组的第 i 位 (1 <= i <= N) 满足如下两个条件中的一个,我们就称这个数组为一个优美的排列。条件:

第 i 位的数字能被 i 整除
i 能被第 i 位上的数字整除
现在给定一个整数 N,请问可以构造多少个优美的排列?

class Solution {
public:
    int res = 0;
    int countArrangement(int n) {
        //回溯算法的全排列
        vector<bool> used(n+1, false);
        backtracking(n, 1, used);
        return res;
    }

    void backtracking(int n, int k, vector<bool>& used) {
        //if(path.size() > nums.size()) return;
        if(k == n+1) {
            res++;
            return;
        }


        for(int i = 1; i <= n; i++) {
            if(used[i] == false) {
                used[i] = true;
                if(i % k == 0 || k % i == 0) {
                    k++;
                    backtracking(n, k, used);//这里不能直接k++;
                    k--;
                }
                used[i] = false;
            }
        }
    }
    
    
};

棋盘问题

51. N 皇后

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

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

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

皇后们的约束条件:

  1. 不能同⾏
  2. 不能同列
  3. 不能同斜线
    在这里插入图片描述
    代码:
class Solution {
public:
    vector<vector<string>> result;
    //n为输入的棋盘的大小
    //row是当前递归的棋盘的第几行
    vector<vector<string>> solveNQueens(int n) {
        result.clear();
        vector<string> chessboard(n, string(n, '.'));
        backtracking(n, 0, chessboard);
        return result;
    }

    void backtracking(int n, int row, vector<string>& chessboard) 
    {
        if(row == n)
        {
            result.push_back(chessboard);
            return;
        }

        for(int col = 0; col < n; col++)
        {
            if(isValid(row, col, chessboard, n))
            {
                chessboard[row][col] = 'Q';
                backtracking(n, row+1, chessboard);
                chessboard[row][col] = '.';
            }
        }
    }

    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;
        }
        //45
        for(int i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++)
        {
            if(chessboard[i][j] == 'Q') return false; 
        }

        for(int i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--)
        {
            if(chessboard[i][j] == 'Q') return false;
        }
        return true;
    }

};

37. 解数独(☆☆☆)

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

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

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

在这里插入图片描述

从图中可以看出其实是一个二维递归(两个for循环嵌套着递归)
一个for循环遍历棋盘的行,一个for循环遍历棋盘的列,一行一列确定下来后,递归遍历这个位置放1-9的可能性
代码:

class Solution {
public:
    void solveSudoku(vector<vector<char>>& board) {
        backtracking(board);
    }

    bool backtracking(vector<vector<char>>& board) {
        for(int i = 0; i < board.size(); i++)
        {
            for(int j = 0; j < board[0].size(); j++)
            {
                if(board[i][j] != '.') continue;
                for(char k = '1'; k <= '9'; k++)
                {
                    if(isValid(i, j, k, board))
                    {
                        board[i][j] = k;//放置k
                        if(backtracking(board)) return true;//如果找到合适饿一组立刻返回
                        board[i][j] = '.';//如果不行的话还要恢复,可以的话走不到这一步
                    }
                }
                return false;
            }
        }
        return true;
    }

    bool isValid(int row, int col, char val, vector<vector<char>>& board)
    {
        for(int i = 0; i < 9; i++)
        {
            if(board[row][i] == val) return false; // 判断⾏⾥是否重复
        }
        for(int i = 0; i < 9; i++)
        {
            if(board[i][col] == val) return false;
        }

        int startRow = (row / 3) * 3;
        int startCol = (col / 3) * 3;
        for(int i = startRow; i < startRow+3; i++)
        {
            for(int j = startCol; j < startCol+3; j++)
            {
                if(board[i][j] == val) return false;
            }
        }
        return true;
    }
};

967. 连续差相同的数字

返回所有长度为 n 且满足其每两个连续位上的数字之间的差的绝对值为 k 的 非负整数 。

请注意,除了 数字 0 本身之外,答案中的每个数字都 不能 有前导零。例如,01 有一个前导零,所以是无效的;但 0 是有效的。

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

递归,从左到右一次遍历判断条件

class Solution {
public:
    vector<int> res;
    vector<int> numsSameConsecDiff(int n, int k) {
        res.clear();
        string s;
        dfs(n, k, s);
        return res;
    }

    void dfs(int n, int k, string &s)
    {
        if(s.size() == n)
        {
            res.push_back(stoi(s));
            return;
        }
        for(int i = 0; i <= 9; i++)
        {
            if(i == 0 && s.size() == 0) continue;//首部不能为0
            if(s.size() == 0 || abs((s.back() - '0') - i) == k)
            {
                s += to_string(i);
                dfs(n, k, s);
                s.pop_back();
            }
        }
    }
};

698. 划分为k个相等的子集(☆☆)

给定一个整数数组 nums 和一个正整数 k,找出是否有可能把这个数组分成 k 个非空子集,其总和都相等。

回溯:令每个子集的和为target
代码:

class Solution {
public:
    bool canPartitionKSubsets(vector<int>& nums, int k) {
        int sum = accumulate(nums.begin(), nums.end(), 0);
        if(sum % k != 0) return false;
        int target = sum / k;
        vector<bool> used(nums.size(), false);
        sort(nums.begin(), nums.end());
        return dfs(nums, k, 0, 0, used, target);
    }

    bool dfs(vector<int>& nums, int count, int startIndex, int curSum, vector<bool>& used, int target)
    {
        if(count == 1) return true; 
        if(curSum == target) return dfs(nums, count - 1, 0, 0, used, target);

        for(int i = startIndex; i < nums.size() && curSum + nums[i] <= target; i++)
        {
            if(i > startIndex && nums[i] == nums[i-1] && nums[i-1] == false) continue;
            if(used[i] == false )
            {
                used[i] = true;
                if(dfs(nums, count, i + 1, curSum + nums[i], used, target)) return true;
                used[i] = false;
            }
            
        }
        return false;//走到这里说明没有找到,说明出现错误
    }
};

22. 括号生成

数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合
输入:n = 3
输出:["((()))","(()())","(())()","()(())","()()()"]

递归:左括号的数目一旦小于右括号的数目,以及,左括号的数目和右括号数目均小于n。

class Solution {
public:
    vector<string> res;
    vector<string> generateParenthesis(int n) {
        res.clear();
        string s;
        dfs(n, s, 0, 0);
        return res;
    }
    void dfs(int n, string s, int left, int right)
    {
        if(left > n || right > n || right > left) return;
        if(right == n && left == n) {
            res.push_back(s);
            return;
        }
        dfs(n, s + "(", left+1, right);
        dfs(n, s + ")", left, right+1);
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值