按照youngyangyang04总结的Leetcode刷题攻略进行整理,链接https://github.com/youngyangyang04/leetcode-master
目录
- 数组
- 链表
- 哈希表
- 字符串
- 双指针
- 栈与队列
- 二叉树
数组
1. 二分法
题目:搜索插入位置
https://leetcode-cn.com/problems/search-insert-position/
二分法关键:定义区间的左右端点L和R,并根据自己定义的区间的开闭性质来设计循环条件,如闭区间则为while(L <= R),左闭右开区间则为while (L < R),在迭代过程中不断根据区间中点M处的值与目标值的大小关系来更新端点L和R。注意可能找到目标值也可能找不到目标值,尤其是找不到目标值的时候的返回值需要根据自己设计的区间来模拟一下,看是返回端点或端点某侧的值。
2. 双指针
题目:原地移除元素
https://leetcode-cn.com/problems/remove-element/
快慢指针初始化为数组起点处,当快指针处值不等于要移除的元素值时,快指针值赋给慢指针后二者同时后移一位,当快指针处值等于要移除的元素值时,快指针值后移一位。相当于慢指针在维护经过移除后的新数组,当快指针找到要删除的值时,它自己后移一位跳过这个值,不会赋值到慢指针也就意味着新数组中没有这个值了。当快指针完成了对整个数组的遍历,结束。
3. 滑动窗口
题目:长度最小的子数组
https://leetcode-cn.com/problems/minimum-size-subarray-sum/
在我的理解上滑动窗口和双指针法一样,只是滑动窗口的双指针构成的区间有其意义,形成了一个窗口。就如本题目,双指针间的“窗口”即为子数组。当子数组和小于目标值时,不断右移右端点扩张窗口,直到子数组和大于目标值,这时开始右移左区间收缩窗口,同时不断记录此时的子数组长度,维护最终的最短子数组长。
链表
1. 删除链表节点
题目:移除链表元素
https://leetcode-cn.com/problems/remove-linked-list-elements/
注意可以使用虚拟头结点来简化实现,另外注意C++需要手动释放删除的节点的内存。
2. 反转链表
题目:反转链表
https://leetcode-cn.com/problems/reverse-linked-list/
方法1:遍历链表并记录,倒序构造新链表;方法2:双指针法:一个指针记录当前遍历到的位置,另一个指针记录上一个位置,不断地把当前指针的next指向另一个指针,即上一个位置。
3. 环形链表
题目:环形链表II
https://leetcode-cn.com/problems/linked-list-cycle-ii/
判断链表有环的方法:可以使用快慢指针法,分别定义fast和slow指针,从头结点出发,fast指针每次移动两个节点,slow指针每次移动一个节点,如果fast和slow指针在途中相遇 ,说明这个链表有环。 这道题我单纯用set来做,存储遍历过程中遇到的每一个节点,也可以解出来。
哈希表
1. 数组用作哈希表
题目:有效的字母异位词
https://leetcode-cn.com/problems/valid-anagram/
适合哈希值比较小的情况,如果哈希值比较少、特别分散、跨度非常大,使用数组就造成空间的极大浪费。
2. 用set来判断快乐数
题目:快乐数
https://leetcode-cn.com/problems/happy-number/
由于题目中指出会无限循环,故可以使用哈希表记录出现过的值并迅速判断是否重复出现。
3. 四数相加
题目:四数相加II
https://leetcode-cn.com/problems/4sum-ii/
由于题目是四个独立的数组中取数,只需要计算有多少种结果而不需要去重,故适合用哈希来做。先二重遍历两个数组,记录其和,再二重遍历另外两个数组求和,对比哈希表,看是否能找到满足要求的情况。
4. 寻找和为目标值的无重复三元组/四元组
题目:三数之和、四数之和
https://leetcode-cn.com/problems/3sum/
https://leetcode-cn.com/problems/4sum/
和四数相加区别在于需要对结果去重,不适合用哈希表来做,而适合用双指针来做,放在双指针里总结。
字符串
1. KMP算法
题目:实现 strStr()
https://leetcode-cn.com/problems/implement-strstr/
模式匹配题目中常用,模板如下:
// 构造next数组
void getNext(int* next, const string& s) {
int j = -1; // 初始化j为-1
next[0] = j; // 前缀表首元素初始化为-1
for(int i = 1; i < s.size(); i++) { // 注意i从1开始※※※
while (j >= 0 && s[i] != s[j + 1]) { // 前后缀不相同时,向前回溯
j = next[j];
}
if (s[i] == s[j + 1]) { // 找到相同的前后缀
j++;
}
next[i] = j; // 将j(前缀的长度)赋给next[i]
}
}
int strStr(string haystack, string needle) {
if (needle.size() == 0) {
return 0;
}
int next[needle.size()]; // 初始化next数组
getNext(next, needle); // 构造next数组
int j = -1; // 因为next数组里记录的起始位置为-1
for (int i = 0; i < haystack.size(); i++) { // 注意i就从0开始※※※
while(j >= 0 && haystack[i] != needle[j + 1]) { // 不匹配
j = next[j]; // j 寻找之前匹配的位置
}
if (haystack[i] == needle[j + 1]) { // 匹配,j和i同时向后移动
j++;
}
if (j == (needle.size() - 1) ) { // 文本串s里出现了模式串t,此时j到达了模式串的末尾※※※
return (i - needle.size() + 1);
}
}
return -1;
}
双指针
1. 寻找和为目标值的无重复三元组
题目:三数之和
https://leetcode-cn.com/problems/3sum/
为了寻找无重复三元组,首先需要对原数组排序。在本题中双指针的使用方法为:for循环固定三元组的首元素,用双指针指向三元组的另外两个元素,分别初始化为首元素的下一位和数组尾部。双指针扫描一遍首元素后的部分:计算首元素与双指针指向的元素构成的三元组的和,若和超过目标值,则说明右指针需要左移,若和低于目标值则说明左指针需要右移。若等于目标值,则说明找到一组解,可以加入解集中,此时左右指针都向中间移动,去找到与各自上一个位置不同的值。
关于去重操作,有几个地方需要注意,首先是for循环移动首元素时,第二次开始需要保证移动到和上次值不同的位置;找到解时左右指针的移动,都需要找到与上一次不同的值。
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> ans;
if (nums.size() < 3) return ans;
sort(nums.begin(), nums.end());
if (nums[0] > 0) return ans;
for (int i = 0; i < nums.size(); i++) {
if (i > 0 && nums[i] == nums[i - 1]) continue;
int L = i + 1, R = nums.size() - 1;
while (L < R) {
int sum = nums[i] + nums[L] + nums[R];
if (sum == 0) {
ans.push_back(vector<int>{nums[i], nums[L], nums[R]});
// 移动
while (L < R && nums[L] == nums[L + 1]) L++;
while (L < R && nums[R] == nums[R - 1]) R--;
L++, R--;
}
else if (sum > 0) R--;
else if (sum < 0) L++;
}
}
return ans;
}
};
2. 寻找和为目标值的无重复四元组
题目:四数之和
https://leetcode-cn.com/problems/4sum/
和三元组同理,外层改为两层暴力for循环,固定四元组里的两个元素。在去重操作时注意两层for循环每次都要移动到与上一次循环不同的元素。
栈与队列
1. 匹配问题(括号等)
2. 后缀表达式求值
3. 单调栈****
题目:每日温度
https://leetcode-cn.com/problems/daily-temperatures/
二叉树
1. DFS
前序遍历模板:
class Solution {
public:
void traversal(TreeNode* cur, vector<int>& vec) {
if (cur == NULL) return;
vec.push_back(cur->val); // 中
traversal(cur->left, vec); // 左
traversal(cur->right, vec); // 右
}
vector<int> preorderTraversal(TreeNode* root) {
vector<int> result;
traversal(root, result);
return result;
}
};
中序遍历模板:
void traversal(TreeNode* cur, vector<int>& vec) {
if (cur == NULL) return;
traversal(cur->left, vec); // 左
vec.push_back(cur->val); // 中
traversal(cur->right, vec); // 右
}
后序遍历模板:
void traversal(TreeNode* cur, vector<int>& vec) {
if (cur == NULL) return;
traversal(cur->left, vec); // 左
traversal(cur->right, vec); // 右
vec.push_back(cur->val); // 中
}
2. BFS
题目:二叉树的层序遍历
https://leetcode-cn.com/problems/binary-tree-level-order-traversal/
广度优先遍历模板(借助队列):
class Solution {
public:
vector<vector<int>> res;
vector<vector<int>> levelOrderBottom(TreeNode* root) {
if (root) {
BFS(root);
}
return res;
}
void BFS(TreeNode* root) {
queue<TreeNode*> q; // 当前层的节点 ※
q.push(root);
while (!q.empty()) {
queue<TreeNode*> qt; // 下一层的节点,用当前层的节点的孩子节点来填充 ※
vector<int> single_layer; // 存储遍历结果
while (!q.empty()) { // 遍历当前层,得到下一层的节点 ※
if (q.front()->left) qt.push(q.front()->left);
if (q.front()->right) qt.push(q.front()->right);
single_layer.push_back(q.front()->val);
q.pop();
}
res.push_back(single_layer);
q = qt; // 用下一层替换当前层
}
}
};
还出现过自底向上层序遍历的题目,只需要翻转结果数组即可。
3. 翻转二叉树
题目:翻转二叉树
https://leetcode-cn.com/problems/invert-binary-tree/
前序和后序都可以实现,但中序不可以(笔头模拟一下会发现有些节点被翻转两次而有些节点没有被翻转)
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
if (!root) {
return nullptr;
}
swap(root->left, root->right);
invertTree(root->left);
invertTree(root->right);
return root;
}
};
4. 检查二叉树是否相同/对称/子树
题目:相同的树
https://leetcode-cn.com/problems/same-tree/
题目:对称二叉树
https://leetcode-cn.com/problems/dui-cheng-de-er-cha-shu-lcof/
这类检查对称/镜像/相同的题目涉及到比较二叉树的左右两个子树,递归参数要传入两个节点,进行后序遍历。
class Solution {
public:
bool isSameTree(TreeNode* p, TreeNode* q) {
/* 均为空 */if (!p && !q) return true;
/* 有空 */ if (!p || !q) return false;
return p->val==q->val && isSameTree(p->left, q->left) && isSameTree(p->right, q->right);
}
};
class Solution {
public:
bool isSymmetric(TreeNode* root) {
return check(root, root);
}
bool check (TreeNode* t1, TreeNode* t2) {
/* 均为空 */if (!t1 && !t2) return true;
/* 有空 */ if (!t1 || !t2) return false;
return t1->val == t2->val && check(t1->left, t2->right) && check(t1->right, t2->left);
}
};
判断子树
class Solution {
public:
bool checkSubTree(TreeNode* t1, TreeNode* t2) {
if (!t1 && !t2) return true;
if (!t1 || !t2) return false;
return isSameTree(t1, t2) || checkSubTree(t1->left, t2) || checkSubTree(t1->right, t2);
}
bool isSameTree(TreeNode* t1, TreeNode* t2) {
if (!t1 && !t2) return true;
if (!t1 || !t2) return false;
return t1->val == t2->val && isSameTree(t1->left, t2->left) && isSameTree(t1->right, t2->right);
}
};
5. 最大/最小深度
题目:二叉树的最大深度
https://leetcode-cn.com/problems/er-cha-shu-de-shen-du-lcof/
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
if (root == NULL) return root;
swap(root->left, root->right); // 中
invertTree(root->left); // 左
invertTree(root->right); // 右所以整体C++代码如下:
class Solution {
public:
int getDepth(TreeNode* node) {
if (node == NULL) return 0;
int leftDepth = getDepth(node->left); // 左
int rightDepth = getDepth(node->right); // 右
int depth = 1 + max(leftDepth, rightDepth); // 中
return depth;
}
int maxDepth(TreeNode* root) {
return getDepth(root);
}
};
代码精简之后C++代码如下:
class Solution {
public:
int maxDepth(TreeNode* root) {
if (root == NULL) return 0;
return 1 + max(maxDepth(root->left), maxDepth(root->right));
}
};
return root;
}
};
题目:二叉树的最小深度
class Solution {
public:
int getDepth(TreeNode* node) { // 后序遍历
if (node == NULL) return 0;
int leftDepth = getDepth(node->left); // 左
int rightDepth = getDepth(node->right); // 右
int depth = 1 + max(leftDepth, rightDepth); // 中
return depth;
}
int maxDepth(TreeNode* root) {
return getDepth(root);
}
};
求最小深度区别于最大深度的一个点是需要处理节点左孩子/右孩子为空的情况,若出现这种情况,该节点的最小深度为另一侧孩子的深度而非直接是0+1=1
class Solution {
public:
int getDepth(TreeNode* node) {
if (node == NULL) return 0;
int leftDepth = getDepth(node->left); // 左
int rightDepth = getDepth(node->right); // 右
// 中
// 当一个左子树为空,右不为空,这时并不是最低点
if (node->left == NULL && node->right != NULL) {
return 1 + rightDepth;
}
// 当一个右子树为空,左不为空,这时并不是最低点
if (node->left != NULL && node->right == NULL) {
return 1 + leftDepth;
}
int result = 1 + min(leftDepth, rightDepth);
return result;
}
int minDepth(TreeNode* root) {
return getDepth(root);
}
};
6. 判断是否平衡
题目:平衡二叉树
https://leetcode-cn.com/problems/balanced-binary-tree/
本题建立在求高度的基础上。判断平衡的标准是左子树平衡&&右子树平衡&&左右子树深度相差不超过1,依然为后序遍历。
class Solution {
public:
bool isBalanced(TreeNode* root) {
if (!root) return true;
return abs(height(root->left) - height(root->right)) < 2 && isBalanced(root->left) && isBalanced(root->right);
}
int height(TreeNode* root) {
if (!root) return 0;
return max(height(root->left), height(root->right)) + 1;
}
};
7. 找二叉树的所有路径
题目:二叉树的所有路径
https://leetcode-cn.com/problems/binary-tree-paths/
先序遍历
class Solution {
public:
vector<string> res;
vector<string> binaryTreePaths(TreeNode* root) {
if (root) {
string s;
DFS(root, s);
}
return res;
}
void DFS(TreeNode* root, string s) {
if (!root) return;
if (!root->left && !root->right) {
s += to_string(root->val);
res.push_back(s);
}
s = s + to_string(root->val) + "->";
DFS(root->left, s);
DFS(root->right, s);
return;
}
};
8. 左叶子之和
题目:左叶子之和
https://leetcode-cn.com/problems/sum-of-left-leaves/
遍历时加上判断条件if (node->left && !node->left->left && !node->left->right),若符合条件则加到总和里。
9. 左下角
题目:找树左下角的值
https://leetcode-cn.com/problems/find-bottom-left-tree-value/
方法1.BFS
方法2.DFS先序遍历时不断判断深度,记录深度最大时的值,一定是最左边的叶子的值。
10. 通过前中/中后序遍历结果恢复二叉树*
题目:从前序与中序遍历序列构造二叉树
https://leetcode-cn.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal/
题目:从中序与后序遍历序列构造二叉树
https://leetcode-cn.com/problems/construct-binary-tree-from-inorder-and-postorder-traversal/
解答看似复杂但很好理解,以中后序恢复二叉树为例:
- 找后序遍历结果末元素,作为根节点root;
- 用该元素分割中序遍历结果,得到左中序结果和右中序结果;
- 将后序遍历结果末元素弹出,按照左中序结果和右中序结果的大小再将剩余的后序遍历结果分割为两段,得到左后序结果和右后续结果;
- 左中序、左后序和右中序、右后序结果分别成为两个子问题的输入,其解正好是root->left和root->right,进入递归。
有几个需要注意的点:注意处理数组为空(返回空指针)和规模为1(无需分割直接作为根节点返回)的情况;用迭代器构造的是左闭右开数组。
若用前中序恢复二叉树,只需要将1改为找先序遍历结果首元素 。
这里列出中后序恢复二叉树的解答:
class Solution {
private:
TreeNode* traversal (vector<int>& inorder, vector<int>& postorder) {
// 空的情况
if (postorder.size() == 0) return NULL;
// 后序遍历数组的最后一个元素,就是当前的中间节点
int rootValue = postorder[postorder.size() - 1];
TreeNode* root = new TreeNode(rootValue);
// 只有一个根节点的情况
if (postorder.size() == 1) return root;
// 找到中序遍历的切割点索引
int delimiterIndex;
for (delimiterIndex = 0; delimiterIndex < inorder.size(); delimiterIndex++) {
if (inorder[delimiterIndex] == rootValue) break;
}
// 切割中序遍历数组
// 左闭右开区间:[0, delimiterIndex)
vector<int> leftInorder(inorder.begin(), inorder.begin() + delimiterIndex);
// [delimiterIndex + 1, end)
// 这里需要注意一下用迭代器构造数组时区间右端点是开的
vector<int> rightInorder(inorder.begin() + delimiterIndex + 1, inorder.end());
// 后序遍历的末元素的分割功能已经用完,弹出它
postorder.pop_back();
// 切割后序数组
// 依然左闭右开,注意这里使用了左中序数组大小作为切割点
// [0, leftInorder.size())
vector<int> leftPostorder(postorder.begin(), postorder.begin() + leftInorder.size());
// [leftInorder.size(), end]
vector<int> rightPostorder(postorder.begin() + leftInorder.size(), postorder.end());
// 已经找到了左右子树各自的中序和后序遍历
root->left = traversal(leftInorder, leftPostorder);
root->right = traversal(rightInorder, rightPostorder);
return root;
}
public:
TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
if (inorder.empty() || postorder.empty()) return nullptr;
return traversal(inorder, postorder);
}
};
11. 合并二叉树
题目:合并二叉树
https://leetcode-cn.com/problems/merge-two-binary-trees/
递归,不多写了
class Solution {
public:
TreeNode* mergeTrees(TreeNode* t1, TreeNode* t2) {
return merge(t1, t2);
}
TreeNode* merge(TreeNode* t1, TreeNode* t2) {
if (!t1&&!t2) {
return nullptr;
}
else if (!t1) {
return t2;
}
else if (!t2) {
return t1;
}
TreeNode* root = new TreeNode(t1->val + t2->val);
root->left = merge (t1->left, t2->left);
root->right = merge (t1->right, t2->right);
return root;
}
};
12. 二叉搜索树
题目:二叉搜索树中的搜索
https://leetcode-cn.com/problems/search-in-a-binary-search-tree/
class Solution {
public:
TreeNode* searchBST(TreeNode* root, int val) {
if (!root) return nullptr;
if (root->val == val) return root;
if (val < root->val) return searchBST(root->left, val);
else return searchBST(root->right, val);
}
};
13. 判断是否是二叉搜索树
题目:验证二叉搜索树
https://leetcode-cn.com/problems/validate-binary-search-tree/
利用二叉搜索树的性质,二叉搜索树的中序遍历结果是严格递增的(二叉搜索树中没有重复元素),故可以进行中序遍历,再对中序遍历结果进行判断。
14. 公共祖先
题目:二叉树的最近公共祖先
https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-tree/
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
// 后序遍历(自然为回溯过程)
if (!root) return nullptr;
// 若找到了两个节点,一路返回
if (q == root || p == root) return root;
TreeNode* L = lowestCommonAncestor(root->left, p, q);
TreeNode* R = lowestCommonAncestor(root->right, p, q);
// 若左子树包含一个,右子树包含一个,则认为找到了最近公共祖先,将其一路返回
if (L && R) return root;
// 若左右并不都包含,谁包含返回谁,都不包含则返回NULL
return !L ? R:L;
}
};
15. 二叉搜索树(BST)中插入元素
题目:BST中的插入操作
https://leetcode-cn.com/problems/insert-into-a-binary-search-tree/
一路找到空节点插入即可
class Solution {
public:
TreeNode* insertIntoBST(TreeNode* root, int val) {
if (root == NULL) {
TreeNode* node = new TreeNode(val);
return node;
}
if (root->val > val) root->left = insertIntoBST(root->left, val);
if (root->val < val) root->right = insertIntoBST(root->right, val);
return root;
}
};
16. 二叉搜索树(BST)中删除元素***
题目:删除二叉搜索树中的节点
https://leetcode-cn.com/problems/delete-node-in-a-bst/
一. 没找到要删除的节点,返回空
二. 找到删除节点
1. 为叶节点,直接删除,返回NULL为根节点
2. 左右孩子其中一个空,另一个不空,不空的补位
3. 左右孩子都非空,右孩子补位,把左孩子放到补位后的节点的最左边节点的左孩子位置上。
class Solution {
public:
TreeNode* deleteNode(TreeNode* root, int key) {
if (root == nullptr) return root; // 第一种情况:没找到删除的节点,遍历到空节点直接返回了
if (root->val == key) {
// 第二种情况:左右孩子都为空(叶子节点),直接删除节点, 返回NULL为根节点
// 第三种情况:其左孩子为空,右孩子不为空,删除节点,右孩子补位 ,返回右孩子为根节点
if (root->left == nullptr) return root->right;
// 第四种情况:其右孩子为空,左孩子不为空,删除节点,左孩子补位,返回左孩子为根节点
else if (root->right == nullptr) return root->left;
// 第五种情况:左右孩子节点都不为空,则将删除节点的左子树放到删除节点的右子树的最左面节点的左孩子的位置
// 并返回删除节点右孩子为新的根节点。
else {
TreeNode* cur = root->right; // 找右子树最左面的节点
while(cur->left != nullptr) {
cur = cur->left;
}
cur->left = root->left; // 把要删除的节点(root)左子树放在cur的左孩子的位置
TreeNode* tmp = root; // 把root节点保存一下,下面来删除
root = root->right; // 返回旧root的右孩子作为新root
delete tmp; // 释放节点内存(这里不写也可以,但C++最好手动释放一下吧)
return root;
}
}
if (root->val > key) root->left = deleteNode(root->left, key);
if (root->val < key) root->right = deleteNode(root->right, key);
return root;
17. 构造不同的二叉搜索树****
题目:不同的二叉搜索树II
https://leetcode-cn.com/problems/unique-binary-search-trees-ii/
class Solution {
public:
// 输入1, n,开始递归
vector<TreeNode*> helper(int start,int end){
vector<TreeNode*> ret;
if(start > end)
ret.push_back(nullptr);
// 分别把i = 1...n作为根节点
for(int i=start;i<=end;i++){
// i作为根节点,i左侧部分作为左子树,右部分作为右子树
vector<TreeNode*> left = helper(start,i-1);
vector<TreeNode*> right = helper(i+1,end);
// 对所有左子树和右子树的组合,把树存进ret里
for(auto l : left){
for(auto r : right){
TreeNode* root = new TreeNode(i);
root -> left = l;
root -> right = r;
ret.push_back(root);
}
}
}
return ret;
}
vector<TreeNode*> generateTrees(int n) {
vector<TreeNode*> ret;
if(n == 0)
return ret;
ret = helper(1,n);
return ret;
}
};
18. 用有序数组构造平衡二叉树
题目:将有序数组转换为二叉搜索树
https://leetcode-cn.com/problems/convert-sorted-array-to-binary-search-tree/
题目描述为:给你一个整数数组 nums ,其中元素已经按 升序 排列,请你将其转换为一棵 高度平衡 二叉搜索树。高度平衡二叉树是一棵满足「每个节点的左右两个子树的高度差的绝对值不超过 1 」的二叉树。
当需要构造平衡二叉树时,需要每次把数组元素对半分到两个子树上,容易想到二分法。数组中间元素作为根节点,左区间元素放在左子树上,右区间元素放在右子树上,再分别对左子树/右子树递归做同样的构造。终止条件为区间端点不再满足左端点<右端点。
class Solution {
public:
TreeNode* sortedArrayToBST(vector<int>& nums) {
return createBST(nums, 0, nums.size() - 1);
}
TreeNode* createBST(vector<int>& nums, int L, int R) {
if (L > R) {
return nullptr;
}
int M = (L + R) / 2;
TreeNode* ptr = new TreeNode(nums[M]);
ptr->left = createBST(nums, L, M - 1);
ptr->right = createBST(nums, M + 1, R);
return ptr;
}
};
19. 二叉搜索树转换累加树
题目:把二叉搜索树转换为累加树
https://leetcode-cn.com/problems/convert-bst-to-greater-tree/
DFS,右中左遍历