https://leetcode-cn.com/problems/combination-sum/submissions/
文章目录
- 前言
- 一、动态规划
- 二、DFS和BFS
- 1.岛屿问题
- 2.橘子腐烂问题
- 3. [剑指 Offer 34. 二叉树中和为某一值的路径](https://leetcode-cn.com/problems/er-cha-shu-zhong-he-wei-mou-yi-zhi-de-lu-jing-lcof/)
- 4. [257. 二叉树的所有路径](https://leetcode-cn.com/problems/binary-tree-paths/)
- 5. [257. 二叉树最大路径和](https://www.nowcoder.com/practice/da785ea0f64b442488c125b441a4ba4a?tpId=117&&tqId=37716&rp=1&ru=/activity/oj&qru=/ta/job-code-high/question-ranking)
- 6. [根节点到叶子节点之和](https://leetcode-cn.com/problems/sum-root-to-leaf-numbers/)
- 三、回溯
前言
一、动态规划
1. 分割类问题
2.子序列问题
① 连续子数组最大和
class Solution {
public:
int FindGreatestSumOfSubArray(vector<int> array) {
int len = array.size();
// 初始化要注意,如果len 为1的话,for循环进不去,要保证直接return 第一个数字
int ret = array[0], sum = array[0];
for (int i = 1; i < len; i++) {
if (sum < 0) {
sum = array[i];
} else {
sum += array[i];
}
ret = max(ret, sum);
}
return ret;
}
};
② 最长公共子串
dp(i,j) =
if (arr1(i) == arr2(j)) {
dp(i,j) = dp(i-1,j-1) + 1
}
ret = max(ret, dp(i,j)); 还可以获取startIndex,最后输出最长公共字符串
③ 最长回文子串
dp(i,j)表示(i-1,j-1)是不是回文子串
i = 0 : len
j = 0 :i
if (arr(i) != arr(j)) {
continue;
} else {
dp(i,j) = dp(j + 1, i - 1) || (i和j的长度小于等于1)
}
不断更新最大长度
④ 最长递增子序列
首先利用动态规划求出最长递增子序列长度,然后开始逆推子序列
dp(i)表示到i(包括i),最长递增子序列长度是多少
i = 0 : len
j = 0 :i
if (arr(i) > arr(j)) {
dp(i) = max(dp(i), dp(j) + 1);
}
maxLen = dp(len);
for (int i = len - 1; i >= 0; i--) {
if (dp[i] == maxLis--) {
ret.push_back(arr[i]);
}
}
reverse(ret.begin(), ret.end());
return ret;
⑤ NC92 最长公共子序列(二)
主要是 逆推res
class Solution {
public:
/**
* longest common subsequence
* @param s1 string字符串 the string
* @param s2 string字符串 the string
* @return string字符串
*/
/*
dp(i,j)
*/
string LCS(string s1, string s2) {
int len1 = s1.size(), len2 = s2.size();
if (len1 == 0 || len2 == 0) {
return "-1";
}
vector<vector<int>> dp(len1 + 1, vector<int> (len2 + 1, 0));
for(int i = 0; i <= len1; i++) {
for (int j = 0; j <= len2; j++) {
if (i == 0 || j == 0) {
dp[i][j] = 0;
} else {
if (s1[i-1] == s2[j-1]) {
dp[i][j] = dp[i-1][j-1] + 1;
} else {
dp[i][j] = max(dp[i-1][j], dp[i][j-1]);
}
}
}
}
// 逆推res
string res = "";
for (int i = len1, j = len2; dp[i][j] >= 1; ) {
if (s1[i-1] == s2[j-1]) {
res += s1[i-1];
i--, j--;
} else if (dp[i-1][j] >= dp[i][j-1]) {
i--;
} else {
j--;
}
}
reverse(res.begin(), res.end());
return res.empty() ? "-1" : res;
// write code here
}
};
3.背包问题
4.股票交易问题
① 买卖股票的最好时机(一)
只能买卖一次,不断更新最小值和当前值和最小值之间的gap,gap最大的则为最好卖出的时机。
② 买卖股票的最佳时机 II
/*
dp(i,0)表示第i天过去后,手中还有0张股票的最大值
若第i天卖完的话
dp(i,0) = dp(i-1, 1) + prices[i];
若第i天本身就没有股票的话
dp(i,0) = dp(i-2);
dp(i,0) = max(dp(i-1, 1) + prices[i],dp(i-2))
dp(i,1)表示第i天过去后,手中还有1张股票的最大值
dp(i,1) = dp(i-1, 0) - prices[i];
dp(i,1) = dp(i-1,1);
dp(i,1) = max(dp(i-1, 0) - prices[i], dp(i-1,1));
*/
③买卖股票的最佳时机,含有冻结期
/*
同714
dp[i][0]表示第i天结束后,手里没有股票时,获益最大值
dp[i][1]表示第i天结束后,手里有1张股票
dp[i][0]有几种情况:
第一种是第i个刚卖完,这个时候i-1肯定有股票 dp[i-1][1] + price[i]
第二种是第i天处于冻结期, dp[i-1][0]
dp[i][0] = max(dp[i-1][1] + price[i], dp[i-1][0])
dp[i][1]有两种情况:
今天有1股,
有可能是昨天的,今天冻结:dp[i-1][1]
有可能是今天刚买的,那么昨天肯定不是刚卖完(如果昨天不是刚卖完,昨晚)dp[i-2] - dp[i]
如果是冻股,昨天刚卖完,dp[][]
第一种是第i填刚入股, dp[i-1][0] - price[i],
第二种是第冻股,第二天必须冬天,说明,dp[i-1][0]
dp[i][1] = max(dp[i-1][0] - price[i], dp[i-1][1]);
返回
dp[len-1][0]
注意变量的初始化和越界情况
*/
class Solution {
public:
int maxProfit(vector<int>& prices) {
int n=prices.size();
vector<vector<int>>dp(n,vector<int>(2));
dp[0][0]=0;
dp[0][1]=-prices[0];
for(int i=1;i<n;i++)
{
dp[i][0]=max(dp[i-1][0],dp[i-1][1]+prices[i]);
dp[i][1]=max(dp[i-1][1],(i>1?dp[i-2][0]:0)-prices[i]);
}
return dp[n-1][0];
}
};
④买卖股票的最佳时机,含有手续费
/*
dp(i,j),j取值为0/1,
dp(i,0)表示第i填结束之后手中持股0个.分为两种情况。
1, 第i天确实是冻结期,dp(i-1, 0)
2, 第i天刚卖完 dp(i-1,1)+prices[i] - fee;
dp(i,0) = max(dp(i-1, 0), dp(i-1,1)+prices[i] - fee)
dp(i,1)表示第i填结束之后手中持股1个.分为两种情况。
1, 第i天确实是冻结期,dp(i-1, 1)
2, 第i天刚吗买完 dp(i-1,0)-prices[i];
dp(i,1) = max(dp(i-1, 1), dp(i-1,0)-prices[i]);
*/
class Solution {
public:
int maxProfit(vector<int>& prices, int fee) {
int len = prices.size();
if (len == 1) {
return 0;
} else if (len == 2) {
int temp = prices[1] - prices[0] - fee;
if (temp > 0) {
return temp;
} else {
return 0;
}
}
vector<vector<int>> dp (len, vector<int>(2, 0));
dp[0][0] = 0, dp[0][1] = -prices[0];
for (int i = 1; i < len; i++) {
dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i] - fee);
dp[i][1] = max(dp[i-1][0] - prices[i], dp[i-1][1]);
}
return dp[len-1][0];
}
};
二、DFS和BFS
1.岛屿问题
① 200.岛屿数量(中等)
class Solution {
public:
int ret = 0;
int numIslands(vector<vector<char>>& grid) {
// 定界
if (grid.size() == 0) {
return 0;
}
int column = grid.size(), line = grid[0].size();
for (int i = 0; i < column; i++) {
for (int j = 0; j < line; j++) {
if (grid[i][j] == '1') {
// 每发现一个岛屿,ret++,然后使用dfs将岛屿淹没
dfs(grid, i, j);
ret ++;
}
}
}
return ret;
}
void dfs(vector<vector<char>> &grid, int x, int y) {
// 定界
int column = grid.size(), line = grid[0].size();
if (x < 0 || x >= column || y < 0 || y >= line) {
return;
}
// 已经是海水, 无需再递归下去
if (grid[x][y] != '1') {
return;
}
// 已经遍历过的直接被水淹掉,避免维护visitede数组
grid[x][y] = '0';
dfs(grid, x + 1, y);
dfs(grid, x - 1, y);
dfs(grid, x, y + 1);
dfs(grid, x, y - 1);
}
};
② 1254.统计封闭岛屿的数目(中等)
class Solution {
public:
// 将与(x,y)相接的陆地都变成海水
void dfs(vector<vector<int>>& grid, int x, int y) {
if (x < 0 || y < 0 || x >= grid.size() || y >= grid[0].size()) {
return;
}
if (grid[x][y] == 1) {
return;
}
grid[x][y] = 1;
dfs(grid, x + 1, y);
dfs(grid, x - 1, y);
dfs(grid, x, y + 1);
dfs(grid, x, y - 1);
}
int closedIsland(vector<vector<int>>& grid) {
int column = grid.size();
int line = grid[0].size();
for (int i = 0; i < column; i++) {
// 上面和下面的岛屿都淹掉
dfs(grid, i, 0);
dfs(grid, i, line - 1);
}
for (int j = 0; j < line; j++) {
// 左面和右面的岛屿都淹掉
dfs(grid, 0, j);
dfs(grid, column - 1, j);
}
int ret = 0;
for (int i = 0; i < column; i++) {
for (int j = 0; j < line; j++) {
if (grid[i][j] == 0) {
ret++;
dfs(grid, i, j);
}
}
}
return ret;
}
};
③ 最大岛屿面积
class Solution {
public:
int ret = 0;
int maxAreaOfIsland(vector<vector<int>>& grid) {
// 定界
if (grid.size() == 0) {
return 0;
}
int column = grid.size(), line = grid[0].size();
for (int i = 0; i < column; i++) {
for (int j = 0; j < line; j++) {
if (grid[i][j] == '1') {
// 每发现一个岛屿,ret++,然后使用dfs将岛屿淹没
ret = max(ret, dfs(grid, i, j));
}
}
}
return ret;
}
// 将与(x,y)相接的陆地都变成海水
int dfs(vector<vector<int>>& grid, int x, int y) {
if (x < 0 || y < 0 || x >= grid.size() || y >= grid[0].size()) {
return 0;
}
if (grid[x][y] == 0) {
return 0;
}
grid[x][y] = 0;
return ( dfs(grid, x + 1, y) +
dfs(grid, x - 1, y) +
dfs(grid, x, y + 1) +
dfs(grid, x, y - 1) + 1);
}
};
④ 子岛屿
class Solution {
public:
/*
Q:如何判断2中的岛屿是不是子岛屿?
A:从正向角度来看,在2中有的,在1中都有。
从反向来看,在2中有的, 但是在1中没有的,相应的岛屿肯定不是子岛屿。
所以现沉默非子岛屿,然后再计算2中其余的岛屿。
*/
void dfs(vector<vector<int>>& grid, int x, int y) {
// 定界
if(x < 0 || x > grid.size() -1 || y < 0 || y > grid[0].size() -1) {
return;
}
// 扩展
if (grid[x][y] == 0) {
return;
}
grid[x][y] = 0;
dfs(grid, x + 1, y);
dfs(grid, x - 1, y);
dfs(grid, x, y + 1);
dfs(grid, x, y - 1);
}
int countSubIslands(vector<vector<int>>& grid1, vector<vector<int>>& grid2) {
int column = grid1.size();
int line = grid1[0].size();
// 沉没非子岛屿
for (int i = 0; i < column; i++) {
for (int j = 0; j < line; j++) {
if (grid1[i][j] == 0 && grid2[i][j] == 1) {
dfs(grid2, i, j);
}
}
}
// 计算子岛屿数目
int ret = 0;
for (int i = 0; i < column; i++) {
for (int j = 0; j < line; j++) {
if (grid2[i][j] == 1) {
ret++;
dfs(grid2, i, j);
}
}
}
return ret;
}
};
④ 统计不同形状的岛屿数量
class Solution {
public:
/*
如何计算呢?
使用string计算路径。上下左右分布是1,2,3,4 反向分别是-1-2-3-4
set<string>st;
st.insert()
最终计算st.size();
1,使用string来表示对应的路径,还有回溯的思想,使用-dir表示已经回头。
2,使用set去重,因此set.size可以直接表示ret
*/
void dfs(vector<vector<int>>& grid, int x, int y, string &str, string dir) {
// 定界
if(x < 0 || x >= grid.size() || y < 0 || y >= grid[0].size()) {
return;
}
if (grid[x][y] == 0) {
return;
}
grid[x][y] = 0;
str += dir;
dfs(grid, x + 1, y, str, "1");
dfs(grid, x - 1, y, str, "2");
dfs(grid, x, y + 1, str, "3");
dfs(grid, x, y - 1, str, "4");
str += "-";
str += dir;
}
int numDistinctIslands(vector<vector<int>>& grid) {
int column = grid.size();
int line = grid[0].size();
set<string> st1;
for (int i = 0; i < column; i++) {
for (int j = 0; j < line; j++) {
if (grid[i][j] == 1) {
string str = "";
dfs(grid, i, j, str, "0");
st1.insert(str);
}
}
}
return st1.size();
}
};
2.橘子腐烂问题
class Solution {
public:
int orangesRotting(vector<vector<int>>& grid) {
if (grid.size() == 0) {
return 0;
}
// 1, 遍历当前数组中的腐烂句子
for (int i = 0; i < grid.size(); ++i) {
for (int j = 0; j < grid[i].size(); ++j) {
if (grid[i][j] == 2)
dfs(grid, i, j, 2);
}
}
// 2, 计算形成最后的画面需要的最长时间
int maxTime = 0;
for (int i = 0; i < grid.size(); ++i) {
for (int j = 0; j < grid[i].size(); ++j) {
if (grid[i][j] == 1) {
return -1;
} else {
maxTime = max(maxTime, grid[i][j]);
}
}
}
return maxTime == 0 ? 0 : maxTime - 2;
}
void dfs(vector<vector<int>>& grid, int x, int y, int time) {
// 基本定界
if (x < 0 || x >= grid.size() || y < 0 || y >= grid[0].size()) {
return;
}
// 如果grid不是新鲜橘子而且到这里腐烂的时间<time,说明之前已经遍历过了
if (grid[x][y] != 1 && grid[x][y] < time) {
return;
}
grid[x][y] = time;
dfs(grid, x + 1, y, time + 1);
dfs(grid, x - 1, y, time + 1);
dfs(grid, x, y + 1, time + 1);
dfs(grid, x, y - 1, time + 1);
}
};
3. 剑指 Offer 34. 二叉树中和为某一值的路径
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
vector<vector<int>> ret;
void dfs(TreeNode* root, vector<int>&vec, int target) {
if (!root) {
return;
}
vec.push_back(root->val);
if (!root->left && !root->right && root->val == target) {
ret.push_back(vec);
/*
1, return; 这里是不能直接return的,需要将后面的数据pop出来。
2, 这里不能直接pop,因为缩窄了pop的范围
*/
}
dfs(root->left, vec, target - root->val);
dfs(root->right, vec, target - root->val);
vec.pop_back();
}
vector<vector<int>> pathSum(TreeNode* root, int target) {
vector<int>vec;
dfs(root, vec, target);
return ret;
}
};
4. 257. 二叉树的所有路径
根节点-叶子节点
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
vector<string>ret;
void dfs(TreeNode* root, string str) {
if (!root) {
return;
}
str += to_string(root->val);
// 叶子节点,将str推入ret中,否则 str+->进行递归
if (!root->left && !root->right) {
ret.push_back(str);
} else {
str += "->";
dfs(root->left, str);
dfs(root->right, str);
}
}
vector<string> binaryTreePaths(TreeNode* root) {
dfs(root, "");
return ret;
}
};
5. 257. 二叉树最大路径和
不设定起始位置
/**
* struct TreeNode {
* int val;
* struct TreeNode *left;
* struct TreeNode *right;
* };
*/
class Solution {
public:
/**
*
* @param root TreeNode类
* @return int整型
dfs:返回当前子树能向父节点所能贡献的最大值
有三种情况
1,只有当前节点
2,当前节点和左子树
3,当前节点和右子树
对应的前面三种选择,选择最大的
*/
int ret = INT_MIN;
int dfs(TreeNode* root) {
if (!root) {
return 0;
}
int left = max (dfs(root->left), 0);
int right = max (dfs(root->right), 0);
ret = max(ret, root->val + left + right);
return root->val+max(left,right);
}
int maxPathSum(TreeNode* root) {
// write code here
if(!root) {
return 0;
}
dfs(root);
return ret;
}
};
6. 根节点到叶子节点之和
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
int ret = 0;
void dfs(TreeNode* root, int sum) {
if (!root) {
return;
}
sum = sum * 10 + root->val;
if (!root->left && !root->right) {
ret += sum;
return;
}
dfs(root->left, sum);
dfs(root->right, sum);
}
int sumNumbers(TreeNode* root) {
dfs(root, 0);
return ret;
}
};
三、回溯
全排列
class Solution {
vector<vector<int>> ret;
public:
void dfs(vector<int>& nums, int s) {
if (s == nums.size()) {
ret.push_back(nums);
return;
}
for(int i = s; i < nums.size(); i++) {
swap(nums[i], nums[s]);
dfs(nums, s + 1);
swap(nums[i], nums[s]);
}
}
vector<vector<int>> permute(vector<int>& nums) {
dfs(nums, 0);
return ret;
}
};
括号生成
class Solution {
public:
vector<string> ret;
void dfs(int left, int right, string out) {
// 递归终止条件(剩余左括号个数>剩余右括号个数)
if (left > right) {
return;
}
// 获取一条正确的路径
if (left == 0 && right == 0) {
ret.push_back(out);
return;
}
// 左右递归。
if (left > 0) {
dfs(left - 1, right, out + "(");
}
if (right > 0) {
dfs(left, right - 1, out + ")");
}
}
vector<string> generateParenthesis(int n) {
dfs(n, n, "");
return ret;
}
};
子集
class Solution {
public:
vector<vector<int>> ret;
vector<vector<int>> subsets(vector<int>& nums) {
vector<int>path;
dfs(nums, 0, path);
return ret;
}
void dfs(vector<int>& nums, int start, vector<int> &path) {
ret.push_back(path);
for (int i = start; i < nums.size(); i++) {
path.push_back(nums[i]);
// 上面path push了nums[i],下面就是从i + 1 开始了。
dfs(nums, i + 1, path);
path.pop_back();
}
}
};
[路径总和] 从root到叶子节点
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
vector<vector<int>> ret;
void dfs(TreeNode* root, vector<int> &path, int targetSum) {
if (!root) {
return;
}
path.push_back(root->val);
targetSum -= root->val;
if (!root->left && !root->right && targetSum == 0) {
ret.push_back(path);
// return; 这里不能return,因为要pop
}
dfs(root->left, path, targetSum);
dfs(root->right, path, targetSum);
path.pop_back();
}
vector<vector<int>> pathSum(TreeNode* root, int targetSum) {
vector<int>vec;
dfs(root, vec, targetSum);
return ret;
}
};
/*
0-1背包问题,找nums中和为cnt的个数
(sum−neg)−neg=sum−2⋅neg=target
所以sum - target一定是偶数
*/
class Solution {
public:
int findTargetSumWays(vector<int>& nums, int target) {
int len = nums.size(), sum = 0;
for (int i = 0; i < len; i++) {
sum += nums[i];
}
int diff = sum - target;
if (diff < 0 || diff & 2 != 0) { // sum - target一定是偶数
return 0;
}
int neg = diff / 2;
vector<vector<int>> dp (len + 1, vector<int>(neg + 1, 0));
dp[0][0] = 1;
for(int i = 1; i <= len; i++) {
for (int j = 0; j <= neg; j++) {
dp[i][j] = dp[i-1][j];
if (j >= nums[i-1]) {
dp[i][j] += dp[i-1][j - nums[i-1]];
}
}
}
return dp[len][neg];
}
};
39. 组合总和
class Solution {
public:
vector<vector<int>> ret;
void dfs(vector<int>& candidates, vector<int> &path, int index, int target) {
if (target < 0) {
return;
}
if (target == 0) {
ret.push_back(path);
}
for (int i = index; i < candidates.size(); i++) {
if (candidates[i] > target) {
continue;
}
path.push_back(candidates[i]);
dfs(candidates, path, i, target - candidates[i]);
path.pop_back();
}
}
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
vector<int>vec;
dfs(candidates, vec, 0, target);
return ret;
}
};
从M中找到N个数的全排列问题
vector<vector<int>>res;
vector<vector<int>> combine(int n, int k) {
if (k <= 0 || n <= 0) {
return res;
}
vector<int> track;
backtrack(n, k, 1, track);
return res;
}
void backtrack(int n, int k, int start, vector<int>& track) {
if(track.size() == k) {
res.push_back(track);
return;
}
for (int i = start; i < n; i++) {
track.push_back(i);
backtrack(n, k, i + 1, track);
track.pop_back();
}
}
有重复子数组的全排列
// 回溯
class Solution {
public:
vector<vector<int>> res;
vector<vector<int>> permuteUnique(vector<int>& nums) {
sort(nums.begin(), nums.end());
vector<int> temp;
vector<bool> visited(nums.size(), false); // 标记访问
DFS(temp, nums, visited);
return res;
}
void DFS(vector<int>& temp, vector<int>& nums, vector<bool>& visited)
{
if(temp.size() == nums.size())
{
res.push_back(temp);
return;
}
for(int i = 0; i < nums.size(); ++i)
{
if(visited[i]) continue;
// 去重 重点是在去重
if(i != 0 && nums[i] == nums[i-1] && !visited[i - 1]) continue;
visited[i] = true;
temp.push_back(nums[i]);
DFS(temp, nums, visited);
temp.pop_back();
visited[i] = false; // 回溯
}
}
};