方法论:
- 看题五分钟,不会做,看解析;
- 先看中文站,再看国际站;
- 选择最优解析;
- 回头再来写
面试四步走:
- 和面试官,探讨题目限制条件;
- 说说可能解,选择最优解;
- 码字;
- 跑测试用例
思维要点:
- 不要人肉进行递归
- 找到最近最简方法,将其拆解成可重复解决的问题(重复子问题)
- 数学归纳法思维
78.子集
题目:给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
class Solution {
void generateSub(const vector<int>& nums, vector<vector<int>>& res, vector<int>& path, int start){
res.push_back(path);
for( int i = start; i < nums.size(); i++){
path.push_back(nums[i]);
generateSub(nums, res, path, i+1 );
path.pop_back();
}
}
public:
vector<vector<int>> subsets(vector<int>& nums) {
vector<vector<int>> res;
vector<int> path;
generateSub(nums, res, path, 0);
return res;
}
};
90.子集II
题目:给定一个可能包含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
# 方法一
class Solution {
void generateSub(const vector<int>& nums, vector<vector<int>>& res, vector<int>& path, int start){
res.push_back(path);
for(int i = start; i < nums.size(); i++){
if(i > start && nums[i] == nums[i-1])
continue;
path.push_back(nums[i]);
generateSub(nums, res, path, i+1);
path.pop_back();
}
}
public:
vector<vector<int>> subsetsWithDup(vector<int>& nums) {
vector<vector<int>> res;
vector<int> path;
sort(nums.begin(), nums.end());
generateSub(nums, res, path, 0);
return res;
}
};
# 方法二
class Solution {
void generateSub(const vector<int>& nums, vector<vector<int>>& res, vector<int>& path, int start){
res.push_back(path);
for(int i = start; i < nums.size(); i++){
path.push_back(nums[i]);
generateSub(nums, res, path, i+1);
path.pop_back();
while (i < nums.size() - 1 && nums[i] == nums[i+1]){
i++;
}
}
}
public:
vector<vector<int>> subsetsWithDup(vector<int>& nums) {
vector<vector<int>> res;
vector<int> path;
sort(nums.begin(), nums.end());
generateSub(nums, res, path, 0);
return res;
}
};
个人倾向于方法2,至于原因,请看Permutations II部分;
46.全排列
题目:给定一个 没有重复 数字的序列,返回其所有可能的全排列。
思路:以下两种方法主要区别是:判断nums[i]是否在path数组中,
- 方法1:是通过标记数组
used
区别; - 方法2:是利用
algorithms
中的find函数;
# 方法1
class Solution {
public:
vector<vector<int>> permute(vector<int>& nums) {
vector<vector<int>> res;
vector<int> path;
vector<bool> used(nums.size(), false);
helper(res, nums, path, used);
return res;
}
void helper(vector<vector<int>>& res, vector<int>& nums, vector<int>&path, vector<bool>used) {
if (path.size() == nums.size()){
res.push_back(path);
return;
}
for (int i = 0; i < nums.size(); i++ ){
if (used[i]) continue;
used[i] = true;
path.push_back(nums[i]);
helper(res, nums, path, used);
used[i] = false;
path.pop_back();
}
}
};
# 方法2
class Solution {
public:
vector<vector<int>> permute(vector<int>& nums) {
vector<vector<int>> res;
vector<int> path;
helper(res, nums, path);
return res;
}
void helper(vector<vector<int>>& res, vector<int>& nums, vector<int>&path) {
if (path.size() == nums.size()){
res.push_back(path);
return;
}
for (int i = 0; i < nums.size(); i++ ){
if (find(path.begin(), path.end(), nums[i]) != path.end()) continue;
path.push_back(nums[i]);
helper(res, nums, path);
path.pop_back();
}
}
};
47.全排列II
题目:给定一个可包含重复数字的序列 nums
,按任意顺序 返回所有不重复的全排列。
思路:
- 方法1:当i的值已经在path中或者 i > 0 && nums[i-1] == nums[i] && i-1没有在path中, 忽略该值,这一行代码比较难理解,个人更加倾向于第二种;
- 方法2:有兴趣的可以看这个视频:来源 Coding Interview Tutorial 31: Permutations II[LeetCode]
# 方法1
class Solution {
public:
vector<vector<int>> permuteUnique(vector<int>& nums) {
vector<vector<int>> res;
vector<bool> used = vector<bool>(nums.size(), false);
vector<int> path;
sort(nums.begin(), nums.end());
helper(nums, res, path, used);
return res;
}
private:
void helper(vector<int>& nums, vector<vector<int>>& res, vector<int>& path, vector<bool> used){
if (path.size() == nums.size()){
res.push_back(path);
return;
}
for (int i =0; i < nums.size(); i++){
if (used[i] || i > 0 && nums[i] == nums[i - 1] && !used[i-1]) continue;
used[i] = true;
path.push_back(nums[i]);
helper(nums, res, path, used);
path.pop_back();
used[i] = false;
}
}
};
# 方法2
class Solution {
public:
vector<vector<int>> permuteUnique(vector<int>& nums) {
vector<vector<int>> res;
vector<bool> used = vector<bool>(nums.size(), false);
vector<int> path;
sort(nums.begin(), nums.end());
helper(nums, res, path, used);
return res;
}
private:
void helper(vector<int>& nums, vector<vector<int>>& res, vector<int>& path, vector<bool> used){
if (path.size() == nums.size()){
res.push_back(path);
return;
}
for (int i =0; i < nums.size(); i++){
if (used[i]) continue;
used[i] = true;
path.push_back(nums[i]);
helper(nums, res, path, used);
path.pop_back();
used[i] = false;
while (i < nums.size() - 1 && nums[i] == nums[i+1]){
i++;
}
}
}
};
以 nums=[1, 1, 2]
说下我的理解:
跟着蓝色线条的脚步走,还是那句话,不要陷入人肉递归,我们看第一个nums[0] == nums[1]
,如果蓝色线条,继续往绿色快走的话,就会出现重复选项,因此需要跳过该块,while(i < nums.size() - 1 && nums[i] = nums[i+1])
就是干这个事的;同理,会跳过第二个黄色块;
39.组合总和
题目:给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。candidates 中的数字可以无限制重复被选取。
class Solution {
public:
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
vector<vector<int>> res;
vector<int> path;
sort(candidates.begin(), candidates.end());
helper(candidates, res, path, target, 0);
return res;
}
void helper(vector<int>& candidates, vector<vector<int>>& res, vector<int>& path, int target, int start){
if (target < 0) return;
if (target == 0){
res.push_back(path);
return;
}
for (int i = start; i < candidates.size(); i++){
path.push_back(candidates[i]);
helper(candidates, res, path, target - candidates[i], i); # 元素无限
path.pop_back();
}
}
};
40.组合总和II
题目: 给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中只能使用一次。
# 方法1
class Solution {
public:
vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
vector<int> path;
vector<vector<int>> res;
sort(candidates.begin(), candidates.end());
helper(candidates, res, path, target, 0);
return res;
}
// 时间复杂度O(2^n) 空间复杂度kn
void helper(vector<int>& candidates, vector<vector<int>>& res, vector<int>& path, int target, int start){
if (target < 0) return;
if (target == 0){
res.push_back(path);
return;
}
for (int i = start; i < candidates.size(); ++i){
if (i > start && candidates[i] == candidates[i - 1]) continue;
path.push_back(candidates[i]);
helper(candidates, res, path, target - candidates[i], i + 1);
path.pop_back();
}
}
};
# 方法2
class Solution {
public:
vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
vector<int> path;
vector<vector<int>> res;
sort(candidates.begin(), candidates.end());
helper(candidates, res, path, target, 0);
return res;
}
// 时间复杂度O(2^n) 空间复杂度kn
void helper(vector<int>& candidates, vector<vector<int>>& res, vector<int>& path, int target, int start){
if (target < 0) return;
if (target == 0){
res.push_back(path);
return;
}
for (int i = start; i < candidates.size(); ++i){
path.push_back(candidates[i]);
helper(candidates, res, path, target - candidates[i], i + 1);
path.pop_back();
while (i < candidates.size() - 1 && candidates[i] == candidates[i+1]){
i++;
}
}
}
};
131.分割回文串
题目:给定一个字符串 s,将 s 分割成一些子串,使每个子串都是回文串。
返回 s 所有可能的分割方案。
class Solution {
public:
vector<vector<string>> partition(string s) {
vector<vector<string>> res;
vector<string> path;
helper(s, res, path, 0);
return res;
}
private:
void helper(string str, vector<vector<string>>& res, vector<string>& path, int start) {
// terminator
if (start == str.size()){
res.push_back(path);
}
//string substr (size_t pos = 0, size_t len = npos) const;
for (int i = start; i < str.size(); i++) {
if (isPalindrome(str, start, i)){
path.push_back(str.substr(start, i - start + 1));
helper(str, res, path, i + 1);
path.pop_back();
}
}
}
bool isPalindrome(string str, int start, int end) {
while(start < end) {
if (str[start++] != str[end--]){
return false;
}
}
return true;
}
};
参考
- A general approach to backtracking questions in Java (Subsets, Permutations, Combination Sum, Palindrome Partioning)
- Coding Interview Tutorial 31: Permutations II[LeetCode]
- leetcode中文站
- leetcode国际站 (将力扣中文链接,后面的-cn去掉,就是该题的国际站)