面试算法——排列组合子集一锅端

方法论:

  1. 看题五分钟,不会做,看解析;
  2. 先看中文站,再看国际站;
  3. 选择最优解析;
  4. 回头再来写

面试四步走:

  1. 和面试官,探讨题目限制条件;
  2. 说说可能解,选择最优解;
  3. 码字;
  4. 跑测试用例

思维要点:

  1. 不要人肉进行递归
  2. 找到最近最简方法,将其拆解成可重复解决的问题(重复子问题)
  3. 数学归纳法思维

78.子集

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

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

90.子集II

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

# 方法一
class Solution {
    void generateSub(const vector<int>& nums, vector<vector<int>>& res, vector<int>& path, int start){
        res.push_back(path);
        for(int i = start; i < nums.size(); i++){
            if(i > start && nums[i] == nums[i-1])
                continue;
            path.push_back(nums[i]);
            generateSub(nums, res, path, i+1);
            path.pop_back();
        }
    }
public:
    vector<vector<int>> subsetsWithDup(vector<int>& nums) {
        vector<vector<int>> res;
        vector<int> path;
        sort(nums.begin(), nums.end());
        generateSub(nums, res, path, 0);
        return res;
    }
};
# 方法二
class Solution {
    void generateSub(const vector<int>& nums, vector<vector<int>>& res, vector<int>& path, int start){
        res.push_back(path);
        for(int i = start; i < nums.size(); i++){
            path.push_back(nums[i]);
            generateSub(nums, res, path, i+1);
            path.pop_back();
            while (i < nums.size() - 1 && nums[i] == nums[i+1]){
                i++;
            }
        }
    }
public:
    vector<vector<int>> subsetsWithDup(vector<int>& nums) {
        vector<vector<int>> res;
        vector<int> path;
        sort(nums.begin(), nums.end());
        generateSub(nums, res, path, 0);
        return res;

    }
};

个人倾向于方法2,至于原因,请看Permutations II部分;

46.全排列

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

思路:以下两种方法主要区别是:判断nums[i]是否在path数组中,

  1. 方法1:是通过标记数组used区别;
  2. 方法2:是利用algorithms中的find函数;
# 方法1
class Solution {
public:
    vector<vector<int>> permute(vector<int>& nums) {
        vector<vector<int>> res;
        vector<int> path;
        vector<bool> used(nums.size(), false);
        helper(res, nums, path, used);
        return res;
    }

    void helper(vector<vector<int>>& res, vector<int>& nums, vector<int>&path, vector<bool>used) {
        if (path.size() == nums.size()){
            res.push_back(path);
            return;
        }
        
        for (int i = 0; i < nums.size(); i++ ){
            if (used[i]) continue;
            used[i] = true;
            path.push_back(nums[i]);
            helper(res, nums, path, used);
            used[i] = false;
            path.pop_back();
        }
    }
};
# 方法2
class Solution {
public:
    vector<vector<int>> permute(vector<int>& nums) {
        vector<vector<int>> res;
        vector<int> path;
        helper(res, nums, path);
        return res;
    }

    void helper(vector<vector<int>>& res, vector<int>& nums, vector<int>&path) {
        if (path.size() == nums.size()){
            res.push_back(path);
            return;
        }

        for (int i = 0; i < nums.size(); i++ ){
            if (find(path.begin(), path.end(), nums[i]) != path.end()) continue;
            path.push_back(nums[i]);
            helper(res, nums, path);
            path.pop_back();
        }
    }
};

47.全排列II

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

思路:

  1. 方法1:当i的值已经在path中或者 i > 0 && nums[i-1] == nums[i] && i-1没有在path中, 忽略该值,这一行代码比较难理解,个人更加倾向于第二种;
  2. 方法2:有兴趣的可以看这个视频:来源 Coding Interview Tutorial 31: Permutations II[LeetCode]
# 方法1
class Solution {
public:
    vector<vector<int>> permuteUnique(vector<int>& nums) {
        vector<vector<int>> res;
        vector<bool> used = vector<bool>(nums.size(), false);
        vector<int> path;

        sort(nums.begin(), nums.end());
        helper(nums, res, path, used);
        return res;
    }
private:
    void helper(vector<int>& nums, vector<vector<int>>& res, vector<int>& path, vector<bool> used){
        if (path.size() == nums.size()){
            res.push_back(path);
            return;
        }

        for (int i =0; i < nums.size(); i++){
            if (used[i] || i > 0 && nums[i] == nums[i - 1] && !used[i-1]) continue;
            used[i] = true;
            path.push_back(nums[i]);
            helper(nums, res, path, used);
            path.pop_back();
            used[i] = false;
        }
    }
};
# 方法2
class Solution {
public:
    vector<vector<int>> permuteUnique(vector<int>& nums) {
        vector<vector<int>> res;
        vector<bool> used = vector<bool>(nums.size(), false);
        vector<int> path;

        sort(nums.begin(), nums.end());
        helper(nums, res, path, used);
        return res;
    }
private:
    void helper(vector<int>& nums, vector<vector<int>>& res, vector<int>& path, vector<bool> used){
        if (path.size() == nums.size()){
            res.push_back(path);
            return;
        }

        for (int i =0; i < nums.size(); i++){
            if (used[i]) continue;
            used[i] = true;
            path.push_back(nums[i]);
            helper(nums, res, path, used);
            path.pop_back();
            used[i] = false;
            while (i < nums.size() - 1 && nums[i] == nums[i+1]){
                i++;
            }
        }
    }
};

nums=[1, 1, 2]说下我的理解:
在这里插入图片描述
跟着蓝色线条的脚步走,还是那句话,不要陷入人肉递归,我们看第一个nums[0] == nums[1],如果蓝色线条,继续往绿色快走的话,就会出现重复选项,因此需要跳过该块,while(i < nums.size() - 1 && nums[i] = nums[i+1])就是干这个事的;同理,会跳过第二个黄色块;

39.组合总和

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

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

    void helper(vector<int>& candidates, vector<vector<int>>& res, vector<int>& path, int target, int start){
        if (target < 0) return;
        if (target == 0){
            res.push_back(path);
            return;
        }

        for (int i = start; i < candidates.size(); i++){
            path.push_back(candidates[i]);
            helper(candidates, res, path, target - candidates[i], i);	# 元素无限
            path.pop_back();
        }
    }
};

40.组合总和II

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

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

# 方法1
class Solution {
public:
    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        vector<int> path;
        vector<vector<int>> res;
        sort(candidates.begin(), candidates.end());
        helper(candidates, res, path, target, 0);
        return res;
    }

    // 时间复杂度O(2^n)  空间复杂度kn
    void helper(vector<int>& candidates, vector<vector<int>>& res, vector<int>& path, int target, int start){
        if (target < 0) return;
        if (target == 0){
            res.push_back(path);   
            return;
        }
        for (int i = start; i < candidates.size(); ++i){
            if (i > start && candidates[i] == candidates[i - 1])    continue;  

            path.push_back(candidates[i]);
            helper(candidates, res, path, target - candidates[i], i + 1);
            path.pop_back();
        }
    }
};
# 方法2
class Solution {
public:
    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        vector<int> path;
        vector<vector<int>> res;
        sort(candidates.begin(), candidates.end());
        helper(candidates, res, path, target, 0);
        return res;
    }

    // 时间复杂度O(2^n)  空间复杂度kn
    void helper(vector<int>& candidates, vector<vector<int>>& res, vector<int>& path, int target, int start){
        if (target < 0) return;
        if (target == 0){
            res.push_back(path);   
            return;
        }
        for (int i = start; i < candidates.size(); ++i){
            path.push_back(candidates[i]);
            helper(candidates, res, path, target - candidates[i], i + 1);
            path.pop_back();
            while (i < candidates.size() - 1 && candidates[i] == candidates[i+1]){
                i++;
            }
        }
    }
};

131.分割回文串

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

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

class Solution {
public:
    vector<vector<string>> partition(string s) {
        vector<vector<string>> res;
        vector<string> path;
        helper(s, res, path, 0);
        return res;
    }

private:
    void helper(string str, vector<vector<string>>& res, vector<string>& path, int start) {
        // terminator
        if (start == str.size()){
            res.push_back(path);
        }

        //string substr (size_t pos = 0, size_t len = npos) const;
        for (int i = start; i < str.size(); i++) {
            if (isPalindrome(str, start, i)){
                path.push_back(str.substr(start, i - start + 1));
                helper(str, res, path, i + 1);
                path.pop_back();
            }
        }
    }

    bool isPalindrome(string str, int start, int end) {
        while(start < end) {
            if (str[start++] != str[end--]){
                return false;
            }
        }
        return true;
    }
};

参考

  1. A general approach to backtracking questions in Java (Subsets, Permutations, Combination Sum, Palindrome Partioning)
  2. Coding Interview Tutorial 31: Permutations II[LeetCode]
  3. leetcode中文站
  4. leetcode国际站 (将力扣中文链接,后面的-cn去掉,就是该题的国际站)
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值