第一题:Leetcode39. 组合总和
题目描述
题解
class Solution {
public:
vector<vector<int>> ans;
vector<int> path;
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
backTracking(candidates, target, 0, 0);
return ans;
}
void backTracking(vector<int>& candidates, int target, int sum,
int startIdx) {
if (sum > target)
return;
if (sum == target) {
ans.push_back(path);
return;
}
for (int i = startIdx; i < candidates.size(); i++) {
path.push_back(candidates[i]);
backTracking(candidates, target, sum + candidates[i], i);
path.pop_back();
}
}
};
- 由于可以使用重复元素,在向深处遍历时,从当前 i 开始;
- 利用函数参数的传值特性,不用对sum进行加减操作。
- sum大于target时,可以进行剪枝。
第二题:Leetcode40. 组合总和 II
题目描述
题解
此题与第一题不同在于:不可以重复选取元素,就算candidates里头有两个1,也只能使用一次。
class Solution {
public:
vector<vector<int>> ans;
vector<int> path;
vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
sort(candidates.begin(), candidates.end());
backTracking(candidates, target, 0, 0);
return ans;
}
void backTracking(vector<int>& candidates, int target, int sum,
int startIdx) {
if (sum > target)
return;
if (sum == target) {
ans.push_back(path);
return;
}
for (int i = startIdx; i < candidates.size(); i++) {
if (i > startIdx && candidates[i] == candidates[i - 1])
continue;
path.push_back(candidates[i]);
backTracking(candidates, target, sum + candidates[i], i + 1);
path.pop_back();
}
}
};
这里为了避免使用同一个元素,首先对candidates进行排序,紧接着才回溯遍历时,使用
if (i > startIdx && candidates[i] == candidates[i - 1])
continue;
跳过重复元素,这个技巧在Leetcode15.三数之和 & Leetcode18.四数之和 见过。
第三题:Leetcode131. 分割回文串
题目描述
题解1——回溯+每次判断是否回文
class Solution {
public:
vector<vector<string>> ans;
vector<string> path;
vector<vector<string>> partition(string s) {
backTracking(s, 0);
return ans;
}
void backTracking(string s, int startIdx) {
if (startIdx >= s.size()) {
ans.push_back(path);
return;
}
for (int i = startIdx; i < s.size(); i++) {
if (isHuiwen(s, startIdx, i)) {
path.push_back(s.substr(startIdx, i - startIdx + 1));
backTracking(s, i + 1);
path.pop_back();
} else
continue;
}
}
// [start,end]
bool isHuiwen(const string s, int start, int end) {
while (start <= end) {
if (s[start++] != s[end--])
return false;
}
return true;
}
};
对于一个字符串是否回文,使用双指针进行遍历即可
bool isHuiwen(const string s, int start, int end) {
while (start <= end) {
if (s[start++] != s[end--])
return false;
}
return true;
}
小技巧
- string s.substr(Idx,length)会返回字符串s从Idx为下标开始、长度为length的子串(length省略,则到末尾)。
- 由于每次都计算,其中会有重复。
- 这个算法的复杂度为O(n*2^n),空间复杂度为(n^2)。
题解2——回溯+预先动态规划计算是否回文
- 预先动态规划计算是否回文:二维数组isHuiWen[i,j]取值表示[i,j]是否为回文字符串,isHuiWin[i][j] 由 s[i] == s[j] 和 isHuiWen[i+1][j-1] 决定,因此可以选用动态规划提前计算好回文串。
- i 依赖于 i+1 ,所以i从大到小遍历;j 依赖于 j-1,所以 j 从小到大遍历;
- i <= j;
- 当 i 等于 j 时,是回文串;
class Solution {
public:
vector<vector<string>> ans;
vector<string> path;
vector<vector<bool>> vecIsHuiwen;
vector<vector<string>> partition(string s) {
computeHuiwen(s);
backTracking(s, 0);
return ans;
}
void backTracking(string s, int startIdx) {
if (startIdx >= s.size()) {
ans.push_back(path);
return;
}
for (int i = startIdx; i < s.size(); i++) {
if (vecIsHuiwen[startIdx][i]) {
// if (isHuiwen(s, startIdx, i)) {
path.push_back(s.substr(startIdx, i - startIdx + 1));
backTracking(s, i + 1);
path.pop_back();
} else
continue;
}
}
// [start,end]
bool isHuiwen(const string s, int start, int end) {
while (start <= end) {
if (s[start++] != s[end--])
return false;
}
return true;
}
void computeHuiwen(const string s) {
vecIsHuiwen.resize(s.size(), vector<bool>(s.size(), false));
// vecIsHuiwen[i][j]代表从i到j是否为回文,[i,j]
// vecIsHuiwen[i][i] = true
// vecIsHuiwen[i][j] = s[i]==s[j]&&
// vecIsHuiwen[i+1][j-1],因此,i需要从大到小,j从小到大。
for (int i = s.size() - 1; i >= 0; i--) {
for (int j = i; j <= s.size(); j++) {
if (i == j)
vecIsHuiwen[i][j] = true;
else if (i == j - 1)
vecIsHuiwen[i][j] = (s[i] == s[j]);
else
vecIsHuiwen[i][j] =
((s[i] == s[j]) && vecIsHuiwen[i + 1][j - 1]);
}
}
}
};
第四题:Leetcode 62.不同路径(一维数组写法)
class Solution {
public:
int uniquePaths(int m, int n) {
vector<int> paths(n, 1);
for (int i = 1; i <= m - 1; i++) {
for (int j = 0; j <= n - 1; j++)
if (j > 0)
paths[j] = paths[j - 1] + paths[j];
}
return paths[n - 1];
}
};
第五题:Leetcode 63.不同路径 II(一维数组写法)
class Solution {
public:
int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {
const int m = obstacleGrid.size();
const int n = obstacleGrid[0].size();
vector<int> paths(n, 0);
for (int j = 0; j < n; j++) {
if (obstacleGrid[0][j] == 1)
break;
else
paths[j] = 1;
}
for (int i = 1; i <= m - 1; i++) {
for (int j = 0; j <= n - 1; j++) {
if (obstacleGrid[i][j] == 1)
paths[j] = 0;
else if (j != 0) {
paths[j] += paths[j - 1];
}
}
}
return paths[n - 1];
}
};
要点
- j 需要从0开始,大于0 时使用递归,等于0时根据是否是障碍物来取值。
第六题:Leetcode96. 不同的二叉搜索树
第一次做不懂什么叫二叉搜索树。
二叉搜索树定义:
- 根节点大于左子树所有节点,小于右子树所有节点;
- 左右子树均为二叉搜索树。
题解
class Solution {
public:
int numTrees(int n) {
vector<int> dp(n + 1, 0);
dp[0] = 1;
// dp[i] 代表i个连续数字组成的二叉搜索树种数:例如dp[5]可以代表从1~5 也可以代表从5~9 的二叉搜索树种类之和
// dp[i] 递推可以这样认为:对于任意 j从[1,i],以 j 为根节点
// 左子树为有 j-1个节点(均小于j)
// 右子树有i-j个节点(均大于j)的搜索树 种类之和
// 即dp[i] 等于 以1为根节点 + 以2为根节点 + 。。。 + 以i为根节点
// 递推公式为 for j=1 up to i: dp[i]+= dp[j-1]*dp[i-j] 左子树是[1,j-1]共j-1个节点,右子树是[j+1,i]共i-j个节点
// 此时,dp[0]没有意义,但是将其初始化为1才能符合条件
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= i; j++)
dp[i] += dp[j - 1] * dp[i - j];
}
return dp[n];
}
};
卡尔对于动态规划的解题思路