要求返回结果集的题目
主要思路
- 把当前结果cur引用传递给递归函数,在它后面添加字符/元素,符合条件再push进result
- 若cur是string,则在调递归时用右值(比如cur + '.'),不改变原cur的值
- 若cur是vector,则先push入cur,调用完递归之后在cur.pop_back
括号生成(lc22)
class Solution {
public:
vector<string> generateParenthesis(int n) {
vector<string> result;
if (!n) return result;
helper(n, n, "", result);
return result;
}
void helper(int left, int right, string cur, vector<string>& result) {
//left表示还需要多少个(,right表示还需要多少个)
if (left > right) return;
if (left == 0 && right == 0) result.push_back(cur);
if (left > 0) helper(left - 1, right, cur + "(", result); // cur是string,所以传cur + "("这个右值
if (right > 0) helper(left, right - 1, cur + ")", result);
}
};
复原ip地址(lc93)
class Solution {
public:
vector<string> restoreIpAddresses(string s) {
vector<string> res;
if (s.size() < 4 || s.size() > 12) return res;
restoreIpAddresses(s, 4, "", res);
return res;
}
void restoreIpAddresses(string s, int k, string cur, vector<string>& res) {
if (k == 1 && isValid(s)) {
res.push_back(cur.substr(1) + "." + s);
return;
}
for (int i = 1; i <= 3; ++i) { // 把前i位分出来
if (i <= s.size() && isValid(s.substr(0, i))) {
restoreIpAddresses(s.substr(i), k - 1, cur + "." + s.substr(0, i), res); // cur是string,所以传cur + "."+...这个右值
}
}
}
bool isValid(string s) {
if (s.empty() || s.size() > 3 || (s.size() > 1 && s[0] == '0')) return false;
int res = atoi(s.c_str());
return res <= 255 && res >= 0;
}
};
电话号码字母的组合(lc17)
- 思路:每次把一个字母attach到cur后面,符合条件就把cur push到result中
vector<string> letterCombinations(string digits) {
vector<string> result;
int n = digits.size();
if (!n) return result;
vector<string> dict{"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
helper(digits, result, 0, "", dict);
return result;
}
void helper(string& digits, vector<string>& result, int pos, string cur, vector<string>& dict) {
if (pos >= digits.size()) { // pos表示到digits的第几位了
result.push_back(cur);
return;
}
int num = digits[pos] - '0';
for (int i = 0; i < dict[num].size(); ++i) { // 每次取出一个attach到cur后面
helper(digits, result, pos + 1, cur + dict[num][i], dict); // cur是string,所以传cur + dict[num][i]这个右值
}
}
路径总和(lc113)
void findPath(TreeNode* root, int sum, vector<vector<int>>& result, vector<int>& cur) {
if (!root) return;
cur.push_back(root->val);
if (!root->left && !root->right) {
if (root->val == sum) result.push_back(cur);
}
if (root->left) findPath(root->left, sum - root->val, result, cur);
if (root->right) findPath(root->right, sum - root->val, result, cur);
cur.pop_back(); // cur是vector,所以先push入cur,传cur,再cur.pop
}
vector<vector<int>> pathSum(TreeNode* root, int sum) {
vector<vector<int>> result;
vector<int> cur;
findPath(root, sum, result, cur);
return result;
}
组合(lc77)
class Solution {
public:
vector<vector<int>> combine(int n, int k) {
vector<vector<int>> result;
if (k > n) return result;
vector<int> cur;
helper(result, n, k, 1, cur);
return result;
}
void helper(vector<vector<int>>& result, int n, int k, int start, vector<int> cur) {
if (cur.size() == k) {
result.push_back(cur);
return;
}
for (int i = start; i <= n; ++i) {
cur.push_back(i);
helper(result, n, k, i + 1, cur);
cur.pop_back();
}
}
};
组合总和(元素可取任意次;完全背包)(lc39)
- 法一:原始递归
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
vector<vector<int>> result;
vector<int> cur;
helper(candidates, result, target, cur, 0);
return result;
}
void helper(vector<int>& candidates, vector<vector<int>>& result, int target, vector<int>& cur, int pos) {
if (target < 0) return;
if (target == 0) {
result.push_back(cur);
return;
}
for (int i = pos; i < candidates.size(); ++i) {
cur.push_back(candidates[i]);
helper(candidates, result, target - candidates[i], cur, i);
cur.pop_back(); // cur是vector,所以先push入cur,传cur,再cur.pop
}
}
- 法二:先排序;遍历每个元素,与第一个元素交换,求从第二个元素开始的结果
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
vector<vector<int>> res;
sort(candidates.begin(), candidates.end());
for (int i = 0; i < candidates.size(); ++i) {
if (candidates[i] > target) break;
if (candidates[i] == target) {res.push_back({candidates[i]}); break;}
vector<int> vec = vector<int>(candidates.begin() + i, candidates.end());
vector<vector<int>> tmp = combinationSum(vec, target - candidates[i]);
for (auto a : tmp) {
a.insert(a.begin(), candidates[i]);
res.push_back(a);
}
}
return res;
}
组合总和Ⅱ(元素只能取1次)(lc40)
class Solution {
public:
vector<vector<int>> combinationSum2(vector<int>& num, int target) {
vector<vector<int>> res;
vector<int> cur;
sort(num.begin(), num.end()); // 避免重复
helper(num, target, 0, cur, res);
return res;
}
void helper(vector<int>& num, int target, int pos, vector<int>& cur, vector<vector<int>>& res) {
if (target < 0) return;
if (target == 0) { res.push_back(cur); return; }
for (int i = pos; i < num.size(); ++i) {
if (i > pos && num[i] == num[i - 1]) continue; // 避免重复
cur.push_back(num[i]);
helper(num, target - num[i], i + 1, cur, res); // 传i + 1
cur.pop_back();
}
}
};
单词拆分(单词可以取任意次;完全背包)(lc139)
- 法一:原始递归
class Solution {
public:
bool wordBreak(string s, vector<string>& wordDict, int pos) {
if (pos == s.size()) return true;
bool res = false;
for (string word : wordDict) {
int len = word.size();
if (s.size() >= pos + len && s.substr(pos, len) == word) res = res || wordBreak(s, wordDict, pos + len);
}
return res;
}
bool wordBreak(string s, vector<string>& wordDict) {
if (!s.size()) return true;
if (!wordDict.size()) return false;
return wordBreak(s, wordDict, 0);
}
};
- 法二:递归+记忆数组
【思路:memo[i] 为 [i, n] 的子字符串是否可以拆分;-1表示没有计算过;如果可以拆分,则赋值为1,反之为0】
class Solution {
public:
bool wordBreak(string s, vector<string>& wordDict) {
unordered_set<string> wordSet(wordDict.begin(), wordDict.end());
vector<int> memo(s.size(), -1);
return check(s, wordSet, 0, memo);
}
bool check(string s, unordered_set<string>& wordSet, int start, vector<int>& memo) {
if (start >= s.size()) return true;
if (memo[start] != -1) return memo[start];
for (int i = start + 1; i <= s.size(); ++i) {
if (wordSet.count(s.substr(start, i - start)) && check(s, wordSet, i, memo)) {
return memo[start] = 1;
}
}
return memo[start] = 0;
}
};
- 法三:dp;dp[i]:开头到第i位是否可拆分
bool wordBreak(string s, vector<string>& wordDict) {
if (!s.size()) return true;
int n = wordDict.size(), m = s.size();
if (!n) return false;
vector<bool> dp(m + 1, false);
dp[0] = true;
for (int i = 1; i <= m; ++i) {
for (string word : wordDict) {
int len = word.size();
if (len <= i && dp[i - len] && word == s.substr(i - len, len)) {
dp[i] = true;
break;
}
}
}
return dp[m];
}
N皇后(lc51)
class Solution {
public:
vector<vector<string>> solveNQueens(int n) {
vector<vector<string>> res;
vector<string> queens(n, string(n, '.'));
helper(0, queens, res);
return res;
}
void helper(int curRow, vector<string>& queens, vector<vector<string>>& res) {
int n = queens.size();
if (curRow == n) {
res.push_back(queens);
return;
}
for (int i = 0; i < n; ++i) {
if (isValid(queens, curRow, i)) {
queens[curRow][i] = 'Q';
helper(curRow + 1, queens, res);
queens[curRow][i] = '.';
}
}
}
bool isValid(vector<string>& queens, int row, int col) {
for (int i = 0; i < row; ++i) {
if (queens[i][col] == 'Q') return false;
}
for (int i = row - 1, j = col - 1; i >= 0 && j >= 0; --i, --j) {
if (queens[i][j] == 'Q') return false;
}
for (int i = row - 1, j = col + 1; i >= 0 && j < queens.size(); --i, ++j) {
if (queens[i][j] == 'Q') return false;
}
return true;
}
};
全排列的题目
主要思路
每次取出一个数a,把a插入到当前res中的每个result中
全排列(lc46)
- 法一:
vector<vector<int>> permute(vector<int>& num) {
vector<vector<int>> res{{}};
for (int a : num) { // 每次取出一个数a
for (int k = res.size(); k > 0; --k) { // 把a插入到当前res中的每个result中
vector<int> t = res.front();
res.erase(res.begin());
for (int i = 0; i <= t.size(); ++i) {
vector<int> one = t;
one.insert(one.begin() + i, a);
res.push_back(one);
}
}
}
return res;
}
- 法二:遍历每个元素,与第一个元素交换,求从第二个元素开始的全排列;记得要换回来!
vector<vector<int>> permute(vector<int>& nums) {
vector<vector<int>> result;
if (!nums.size()) return result;
if (nums.size() == 1) {
result.push_back({nums[0]});
return result;
}
for (int i = 0; i < nums.size(); ++i) {
if (nums[i] != nums[0]) swap(nums[i], nums[0]);
vector<int> recurVec;
recurVec.assign(nums.begin() + 1, nums.end());
vector<vector<int>> recurRes = permute(recurVec);
for (auto v : recurRes) {
v.insert(v.begin(), nums[0]);
result.push_back(v);
}
if (nums[i] != nums[0]) swap(nums[i], nums[0]);
}
return result;
}
求所有子集(lc78)
vector<vector<int>> subsets(vector<int>& nums) {
vector<vector<int>> res(1);
int n = nums.size();
for (int i = 0; i < n; ++i) { // 依次取出nums中的数a
int size = res.size();
for (int j = 0; j < size; ++j) { // 在当前所有子集中都插入a
res.push_back(res[j]);
res.back().push_back(nums[i]);
}
}
return res;
}
其他题目
把数组排成最小的数(jz32)
- 思路:遍历、交换
string PrintMinNumber(vector<int> numbers) {
int n = numbers.size();
if (!n) return "";
for (int i = 0; i < n - 1; ++i) {
for (int j = i + 1; j < n; ++j) {
string str1 = to_string(numbers[i]) + to_string(numbers[j]);
string str2 = to_string(numbers[j]) + to_string(numbers[i]);
if (str1 > str2) swap(numbers[i], numbers[j]);
}
}
string res = "";
bool flag = false; // 处理"00"的情况
for (int i = 0; i < n; ++i) {
if (!flag && numbers[i] == 0) continue;
res += to_string(numbers[i]);
flag = true;
}
return flag ? res : "0";
}
- 更简洁的写法
string largestNumber(vector<int>& nums) {
string res;
sort(nums.begin(), nums.end(), [](int a, int b) {
return to_string(a) + to_string(b) > to_string(b) + to_string(a);
});
for (int i = 0; i < nums.size(); ++i) {
res += to_string(nums[i]);
}
return res[0] == '0' ? "0" : res;
}
下一个排列(lc31)
- 思路:从右往左扫描,找到第一个满足num[i] > num[i - 1]的位置,把num[i - 1]和i及之后的数字中最后一个大于num[i-1]的数字交换,再sort num[i]~最后;若找不到满足num[i] > num[i - 1]的位置,reverse整个数组
1-n可以构成多少棵二叉搜索树(lc96)
- 思路:f(n) = f(n - 1) * f(0) // 以n为根,左子树大小为n - 1,右子树大小为0;设f(0) = 1
+ f(n - 2) * f(1) // 以n-1为根,左子树大小为n - 2,右子树大小为1
+ ...
+ f(0) * f(n - 1) // 以1为根,左子树大小为0,右子树大小为n - 1;设f(0) = 1
int numTrees(int n) {
if (!n) return 0;
vector<int> dp(n + 1);
dp[0] = 1;
dp[1] = 1;
for (int i = 2; i <= n; ++i) {
int cur = 0;
for (int j = 0; j < i; ++j) {
cur += dp[i - j - 1] * dp[j];
}
dp[i] = cur;
}
return dp[n];
}
最大交换(lc670)
- 法一:递归,从左到右遍历pos;每次求pos及其右边的最大值,若最大值不是pos处的数则交换它俩,否则pos++
class Solution {
public:
void helper(vector<int>& nums, int pos) {
if (pos == 0) return;
int max_num = nums[pos], index = pos;
for (int i = 0; i < pos; ++i) {
if (nums[i] > max_num) {
max_num = nums[i];
index = i;
}
}
if (index != pos) swap(nums[index], nums[pos]);
else helper(nums, pos - 1);
}
int maximumSwap(int num) {
vector<int> numArray;
while (num) {
numArray.push_back(num % 10);
num /= 10;
}
helper(numArray, numArray.size() - 1);
int res = 0;
reverse(numArray.begin(), numArray.end());
for (int i : numArray) {
res = res * 10 + i;
}
return res;
}
};
- 法二:从右到左找到每个数字右边的最大数字(包括其自身);再从左到右遍历,如果某一位上的数字小于其右边的最大数字,说明需要调换;由于最大数字可能不止出现一次,这里希望能跟较低位的数字置换,这样置换后的数字最大,所以就从低位向高位遍历来找那个最大的数字,找到后进行调换即可
int maximumSwap(int num) {
string res = to_string(num);
string back = res;
for (int i = back.size() - 2; i >= 0; --i) {
back[i] = max(back[i + 1], back[i]);
}
for (int i = 0; i < res.size(); ++i) {
for (int j = res.size() - 1; j > i; --j) {
if (res[j] == back[i] && res[i] != res[j]) { // 若res[i]=res[j],则无需swap
swap(res[i], res[j]);
return stoi(res);
}
}
}
return stoi(res);
}
水壶问题(lc365)
- 思路:z = m * x + n * y,根据裴蜀定理,只需z是x、y最大公约数的倍数即可
class Solution {
public:
bool canMeasureWater(int x, int y, int z) {
return z == 0 || (x + y >= z && z % gcd(x, y) == 0);
}
int gcd(int x, int y) {
return y == 0 ? x : gcd(y, x % y);
}
};
字典序排数(lc386)
class Solution {
public:
vector<int> lexicalOrder(int n) {
vector<int> res;
for (int i = 1; i <= 9; ++i) {
helper(i, n, res);
}
return res;
}
void helper(int cur, int n, vector<int>& res) {
if (cur > n) return;
res.push_back(cur);
for (int i = 0; i <= 9; ++i) {
if (cur * 10 + i <= n) {
helper(cur * 10 + i, n, res);
} else break;
}
}
};