122. Best Time to Buy and Sell Stock II
很简单的一题,但我没能一眼看出... 记录这道题是想记录一个大概的思想,就是可以把一个连续的过程分为各个离散的易求的过程。这样说可能有点泛,意会吧。
class Solution {
public:
int maxProfit(vector<int>& prices) {
int ans = 0;
int n = prices.size();
for(int i=1;i<n;++i)
if(prices[i]>prices[i-1])
ans += prices[i]-prices[i-1];
return ans;
}
};
930. Binary Subarrays With Sum
求一串“01”序列中和为指定值的子串的个数。刷Leetcode后习惯性的想先思考时间复杂度为O(n)的解法,但自己想不到,只能学Discuss里别人的代码了。思路我感觉类似于滑动窗口。下面的题解很好,可以为我们往后解类似的题提供简明有效的思路。
class Solution {
public:
int numSubarraysWithSum(vector<int>& A, int S) {
unordered_map<int,int> cnt;
cnt[0]=1;//针对当前访问到的子串的和刚好为S的情况
int sum=0,ans=0;
//按顺序访问序列中的元素,把当前已经访问的子串分为两部分,子串的和为sum
//如果该子串的前后两部分有一部分的和为sum-S,则说明另一部分为S,即有子串的和为S
for(int a:A)
{
sum+=a;
ans+=cnt[sum-S];
cnt[sum]++;
}
return ans;
}
};
▲对于二叉树的三种遍历方式,我们可以很容易地写出递归版本,但是要写非递归版本却有一定难度,我觉得掌握二叉树遍历的非递归版本会加深我们对栈的使用理解。就是把递归版本的代码用stack表示出来,因为递归其实就是用了计算机里面的栈。
Leetcode上的相关题目:94.中序遍历,144.先序遍历,145.后序遍历
中序遍历:把节点压栈直到当前节点没有左子节点,则当前节点就是要按中序遍历要访问到的节点。
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
//判断节点是否已经访问或者待访问
//因为中序遍历是先访问左子树再访问右子树,所以一个节点会被访问两次
//如果没有加这个判断,会陷入无限循环。
unordered_set<TreeNode*> inset;
if(root==NULL)
return (vector<int>) {};
vector<int> inorder;
stack<TreeNode* > s;
s.push(root);
inset.insert(root);
while(!s.empty())
{
TreeNode* cur = s.top();
while(cur->left&&inset.find(cur->left)==inset.end())
{
s.push(cur->left);
inset.insert(cur->left);
cur = cur->left;
}
TreeNode* top = s.top();
s.pop();
inorder.push_back(top->val);
if(top->right)
s.push(top->right),inset.insert(top->right);
}
return inorder;
}
};
先序遍历:先序遍历和中序遍历都相对简单一点,复现了递归版本的思路。下面的代码可能是我学习别人的代码,比我按自己的想法【如上面的中序遍历】写出来的要简明一点。
vector<int> preorderTraversal(TreeNode* root) {
vector<int> preorder;
stack<TreeNode*> s;
while(root!=NULL || !s.empty())
{
while(root!=NULL)
{
preorder.push_back(root->val);
s.push(root);
root=root->left;
}
root = s.top();
s.pop();
root = root->right;
}
return preorder;
}
后序遍历:比较难实现,下面给出三种解法,第一种是比较常规的,后两种是在Discuss里看到的别人的解法。
class Solution {
public:
vector<int> postorderTraversal(TreeNode* root)
{
//严格依照:左子树-右子树-当前节点 去设计算法。
vector<int> postorder;
stack<TreeNode*> s;
while(true)
{
while(root!=NULL) //访问左子树
s.push(root),root=root->left;
//下面的root,一开始为NULL(由上面的while可得),
//判断当前节点是不是叶子节点,是的话就直接存进输出序列
//因为此时对于当前节点来说,其左子树已经访问过,
//如果右子树的节点可以存进输出序列,也就说明右子树也访问完成
//那么接下来就是把当前节点存进输出序列了。
//(或许可以借助一棵只有右子树的链式的二叉树去理解)
while(!s.empty()&&s.top()->right==root)
{
root = s.top();
postorder.push_back(root->val);
s.pop();
}
if(s.empty())
break;
root = s.top()->right; //访问右子树,然后继续按照左-右-当前的次序
}
return postorder;
}
};
class Solution {
public:
vector<int> postorderTraversal(TreeNode* root)
{
//后序遍历先访问了当前节点的左子树和右子树:访问完左子树后回到当前节点(1),
//然后接着访问右子树,访问完后回到当前节点(2),这时候当前节点的元素存进后序遍历的序列
//所以我们每次将一个节点重复压栈两次,某个节点第二次出栈时说明访问到它了。
vector<int> postorder;
stack<TreeNode*> s;
s.push(root);
s.push(root);
while(!s.empty())
{
root = s.top();
s.pop();
if(!s.empty()&&root==s.top())
{
//栈的性质是后进先出,所以先压右子树再压左子树
if(root&&root->right)
s.push(root->right),s.push(root->right);
if(root&&root->left)
s.push(root->left),s.push(root->left);
}
else//第二次访问
if(root!=NULL)
postorder.push_back(root->val);
}
return postorder;
}
};
▲第三种解法,利用了一个性质【后序遍历等于:当前节点—右子树—左子树顺序访问,然后将结果反转,相当于变形的先序遍历】,下面这种解法稍作修改就是先序遍历的解法了。
vector<int> postorderTraversal(TreeNode *root)
{
if(root==NULL)
return {};
stack<TreeNode*> st;
vector<int> ans;
st.push(root);
while(!st.empty())
{
TreeNode* cur= st.top();
ans.push_back(cur->val);
st.pop();
if(cur->left)
st.push(cur->left);
if(cur->right)
st.push(cur->right);
}
reverse(ans.begin(),ans.end());
return ans;
}
另外还有树的层次遍历(102. Binary Tree Level Order Traversal),我一般都是借助队列用BFS一层一层的记录,很常规很好写代码。
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int>> ans;
queue<TreeNode*> q;
if(root)
q.push(root);
while(!q.empty())
{
int size = q.size(); //每一层的节点数
queue<TreeNode*> temp;
vector<int> t;
while(size--)
{
TreeNode* cur = q.front();
q.pop();
t.push_back(cur->val);
if(cur->left)
temp.push(cur->left);
if(cur->right)
temp.push(cur->right);
}
q = temp;
ans.push_back(t);
}
return ans;
}
};
二叉树的层次遍历也可以用DFS,递归的去访问每一个节点,同时记录该节点所在的层数,然后将相同层上的节点存在同一个数组里,所以这一般要用一个二维数组来存储整棵树的遍历结果。
124. Binary Tree Maximum Path Sum
求二叉树中节点值的和最大的一条路径,只要得到最大和就好。
所有节点都必须访问,答案所涉及的节点可能是某个节点以及其左子节点组成的一条链,或者是右子节点组成的一条链,或者涉及了某个节点及其左右子树上的节点。所以思路是一条路走到底,对每个节点,先计算其左右子树的最大和路径,再做比较得到答案。
class Solution {
public:
int dfs(TreeNode* root,int& ans)
{
if(root==NULL)
return 0;
int left = dfs(root->left,ans),right = dfs(root->right,ans),v = root->val;
int maxlen = max(v,max(v+left,v+right));//因为节点值可能为负数,所以要这样判断
ans = max(ans,max(maxlen,v+left+right));
return maxlen;
}
int maxPathSum(TreeNode* root) {
int ans = INT_MIN;
dfs(root,ans);
return ans;
}
};
814. Binary Tree Pruning
二叉树剪枝,删去不包含节点值为1的所有子树。需要从叶子节点开始判断、操作,层层往上,所以可以用DFS,自底向上剪枝。
发现一些关于二叉树的题,其实或许可以看作是树的三种遍历方式的变形,对该题,则是后序遍历的变形:左子树—右子树—当前节点是否要删除。
class Solution {
public:
TreeNode* pruneTree(TreeNode* root)
{
if(root==NULL)
return NULL;
root->left = pruneTree(root->left);
root->right = pruneTree(root->right);
if(root->left==NULL && root->right==NULL && root->val==0)
{
delete root;
root=NULL;
}
return root;
}
};
823. Binary Trees With Factors
给定一组存放整型数的序列,这些序列里面每个数字可以多次使用,用这些数能构成多少棵二叉树,这些二叉树满足数的每一个非叶子节点的值是其左右子节点的值的乘积。
解题要明白两点:1、数值越大越有成为根节点的可能;2、不需要判断是否有a*b=c且a,b,c都在序列中,只序列判断是否有a%b==0且a,b,a/b在序列中即可。
class Solution {
public:
int numFactoredBinaryTrees(vector<int>& A) {
long modulo = pow(10,9)+7;
long ans = 0;
sort(A.begin(),A.end());//越大的数越有作为根节点的可能,先排序
unordered_map<int,long> dp; //表示以 int作为根节点可以有多少棵子树 long
for(int i=0;i<A.size();++i)
{
dp[A[i]]=1; //所有点都可以作为只有一个节点的树
for(int j=0;j<i;++j)
{
if(A[i]%A[j]==0&&dp.count(A[i]/A[j]))
//dp[A[j]]*dp[A[i]/A[j] : 排列组合的知识
//这里不用×2,因为如果a*b=c,那么我们遍历所有小于c的数时一定会分别算到a和b
dp[A[i]] = (dp[A[i]]+dp[A[j]]*dp[A[i]/A[j]])%modulo;
}
}
for(auto d:dp)
ans = (ans+d.second)%modulo;
return ans;
}
};
401. Binary Watch
这题有点无聊,但提醒我对于一些可能发生的情况都是可列举的,数量不多的题,不用想得太复杂,直接考虑每一种可能发生的情况即可。不多说,既然提到了就附上我的解法,很直接。
class Solution {
public:
vector<string> readBinaryWatch(int num) {
vector<string> ans;
if(num<0||num>8)
return {};
for(int i=0; i<12; ++i)
{
int hour = i;
int sum = 0;
while(hour)
{
if(hour&1)
sum++;
hour=hour>>1;
}
for(int j=0; j<60; ++j)
{
int minute = j;
int cnt = 0;
while(minute)
{
if(minute&1)
cnt++;
minute=minute>>1;
}
if(sum+cnt==num)
{
string time;
time += to_string(i);
time+=':';
time+=j/10+'0';
time+=j%10+'0';
ans.push_back(time);
}
}
}
return ans;
}
};
859. Buddy Strings
给定两个字符串,判断是否其中一个字符串中两个字符交换后得到的新字符串会等于另一个字符串。
该题比较简单,重在分情况讨论,要明确哪些情况满足题目要求。通过这题,在往后做题时提醒自己分情况讨论的重要性。一道题目如果可以把有限的会发生的情况列出来,往往能够迎刃而解。
class Solution {
public:
bool buddyStrings(string A, string B) {
int ans = 0,bit=0;
bool two = false; //标记一个字符串中是否有两个相同的字符。
unordered_set<char> s;
if(A.size()!=B.size())
return false;
for(int i=0;i<A.size();++i)
{
if(s.count(A[i]))
two=true;
s.insert(A[i]);
if(A[i]==B[i])
bit++; //biyt最后用来判断是否仅交换了两个元素(题目要求)
ans+=A[i]-B[i]; //ans最后用来判断是否交换元素后两个字符串相等
}
return ((!ans)&&(bit==A.size()-2))||(A==B&&two);
}
};