leetcode 热点——排列组合问题

排列组合问题

1、子集问题

1.1、求数字的所有子集。

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

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

输入: nums = [1,2,3]
输出:
[
  [3],
  [1],
  [2],
  [1,2,3],
  [1,3],
  [2,3],
  [1,2],
  []
]

1.2、思路

解决一个回溯问题,实际上就是一个决策树的遍历过程。你只需要思考 3 个问题:

  • 1、路径:也就是已经做出的选择。
  • 2、选择列表:也就是你当前可以做的选择。
  • 3、结束条件:也就是到达决策树底层,无法再做选择的条件。
fun backtrack(路径, 选择列表):
    if 满足结束条件:
        result.add(路径)
        return
    for 选择 in 选择列表:
        做选择
        backtrack(路径, 选择列表)
        撤销选择

写 backtrack 函数时,需要维护走过的「路径」和当前可以做的「选择列表」,当触发「结束条件」时,将「路径」记入结果集。

下面对子集说明:
画出递归树:
在这里插入图片描述
1、找结束条件
所有路径都应该加入结果集,所以不存在结束条件。或者说当start参数越过数组边界的时候,程序就自己跳过下一层递归了,因此不需要手写结束条件,直接加入结果集

res为结果集
res.push_back(path);//把每一条路径加入结果集

2、选择列表,做出选择

 for(int i=start;i<nums.size();i++)
    {
        track.push_back(nums[i]);//做出选择
        backtrack(nums,path,i+1);//递归进入下一层,注意i+1,标识下一个选择列表的开始位置,最重要的一步
        track.pop_back();//撤销选择
    }

重点
子集、组合类问题,关键是用一个start参数来控制选择列表

①画出递归树,找到状态变量(回溯函数的参数),这一步非常重要※

②根据题意,确立结束条件

③找准选择列表(与函数参数相关),与第一步紧密关联※

④判断是否需要剪枝

⑤作出选择,递归调用,进入下一层

⑥撤销选择

1.3、题解

vector<vector<int>> res;

vector<vector<int>> subsets(vector<int>& nums) {
    // 记录走过的路径
    vector<int> track;
    backtrack(nums, 0, track);
    return res;
}

void backtrack(vector<int>& nums, int start, vector<int>& track) {
    res.push_back(track);
    // 注意 i 从 start 开始递增
    for (int i = start; i < nums.size(); i++) {
        // 做选择
        track.push_back(nums[i]);
        // 回溯
        backtrack(nums, i + 1, track);
        // 撤销选择
        track.pop_back();
    }
}

2、组合问题

2.1、 k 个数的组合

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

输入: n = 4, k = 2
输出:
[
  [2,4],
  [3,4],
  [2,3],
  [1,2],
  [1,3],
  [1,4],
]

2.2、思路

在这里插入图片描述
参考上面的子集

2.3、题解

class Solution {
public:
    vector<vector<int>>res;
    vector<vector<int>> combine(int n, int k) {
        if (k <= 0 || n <= 0) return res;
        vector<int> track;
        backtrack(n, k, 1, track);
        return res;
    }

    void backtrack(int n, int k, int start, vector<int>& track) {
        // 到达树的底部
        if (k == track.size()) {
            res.push_back(track);
            return;
        }
        // 注意 i 从 start 开始递增
        for (int i = start; i <= n; i++) {
            // 做选择
            track.push_back(i);
            backtrack(n, k, i + 1, track);
            // 撤销选择
            track.pop_back();
        }
    }
};

3、排列

3.1、无重复字符串的排列

题目链接
无重复字符串的排列组合。编写一种方法,计算某字符串的所有排列组合,字符串每个字符均不相同。

示例1:

 输入:S = "qwe"
 输出:["qwe", "qew", "wqe", "weq", "ewq", "eqw"]

示例2:

 输入:S = "ab"
 输出:["ab", "ba"]

提示:

1、字符都是英文字母。
2、字符串长度在[1, 9]之间。

3.1.1、思路

1、递归树
在这里插入图片描述
首先,我们固定1,然后只有2、3可选:如果选2,那就只剩3可选,得出结果[1,2,3];如果选3,那就只剩2可选,得出结果[1,3,2]。再来,如果固定2,那么只有1,3可选:如果选1,那就只剩3,得出结果[2,1,3]…

  • 如果我们固定了(选择了)某个数,那么他的下一层的选择列表就是——除去这个数以外的其他数!!

2、结束条件

  if(path.size()==nums.size())
        {
            res.push_back(path);
            return;
        }

3、选择列表、作出选择

for(int i=0;i<nums.size();i++)
{
    if(!used[i])//从给定的数中除去用过的,就是当前的选择列表
    {
        path.push_back(nums[i]);//做选择
        used[i]=true;//设置当前数已用
        backtrack(nums,used,path);//进入下一层
        used[i]=false;//撤销选择
        path.pop_back();//撤销选择
    }
}

重点

“排列”类型问题和“子集、组合”问题不同在于:“排列”问题使用used数组来标识选择列表,而“子集、组合”问题则使用start参数。

3.1.2、题解
class Solution {
public:
    vector<string>res;
    vector<string> permutation(string S) {
        if(S.size()==0)
            return{};
        string temp="";
        vector<bool>used(S.size());
        backtrack(S,temp,used);
        return res;
    }
    void backtrack(string s,string& path,vector<bool>& used)//used数组
    {
        if(path.size()==s.size())
        {
            res.push_back(path);
            return;
        }
        for(int i=0;i<s.size();i++)
        {
            if(!used[i])
            {
                path.push_back(s[i]);
                used[i]=true;
                backtrack(s,path,used);
                used[i]=false;
                path.pop_back();
            }   
        }
    }
};

3.2、有重复字符串的排列组合

原题链接
有重复字符串的排列组合。编写一种方法,计算某字符串的所有排列组合。

示例1:

 输入:S = "qqe"
 输出:["eqq","qeq","qqe"]
示例2:

 输入:S = "ab"
 输出:["ab", "ba"]

提示:

  • 1、字符都是英文字母。
  • 2、字符串长度在[1, 9]之间。
3.2.1、思路

1、递归树
在这里插入图片描述

  • 1、首先要对给出的nums数组排序,让重复的元素并列排在一起,
  • 2、添加剪枝条件:if(i>0&&nums[i]==nums[i-1]&&!used[i-1]),当i可以选第一个元素之后的元素时,然后判断当前元素是否和上一个元素相同?
    • 如果相同,再判断上一个元素是否能用?如果三个条件都满足,那么该分支一定是重复的,应该剪去
3.2.2、题解
//vector<string>res为全局变量,表示最终的结果集,最后要返回的
class Solution {
public:
 vector<string>res;
 vector<string> permutation(string s)
  {
            if(s.size()==0)
                return{};
            string temp="";
            sort(s.begin(),s.end());
            vector<bool>used(s.size());
            backtrack(s,temp,used);
            return res;
  }
 void backtrack(string s,string& path,vector<bool>& used)//used数组
    {
        if(path.size()==s.size())
        {
            res.push_back(path);
            return;
        }
        for(int i=0;i<s.size();i++)
        {
            if(!used[i])
            {
                if(i>=1&&s[i-1]==s[i]&&!used[i-1])//判重剪枝
                    continue;
                path.push_back(s[i]);
                used[i]=true;
                backtrack(s,path,used);
                used[i]=false;
                path.pop_back();
            }   
        }
    }

    
};

参考

1、https://leetcode-cn.com/problems/subsets/solution/c-zong-jie-liao-hui-su-wen-ti-lei-xing-dai-ni-gao-/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值