122.买卖股票的最佳时机II
- 确定dp数组(dp table)以及下标的含义
dp[i][j] j=0 不持有股票
j=1持有股票
i:第i天
dp[i][j]:第i天,持有状态为j时的最大现金 - 确定递推公式
dp[i][0]=max(dp[i-1][0], dp[i-1][1]+prices[i]);
第i天不持有股票,那可以是前一天持有股票然后卖出去了,和前一天就不持有股票
dp[i][1]=max(dp[i-1][1],dp[i-1][0]-prices[i];
第i天持有股票,可以是前一天就持有股票,或者前一天不持有,今天买入 - dp数组如何初始化
dp[0][0]=0;
dp[0][1]=-prices[0]; - 确定遍历顺序
从左到右,没啥好说的
for(int i=1;i<prices.size();i++){
dp[i][0]=max(dp[i-1][0], dp[i-1][1]+prices[i]);
dp[i][1]=max(dp[i-1][1],dp[i-1][0]-prices[i]);
}
- 举例推导dp数组
最后代码如下:
class Solution {
public:
int maxProfit(vector<int>& prices) {
vector<vector<int>> dp(prices.size(),vector<int>(2,0));
dp[0][0]=0;
dp[0][1]=-prices[0];
for(int i=1;i<prices.size();i++){
dp[i][0]=max(dp[i-1][0], dp[i-1][1]+prices[i]);
dp[i][1]=max(dp[i-1][1],dp[i-1][0]-prices[i]);
}
return dp[prices.size()-1][0];
}
};
93.复原ip地址
相当于分割字符串,而且要返回的是所有的分割方法
那就是回溯了
分割问题可以使用回溯搜索法把所有可能性搜出来
- 确定递归参数
需要一个全局变量的vector<string>保存所有的回溯结果
需要字符串s
因为不能重复分割,所以需要一个startIndex
还需要一个记录当前是第几个分割点的pointNum,因为只能分割成4段
不需要递归结束后计算什么,所以不需要返回值
vector<string> result;
void backtracking(string& s, int startIndex,int pointNum)
- 确定递归终止条件
一共只能切成四段,所以pointNum==3的时候,就会终止递归。不过此时还得判断一下最后一段是否合法,合法才能加进结果集
if(pointNum==3)
{
if(isValid(s,startIndex,s.size()-1))
result.push_back(s);
}
- 单层搜索的逻辑
判断[startIndex, i]是否合法,合法就加上’.‘,分割该字串,然后递归下一层,因为字符串加上了一个’.',pointNum++,所以下一个递归的startIndex应该从i+2开始。不合法就结束本层
for(int i=startIndex;i<s.size();i++)
{
if(isValid(s,startIndex,i)){
s.insert(s.begin()+i+1,'.');
pointNum++;
backtracking(s,i+2,pointNum);
pointNum--;
s.erase(s.begin()+i+1);
}else break;
接下来写一下如何判断字符串合法
有效 IP 地址 正好由四个整数(每个整数位于 0 到 255 之间组成,且不能含有前导 0)
那就是s[start]=='0’且start!=end,false
s[i] > ‘9’ || s[i] < ‘0’ false
num>255 false
bool isValid(string s,int start, int end)
{
if(s[start]=='0'&& start != end)
return false;
int num=0;
for(int i=start;i<=end;i++){
if(s[i] > '9' || s[i] < '0')
return false;
num = num * 10 + (s[i] - '0');
if (num > 255) { // 如果大于255了不合法
return false;
}
}
return true;
}
不过还存在另一种不合法的情况
当s="101023"时,我的代码输出了[“1.0.10.23”,“1.0.102.3”,“10.1.0.23”,“10.10.2.3”,“10.10.23.”,“10.102.3.”,“101.0.2.3”,“101.0.23.”]
而预期结果是[“1.0.10.23”,“1.0.102.3”,“10.1.0.23”,“10.10.2.3”,“10.10.23.”,“10.102.3.”,“101.0.2.3”,“101.0.23.”]
可以看到我的代码在字符串结尾加了分割
这是因为回溯的时候,i+2有可能大于s.size()
所以要判定一下start和end的大小
bool isValid(string s,int start, int end)
{
if(start>end)
return false;
if(s[start]=='0'&& start != end)
return false;
int num=0;
for(int i=start;i<=end;i++){
if(s[i] > '9' || s[i] < '0')
return false;
num = num * 10 + (s[i] - '0');
if (num > 255) { // 如果大于255了不合法
return false;
}
}
return true;
}
整体代码:
class Solution {
public:
bool isValid(string s,int start, int end)
{
if (start > end) {
return false;
}
if(s[start]=='0'&& start != end)
return false;
int num=0;
for(int i=start;i<=end;i++){
if(s[i] > '9' || s[i] < '0')
return false;
num = num * 10 + (s[i] - '0');
if (num > 255) { // 如果大于255了不合法
return false;
}
}
return true;
}
vector<string> result;
void backtracking(string& s, int startIndex, int pointNum) {
if (pointNum == 3) {
if (isValid(s, startIndex, s.size() - 1))
result.push_back(s);
return;
}
for (int i = startIndex; i < s.size(); i++) {
if (isValid(s, startIndex, i)) {
s.insert(s.begin() + i + 1, '.');
pointNum++;
backtracking(s, i + 2, pointNum);
pointNum--;
s.erase(s.begin() + i + 1);
} else {
break;
}
}
}
vector<string> restoreIpAddresses(string s) {
backtracking(s,0,0);
return result;
}
};
96.不同的二叉搜索树
要返回满足题意的二叉搜索树种类数
同时呢,n个节点的二叉搜索树的构建可以由n-1个节点的二叉搜索树推导出来
所以是DP
-
确定dp数组(dp table)以及下标的含义
如图,头节点为1时,其左子树节点数量为0,右子树节点数量为2,节点数为0有1种构建方式,节点数为2有两种构建方式,所以头节点为1时,有1*2种构建方式
那么我们可以令dp[i]为当节点数为i时,有几种不同的构建方式 -
确定递推公式
dp[i]+=dp[j-1]*dp[i-j],j-1 为j为头结点左子树节点数量,i-j 为以j为头结点右子树节点数量
- dp数组如何初始化
节点数为0有1种构建方式
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];
- 举例推导dp数组
最后代码如下:
class Solution {
public:
int numTrees(int n) {
vector<int> dp(n+1,0);
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];
}
};
95.不同的二叉搜索树II
这道题要返回所有的二叉搜索树了,那就是回溯了
涉及到树,后序遍历
- 确定递归参数
因为要对返回的左右子树的结果构造二叉平衡树,所以需要返回值,返回值为当前构造的树的根节点的集(因为可以有多个节点都可做当前子树的根节点
因为要构建左右子树,且是平衡二叉树,所以从[left,right]中选数字i作为根节点,[left,i-1]就是左子树可选节点,[i+1,right]就是右子树可选节点
vector<TreeNode*> backtracking(int left,int right)
- 回溯函数终止条件
当left>right时,就没法产生子树了,也就返回空节点
if(left>right)
{
ans.push_back(nullptr);
return ans;
}
- 回溯搜索的遍历过程
在[left,right]中遍历每一个i,作为根节点
先得到左右子树根节点的候选集
vector<TreeNode*> left_nodes=backtracking(left,i-1);
vector<TreeNode*> right_nodes=backtracking(i+1,right);
i左边的序列可以作为左子树结点,且左儿子可能有多个,所以有vector<TreeNode *> left_nodes = generate(left, i - 1);;
i右边的序列可以作为右子树结点,同上所以有vector<TreeNode *> right_nodes = generate(i + 1, right);;
因此,产生的以当前i为根结点的子树有left_nodes.size() * right_nodes.size()个,遍历每种情况,即可生成以i为根节点的子树序列;
for(TreeNode* left_node:left_nodes)
for(TreeNode* right_node:right_nodes)
{
TreeNode* tmp=new TreeNode(i);
tmp->left=left_node;
tmp->right=right_node;
ans.push_back(tmp);
}
即:
for(int i=left;i<=right;i++){
vector<TreeNode*> left_nodes=backtracking(left,i-1);
vector<TreeNode*> right_nodes=backtracking(i+1,right);
for(TreeNode* left_node:left_nodes)
for(TreeNode* right_node:right_nodes)
{
TreeNode* tmp=new TreeNode(i);
tmp->left=left_node;
tmp->right=right_node;
ans.push_back(tmp);
}
}
整理得到整个代码:
class Solution {
public:
vector<TreeNode*> backtracking(int left,int right){
vector<TreeNode *> ans;
if(left>right)
{
ans.push_back(nullptr);
return ans;
}
for(int i=left;i<=right;i++){
vector<TreeNode*> left_nodes=backtracking(left,i-1);
vector<TreeNode*> right_nodes=backtracking(i+1,right);
for(TreeNode* left_node:left_nodes)
for(TreeNode* right_node:right_nodes)
{
TreeNode* tmp=new TreeNode(i);
tmp->left=left_node;
tmp->right=right_node;
ans.push_back(tmp);
}
}
return ans;
}
vector<TreeNode*> generateTrees(int n) {
return backtracking(1,n);
}
};