文章目录
101. 对称二叉树
题意
- 给定一棵二叉树,判断它是否轴对称
- 轴对称:左孩子 = 右孩子
- 左孩子 = 右孩子:
- 左孩子的左孩子 = 右孩子的右孩子;
- 左孩子的右孩子 = 右孩子的左孩子。
解法1 递归
给定一棵二叉树:
- 若
root==NULL
,则轴对称;否则比较root->left
和root->right
; - 检查
root->left
是否等于root->right
:- 若
left==right==NULL
,则轴对称; - 若
left==NULL || right==NULL
,则非轴对称;(注意,由于先判断的第一条,所以这里只要有一个为NULL,则说明非轴对称;且此后的三条的前提都是left和right都不是NULL) - 比较
left->left
和right->right
; - 比较
left->right
和right->left
; - 比较
left->val
和right->val
。
- 若
/**
* 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:
bool check(TreeNode* left,TreeNode* right)
{
//左子树?=右子树
//左子树的左孩子?=右子树的右孩子
//左子树的右孩子?=右子树的左孩子
if(left==NULL&&right==NULL) return true;
if(left==NULL||right==NULL) return false;
return check(left->left,right->right)&&check(left->right,right->left)&&left->val==right->val;
}
bool isSymmetric(TreeNode* root) {
if(root==NULL) return true;
return check(root->left,root->right);
}
};
ATTENTION
- 树的很多判定都是递归实现的
解法2 迭代(队列)(待实现)
102. 二叉树的层序遍历
题意
- 获取二叉树的层次遍历
- 但是要根据层次分别存储每层的节点值
解法1 BFS广度优先搜索(维护相邻两层节点数)
要获取二叉树的层次遍历,最典型的就是基于队列:
- 将
root
加入到队列中 - 只要队列非空,每次从队列中获取一个节点(
front
&pop
),然后将其左孩子left
和右孩子right
都加入到队列中(如果孩子存在的话)。
为了按层获取节点,额外维护 pre_level_cnt
和 next_level_cnt
两个变量,分别记录上一层的节点数和下一层的节点数。
- 每一个节点从队列中
pop
出时,上一层的节点数pre_level_cnt
就减 1 ; - 每当一个孩子被
cur_level_cnt
就加 1。
/**
* 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>> levelOrder(TreeNode* root) {
int pre_level_cnt = 0;
vector<vector<int> > ans;
queue<TreeNode*> q;
vector<int> cur_level;
if(root)
{
q.push(root);
pre_level_cnt=1;
}
int next_level_cnt = 0;
while(!q.empty())
{
if(pre_level_cnt==0)
{
pre_level_cnt=next_level_cnt;
next_level_cnt=0;
ans.push_back(cur_level);
cur_level.clear();
}
TreeNode* cur = q.front();
q.pop();
cur_level.push_back(cur->val);
pre_level_cnt--;
if(cur->left)
{
next_level_cnt+=1;
q.push(cur->left);
}
if(cur->right)
{
next_level_cnt+=1;
q.push(cur->right);
}
}
if(cur_level.size()!=0)
ans.push_back(cur_level);
return ans;
}
};
解法2 BFS 广度优先搜索
修改后的广度优先搜索:
root
入队- 当队列不为空时:
- 求当前队列的长度
si
- 依次从队列中取出
si
个元素进行拓展,然后进行下一次迭代
- 求当前队列的长度
注意代码中的 for 循环,虽然还是 pop 一个父节点,push 两个孩子,但是,当一轮循环结束时,上一层的节点已经全部被 pop 出了,而下一层的节点此时全在队列中,因此,队列的长度就是当前层的节点个数。
//作者:LeetCode-Solution
//链接:https://leetcode.cn/problems/binary-tree-level-order-traversal/solution/er-cha-shu-de-ceng-xu-bian-li-by-leetcode-solution/
//来源:力扣(LeetCode)
//著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
vector <vector <int>> ret;
if (!root) {
return ret;
}
queue <TreeNode*> q;
q.push(root);
while (!q.empty()) {
int currentLevelSize = q.size();
ret.push_back(vector <int> ());
for (int i = 1; i <= currentLevelSize; ++i) {
auto node = q.front(); q.pop();
ret.back().push_back(node->val);
if (node->left) q.push(node->left);
if (node->right) q.push(node->right);
}
}
return ret;
}
};
// 每层单独建立一个vector变量存储
/**
* 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>> levelOrder(TreeNode* root) {
vector< vector<int>> ret;
if(!root) return ret;
queue<TreeNode*> q;
q.push(root);
while(!q.empty())
{
int cur_level_cnt=q.size();
vector<int> cur_level;
for(int i=1;i<=cur_level_cnt;i++)
{
TreeNode* cur=q.front();
q.pop();
cur_level.push_back(cur->val);
if(cur->left) q.push(cur->left);
if(cur->right) q.push(cur->right);
}
ret.push_back(cur_level);
}
return ret;
}
};
104. 二叉树的最大深度
题意
- 计算二叉树的最大深度
解法1 DFS深度优先搜索(递归实现)
root
节点的最大深度 = max(左孩子的最大深度,右孩子的最大深度) + 1
/**
* 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 maxDepth(TreeNode* root) {
if(!root) return 0;
return max(maxDepth(root->left),maxDepth(root->right))+1;
}
};
解法2 BFS广度优先搜索(队列实现)
二叉树的最大深度 即 二叉树的层数。基于层次遍历,获取层数。
/**
* 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 maxDepth(TreeNode* root) {
queue<TreeNode*> q;
if(!root) return 0;
q.push(root);
int ans=0;
while(!q.empty())
{
int cur_level_cnt=q.size();
for(int i=1;i<=cur_level_cnt;i++)
{
TreeNode* cur=q.front();
q.pop();
if(cur->left) q.push(cur->left);
if(cur->right) q.push(cur->right);
}
ans++;
}
return ans;
}
};
105. 从前序与中序遍历序列构造二叉树
题意
- 根据前序遍历和中序遍历构造二叉树
- 前序遍历:根左右,
[ 根节点, [左子树的前序遍历结果], [右子树的前序遍历结果] ]
- 中序遍历:左根右,
[ [左子树的中序遍历结果], 根节点, [右子树的中序遍历结果] ]
解法1 自己写的(逻辑不够清晰)
主要想法没有问题:
- 维护以每个节点为根的
preorder
和inorder
- 首先得到
root->val
(preorder
的第一个值) - 然后在
inorder
中寻找root->val
,根据root_idx
可以判断该节点有没有左孩子和右孩子 - 如果有左孩子/右孩子,分别维护孩子的
preorder
和inorder
,继续递归。
/**
* 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:
TreeNode* subTree(vector<int>& preorder,vector<int>& inorder,int p_left,int p_right,int i_left,int i_right)
{
int root_val=preorder[p_left];
TreeNode* root=new TreeNode(root_val,NULL,NULL);
//找 inorder 中的根 root_idx
int root_idx=-1;
for(int i=i_left;i<=i_right;i++)
if(inorder[i]==root_val)
{
root_idx=i;
break;
}
int left_num=root_idx-i_left;
// 要判断有没有左右子树
if(root_idx==i_left) // 没有左子树
root->left=NULL;
else
root->left=subTree(preorder,inorder,p_left+1,p_left+left_num,i_left,root_idx-1);
if(root_idx==i_right) // 没有右子树
root->right=NULL;
else
root->right=subTree(preorder,inorder,p_left+left_num+1,p_right,root_idx+1,i_right);
return root;
}
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
return subTree(preorder,inorder,0,preorder.size()-1,0,inorder.size()-1);
}
};
解法2 官方解答
对于每一棵子树,首先判断子树是否存在,如果存在,再进行构造,否则直接返回 NULL
。
//作者:LeetCode-Solution
//链接:https://leetcode.cn/problems/construct-binary-tree-from-preorder-and-inorder-traversal/solution/cong-qian-xu-yu-zhong-xu-bian-li-xu-lie-gou-zao-9/
//来源:力扣(LeetCode)
//著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
class Solution {
private:
unordered_map<int, int> index;
public:
TreeNode* myBuildTree(const vector<int>& preorder, const vector<int>& inorder, int preorder_left, int preorder_right, int inorder_left, int inorder_right) {
if (preorder_left > preorder_right) {
return nullptr;
}
// 前序遍历中的第一个节点就是根节点
int preorder_root = preorder_left;
// 在中序遍历中定位根节点
int inorder_root = index[preorder[preorder_root]];
// 先把根节点建立出来
TreeNode* root = new TreeNode(preorder[preorder_root]);
// 得到左子树中的节点数目
int size_left_subtree = inorder_root - inorder_left;
// 递归地构造左子树,并连接到根节点
// 先序遍历中「从 左边界+1 开始的 size_left_subtree」个元素就对应了中序遍历中「从 左边界 开始到 根节点定位-1」的元素
root->left = myBuildTree(preorder, inorder, preorder_left + 1, preorder_left + size_left_subtree, inorder_left, inorder_root - 1);
// 递归地构造右子树,并连接到根节点
// 先序遍历中「从 左边界+1+左子树节点数目 开始到 右边界」的元素就对应了中序遍历中「从 根节点定位+1 到 右边界」的元素
root->right = myBuildTree(preorder, inorder, preorder_left + size_left_subtree + 1, preorder_right, inorder_root + 1, inorder_right);
return root;
}
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
int n = preorder.size();
// 构造哈希映射,帮助我们快速定位根节点
for (int i = 0; i < n; ++i) {
index[inorder[i]] = i;
}
return myBuildTree(preorder, inorder, 0, n - 1, 0, n - 1);
}
};
/**
* 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:
TreeNode* subTree(vector<int>& preorder,vector<int>& inorder,int p_left,int p_right,int i_left,int i_right)
{
//判断该子树有没有节点
if(p_left>p_right) return NULL;
int root_val=preorder[p_left];
TreeNode* root=new TreeNode(root_val,NULL,NULL);
//找 inorder 中的根 root_idx
int root_idx=-1;
for(int i=i_left;i<=i_right;i++)
if(inorder[i]==root_val)
{
root_idx=i;
break;
}
int left_num=root_idx-i_left;
root->left=subTree(preorder,inorder,p_left+1,p_left+left_num,i_left,root_idx-1);
root->right=subTree(preorder,inorder,p_left+left_num+1,p_right,root_idx+1,i_right);
return root;
}
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
return subTree(preorder,inorder,0,preorder.size()-1,0,inorder.size()-1);
}
};
114. 二叉树展开为链表
题解
解法1 根据前序遍历存储各节点,然后修改各节点指向
首先通过一次前序遍历 preOrder(root,ans)
将各节点存储在vector<TreeNode*> ans
中,然后ans
修改每个节点的左右孩子。
ATTENTION
- 若树非空,则
ans[0]
和root
指向同意内存空间。 - 必须在指针
p
指向root
后,再赋值q=p
才能使指针q
指向树。如果一开始赋值q=p=NULL
,再另p
指向root
,q
并不会随着p
的指向修改而修改,所以q
仍旧指向NULL
。
/**
* 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:
void preOrder(TreeNode* root,vector<TreeNode*>& ans)
{
if(!root)
{
return;
}
ans.push_back(root);
preOrder(root->left,ans);
preOrder(root->right,ans);
}
void flatten(TreeNode* root) {
if(!root) return ;
vector<TreeNode*> ans;
preOrder(root,ans);
// cout<<root<<endl;
// cout<<ans[0]<<endl;
TreeNode* p=root; // root 和 ans[0] 指向的是同一个内存单元 (cout 后证实)
TreeNode* q=p;
for(int i=1;i<ans.size();i++)
{
p->left=NULL;
p->right=ans[i];
p=p->right;
}
}
};
解法2 (另外建树,修改root
,但是输出发现 root
不能被修改)
root
,但是输出发现 root
不能被修改)同样地,首先根据前序遍历 preOrder(root,ans)
将各节点存储在vector<TreeNode*> ans
中,然后根据 ans
重新建树。但是 debug 发现新树建立正确,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:
void preOrder(TreeNode* root,vector<TreeNode*>& ans)
{
if(!root)
{
return;
}
ans.push_back(root);
preOrder(root->left,ans);
preOrder(root->right,ans);
}
// void preOrderInput(TreeNode* root)
// {
// if(!root)
// {
// cout<<"NULL,";
// return;
// }
// cout<<root->val<<",";
// preOrderInput(root->left);
// preOrderInput(root->right);
// }
void flatten(TreeNode* root) {
if(!root) return ;
vector<TreeNode*> ans;
preOrder(root,ans);
TreeNode* p=new TreeNode(root->val,NULL,NULL);
TreeNode* q=p;
for(int i=1;i<ans.size();i++)
{
p->right=new TreeNode(ans[i]->val,NULL,NULL);
// cout<<p->val<<" "<<p->right->val<<endl;
p=p->right;
}
// cout<<root<<endl;
root=q;
// cout<<root<<endl;
// preOrderInput(q);
}
};
// input
[1,2,5,3,4,null,6]
// expected output
[1,null,2,null,3,null,4,null,5,null,6]
// output
[1,2,5,3,4,null,6]
// stdout
1 2
2 3
3 4
4 5
5 6
0x603000000040
0x603000000190
1,NULL,2,NULL,3,NULL,4,NULL,5,NULL,6,NULL,NULL,
121. 买卖股票的最佳时机
题意
- 选择一天买入,并选择在未来的某一天卖出
- 求最大利润
- 如果不能获取利润,则返回 0
解法1 动态规划
维护 dp[i]
,表示在前 i
天买入的最低价格,所以有dp[i]=min(dp[i-1],prices[i])
。初始状态:dp[0]=0
(表示在第 0 天以前买入的最低价为 0 )。
假设你想在第 i
天卖出这只股票,那么只有在前 i-1
天的最低价时买入这只股票,利润才是最大的,所以有ans=max(ans,prices[i]-dp[i-1])
。
首先判断临界条件,即,当prices.size()==1
时,此时定不能获取利润,所以直接return 0
。
然后从第 1 天开始,更新维护ans
和dp[i]
。
最后直接retrurn ans
即可。
class Solution {
public:
int maxProfit(vector<int>& prices) {
int n=prices.size();
if(n==1) return 0;
vector<int> dp(n);
dp[0]=prices[0];
int ans=0;
for(int i=1;i<n;i++)
{
ans=max(ans,prices[i]-dp[i-1]);
dp[i]=min(dp[i-1],prices[i]);
}
ans=max(ans,prices[n-1]-dp[n-2]);
return ans;
}
};
124. 二叉树中的最大路径和
题意
- 在树中寻找一条权重最大的路径。
- 路径可以从树的任意一个节点开始,即,路径不一定从根节点开始。
- 路径至少包含一个节点。
- 一个节点不可被重复经过。
解法1 递归
一个路径上的节点 root
,要么是起点,要么是中间点:
- 如果是起点,那么这条路径应该通向以
root
为根的子树; - 如果是中间点,那么应该是
root->left , root , root->right
;
考虑函数maxGin(TreeNode* root)
,计算每个节点的两种情况:
- 若
root
是起点,那么这条路径上的下一个点应该是root
的左孩子和右孩子中maxGain
更大的那个点,所以有return root->val+max(left_gain,right_gain)
; - 若 root 是中间点,那么维护
ans=max(ans,left_gain+root->val_right_gain)
;
为了能够在计算每个节点的 gain
时直接获得路径的最大权重,不用再次遍历树,维护一个全局变量 ans
,初始值为 INT_MIN
,并且在遍历到每个节点的时候,直接更新。
/**
* 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 {
private:
int ans=-1e9;
public:
int maxGain(TreeNode* root)
{
if(!root) return 0;
int l_gain=max(0,maxGain(root->left));
int r_gain=max(0,maxGain(root->right));
ans=max(ans,l_gain+root->val+r_gain);
return root->val+max(l_gain,r_gain);
}
int maxPathSum(TreeNode* root) {
maxGain(root);
return ans;
}
};
128. 最长连续序列
题意
- 给一个未排序的整数数组,返回一个数字连续的最长序列的长度
- 子序列在原数组中可以不连续
- 可以给数组排序!
- 要去重!因为在原数组中可以不连续!
解法1 sort + 队列
先给 nums
数组排序,然后去重!最后利用一个queue
,获得连续的最长序列的长度。
class Solution {
public:
int longestConsecutive(vector<int>& nums) {
int n=nums.size();
int ans=1;
if(n==0) return 0;
if(n==1) return ans;
sort(nums.begin(),nums.end());
// 要去重!
nums.erase(unique(nums.begin(),nums.end()),nums.end());
queue<int>q;
q.push(nums[0]);
for(int i=1;i<n;i++)
{
int tmp=q.back();
if(nums[i]==tmp+1)
{
q.push(nums[i]);
}
else
{
ans=ans<=q.size()?q.size():ans;
while(!q.empty()) q.pop();
q.push(nums[i]);
}
}
ans=ans<=q.size()?q.size():ans;
return ans;
}
};
ATTENTION
queue
常用函数q.front()
:返回队列的第一个元素q.back()
:返回队列的最后一个元素q.empty()
:判断队列是否为空,不会清空队列!- 清空队列:
while(!q.empty()) q.pop();
vector
数组去重nums.erase(unique(nums.begin(),nums.end()),nums.end());
vec.unique(start, end)
:从头到尾,判断当前元素是否等于上一个元素,将不重复的元素移到前面来(赋值操作),而不是将重复的元素移动到后面去。
136. 只出现一次的数字
题意
- 给定一个数组,除了一个数只出现一次,其他的数都会出现两次,找出那个只出现了一次的数。
解法 异或
异或 ^
:(妙哉)
a ^ a = 0
0 ^ a = a
class Solution {
public:
int singleNumber(vector<int>& nums) {
int ans=0;
for(auto num:nums)
{
ans^=num;
}
return ans;
}
};
139. 单词拆分
题意
- dp
解法1
class Solution {
public:
bool wordBreak(string s, vector<string>& wordDict) {
int n=s.size();
vector<bool> dp(n+1,false);
dp[0]=true;
for(int i=1;i<=n;i++)
{
for(int j=0;j<i;j++)
{
string tmp=s.substr(j,i-j);
if(dp[j]&&find(wordDict.begin(),wordDict.end(),tmp)!=wordDict.end())
{
dp[i]=true;
}
}
}
return dp[n];
}
};
141. 环形链表
题意
- 判断链表是否有环
解法1 哈希表
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
bool hasCycle(ListNode *head) {
map<ListNode*,bool> mp;
while(head!=NULL)
{
if(mp[head]==false)
{
mp[head]=true;
head=head->next;
}
else
{
return true;
}
}
return false;
}
};
解法2 快慢指针
如果无环,fast
指针一定比 slow
先到达 NULL
,又由于 fast
一次走两步,所以只需要判断fast==NULL||fast->next==NULL
。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
bool hasCycle(ListNode *head) {
if(head==NULL||head->next==NULL) return false;
// slow 、 fast 一定有非NULL值
ListNode* slow=head,*fast=head->next;
while(slow!=fast)
{
if(fast==NULL||fast->next==NULL) return false;
//if(slow==NULL||fast==NULL||fast->next==NULL) return false;
slow=slow->next;
fast=fast->next->next;
}
return true;
}
};
142. 环形链表 II
题意
- 无环返回
NULL
,有环返回入环口节点。
解法1 哈希表
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
map<ListNode*,int> mp;
while(head)
{
if(mp[head]==1)
{
return head;
}
else
{
mp[head]=1;
head=head->next;
}
}
return NULL;
}
};
解法2 快慢指针
在 slow
和 fast
相遇后,另外创建一个指针 cur
,使其从链表头开始走,与 slow
同速,cur
与 slow
会在入环口相遇。
和上一题不一样的是,slow
和 fast
起点相同。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
if(head==NULL||head->next==NULL) return NULL;
ListNode* slow=head,*fast=head;
bool flag=false;
while(slow!=fast||!flag)
{
flag=true;
if(fast==NULL||fast->next==NULL) return NULL;
slow=slow->next;
fast=fast->next->next;
}
ListNode* cur=head;
while(slow!=cur)
{
slow=slow->next;
cur=cur->next;
}
return cur;
// return NULL;
}
};
239. 滑动窗口最大值
题意
- 返回滑动窗口的最大值
解法1 优先队列
利用优先队列求得滑动窗口的最大值,注意在得到最大值时,需要 判断是否在滑动窗口内,若不在滑动窗口内,需要 pop()
。
class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
int n = nums.size();
priority_queue<pair<int, int> > q;
vector<int> ans;
for(int i = 0; i < n; i++)
{
q.emplace(nums[i], i); // 每次将值 push 到优先队列中,利用优先队列获取最大值
if(i >= k - 1) // 该最大值是否在滑动窗口范围内,不在则 pop(),直到找到在滑动窗口范围内的最大值
{
while(!q.empty() && q.top().second < i - k + 1)
{
q.pop();
}
ans.emplace_back(nums[q.top().second]); // 保存每个滑动窗口的最大值
}
}
return ans;
}
};
复杂度
时间复杂度:
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn),最坏的情况是 nums[]
本身就是一个递增序列,每 push 一个值的时间复杂度为
O
(
l
o
g
n
)
O(logn)
O(logn)。
空间复杂度:
O
(
n
)
O(n)
O(n),最坏的情况是 nums[]
本身就是一个递增序列,那么优先队列永远不会有值被 pop()
。
解法2 单调队列
对于当前滑动窗口中的两个数 nums[i]
和 nums[j]
来说,假设 nums[i] < nums[j] && i < j
,那么,在滑动窗口 右移 时,最大值绝不可能是 nums[i]
,因此,nums[i]
可以不保存。也就是说,当遇到一个比当前值大的值的时候,当前值可以弃置。
因此,引入单调递减栈,在滑动窗口右移时,如果当前值 nums[i] >= q.top()
,那么 q.pop()
,直到nums[i] < q.top()
。
但是,由于滑动窗口的左界也在更新,单调递减栈不能更新左界,所以引入单调递减队列,每次更新左界,若超出左界范围,则 q.pop_front()
。
class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
int n = nums.size();
deque<int> q;
vector<int> ans;
for(int i = 0; i < n; i++)
{
while(!q.empty() && nums[q.back()] < nums[i]) // 当遇到一个比当前值大的值的时候,当前值可以弃置
{
q.pop_back();
}
q.push_back(i);
if(i >= k - 1)
{
while(!q.empty() && q.front() < i - k + 1) // 判断左界
{
q.pop_front();
}
ans.emplace_back(nums[q.front()]);
}
}
return ans;
}
};
297. 二叉树的序列化与反序列化
题意
解法1 层次遍历
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Codec {
public:
// Encodes a tree to a single string.
string serialize(TreeNode* root) {
string res = "";
queue<TreeNode*> q;
if(root == nullptr) return res;
res += to_string(root->val) + ",";
q.push(root);
while(!q.empty())
{
TreeNode* cur = q.front();
q.pop();
if(cur->left != nullptr)
{
q.push(cur->left);
res += to_string(cur->left->val) + ",";
}
else
{
res += "null,";
}
if(cur->right != nullptr)
{
q.push(cur->right);
res += to_string(cur->right->val) + ",";
}
else
{
res += "null,";
}
}
res.pop_back();
return res;
}
vector<string> split(string s)
{
vector<string> res; // 此时 res.size() = 0
s += ",";
int idx = 0, pre = 0;
while(idx < s.size())
{
if(s[idx] == ',')
{
string tmp = s.substr(pre, idx - pre);
res.emplace_back(tmp);
pre = idx + 1;
}
idx++;
}
// for(auto s : res)
// cout<<s;
// cout<<endl;
return res;
}
// Decodes your encoded data to tree.
TreeNode* deserialize(string data) {
vector<string> val = split(data);
for(auto s : val)
cout<<s<<"*";
// cout<<endl;
if(val.size() == 0 || val[0] == "") return nullptr;
cout<<val[0].c_str()<<"&"<<endl;
TreeNode* root = new TreeNode(atoi(val[0].c_str()));
TreeNode* cur = root;
int curLayer = 0, preLayer = 1;
queue<TreeNode*> q;
q.push(cur);
int idx = 1;
while(idx < val.size())
{
for(int i = 0; i < preLayer; i++)
{
TreeNode* tmp = q.front();
q.pop();
if(val[idx] != "null")
{
tmp->left = new TreeNode(atoi(val[idx].c_str()));
q.push(tmp->left);
curLayer++;
}
if(val[idx + 1] != "null")
{
tmp->right = new TreeNode(atoi(val[idx + 1].c_str()));
q.push(tmp->right);
curLayer++;
}
idx+=2;
}
preLayer = curLayer;
curLayer = 0;
}
return root;
}
};
// Your Codec object will be instantiated and called as such:
// Codec ser, deser;
// TreeNode* ans = deser.deserialize(ser.serialize(root));
309. 买卖股票的最佳时机含冷冻期
题意
- 股票可以多次买入和卖出
- 计算可以获得的最大价值
- 股票在卖出后有一天冷冻期
解法 动态规划
设 dp[i]
表示第 i
天操作(买入/卖出/不操作)后,可以获得的最大利益。一共有三种状态,其中:
dp[i][0]
:在结束第i
天的操作后(买入/卖出/不操作),空仓,且非冷冻期- 第
i - 1
天就是空仓,且非冷冻期,第i
天不操作 - 第
i - 1
天是冷冻期,第i
天不操作 - 因此,
dp[i][0] = max(dp[i - 1][0], dp[i - 1][2])
- 第
dp[i][1]
:在结束第i
天的操作(买入/卖出/不操作)后,持股- 第
i - 1
天是空仓,且非冷冻期,第i
天买入 - 第
i - 1
天就持股,第i
天不操作 - 因此,
dp[i][1] = max(dp[i - 1][0] - prices[i], dp[i - 1][1])
- 第
dp[i][2]
:在结束第i
天的操作(买入/卖出/不操作)后,空仓,且冷冻期- 第
i - 1
天持股,第i
天卖出 - 因此,
dp[i][2] = dp[i - 1][1] + prices[i]
- 第
初始状态:
dp[0][0] = 0
dp[0][1] = -prices[0]
,第0
天操作后要持股,只能进行买入操作,因此此时最大收益为-prices[0]
dp[0][2] = 0
class Solution {
public:
int maxProfit(vector<int>& prices) {
int n = prices.size();
vector<vector<int> > dp(n, vector<int>(3, 0));
// dp[i]:表示第 i 天结束后可得的最大收益,0 <= i < n
// dp[i][0]:操作后(买入/卖出/不操作),空仓,非冷冻期
// dp[i][1]:操作后(买入/卖出/不操作),持股
// dp[i][2]:操作后(买入/卖出/不操作),空仓,冷冻期
dp[0][0] = 0;
dp[0][1] = -prices[0];
dp[0][2] = 0;
for(int i = 1; i < n; i++)
{
dp[i][0] = max(dp[i - 1][0], dp[i - 1][2]);
dp[i][1] = max(dp[i - 1][0] - prices[i], dp[i - 1][1]);
dp[i][2] = dp[i - 1][1] + prices[i];
}
return max(dp[n - 1][0], max(dp[n - 1][1], dp[n - 1][2]));
}
};
复杂度
时间复杂度:
O
(
n
)
O(n)
O(n),遍历一遍。
空间复杂度:
O
(
n
)
O(n)
O(n),dp[][]
数组的大小。
复杂度
时间复杂度:
O
(
n
)
O(n)
O(n),出队入队的时间复杂度都是
O
(
1
)
O(1)
O(1)。
空间复杂度:
O
(
k
)
O(k)
O(k),单调队列永远只有
k
k
k 大小。
312. 戳气球
题意
- 戳破第 i i i 个气球,获得 n u m s [ i − 1 ] ∗ n u m s [ i ] ∗ n u m s [ i + 1 ] nums[i - 1] * nums[i] * nums[i + 1] nums[i−1]∗nums[i]∗nums[i+1] 个硬币;
- 求可以获取的硬币的最大数量
解法 动态规划(参考 labuladong 解答)
要求最值,肯定得穷举所有情况,而要穷举所有情况,要么 i). 回溯;ii). 动态规划。
若选择回溯。要穷举所有获取硬币的情况,实际上就相当于穷举所有戳破气球的方案,即,全排列问题,因此:
int res = Integer.MIN_VALUE;
/* 输入一组气球,返回戳破它们获得的最大分数 */
int maxCoins(int[] nums) {
backtrack(nums, 0);
return res;
}
/* 回溯算法的伪码解法 */
void backtrack(int[] nums, int socre) {
if (nums 为空) {
res = max(res, score);
return;
}
for (int i = 0; i < nums.length; i++) {
int point = nums[i-1] * nums[i] * nums[i+1];
int temp = nums[i];
// 做选择
在 nums 中删除元素 nums[i]
// 递归回溯
backtrack(nums, score + point);
// 撤销选择
将 temp 还原到 nums[i]
}
}
作者:labuladong
链接:https://leetcode.cn/problems/burst-balloons/solutions/247603/dong-tai-gui-hua-tao-lu-jie-jue-chuo-qi-qiu-wen-ti/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
但是显然这道题并不能用这种方法,因为这样的复杂度为 n ! n! n!,即便 1 < = n < = 300 1 <= n <= 300 1<=n<=300,也会超时。
因此这道题选择动态规划做。
将问题转换为:在一排气球中,戳破 0 0 0 和 n + 1 n+1 n+1 之间所有的气球,计算能够获取的硬币的最大数量。
设 dp[i][j]
表示戳破 i
和 j
之间所有的气球,可以获取的硬币的最大数量,则要求取的就是 dp[0][n+1]
。
状态转移方程为:
设最后一个戳破的气球为 k
,那么:
dp[i][j] = max(dp[i][j], dp[i][k] + dp[k][j] + myNums[i] * myNums[k] * myNums[j]);
为什么是
myNums[i] * myNums[k] * myNums[j]
呢,因为此时dp[i][k]
和dp[k][j]
都被戳破了,所以最后只剩下气球i
、气球k
和气球j
,因此自然是myNums[i] * myNums[k] * myNums[j]
。
而由于在计算 dp[i][j]
之前需要计算 dp[i][k]
和 dp[k][j]
,所以要自下而上,自左而右遍历:
class Solution {
public:
int maxCoins(vector<int>& nums) {
int n = nums.size();
vector<vector<int> > dp(n+2, vector<int>(n+2, 0));
vector<int> myNums(n+2);
myNums[0] = myNums[n+1] = 1;
for(int i = 1; i <= n; i++)
myNums[i] = nums[i-1];
for(int i = n + 1; i >= 0; i--)
{
for(int j = i + 1; j < n + 2; j++)
{
for(int k = i + 1; k < j; k++)
{
dp[i][j] = max(dp[i][j], dp[i][k] + dp[k][j] + myNums[i] * myNums[k] * myNums[j]);
}
}
}
return dp[0][n+1];
}
};
复杂度:
时间复杂度:O(
n
3
n^3
n3)
空间复杂度:O(
n
2
n^2
n2)
322. 零钱兑换
题意
- 给定不同面额的硬币
coins[]
,以及一个整数amount
; - 计算可以凑成
amount
的最少硬币数; - 硬币可以多次使用;
- 本质上是一个 完全背包问题 ;
解法 dp
设 dp[i]
表示凑成金额 i
需要的最少硬币数,那么状态转移公式为:
dp[i] = min(dp[i], dp[i - coin] + 1)
也就是说,每次从 coins[]
中选择一个硬币。
class Solution {
public:
int maxn = 1e9;
int coinChange(vector<int>& coins, int amount) {
int n = coins.size();
vector<int> dp(amount + 1, maxn);
dp[0] = 0;
for(auto coin : coins)
{
if(coin <= amount)
dp[coin] = 1;
}
for(int i = 1; i <= amount; i++)
{
for(auto coin : coins)
{
if(i >= coin)
{
dp[i] = min(dp[i], dp[i - coin] + 1);
}
}
}
return dp[amount] == maxn ? -1 : dp[amount];
}
};
一开始想的是:
dp[i][j]
表示使用前 i
中面额的硬币,凑成 j
的最少硬币数,状态转移方程为:
dp[i][j] = min(dp[i][j], dp[i-1][j]); if j < coins[i]
dp[i][j] = min(dp[i][j], dp[i-1][j], dp[i-1][j-k*coins[i]] + k); other,其中 j-k*coins[i]>=0
但是这样的复杂度就是 O(amount * coins.size() * k),就,很容易超时,当然结果果然超时了,惨惨。
// TLE
class Solution {
public:
int maxn = 1e9;
int coinChange(vector<int>& coins, int amount) {
int n = coins.size();
vector<vector<int> > dp(n + 1, vector<int>(amount + 1, maxn));
for(int i = 0; i < n; i++) dp[i][0] = 0;
for(int i = 0; i < coins.size(); i++)
{
for(int j = 1; j * coins[i] <= amount; j++)
{
dp[i][j * coins[i]] = j;
}
}
// 用前i种数额组合,得到j
for(int i = 1; i < n; i++)
{
for(int j = 1; j <= amount; j++)
{
if(j < coins[i])
{
dp[i][j] = min(dp[i][j], dp[i-1][j]);
}
else
{
for(int k = 1; k * coins[i] <=j; k++) // TLE 可恶捏
{
dp[i][j] = min(dp[i][j], dp[i-1][j]);
dp[i][j] = min(dp[i][j], dp[i-1][j-k*coins[i]] + k);
}
}
}
}
return dp[n-1][amount] == maxn ? -1 : dp[n-1][amount];
}
};
复杂度:
时间复杂度:O(amount * coins.size())
空间复杂度:O(amount)