77.组合
题目:给定两个整数 n
和 k
,返回范围 [1, n]
中所有可能的 k
个数的组合。你可以按 任何顺序 返回答案。
思路:递归+回溯。有一个地方需要注意,比如第一次取1,第二次取2;那么第二轮的时候就不能第一次取2,第二次取1。所以要有一个start_index标识开始取数的位置。
通过代码:
class Solution {
public:
vector<vector<int>> res;
vector<int> once;
void dfs(int n, int k, int start_index){
if(once.size() == k)
{
res.push_back(once);
return;
}
for(int i = start_index; i <= n; i++)
{
if(k - once.size() <= n - start_index + 1)
{
once.push_back(i);
dfs(n, k, i + 1);
once.pop_back();
}
}
}
vector<vector<int>> combine(int n, int k) {
dfs(n, k, 1);
return res;
}
};
216.组合总和III
题目:找出所有相加之和为 n
的 k
个数的组合,且满足下列条件:
- 只使用数字1到9
- 每个数字 最多使用一次
返回 所有可能的有效组合的列表 。该列表不能包含相同的组合两次,组合可以以任何顺序返回。
思路:本题和上一题类似,只需要多维护一个sum即可。
通过代码:
class Solution {
public:
vector<vector<int>> res;
vector<int> once;
int sum = 0;
void dfs(int k, int n, int start_num){
if(once.size() == k)
{
if(sum == n)
res.push_back(once);
return;
}
for(int i = start_num; i <= 9; i++)
{
if(sum > n)
return;
once.push_back(i);
sum += i;
dfs(k, n, i + 1);
once.pop_back();
sum -= i;
}
}
vector<vector<int>> combinationSum3(int k, int n) {
dfs(k, n, 1);
return res;
}
};
17.电话号码的字母组合
题目:给定一个仅包含数字 2-9
的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
思路:递归+回溯,没啥好说的
通过代码:
class Solution {
public:
string map[10]={"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
vector<string> res;
string once;
void dfs(string digits, int depth){
if(depth == digits.size())
{
res.push_back(once);
return;
}
int num = digits[depth] - '0';
for(char c : map[num])
{
once += c;
dfs(digits, depth + 1);
once.pop_back();
}
}
vector<string> letterCombinations(string digits) {
if(digits.empty())
return res;
dfs(digits, 0);
return res;
}
};
39. 组合总和
题目:给你一个无重复元素的整数数组candidates
和一个目标整数target
,找出candidates
中可以使数字和为目标数target
的所有不同组合 ,并以列表形式返回。你可以按任意顺序返回这些组合。
candidates
中的同一个数字可以无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。
对于给定的输入,保证和为 target
的不同组合数少于 150
个。
思路:类似第77题,注意修改start_index的位置即可。
通过代码:
class Solution {
public:
vector<vector<int>> res;
vector<int> once;
int sum = 0;
void dfs(vector<int> &candidates, int target, int start_index){
if(sum > target)
return;
if(sum == target)
{
res.push_back(once);
return;
}
for(int i = start_index; i < candidates.size(); i++)
{
once.push_back(candidates[i]);
sum += candidates[i];
dfs(candidates, target, i);
sum -= candidates[i];
once.pop_back();
}
}
vector<vector<int>> combinationSum(vector<int>& candidates, int target){
dfs(candidates, target, 0);
return res;
}
};
40.组合总和II
题目:给定一个候选人编号的集合candidates
和一个目标数target
,找出candidates
中所有可以使数字和为target
的组合。
candidates
中的每个数字在每个组合中只能使用 一次 。
注意:解集不能包含重复的组合。
思路:本题和上一题的区别在于candidates里有重复的元素,这就导致解集里有可能有重复组合。所以重点在于去重。首先对candidates排序,这样相同的元素就在一起了。for循环代表的是横向遍历,递归代表的是纵向。重复组合出现的情况就在于横向的时候,所以当candidates[i] == candidates[i-1]
的时候跳过即可。
通过代码:
class Solution {
public:
vector<vector<int>> res;
vector<int> once;
int sum = 0;
void dfs(vector<int> &candidates, int target, int start_index){
if(sum == target)
{
res.push_back(once);
return;
}
for(int i = start_index; i < candidates.size() && sum + candidates[i] <= target; i++)
{
if(i > start_index && candidates[i] == candidates[i - 1])
continue;
once.push_back(candidates[i]);
sum += candidates[i];
dfs(candidates, target, i + 1);
sum -= candidates[i];
once.pop_back();
}
}
vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
sort(candidates.begin(), candidates.end());
dfs(candidates, target, 0);
return res;
}
};
131.分割回文串
题目:给你一个字符串s
,请你将s
分割成一些子串,使每个子串都是回文串 。返回s
所有可能的分割方案。
思路:类似组合问题。组合问题是先选一个,再在剩下的里面继续选。分割的话就是先切一段,再在剩下的里面继续切。不断递归下去的就是在剩下的字符串里继续切,for循环里枚举单层递归切多长即可。只有切下来的是回文串才会继续往下切,所以最后字符串为空说明切完了,保存一下结果。
通过代码:
class Solution {
public:
vector<vector<string>> res;
vector<string> once;
bool check(string s){
string tmp = s;
reverse(tmp.begin(), tmp.end());
return tmp == s;
}
void dfs(string s){
if(s.empty())
{
res.push_back(once);
return;
}
for(int i = 1; i <= s.size(); i++)
{
string str = s.substr(0, i);
string left = s.substr(i, s.size() - str.size());
if(!check(str))
continue;
once.push_back(str);
dfs(left);
once.pop_back();
}
}
vector<vector<string>> partition(string s) {
dfs(s);
return res;
}
};
93.复原IP地址
题目:有效IP 地址正好由四个整数(每个整数位于0
到255
之间组成,且不能含有前导0
),整数之间用'.'
分隔。
- 例如:
"0.1.2.201"
和"192.168.1.1"
是 有效 IP 地址,但是"0.011.255.245"
、"192.168.1.312"
和"192.168@1.1"
是 无效 IP 地址。
给定一个只包含数字的字符串s
,用以表示一个IP地址,返回所有可能的有效IP地址,这些地址可以通过在s
中插入'.'
来形成。你 不能 重新排序或删除 s
中的任何数字。你可以按 任何 顺序返回答案。
思路:类似分割回文串,只不过明确有4段,加一些限制条件即可。
通过代码:
class Solution {
public:
vector<string> res;
vector<string> once;
bool check(string s){
if(s.starts_with('0') && s.size() > 1)
return false;
if(s.size() > 3)
return false;
int num = stoi(s);
return num >=0 && num <= 255;
}
void dfs(string s){
if(s.empty() && once.size() == 4)
{
string ip;
for(int i = 0; i < once.size(); i++)
{
if(i > 0)
ip += ".";
ip += once[i];
}
res.push_back(ip);
return;
}
int max_len = min(3, (int)s.size());
for(int i = 1; i <= max_len && once.size() < 4; i++)
{
string str = s.substr(0, i);
string left = s.substr(i, s.size() - str.size());
if(!check(str))
continue;
once.push_back(str);
dfs(left);
once.pop_back();
}
}
vector<string> restoreIpAddresses(string s) {
dfs(s);
return res;
}
};
78.子集
题目:给你一个整数数组nums
,数组中的元素互不相同 。返回该数组所有可能的子集(幂集)。解集不能包含重复的子集。你可以按任意顺序返回解集。
思路:
通过代码:
class Solution {
public:
vector<vector<int>> res = {{}};
vector<int> once;
void dfs(vector<int> &nums, int start_index){
if(start_index == nums.size())
return;
for(int i = start_index; i < nums.size(); i++)
{
once.push_back(nums[i]);
res.push_back(once);
dfs(nums, i + 1);
once.pop_back();
}
}
vector<vector<int>> subsets(vector<int>& nums) {
dfs(nums, 0);
return res;
}
};
90.子集II
题目:给你一个整数数组nums
,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。
解集不能包含重复的子集。返回的解集中,子集可以按任意顺序排列。
思路:和上一题类似,做好去重即可。去重还是老套路,排完序之后遇到之前出现过的continue即可。
通过代码:
class Solution {
public:
vector<vector<int>> res = {{}};
vector<int> once;
void dfs(vector<int> &nums, int start_index){
if(start_index == nums.size())
return;
for(int i = start_index; i < nums.size(); i++)
{
if(i > start_index && nums[i] == nums[i - 1])
continue;
once.push_back(nums[i]);
res.push_back(once);
dfs(nums, i + 1);
once.pop_back();
}
}
vector<vector<int>> subsetsWithDup(vector<int>& nums) {
sort(nums.begin(), nums.end());
dfs(nums, 0);
return res;
}
};
491.非递减子序列
题目:给你一个整数数组nums
,找出并返回所有该数组中不同的递增子序列,递增子序列中至少有两个元素 。你可以按任意顺序返回答案。
数组中可能含有重复元素,如出现两个整数相等,也可以视作递增序列的一种特殊情况。
思路:和之前回溯题目的套路类似,只不过不能通过排序去重了。为了避免在同一层使用已经遇到过的元素,所以需要设置一个flag数组标记某个值是否出现过。题目里元素的取值范围为[-100, 100],所以用数组就行,用unordered_set速度会慢一些。
通过代码:
class Solution {
public:
vector<vector<int>> res;
vector<int> once;
void dfs(vector<int> &nums, int start_index){
if(start_index == nums.size())
return;
bool flag[201] = {};
for(int i = start_index; i < nums.size(); i++)
{
if(!flag[nums[i] + 100] && (once.empty() || nums[i] >= once.back()))
{
once.push_back(nums[i]);
flag[nums[i] + 100] = true;
}
else
continue;
if(once.size() > 1)
res.push_back(once);
dfs(nums, i + 1);
once.pop_back();
}
}
vector<vector<int>> findSubsequences(vector<int>& nums) {
dfs(nums, 0);
return res;
}
};
46.全排列
题目:给定一个不含重复数字的数组 nums
,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。
思路:类似之前的题目,只不过不需要start_index了。但是需要一个used数组,标记已经选择的元素。
通过代码:
class Solution {
public:
vector<vector<int>> res;
vector<int> once;
bool used[21] = {};
void dfs(vector<int> &nums){
if(once.size() == nums.size())
{
res.push_back(once);
return;
}
for(int i = 0; i < nums.size(); i++)
{
if(used[nums[i] + 10])
continue;
used[nums[i] + 10] = true;
once.push_back(nums[i]);
dfs(nums);
once.pop_back();
used[nums[i] + 10] = false;
}
}
vector<vector<int>> permute(vector<int>& nums) {
dfs(nums);
return res;
}
};
47.全排列 II
题目:给定一个可包含重复数字的序列 nums
,按任意顺序返回所有不重复的全排列。
思路:本题和上题的区别在于包含重复数字,因此上题当哈希表用的used数组得改一下,改成和nums等长的一对一的used数组,通过下标来判断是否使用过。还有一个需要改的就是去重,和以前的思路一样,先排序,然后判断前一个是否是一样的。
通过代码:
class Solution {
public:
vector<vector<int>> res;
vector<int> once;
void dfs(vector<int> &nums, vector<bool> &used){
if(once.size() == nums.size())
{
res.push_back(once);
return;
}
for(int i = 0; i < nums.size(); i++)
{
if(used[i])
continue;
if(i > 0 && nums[i] == nums[i - 1] && !used[i - 1])
continue;
used[i] = true;
once.push_back(nums[i]);
dfs(nums, used);
once.pop_back();
used[i] = false;
}
}
vector<vector<int>> permuteUnique(vector<int>& nums) {
vector<bool> used(nums.size(), false);
sort(nums.begin(), nums.end());
dfs(nums, used);
return res;
}
};
332.重新安排行程
题目:给你一份航线列表tickets
,其中tickets[i] = [fromi, toi]
表示飞机出发和降落的机场地点。请你对该行程进行重新规划排序。
所有这些机票都属于一个从JFK
(肯尼迪国际机场)出发的先生,所以该行程必须从JFK
开始。如果存在多种有效的行程,请你按字典排序返回最小的行程组合。
- 例如,行程
["JFK", "LGA"]
与["JFK", "LGB"]
相比就更小,排序更靠前。
假定所有机票至少存在一种合理的行程。且所有的机票必须都用一次且只能用一次。
思路:如果用普通的深搜+回溯的话会超时。即便按照tickets的第二个元素排序之后还是有两个样例过不了。通过分析可知我们需要迅速定位特定出发地的机票,因此可以采用unordered_map。key存出发地,value存目的地。由于可能存在重复的机票,所以最好有一个int来保存机票数量。因此value最好也是一个map。由于题目要求字典序最小,所以采用map还可以保证找到的第一个行程就是字典序最小的(因为map有序)。综上,设置一个unordered_map<string, map<string, int>>的数据结构来重新组织机票信息。由于找到第一个就可以返回了,所以设置一个布尔类型的返回值来表示是否找到。接下来按常规的思路继续做即可。
通过代码:
class Solution {
public:
vector<string> path = {"JFK"};
unordered_map<string, map<string, int>> targets;
bool dfs(int ticket_num){
if(path.size() == ticket_num + 1)
return true;
for(pair<const string, int> &dest : targets[path.back()])
{
if(dest.second < 1)
continue;
dest.second--;
path.push_back(dest.first);
if(dfs(ticket_num))
return true;
path.pop_back();
dest.second++;
}
return false;
}
vector<string> findItinerary(vector<vector<string>>& tickets) {
for(vector<string> ticket : tickets)
targets[ticket[0]][ticket[1]]++;
dfs(tickets.size());
return path;
}
};
51. N皇后
题目:按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。
n 皇后问题 研究的是如何将 n
个皇后放置在 n×n
的棋盘上,并且使皇后彼此之间不能相互攻击。
给你一个整数 n
,返回所有不同的 n 皇后问题 的解决方案。
每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 'Q'
和 '.'
分别代表了皇后和空位。
思路:由于每行只能有一个皇后,所以递归可以按行进行。单次递归里通过for循环遍历该行里的列。
通过代码:
class Solution {
public:
vector<vector<string>> res;
bool check(int row, int col, int n, vector<string> &board){
for(int i = 0; i < row; i++)
if(board[i][col] == 'Q')
return false;
for(int i = row -1, j = col - 1; i >= 0 && j >= 0; i--, j--)
if(board[i][j] == 'Q')
return false;
for(int i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++)
if(board[i][j] == 'Q')
return false;
return true;
}
void dfs(int n, int row, vector<string> &board){
if(row == n)
{
res.push_back(board);
return;
}
for(int j = 0; j < n; j++)
{
if(check(row, j, n, board))
{
board[row][j] = 'Q';
dfs(n, row + 1, board);
board[row][j] = '.';
}
}
}
vector<vector<string>> solveNQueens(int n) {
vector<string> board(n, string(n, '.'));
dfs(n, 0, board);
return res;
}
};
37. 解数独
题目:编写一个程序,通过填充空格来解决数独问题。
数独的解法需 遵循如下规则:
- 数字
1-9
在每一行只能出现一次。 - 数字
1-9
在每一列只能出现一次。 - 数字
1-9
在每一个以粗实线分隔的3x3
宫内只能出现一次。(请参考示例图)
数独部分空格内已填入了数字,空白格用 '.'
表示。
思路:依次枚举每一个空白格中填的数字,通过递归 + 回溯的方法枚举所有可能的填法。当递归到最后一个空白格后,如果仍然没有冲突,说明我们找到了答案;在递归的过程中,如果当前的空白格不能填下任何一个数字,那么就进行回溯。
通过代码:
class Solution {
public:
bool check(int row, int col, char num, vector<vector<char>> &board){
for(int j = 0; j < 9; j++)
if(j != col && board[row][j] == num)
return false;
for(int i = 0; i < 9; i++)
if(i != row && board[i][col] == num)
return false;
int si = row / 3, sj = col / 3;
for(int i = si * 3; i < (si + 1) * 3; i++)
for(int j = sj * 3; j < (sj + 1) * 3; j++)
if(i != row && j != col && board[i][j] == num)
return false;
return true;
}
bool dfs(vector<vector<char>> &board){
for(int i = 0; i < 9; i++)
for(int j = 0; j < 9; j++)
{
if(board[i][j] == '.')
{
for(char c = '1'; c <= '9'; c++)
{
if(check(i, j, c, board))
{
board[i][j] = c;
if(dfs(board))
return true;
board[i][j] = '.';
}
}
return false;
}
}
return true;
}
void solveSudoku(vector<vector<char>>& board) {
dfs(board);
}
};