递归 回溯 leetcode 问题记录

leetcode 17:17. 电话号码的字母组合 

给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。

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

接口:

class Solution {
public:
    vector letterCombinations(string digits) {
        
    }
};

思路:

① digits[0...N-1] = digits[0] + digits[1...N-1]  = digits[0] + digits[1] + ... + digits[N-1]。因此,递归函数可以定义为,结果字符串加上 index 位的数字所对应的一种可能的字母,并继续向后递归,递归边界是 index == N-1,这时结果字符串就是一个可能的组合,将它保存下来。

class Solution {
public:
    vector ret;
    vector table = {"","", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
    void helper(int index, const string& digits, const string& s)
    {
        if(index == digits.size())
        {
            ret.push_back(s);
            return;
        }
        int num = digits[index] - '0';
        for (int i = 0; i < table[num].size(); ++i)
        {
            helper(index + 1, digits, s + table[num][i]);
        }
    }
    vector letterCombinations(string digits) {
        if(digits.size() == 0) return ret;
        helper(0, digits, "");
        return ret;
    }
};

② 递归函数还可以定义为,返回 [index +1, N-1] 的所有可能的组合,然后加上 index 位的数字所对应的每一种可能的字母,这样就得到了 [index, N-1] 的所有可能的组合,递归边界也是 index == N - 1。

class Solution {
public:
    vector ret;
    vector table = {"","", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
    vector helper(int index, const string& digits)
    {
        vector ret;
        if(index == digits.size() - 1)
        {
            int num = digits[index] - '0';
            for (int i = 0; i < table[num].size(); ++i)
            {
                ret.push_back({table[num][i]});
            }
            return ret;
        }
        vector back = helper(index + 1, digits);
        int num = digits[index] - '0';
        for (int i = 0; i < table[num].size(); ++i)
        {
            for (int j = 0; j < back.size(); ++j)
            {
                string tmp = table[num][i] + back[j];
                ret.push_back(tmp);
            }
        }
        return ret;
    }
    vector letterCombinations(string digits) {
        if(digits.size() == 0) return ret;
        ret = helper(0, digits);
        return ret;
    }
};

 

leetcode 22: 22. 括号生成:

给出 n 代表生成括号的对数,请你写出一个函数,使其能够生成所有可能的并且有效的括号组合。

接口:

class Solution {
public:
    vector generateParenthesis(int n) {
        
    }
};

思路:

① 有效的括号组合,最左边一定是"(",最右边一定是")",因此,这个问题本质上是在 2 * n - 2 个位置里,选 n - 1个位置,填入左括号,再判断是否有效。最直接的做法就是先得到所有的可能取法,再依次判断这种括号组合是否有效。如下:

class Solution {
public:
    vector ret;
    bool isValid(vector &used)
    {
        stack st;
        for (int i = 0; i < used.size(); ++i)
        {
            if(used[i])
            {
                st.push('(');
            }
            else
            {
                if(st.empty()) return false;
                st.pop();
            }
        }
        if(st.empty()) return true;
        return false;
    }
    void helper(int n, int start, int leftCnt, vector &used)
    {
        if(leftCnt == n)
        {
            if(isValid(used))
            {
                string str;
                for (int i = 0; i < used.size(); ++i)
                {
                    if(used[i]) str += "(";
                    else str += ")";
                }
                ret.push_back(str);
            }
            return;
        }
        // [i, 2n-1)应该至少还有 n - leftCnt 个位置可以填左括号
        for (int i = start; i + n - leftCnt <= 2 * n - 1; ++i)
        {
            used[i] = true;
            helper(n, i + 1, leftCnt + 1, used);
            used[i] = false;
        }
    }
    vector generateParenthesis(int n) {
        vector used(2*n, 0);
        used[0] = true;
        helper(n, 1, 1, used);
        return ret;
    }
};

② 也可以对这 2*n 个位置逐位处理,当前位要么是左括号,要么是右括号,再进一步递归,边界是左括号的数量或右括号的数量等于n,无效情况是右括号的数量已经大于左括号的数量,或者某种括号的数量已经大于n。

class Solution {
public:
    vector generateParenthesis(int n) {
        vector res;
        func(res, "", 0, 0, n);
        return res;
    }
    
    void func(vector &res, string str, int l, int r, int n){
        if(l > n || r > n || r > l) return ;
        if(l == n && r == n) {res.push_back(str); return;}
        func(res, str + '(', l+1, r, n);
        func(res, str + ')', l, r+1, n);
        return;
    }
};

leetcode 93: 93. 复原IP地址

给定一个只包含数字的字符串,复原它并返回所有可能的 IP 地址格式。

接口:

class Solution {
public:
    vector restoreIpAddresses(string s) {
        
    }
};

思路:可能的IP格式需要在字符串中增加三个”.",并保证被"."分割开的四段,每一段的值都不超过255。因此,问题的本质是在[0,N-1]这N个位置中,选三个位置进行截断,并判断选择的位置组合是否合理。

class Solution {
public:
    vector ret;
    bool isValid(const string& s)
    {
        if(s.length() == 0 || s.length() > 3 || stoi(s) > 255 || (s[0] == '0' && s.size() > 1)) return false;
        return true;
    }
    // tmp存放截断的三个位置的下标,例如,tmp = {0, 1, 2}, str = "25525511135",那么截断后的四个字符串是2.5.5.25511135
    void helper(const string &s, int start, vector &tmp)
    {
        if(tmp.size() == 3)
        {
            string s1 = s.substr(0, tmp[0] + 1);
            if(!isValid(s1)) return;
            string s2 = s.substr(tmp[0]+1, tmp[1] - tmp[0]);
            if(!isValid(s2)) return;
            string s3 = s.substr(tmp[1]+1, tmp[2] - tmp[1]);
            if(!isValid(s3)) return;
            string s4 = s.substr(tmp[2]+1, s.size() - 1 - tmp[2]);
            if(!isValid(s4)) return;
            string str = s1 + "." + s2 + "." + s3 + "." + s4;
            ret.push_back(str);
            return;
        }
        for (int i = start; i < s.length(); ++i)
        {
            //剪枝,剩下的长度大于合理长度或小于合理长度,跳过。
            if(s.length() - i - 1> 3 * (3 - tmp.size()) || s.length() - i - 1 < 3 - tmp.size()) continue;
            tmp.push_back(i);
            helper(s, i + 1, tmp);
            tmp.pop_back();
        }
    }
    vector restoreIpAddresses(string s) {
        if(s.size() > 12 || s.size() < 4) return ret;
        vector tmp;
        helper(s, 0, tmp);
        return ret;
    }
};

leetcode131: 131. 分割回文串

给定一个字符串 s,将 s 分割成一些子串,使每个子串都是回文串。

返回 s 所有可能的分割方案。

接口:

class Solution {
public:
    vector> partition(string s) {
        
    }
};

思路:如果 [begin, i]是回文串,那么继续递归寻找[i+1, N-1]的所有回文串,递归边界是 begin == N。

class Solution {
public:
    bool isParlindrome(const string &s)
    {
        for (int i = 0; i < s.size() / 2; ++i)
        {
            if(s[i] != s[s.size() - 1- i]) return false;
        }
        return true;
    }
    vector> ret;
    vector tmp;
    // 如果[begin, i]是回文串,将它存储到tmp中,并继续递归找到[i+1,s.size-1]的回文串分割。
    void helper(const string &s, int begin)
    {
        if(begin == s.size())
        {
            ret.push_back(tmp);
            return;
        }
        for (int i = begin; i < s.size(); ++i)
        {
            if(isParlindrome(s.substr(begin, i + 1 - begin)))
            {
                tmp.push_back(s.substr(begin, i + 1 - begin));
                helper(s, i + 1);
                tmp.pop_back();
            }
        }
    }
    vector> partition(string s) {
        helper(s, 0);
        return ret;
    }
};

leetcode 46: 46. 全排列

给定一个没有重复数字的序列,返回其所有可能的全排列。

接口:

class Solution {
public:
    vector> permute(vector& nums) {
        
    }
};

思路:相当于从N个数里,每次选1个数,选N次的问题。因为选择过的元素不能再用,所以要另外开数组记录元素的使用情况,选完第 index 个元素后,递归选择 index+1 个数,直到 index == N。

class Solution {
public:
    vector> ret;
    vector tmp;
    void helper(vector &nums, int index, vector &used)
    {
        if(index == nums.size())
        {
            ret.push_back(tmp);
            return;
        }
        for (int i = 0; i < nums.size(); ++i)
        {
            if(!used[i])
            {
                used[i] = true;
                tmp.push_back(nums[i]);
                helper(nums, index + 1, used);
                tmp.pop_back();
                used[i] = false;
            }
        }
    }
    vector> permute(vector& nums) {
        if(nums.size() == 0) return ret;
        vector used(nums.size(), 0);
        helper(nums, 0, used);
        return ret;
    }
};

leetcode 47: 47. 全排列 II

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

接口:

class Solution {
public:
    vector> permuteUnique(vector& nums) {
        
    }
};

思路:因为这里有重复的数字,而要求输出不能重复,所以问题不再是从N个数字中每次选1个,选N次这么简单了。避免重复的办法是,先对数组进行排序,这样能保证相同的数字都相邻。之后,考察当前选取的第 index 个数,如果在原数组中,它与前面的数相同,但是前面的数没有被使用,那么就需要跳过这个数,因为这个数在index位的所有组合的可能都已经在之前保存过了。

class Solution {
public:
    vector> ret;
    vector tmp;
    void helper(vector&nums, int index, vector &used)
    {
        if(index == nums.size())
        {
            ret.push_back(tmp);
            return;
        }
        for (int i = 0; i < nums.size(); ++i)
        {
            if(!used[i])
            {
                if(i > 0 && nums[i] == nums[i-1] && !used[i-1]) continue;
                used[i] = true;
                tmp.push_back(nums[i]);
                helper(nums, index + 1, used);
                tmp.pop_back();
                used[i] = false;
            }
        }
    }
    vector> permuteUnique(vector& nums) {
        if(nums.size() == 0) return ret;
        sort(nums.begin(), nums.end());
        vector used(nums.size(), 0);
        helper(nums, 0, used);
        return ret;
    }
};

leetcode77: 77. 组合

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

接口:

class Solution {
public:
    vector> combine(int n, int k) {
        
    }
};

思路:

① 相当于从n个数里,每次选1个数,选k次的问题,但是与排列问题不同的是,先选1后选2 和 先选2后选1是一样的,所以在选择的策略需要改变,选完第 index 个数后,需要在 [index + 1, N] 之间继续选第index +1 个数,而不能在前面选。而在[index + 1, N] 中选剩下的数这一步,可以进行剪枝。

class Solution {
public:
    vector> ret;
    vector tmp;
    void helper(int n, int k, int start)
    {
        if(tmp.size() == k)
        {
            ret.push_back(tmp);
            return;
        }
        //剪枝,[i + 1, n]中至少应该还有 k - tmp.size个数可以选
        for (int i = start; i + k - tmp.size() - 1<= n; ++i)
        {
            tmp.push_back(i);
            helper(n, k, i + 1);
            tmp.pop_back();
        }
    }
    vector> combine(int n, int k) {
        if(n < k) return ret;
        helper(n, k, 1);
        return ret;
    }
};

② 相当于依次处理 1~n 这n个数,每个数都有选和不选两条路,直到选满了k个数,保存选出的序列即可。同样的,在进入选或不选的分支前,根据剩下元素的数量进行剪枝。

class Solution {
public:
    vector> ret;
    vector tmp;
    void helper(int n, int k, int index)
    {
        if(tmp.size() == k)
        {
            ret.push_back(tmp);
            return;
        }
        //剪枝
        if(n - index + 1< k - tmp.size()) return;
        tmp.push_back(index);
        helper(n, k, index + 1);
        tmp.pop_back();
        helper(n, k, index + 1);
    }
    vector> combine(int n, int k) {
        if(n < k) return ret;
        helper(n, k, 1);
        return ret;
    }
};

leetcode39: 39. 组合总和

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

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

说明:

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

接口:

class Solution {
public:
    vector> combinationSum(vector& candidates, int target) {
        
    }
};

思路:

① 解集不能重复,所以在选完当前元素后,不能再向前找,而只能在当前元素及以后决定剩下的元素。(题目已经保证了数组元素无重复)。

class Solution {
public:
    vector> ret;
    vector tmp;
    void helper(vector &candidates, int target, int start)
    {
        if(target < 0) return;
        if(target == 0)
        {
            ret.push_back(tmp);
            return;
        }
        for (int i = start; i < candidates.size(); ++i)
        {
            tmp.push_back(candidates[i]);
            helper(candidates, target - candidates[i], i);
            tmp.pop_back();
        }
    }
    vector> combinationSum(vector& candidates, int target) {
        helper(candidates, target, 0);
        return ret;
    }
};

② 依次处理每个元素,每个元素都有选或不选两种分支,注意元素可重复选,处理完第index个元素后,要继续递归处理index号元素,直到和大于target。

class Solution {
public:
    vector> ret;
    vector tmp;
    void helper(vector& candidates, int target, int index)
    {
        if(target == 0)
        {
            ret.push_back(tmp);
            return;
        }
        if(index == candidates.size() || target < 0) return;
        tmp.push_back(candidates[index]);
        helper(candidates, target - candidates[index], index);
        tmp.pop_back();
        helper(candidates, target, index + 1);
    }
    vector> combinationSum(vector& candidates, int target) {
        helper(candidates, target, 0);
        return ret;
    }
};

leetcode 40: 40. 组合总和 II

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

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

说明:

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

接口:

class Solution {
public:
    vector> combinationSum2(vector& candidates, int target) {
        
    }
};

思路:

由于元素可能有重复的,但是解集不能包含重复组合,所以要考虑去重。去重的策略和之前一样,先将数组排序,在寻找以下标index开头的解集时,判断 index 和前一个元素是否相同,如果是则跳过,因为以 index 开头的解集已经被保存了。

class Solution {
public:
    vector> ret;
    vector tmp;
    void helper(vector& candidates, int target, int start)
    {
        if(target < 0) return;
        if(target == 0)
        {
            ret.push_back(tmp);
            return;
        }
        for (int i = start; i < candidates.size(); ++i)
        {
            if(target - candidates[i] < 0) break;
            if(i > start && candidates[i] == candidates[i-1]) continue;
            tmp.push_back(candidates[i]);
            helper(candidates, target - candidates[i], i + 1);
            tmp.pop_back();
        }
    }
    vector> combinationSum2(vector& candidates, int target) {
        sort(candidates.begin(), candidates.end())
        helper(candidates, target, 0);
        return ret;
    }
};

leetcode216: 216. 组合总和 III

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

说明:

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

接口:

class Solution {
public:
    vector> combinationSum3(int k, int n) {
        
    }
};

思路:

① 相当于1 ~ 9 每个数只能选一次,选k个数和为n,由于可选元素本身没有重复的,那么避免出现重复解只需要每次向后查找即可。

class Solution {
public:
    vector> ret;
    vector tmp;
    void helper(int k, int n, int start)
    {
        if(n < 0 || k < 0) return;
        if(n == 0 && k == 0)
        {
            ret.push_back(tmp);
            return;
        }
        for (int i = start; i <= 9; ++i)
        {
            if(n < i) break;
            tmp.push_back(i);
            helper(k - 1, n - i, i + 1);
            tmp.pop_back();
        }
    }
    vector> combinationSum3(int k, int n) {
        helper(k, n, 1);
        return ret;
    }
};

② 依次处理 1 ~ 9 中每个数,都有选和不选两条分支。

class Solution {
public:
    vector> ret;
    vector tmp;
    void helper(int k, int n, int index)
    {
        if(n == 0 && k == 0)
        {
            ret.push_back(tmp);
            return;
        }
        if(index > 9 || n < 0 || k < 0) return;
        tmp.push_back(index);
        helper(k-1, n - index, index + 1);
        tmp.pop_back();
        helper(k, n, index + 1);
    }
    vector> combinationSum3(int k, int n) {
        helper(k, n, 1);
        return ret;
    }
};

leetcode 78: 78. 子集

给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。

说明:解集不能包含重复的子集。

接口:

class Solution {
public:
    vector> subsets(vector& nums) {
        
    }
};

思路:

① 仍然是从N个数里选数的问题,不过是要保存所有选数的可能。因为数组元素不重复,避免重复子集的方法还是每次只向后查找。每次增加元素时,将当前集合保存。最后需要额外加上空集。

class Solution {
public:
    vector> ret;
    vector tmp;
    void helper(vector&nums, int start)
    {
        for (int i = start; i < nums.size(); ++i)
        {
            tmp.push_back(nums[i]);
            ret.push_back(tmp);
            helper(nums, i + 1);
            tmp.pop_back();
        }
    }
    vector> subsets(vector& nums) {
        helper(nums, 0);
        ret.push_back(vector());
        return ret;
    }
};

② 依次处理每个元素,都有选和不选两条分支。每次增加元素时,将当前集合保存。最后额外加上空集。

class Solution {
public:
    vector> ret;
    vector tmp;
    void helper(vector&nums, int index)
    {
        if(index == nums.size()) return;
        tmp.push_back(nums[index]);
        ret.push_back(tmp);
        helper(nums, index + 1);
        tmp.pop_back();
        helper(nums, index + 1);
    }
    vector> subsets(vector& nums) {
        helper(nums, 0);
        ret.push_back(vector());
        return ret;
    }
};

③ 从后往前看,定义递归函数返回从 [index, N-1]的所有子集,对 [index + 1, N-1]的所有子集,加和不加 index号元素,就形成了 [index, N -1]的所有子集。递归边界是index == N - 1。

class Solution {
public:
    vector> helper(vector &nums, int index)
    {
        vector> ret;
        if(index == nums.size() - 1)
        {
            ret.push_back(vector());
            ret.push_back({nums[index]});
            return ret;
        }
        vector> back = helper(nums, index + 1);
        for (int i = 0; i < back.size(); ++i)
        {
            ret.push_back(back[i]);
            back[i].push_back(nums[index]);
            ret.push_back(back[i]);
        }
        return ret;
    }
    vector> subsets(vector& nums) {
        vector> ret = helper(nums, 0);
        return ret;
    }
};

leetcode 90: 90. 子集 II

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

说明:解集不能包含重复的子集。

接口:

class Solution {
public:
    vector> subsetsWithDup(vector& nums) {
        
    }
};

思路:

仍然是涉及到有重复元素的数组的非重复取法问题,去重的策略还是先排序,然后判断是否需要跳过。

class Solution {
public:
    vector> ret;
    vector tmp;
    void helper(vector&nums, int start)
    {
        for (int i = start; i < nums.size(); ++i)
        {
            if(i > start && nums[i] == nums[i-1]) continue;
            tmp.push_back(nums[i]);
            ret.push_back(tmp);
            helper(nums, i + 1);
            tmp.pop_back();
        }
    }
    vector> subsetsWithDup(vector& nums) {
        sort(nums.begin(), nums.end());
        helper(nums, 0);
        ret.push_back(vector());
        return ret;
    }
};

leetcode 491: 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]]
说明:

给定数组的长度不会超过15。
数组中的整数范围是 [-100,100]。
给定数组中可能包含重复数字,相等的数字应该被视为递增的一种情况。

 

这一题与上面组合问题的去重不一样的地方是,不可以排序。示例中的两个重复数字7是相邻的,但是测试用例中会有例如1,2,1,2这样的示例,因此不能用 if (i > start && nums[i] == nums[i-1]) continue; 这样的策略来去重。

思考一下  if (i > start && nums[i] == nums[i-1]) continue;  的含义:在同一层(start),相同的数字只取第一个。那么,对于这一题,由于相同的数字不一定相邻,怎么保证在同一层相同的数字只取第一个呢?答案是用set存储当前层已经被取过的数字,在同一层,后面如果出现重复的数字,就跳过,因为只取第一个。

class Solution {
private:
    vector> ret;
    vector tmp;
    void dfs(int start, vector& nums)
    {
        if(tmp.size() > 1) ret.emplace_back(tmp);
        unordered_set set;
        for (int i = start; i < nums.size(); ++i)
        {
            //同一层相同的数只取第一个
            if(set.count(nums[i])) continue;
            if((!tmp.empty() && nums[i] < tmp.back())) continue;
            set.insert(nums[i]);
            tmp.push_back(nums[i]);
            dfs(i+1, nums);
            tmp.pop_back();
        }
    }
public:
    vector> findSubsequences(vector& nums) {
        dfs(0, nums);
        return ret;
    }
};

leetcode 401: 401. 二进制手表

二进制手表顶部有 4 个 LED 代表小时(0-11),底部的 6 个 LED 代表分钟(0-59)

每个 LED 代表一个 0 或 1,最低位在右侧。给定一个非负整数 代表当前 LED 亮着的数量,返回所有可能的时间。

接口:

class Solution {
public:
    vector readBinaryWatch(int num) {
        
    }
};

思路:本质上是从10个位置里选 num 个位置填1的问题,只要这10个位置的取值确定了,对应的时间就可以得到。

class Solution {
public:
    vector ret;
    bool calTime(vector &used, string &str)
    {
        int hour = used[0] * 8 + used[1] * 4 + used[2] * 2 + used[3];
        int min = used[4] * 32 + used[5] * 16 + used[6] * 8 + used[7] * 4 + used[8] * 2 + used[9];
        if(hour <= 11 && min <= 59)
        {
            str = to_string(hour) + ":";
            if(min < 10) str += "0" + to_string(min);
            else str += to_string(min);
            return true;
        }
        return false;
    }
    void helper(int num, vector used, int start)
    {
        if(num == 0)
        {
            string str;
            if(calTime(used, str)) ret.push_back(str);
            return;
        }
        for (int i = start; i < 10; ++i)
        {
            if(!used[i])
            {
                used[i] = 1;
                helper(num - 1, used, i + 1);
                used[i] = 0;
            }
        }
    }
    vector readBinaryWatch(int num) {
        vector used(10, 0);
        helper(num, used,0);
        return ret;
    }
};

leetcode 51: 51. N皇后

思路:

每次处理一行,遍历 [0, n-1]中所有列号,找到满足没有重复列、对角线的列号,递归处理下一行,直到n行全部处理完,输出。

class Solution {
public:
    vector> ret;
    void helper(int n, int index, vector &ans, vector&col, vector&sum, vector&sub)
    {
        if(index == n)
        {
            vector s(n, string(n, '.'));
            for (int i = 0; i < n; ++i)
            {
                s[i][ans[i]] = 'Q';
            }
            ret.push_back(s);
            return;
        }
        for (int column = 0; column < n; ++column)
        {
            if(!col[column] && !sum[index + column] && !sub[index - column + n - 1])
            {
                col[column] = true;
                sum[index + column] = true;
                sub[index - column + n - 1] = true;
                ans.push_back(column);
                helper(n, index + 1, ans, col, sum, sub);
                ans.pop_back();
                sub[index - column + n - 1] = false;
                sum[index + column] = false;
                col[column] = false;
            }
        }
    }
    vector> solveNQueens(int n) {
        vector ans;
        vector col(n, 0), sum(2*n, 0), sub(2*n, 0);
        helper(n, 0, ans, col, sum, sub);
        return ret;
    }
};

 

你可能感兴趣的:(leetcode)