class Solution {
vector<vector<int>>result;
void backTrack(vector<int>&can,vector<int>&curr_set,int start)
{
result.push_back(curr_set);
//代表一轮选择,其选择第一个数为start
for(int i=start;i<can.size();++i){
curr_set.push_back(can[i]);
//每一个数字只能用一次,因此下一轮起始点为i+1
backTrack(can,curr_set,i+1);
curr_set.pop_back();
}
}
public:
vector<vector<int>> subsets(vector<int>& nums) {
sort(nums.begin(),nums.end());
vector<int>curr;
backTrack(nums,curr,0);
return result;
}
};
注意对防止方案重复的处理
class Solution {
vector<vector<int>>result;
void backTrack(vector<int>&can,vector<int>&curr_set,int start)
{
result.push_back(curr_set);
//代表一轮选择,其选择第一个数为start
for(int i=start;i<can.size();++i){
//防止方案重复,当前轮选取时,起始第一个数不重复
if(i>start && can[i]==can[i-1] ) continue;
curr_set.push_back(can[i]);
//每一个数字只能用一次,因此下一轮起始点为i+1
backTrack(can,curr_set,i+1);
curr_set.pop_back();
}
}
public:
vector<vector<int>> subsetsWithDup(vector<int>& nums) {
sort(nums.begin(),nums.end());
vector<int>curr;
backTrack(nums,curr,0);
return result;
}
};
class Solution {
vector<vector<int>>result;
void backTrack(vector<int>&can,vector<int>&curr_set,int start,int k)
{
if(curr_set.size()==k){
result.push_back(curr_set);
return;
}else if(curr_set.size()>k){
return;
}
//代表一轮选择,其选择第一个数为start
for(int i=start;i<can.size();++i){
curr_set.push_back(can[i]);
//每一个数字只能用一次,因此下一轮起始点为i+1
backTrack(can,curr_set,i+1,k);
curr_set.pop_back();
}
}
public:
vector<vector<int>> combine(int n, int k) {
vector<int>nums;
for(int i=1;i<=n;++i){
nums.push_back(i);
}
vector<int>curr;
backTrack(nums,curr,0,k);
return result;
}
};
全排列与组合的区别是,所有数字都要被选上,有顺序的区别,所以需要
用一个辅助数组去辨别当前未被选中的元素
class Solution {
vector<vector<int>>result;
vector<bool>use;
void backTrack(vector<int>&can,vector<int>&curr_set,int start)
{
if(curr_set.size() == can.size()){
result.push_back(curr_set);
return;
}
//代表一轮选择,其选择第一个数为start
for(int i=0;i<can.size();++i){
if(!use[i]){
use[i]=true;
curr_set.push_back(can[i]);
//每一个数字只能用一次,因此下一轮起始点为i+1
backTrack(can,curr_set,i+1);
curr_set.pop_back();
use[i]=false;
}
}
}
public:
vector<vector<int>> permute(vector<int>& nums) {
//sort(nums.begin(),nums.end());
vector<int>curr;
use.resize(nums.size(),false);
backTrack(nums,curr,0);
return result;
}
};
注意方案去重的方法
class Solution{
vector<vector<int>>result;
vector<bool>use;
//此处防止方案重复方法是,排序后,相同元素挨着。
//如果当前元素与上一个元素相同,且上一个元素未使用,则当前方案为重复方案,跳过
void backTrack(vector<int>&can,vector<int>&curr_set,int start)
{
if(curr_set.size() == can.size()){
result.push_back(curr_set);
return;
}
//代表一轮选择,其选择第一个数为start
for(int i=0;i<can.size();++i){
if(!use[i]){
//去除重复方案
if(i>0 && can[i]==can[i-1] && use[i-1]==false) continue;
use[i]=true;
curr_set.push_back(can[i]);
//每一个数字只能用一次,因此下一轮起始点为i+1
backTrack(can,curr_set,i+1);
curr_set.pop_back();
use[i]=false;
}
}
}
public:
vector<vector<int>> permuteUnique(vector<int>& nums) {
sort(nums.begin(),nums.end());
vector<int>curr;
use.resize(nums.size(),false);
backTrack(nums,curr,0);
return result;
}
};
class Solution {
private:
vector<vector<int>>result;
void backTrack(vector<int> &candidates ,vector<int>&currSet, int target,int currSum,int start)
{
//从决策树角度来讲
//第0个数一定选择,再从0...N里面选择剩下数 [因为每一种数都可以重复]
//第1个数一定选择,再从1...N里面选择省剩下数
for(int i = start; i < candidates.size() ;i++)
{
if(currSum + candidates[i] == target)
{
currSet.push_back(candidates[i]);
result.push_back(currSet);
currSet.pop_back();
}
else if(currSum + candidates[i] < target)
{
currSet.push_back(candidates[i]);
backTrack(candidates,currSet,target,currSum+candidates[i],i);//由于可以重复,下一轮依然从i开始
currSet.pop_back();
}
else break;
}
}
public:
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
sort(candidates.begin(),candidates.end());
vector<int>curr;
backTrack(candidates,curr,target,0,0);
return result;
}
};
注意如何防止方案重复
注意每个数只能用一次
class Solution {
vector<vector<int>>result;
void backTrack(vector<int>&can,int target,vector<int>&curr_set,int curr_sum,int start)
{
//假设一共选取了K次,达到目标。则一共要调用backTrack K轮
//设第1轮的选取起始点为start ,依次遍历start可能的值。则为了每种数最多拿一次,下一轮选取起始点为start+1位置
//此外,为了防止方案重复,当回到第1轮时,如果下一个遍历的start值与上一次遍历值相等,则应该跳过。
//代表一轮选择,其选择起始点为start
for(int i=start;i<can.size();++i){
if(i>start && can[i-1]==can[i]) continue;//跳过同一轮选取时重复的起点数
if(can[i]+curr_sum==target){
curr_set.push_back(can[i]);
result.push_back(curr_set);
curr_set.pop_back();
}else if(can[i]+curr_sum<target){
curr_set.push_back(can[i]);
//每一个数字只能用一次,因此下一轮起始点为i+1
backTrack(can,target,curr_set,curr_sum+can[i],i+1);
curr_set.pop_back();
}else{
break;
}
}
}
public:
vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
sort(candidates.begin(),candidates.end());
vector<int>curr;
backTrack(candidates,target,curr,0,0);
return result;
}
};
原理与组合数之和2一致。
class Solution {
vector<vector<int>>result;
void backTrack(vector<int>&can,int target,vector<int>&curr_set,int curr_sum,int start,int k)
{
//假设一共选取了K次,达到目标。则一共要调用backTrack K轮
//设第1轮的选取起始点为start ,依次遍历start可能的值。则为了每种数最多拿一次,下一轮选取起始点为start+1位置
//此外,为了防止方案重复,当回到第1轮时,如果下一个遍历的start值与上一次遍历值相等,则应该跳过。
//代表一轮选择,其选择起始点为start
for(int i=start;i<can.size();++i){
if(can[i]+curr_sum==target && curr_set.size()==(k-1)){
curr_set.push_back(can[i]);
result.push_back(curr_set);
curr_set.pop_back();
}else if(can[i]+curr_sum<target && curr_set.size()<k){
curr_set.push_back(can[i]);
//每一个数字只能用一次,因此下一轮起始点为i+1
backTrack(can,target,curr_set,curr_sum+can[i],i+1,k);
curr_set.pop_back();
}else{
break;
}
}
}
public:
vector<vector<int>> combinationSum3(int k, int target) {
vector<int>candidates={1,2,3,4,5,6,7,8,9};
vector<int>curr;
backTrack(candidates,target,curr,0,0,k);
return result;
}
};
相当于找4轮组合,每个组合的和相等,等于sum/4。
因此状态有两种,
一种是当前轮组合还没找完,需要继续找。
另一种是当前轮组合已经找完,需要找下一轮。
class Solution {
bool findSolve=false;
int len=0;
vector<bool>visit;
void splitSum(unsigned int curr_target,int k,int start_pos,vector<int>& nums){
//前3轮 数字都找到,如果剩下还有数字,其和必然为sum/4
if(k==1){
findSolve = true;
}else{
for(int i=start_pos;i<nums.size();++i){
if(visit[i]==true) continue;
//第K边 sum 目标满足,从0开始寻找下一边的摆放方式
if(nums[i]==curr_target ){
visit[i]=true;
splitSum(len,k-1,0,nums);
visit[i]=false;
if(findSolve) return;
//继续找第K轮 sum的元素
}else if(nums[i]<len){
visit[i]=true;
splitSum(curr_target-nums[i],k,i+1,nums);
visit[i]=false;
if(findSolve) return;
}else if(nums[i]>len){
break;
}
if(findSolve) return;
}
}
return;
}
public:
bool makesquare(vector<int>& nums) {
if(nums.size()<4)return false;
unsigned int sum=0;
for(auto i:nums) sum+=i;
if(sum<=0) return false;
if(sum%4 !=0) return false;
sort(nums.begin(),nums.end());
if(nums.back()>(sum/4)) return false;
len=sum/4;
visit.resize(nums.size(),false);
splitSum(len,4,0,nums);
return findSolve;
}
};
分割得到第一个可用回文串后,递归回溯剩余串
class Solution {
bool isPalindrome(string s, int low, int high){
while(low < high) if(s.at(low++) != s.at(high--)){
return false;
}
return true;
}
vector<vector<string>>result;
void backtrack(vector<string> & curr, string s){
if(s.empty()){
result.push_back(curr);
return;
}
for(int i = 0; i < s.length(); i++){
if(isPalindrome(s, 0, i)){
curr.push_back(s.substr(0, i + 1));
backtrack(curr,s.substr(i+1,s.size()));
curr.pop_back();
}
}
}
public:
vector<vector<string>> partition(string s) {
vector<string>curr;
backtrack(curr,s);
return result;
}
};
按行进行选择,对当前行可行位置依次遍历时,并迭代进入下一行。
class Solution {
// 路径:board 中小于 row 的那些行都已经成功放置了皇后
// 选择列表:第 row 行的所有列都是放置皇后的选择
// 结束条件:row 超过 board 的最后一行
void backtrack(vector<string>& board, int row) {
// 触发结束条件
if (row == board.size()) {
res.push_back(board);
return;
}
int n = board[row].size();
for (int col = 0; col < n; col++) {
// 排除不合法选择
if (!isValid(board, row, col))
continue;
// 做选择
board[row][col] = 'Q';
// 进入下一行决策
backtrack(board, row + 1);
// 撤销选择
board[row][col] = '.';
}
}
/* 是否可以在 board[row][col] 放置皇后? */
bool isValid(vector<string>& board, int row, int col) {
int n = board.size();
// 检查列是否有皇后互相冲突
for (int i = 0; i < n; i++) {
if (board[i][col] == 'Q')
return false;
}
// 检查右上方是否有皇后互相冲突
for (int i = row - 1, j = col + 1;
i >= 0 && j < n; i--, j++) {
if (board[i][j] == 'Q')
return false;
}
// 检查左上方是否有皇后互相冲突
for (int i = row - 1, j = col - 1;
i >= 0 && j >= 0; i--, j--) {
if (board[i][j] == 'Q')
return false;
}
return true;
}
vector<vector<string>> res;
public:
/* 输入棋盘边长 n,返回所有合法的放置 */
vector<vector<string>> solveNQueens(int n) {
// '.' 表示空,'Q' 表示皇后,初始化空棋盘。
vector<string> board(n, string(n, '.'));
backtrack(board, 0);
return res;
}
};
是1的简化版,代码略。
求第K个排列序列
注意n个数的全排列数目为n!个,由此可以计算得到第k个排列时的字母顺序。
class Solution {
string ret;
unsigned int jiecheng(unsigned int m){
unsigned int ret=1;
for(int i=1;i<=m;++i){
ret=ret*i;
}
return ret;
}
void generate1(vector<int>&canditates,vector<int>&curr,int&k){
if(canditates.size()>1){
unsigned int curr_pos=(k-1)/jiecheng(canditates.size()-1);
curr.push_back(canditates[curr_pos]);
k=k-curr_pos*jiecheng(canditates.size()-1);
canditates.erase(canditates.begin()+curr_pos);
generate1(canditates,curr,k);
}else{
curr.push_back(canditates[0]);
ret.clear();
for(auto i:curr){
ret.push_back((char)('0'+i));
}
return ;
}
}
public:
string getPermutation(int n, int k) {
if(n<1 ||n>9 || k<1) return "";
vector<int>curr;
vector<int>canditates;
for(int i=1;i<=n;++i){
canditates.push_back(i);
}
generate1(canditates,curr,k);
return ret;
}
};
类似于切割回文字符串,先从当前串中找到一个有效IP字段,在进一步对剩余串迭代。
注意成功条件是得到4个有效字段,且剩余串长度为0.
class Solution {
vector<string>result;
bool isValidIPField(string s){
if(s.empty()) return false;
if(s[0]=='0'){
if(s.size()==1) return true;
else return false;
}
if(s.size()==1) return s[0]>='0' && s[0]<='9';
if(s.size()==2){
int k=(s[0]-'0')*10+(s[1]-'0');
return k>=0&&k<=255;
}
if(s.size()==3){
int k=(s[0]-'0')*100+(s[1]-'0')*10+(s[2]-'0');
return k>=0&&k<=255;
}
return false;
}
void backtrack(string s,vector<string>&curr){
if(curr.size()==4 && s.empty()){
string s;
for(auto c:curr){
s+=c+".";
}
s.resize(s.size()-1);
result.push_back(s);
return;
}else if(curr.size()>4){
return;
}
for(int i=0;i<s.size();++i){
if(isValidIPField(s.substr(0,i+1))){
curr.push_back(s.substr(0,i+1));
backtrack(s.substr(i+1,s.size()),curr);
curr.pop_back();
}
}
}
public:
vector<string> restoreIpAddresses(string s) {
vector<string>curr;
backtrack(s,curr);
return result;
}
};
参考文章 生成括号
class Solution {
//本题最大难点是确定 左右括号的不平衡数目,即至少多少左右括号需要被分别删除
void helper(string&s,int index,string path,int left,int right,int pair,set<string>&ret){
//结束条件
if(left==0&&right==0&&pair==0&&index==s.size()){
ret.insert(path);
return;
}else{
if(index>=s.size()) return;
if(s[index]=='('){
if(left>0){
//删除当前(
helper(s,index+1,path,left-1,right,pair,ret);
}
//保留当前左括号
helper(s,index+1,path+"(",left,right,pair+1,ret);
}else if(s[index]==')'){
if(right>0){
//删除当前右括号
helper(s,index+1,path,left,right-1,pair,ret);
}
//保留当前右括号
if(pair>0){
helper(s,index+1,path+")",left,right,pair-1,ret);
}
}else{
path.push_back(s[index]);
helper(s,index+1,path,left,right,pair,ret);
}
}
}
public:
vector<string> removeInvalidParentheses(string s) {
int left_more=0;
int right_more=0;
//1.确定左右括号的不平衡数目
for(auto i:s){
if(i=='(')++left_more;
else if(i==')'){
if(left_more>0)--left_more;
else ++right_more;
}
}
set<string>se;
helper(s,0,"",left_more,right_more,0,se);
vector<string>ret={se.begin(),se.end()};
return ret;
}
};
数独求解
参考文章