2、组合问题
给定两个整数 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]]
题目分析:
套好公式就行 主要要记得这个是每个数字只能取一个,所有startindex,在深度与宽度都会增加
class Solution {
public:
vector<vector<int>> result;//存放选择好的组合
vector<int>path;//存放我们选择中的数组
//回溯函数
void backtracking(int n,int k, int startIndex)
{
if(path.size() == k)//当path数量到了,就存放result中去
{
result.push_back(path);
return;//跳回本层
}
for(int i = startIndex;i <=n;i++)//每次for结束也加一,利用for循环结束跳到上一层
{
path.push_back(i);
backtracking(n,k,i+1);//每次深入一次,i+1
path.pop_back();
}
}
vector<vector<int>>combine(int n,int k){
backtracking(n,k,1);
return result;
}
};
4、组合总和||||
找出所有相加之和为 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,没有有效的组合。
题目分析
这个与上面一样,只是多了一个sum而已
class Solution {
public:
vector<int>path;
vector<vector<int>> result;
int sum = 0;
vector<vector<int>> combinationSum3(int k, int n) {
backtracking(k,n,1);
return result;
}
void backtracking(int k,int n,int index){
if(sum == n && path.size() == k){
result.push_back(path);
return;
}
for(int i = index; i <= 9; i++){
sum += i;
path.push_back(i);
backtracking(k,n,i+1);
path.pop_back();
sum-=i;
}
}
};
5、电话号码的字母组合
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
示例 1:输入:digits = “23” 输出:[“ad”,“ae”,“af”,“bd”,“be”,“bf”,“cd”,“ce”,“cf”] 示例
2:输入:digits = “” 输出:[]
//数字和字母如何映射
//与之前不同的是我们可以选择的数组不是一样的
//index 表示遍历我们输入字符的第几个数字
//digits是我们输入的字符
//
class Solution {
public:
const string letterMap[10] = {
"",//0
"",//1
"abc",//2
"def",//3
"ghi",//4
"jkl",//5
"mno",//6
"pqrs",//7
"tuv",//8
"wxyz"//9
};
string s;
vector<string> result;
void backtracking(const string& digits, int index)
{
if(index ==digits.size()){
result.push_back(s);
return;
}
int digit = digits[index] -'0';//将字符型转成int,获取数字
string letters = letterMap[digit];//取数字对应的字符集合
for(int i = 0; i<letters.size();i++)
{
s.push_back(letters[i]);
backtracking(digits,index+1);//index加一是要处理下一个数字
s.pop_back();
}
}
vector<string> letterCombinations(string digits) {
if(digits.size() == 0) return result;
backtracking(digits,0);
return result;
}
};
7、组合总和
这个是要求可以取重复的,
class Solution {
public:
vector<int> path;
vector<vector<int>> result;
void backtracking(vector<int>& candidates,int sum,int target,int startindex)
{
//终止条件
if(sum > target) return;//直接回上一层
if(sum == target){
result.push_back(path);//保存
return;//回上一层
}
//单层递归逻辑
for(int i = startindex; i<candidates.size();i++){
path.push_back(candidates[i]);
sum+=candidates[i];
backtracking(candidates,sum,target,i);
sum-=candidates[i];//回溯,踢出一个
path.pop_back();
}
}
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
backtracking(candidates,0,target,0);
return result;
}
};
8、组合总数||
这个要求每个数字只能出现一次,但是可以选择的地方有重复数字,所有要将结果去重
重也简单就是只要将我们容器排序,在单步插入的时候,判断要插入到path中的与上一个是否一样,要是一样就是continue
class Solution {
public:
vector<int> path;
vector<vector<int>> result;
void backtracking(vector<int>& candidates,int sum,int target,int startindex)
{
//终止条件
if(sum > target) return;//直接回上一层
if(sum == target){
result.push_back(path);//保存
return;//回上一层
}
//单层递归逻辑
for(int i = startindex; i<candidates.size();i++){
// 要对同一树层使用过的元素进行跳过
if (i > startindex && candidates[i] == candidates[i - 1]) continue;
path.push_back(candidates[i]);
sum+=candidates[i];
backtracking(candidates,sum,target,i+1);
sum-=candidates[i];//回溯,踢出一个
path.pop_back();
}
}
vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
//给candidates排序,去重
sort(candidates.begin(),candidates.end());
backtracking(candidates,0,target,0);
return result;
}
};
9、分割回文串
分析:
终止条件
startindex 是从0,当startindex >=s.size(),已经切空了,终止
我们在切割好前要判断,切下的是不是回文字符串,其他地方与组合差不多,两个地方需要使用startindex +1
s.substr(begin(),n)代表切割从零下标开始的n个字符
回文字符串
首尾两个指针,同时从两端开始遍历,两者结果一样
class Solution {
public:
vector<vector<string>> result; //存放结果
vector<string>path;//放已经回文的子串
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);//从下标为startindex开始,截取i-startindex + 1个字符
path.push_back(str);
}
else//不是回文字符跳过
continue;
backtracking(s,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;
}
vector<vector<string>> partition(string s) {
backtracking(s,0);
return result;
}
};
10、复原IP地址
• 段位以0为开头的数字不合法 • 段位里有非正整数字符不合法(一般只要大于9小于0,就可以会自动转换成sacll码计算) •
段位如果大于255了不合法 for(int i = 0; i < = end;i++) num = 10*num + 单个元素 注意
在划分结束后,加一个逗号,一共要有三个逗号,分四段 在划分之前要看看划分的那段合不合法
不合法的话直接break,因为前面不合法,后面也是不合法,同树层没必要继续下去
class Solution {
public:
vector<string> result;//记录结果
//startindex:搜索的起始位置,pointnum:添加逗号的数量
void backtracking(string& s,int startindex,int pointnum){
if(pointnum == 3)//单逗号符号数目为三时,分隔结束
{
//判断第四段是否合法
if(isVaild(s,startindex,s.size()-1)){
result.push_back(s);
}
return;
}
for(int i = startindex; i < s.size();i++){
if(isVaild(s,startindex,i))//判断[startindex,i]区间的字符串是否合法
{
s.insert(s.begin() + i+1,'.');//在后面插入一个逗号
pointnum++;//逗号数目加一
backtracking(s,i+2,pointnum);//插入逗号后下一个起始位置是i+2
s.erase(s.begin()+i+1);//删掉逗号
pointnum--;
}
else//已经有不合法了,通树层再移动也是不合法的
break;
}
}
//判断字符串s在左闭右闭区间[start,end]所组成的数字是否合法
bool isVaild(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')//遇到非数字字符不合法,其他符号会自动转成ask码计算
return false;
num = num *10 + (s[i] - '0');
if(num > 255)//大于255不合法
return false;
}
return true;
}
vector<string> restoreIpAddresses(string s) {
backtracking(s,0,0);
return result;
}
};
11、子集
集树形结构中树的所有节点的结果,就是收集过程量,而不是只收集最后结果 每次进入
终止条件,startindex >=nums.size() 已经大于数组长度了,没有元素可以取了,已经到底了
1、为什么不需要终止条件? 因为只要遍历到最后一个,在继续backtracking机会i+1,机会结束函数,跳回本层
当然 我们也可以写这个终止函数,我觉得还是写吧
2、为甚么result的记录放在那个位置? ,只要我们运行了backtracking,就会跳到result就会记录一次
class Solution {
public:
vector<int>path;
vector<vector<int>>result;
void backtracking(vector<int> &nums,int startindex)
{
//每一步的结果,都是子串,都要收集
result.push_back(path);
if(startindex >= nums.size()) return;
for(int i = startindex ; i < nums.size(); i++)
{
path.push_back(nums[i]);
backtracking(nums,i+1);
path.pop_back();
}
}
vector<vector<int>> subsets(vector<int>& nums) {
backtracking(nums,0);
return result;
}
};
13、子集||
当给定的一个数组包含重复数字,结果机会产生重复(在横向遍历的时候会重复)
1、将输入字符数组进行sort排序
1、使用一个used数组,要是以及使用过了,就赋给1,不然默认都是零
区分树枝去重合树层去重,不可以只写num[i] == nums[i-1]就continue,会去除额外部分
树层去重是不可以的,那个时候used【i-1】 = false//前一个没使用了
树枝去重是可以的,那个时候used[i-1] = true;//前一个还在使用
2、在插入的时候,判断现在这个数是否和之前的i一样,要是一样就continue,继续横向寻找下一个
class Solution {
public:
vector<int>path;
vector<vector<int>>result;
void backtracking(vector<int> &nums,int startindex,vector<bool>& used)
{
//每一步的结果,都是子串,都要收集
result.push_back(path);
if(startindex >= nums.size()) return;
for(int i = startindex ; i < nums.size(); i++)
{
if( i > 0 && nums[i] == nums[i-1] && used[i-1] == false) continue;
path.push_back(nums[i]);
used[i] =true;
backtracking(nums,i+1,used);
path.pop_back();
used[i] = false;
}
}
vector<vector<int>> subsetsWithDup(vector<int>& nums) {
vector<bool>used(nums.size(),false);
sort(nums.begin(),nums.end());
backtracking(nums,0,used);
return result;
}
};
14、递增子序列
要求子序列是递增的,长度至少是2,还要去重
使用哈希表去重,横向遍历中的元素存入哈希表中,要是在该树层发现重复就continue,要是发现不是递归也continue
每次进行递归之前都将哈希表清空,这样就不会影响一个树枝
class Solution {
public:
vector<int> path;
vector<vector<int>> result;
void backtracking(vector<int>& nums,int startindex)
{
if(path.size() > 1) result.push_back(path);//长度至少为2才要
//终止条件
if(startindex >= nums.size()) return;
unordered_set<int> uset;//使用set给本层去重每次递归都会清零
for(int i = startindex; i <nums.size(); i++){
//判断是否是递增子序列,然后判断是否重复
if(!path.empty() && nums[i] < path.back()||uset.find(nums[i]) != uset.end()) continue;
{
uset.insert(nums[i]); // 记录这个元素在本层用过了,本层后面不能再用了
path.push_back(nums[i]);
backtracking(nums, i + 1);
path.pop_back();
}
}
}
vector<vector<int>> findSubsequences(vector<int>& nums) {
backtracking(nums,0);
return result;
}
};
16、全排列
排列问题与之前的组合不同,{1,2} 与 [2,1}是不同的数组,所以startindex就不需要了,但是一个排列中不是一个元素多次使用,所有需要要used数组,记录那个被使用了
每次都是从零开始搜索的,而不是startindex
终止条件
就是path中大小等于nums大小
单层逻辑
used数组存放我们判断是否使用的数字,要是第一个被用了,就将数组第一个元素赋给true,没有被使用就false,开始默认构造是false
一个元素只能使用一次,要是以及使用就直接continue跳过for横向遍历下一个
不管是深度加一还是横向遍历加一,都是找到没有被遍历的元素
class Solution {
public:
vector<int> path;
vector<vector<int>> result;
void backtracking(vector<int>& nums,int startindex)
{
if(path.size() > 1) result.push_back(path);//长度至少为2才要
//终止条件
if(startindex >= nums.size()) return;
unordered_set<int> uset;//使用set给本层去重每次递归都会清零
for(int i = startindex; i <nums.size(); i++){
//判断是否是递增子序列,然后判断是否重复
if(!path.empty() && nums[i] < path.back()||uset.find(nums[i]) != uset.end()) continue;
{
uset.insert(nums[i]); // 记录这个元素在本层用过了,本层后面不能再用了
path.push_back(nums[i]);
backtracking(nums, i + 1);
path.pop_back();
}
}
}
vector<vector<int>> findSubsequences(vector<int>& nums) {
backtracking(nums,0);
return result;
}
};
17、全排列||(数组重复)
在原来全排列的基础上,加上当前元素若是与前一个相同,并且当前元素前一个元素已经遍历过了也急速used[i-1] = false;就continue跳过,
总之就是横向选择的时候不能选择相同的元素
class Solution {
public:
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,说明同一树枝nums[i - 1]使用过
// used[i - 1] == false,说明同一树层nums[i - 1]使用过
// 如果同一树层nums[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;
}
}
}
vector<vector<int>> permuteUnique(vector<int>& nums) {
vector<bool> used (nums.size(),false);
sort(nums.begin(),nums.end());
backtracking(nums,used);
return result;
}
};