diff --git a/README.md b/README.md index 552450dc..b7f926fe 100644 --- a/README.md +++ b/README.md @@ -281,6 +281,7 @@ |[0131.分割回文串](https://github.com/youngyangyang04/leetcode/blob/master/problems/0131.分割回文串.md) |回溯 |中等|**回溯**| |[0141.环形链表](https://github.com/youngyangyang04/leetcode/blob/master/problems/0141.环形链表.md) |链表 |简单|**快慢指针/双指针**| |[0142.环形链表II](https://github.com/youngyangyang04/leetcode/blob/master/problems/0142.环形链表II.md) |链表 |中等|**快慢指针/双指针**| +|[0143.重排链表](https://github.com/youngyangyang04/leetcode/blob/master/problems/0143.重排链表.md) |链表 |中等|**快慢指针/双指针** 也可以用数组,双向队列模拟,考察链表综合操作的好题| |[0144.二叉树的前序遍历](https://github.com/youngyangyang04/leetcode/blob/master/problems/0144.二叉树的前序遍历.md) |树 |中等|**递归** **迭代/栈**| |[0145.二叉树的后序遍历](https://github.com/youngyangyang04/leetcode/blob/master/problems/0145.二叉树的后序遍历.md) |树 |困难|**递归** **迭代/栈**| |[0151.翻转字符串里的单词](https://github.com/youngyangyang04/leetcode/blob/master/problems/0151.翻转字符串里的单词.md) |字符串 |中等|**模拟/双指针**| @@ -339,6 +340,7 @@ |[0705.设计哈希集合](https://github.com/youngyangyang04/leetcode/blob/master/problems/0705.设计哈希集合.md) |哈希表 |简单|**模拟**| |[0707.设计链表](https://github.com/youngyangyang04/leetcode/blob/master/problems/0707.设计链表.md) |链表 |中等|**模拟**| |[0841.钥匙和房间](https://github.com/youngyangyang04/leetcode/blob/master/problems/0841.钥匙和房间.md) |孤岛问题 |中等|**bfs** **dfs**| +|[0844.比较含退格的字符串](https://github.com/youngyangyang04/leetcode/blob/master/problems/0844.比较含退格的字符串.md) |字符串 |简单|**栈** **双指针优化** 使用栈的思路但没有必要使用栈| |[0977.有序数组的平方](https://github.com/youngyangyang04/leetcode/blob/master/problems/0977.有序数组的平方.md) |数组 |中等|**双指针**| |[1002.查找常用字符](https://github.com/youngyangyang04/leetcode/blob/master/problems/1002.查找常用字符.md) |栈 |简单|**栈**| |[1047.删除字符串中的所有相邻重复项](https://github.com/youngyangyang04/leetcode/blob/master/problems/1047.删除字符串中的所有相邻重复项.md) |哈希表 |简单|**哈希表/数组**| diff --git a/pics/143.重排链表.png b/pics/143.重排链表.png new file mode 100644 index 00000000..2fc94408 Binary files /dev/null and b/pics/143.重排链表.png differ diff --git a/problems/0042.接雨水.md b/problems/0042.接雨水.md index fd5d5d3a..05cf9e08 100644 --- a/problems/0042.接雨水.md +++ b/problems/0042.接雨水.md @@ -170,7 +170,13 @@ public: 那么本题使用单调栈有如下几个问题: -1. 使用单调栈内元素的顺序 +1. 首先单调栈是按照行方向来计算雨水,如图: + + + +知道这一点,后面的就可以理解了。 + +2. 使用单调栈内元素的顺序 从大到小还是从小打到呢? @@ -183,7 +189,7 @@ public: -2. 遇到相同高度的柱子怎么办。 +3. 遇到相同高度的柱子怎么办。 遇到相同的元素,更新栈内下表,就是将栈里元素(旧下标)弹出,将新元素(新下标)加入栈中。 @@ -197,7 +203,7 @@ public: -3. 栈里要保存什么数值 +4. 栈里要保存什么数值 是用单调栈,其实是通过 长 * 宽 来计算雨水面积的。 diff --git a/problems/0084.柱状图中最大的矩形.md b/problems/0084.柱状图中最大的矩形.md new file mode 100644 index 00000000..2c0eae97 --- /dev/null +++ b/problems/0084.柱状图中最大的矩形.md @@ -0,0 +1,31 @@ + +# 链接 + +https://leetcode-cn.com/problems/largest-rectangle-in-histogram/ + +## 思路 + +``` +class Solution { +public: + int largestRectangleArea(vector& heights) { + int sum = 0; + for (int i = 0; i < heights.size(); i++) { + int left = i; + int right = i; + for (; left >= 0; left--) { + if (heights[left] < heights[i]) break; + } + for (; right < heights.size(); right++) { + if (heights[right] < heights[i]) break; + } + int w = right - left - 1; + int h = heights[i]; + sum = max(sum, w * h); + } + return sum; + } +}; +``` + +如上代码并不能通过leetcode,超时了,因为时间复杂度是O(n^2)。 diff --git a/problems/0143.重排链表.md b/problems/0143.重排链表.md new file mode 100644 index 00000000..a51f79b3 --- /dev/null +++ b/problems/0143.重排链表.md @@ -0,0 +1,160 @@ + +# 思路 + +本篇将给出三种C++实现的方法 + +* 数组模拟 +* 双向队列模拟 +* 直接分割链表 + +## 方法一 + +把链表放进数组中,然后通过双指针法,一前一后,来遍历数组,构造链表。 + +代码如下: + +``` +class Solution { +public: + void reorderList(ListNode* head) { + vector vec; + ListNode* cur = head; + if (cur == nullptr) return; + while(cur != nullptr) { + vec.push_back(cur); + cur = cur->next; + } + cur = head; + int i = 1; + int j = vec.size() - 1; + int count = 0; // 计数,用来取前面,取后面 + while (i <= j) { + if (count % 2 == 0) { + cur->next = vec[j]; + j--; + } else { + cur->next = vec[i]; + i++; + } + cur = cur->next; + count++; + } + if (vec.size() % 2 == 0) { + cur->next = vec[i]; + cur = cur->next; + } + cur->next = nullptr; // 注意结尾 + } +}; +``` + +## 方法二 + +把链表放进双向队列,然后通过双向队列一前一后弹出数据,来构造新的链表。这种方法比操作数组容易一些,不用双指针模拟一前一后了 +``` +class Solution { +public: + void reorderList(ListNode* head) { + deque que; + ListNode* cur = head; + if (cur == nullptr) return; + + while(cur->next != nullptr) { + que.push_back(cur->next); + cur = cur->next; + } + + cur = head; + int count = 0; + ListNode* node; + while(que.size()) { + if (count % 2 == 0) { + node = que.back(); + que.pop_back(); + } else { + node = que.front(); + que.pop_front(); + } + count++; + cur->next = node; + cur = cur->next; + } + cur->next = nullptr; + } +}; +``` + +## 方法三 + +将链表分割成两个链表,然后把第二个链表反转,之后在通过两个链表拼接成新的链表。 + +如图: + + + +这种方法,比较难,平均切割链表,看上去很简单,真正代码写的时候有很多细节,同时两个链表最后拼装整一个新的链表也有一些细节需要注意! + +代码如下: + +``` +class Solution { +private: + ListNode* reverseList(ListNode* head) { + ListNode* temp; // 保存cur的下一个节点 + ListNode* cur = head; + ListNode* pre = NULL; + while(cur) { + temp = cur->next; // 保存一下 cur的下一个节点,因为接下来要改变cur->next + cur->next = pre; // 翻转操作 + // 更新pre 和 cur指针 + pre = cur; + cur = temp; + } + return pre; + } + +public: + void reorderList(ListNode* head) { + if (head == nullptr) return; + // 使用快慢指针法,将链表分成长度均等的两个链表head1和head2 + // 如果总链表长度为奇数,则head1相对head2多一个节点 + ListNode* fast = head; + ListNode* slow = head; + while (fast && fast->next && fast->next->next) { + fast = fast->next->next; + slow = slow->next; + } + ListNode* head1 = head; + ListNode* head2; + head2 = slow->next; + slow->next = nullptr; + + // 对head2进行翻转 + head2 = reverseList(head2); + + // 将head1和head2交替生成新的链表head + ListNode* cur1 = head1; + ListNode* cur2 = head2; + ListNode* cur = head; + cur1 = cur1->next; + int count = 0; + while (cur1 && cur2) { + if (count % 2 == 0) { + cur->next = cur2; + cur2 = cur2->next; + } else { + cur->next = cur1; + cur1 = cur1->next; + } + count++; + cur = cur->next; + } + if (cur2 != nullptr) { + cur->next = cur2; + } + if (cur1 != nullptr) { + cur->next = cur1; + } + } +}; +``` diff --git a/problems/0219.存在重复元素II.md b/problems/0219.存在重复元素II.md index ddc67051..4835a7f0 100644 --- a/problems/0219.存在重复元素II.md +++ b/problems/0219.存在重复元素II.md @@ -30,6 +30,22 @@ public: }; ``` +代码精简之后 + +``` +class Solution { +public: + bool containsNearbyDuplicate(vector& nums, int k) { + unordered_map map; + for (int i = 0; i < nums.size(); i++) { + if (map.find(nums[i]) != map.end() && i - map[nums[i]] <= k) return true; + map[nums[i]] = i; + } + return false; + } +}; +``` + > 更过算法干货文章持续更新,可以微信搜索「代码随想录」第一时间围观,关注后,回复「Java」「C++」 「python」「简历模板」「数据结构与算法」等等,就可以获得我多年整理的学习资料。 diff --git a/problems/0222.完全二叉树的节点个数.md b/problems/0222.完全二叉树的节点个数.md index 6387e25f..183ba488 100644 --- a/problems/0222.完全二叉树的节点个数.md +++ b/problems/0222.完全二叉树的节点个数.md @@ -21,7 +21,7 @@ https://leetcode-cn.com/problems/count-complete-tree-nodes/ 依然可以使用递归法和迭代法来解决。 -这道题目的递归法和求二叉树的深度写法类似, 而迭代法:二叉树层序遍历模板稍稍修改一下,记录遍历的节点数量就可以了。 +这道题目的递归法和求二叉树的深度写法类似, 而迭代法,[二叉树:层序遍历登场!](https://mp.weixin.qq.com/s/Gb3BjakIKGNpup2jYtTzog)遍历模板稍稍修改一下,记录遍历的节点数量就可以了。 递归遍历的顺序依然是后序(左右中)。 diff --git a/problems/0701.二叉搜索树中的插入操作.md b/problems/0701.二叉搜索树中的插入操作.md index 21b71876..045c7f73 100644 --- a/problems/0701.二叉搜索树中的插入操作.md +++ b/problems/0701.二叉搜索树中的插入操作.md @@ -1,37 +1,98 @@ ## 题目地址 https://leetcode-cn.com/problems/insert-into-a-binary-search-tree/ -## 思路 +> 开始修改二叉搜索树 -其实这道题目是一道很简单的题目,**但是题目中的提示:有多种有效的插入方式,还可以重构二叉搜索树,一下子吓退了不少人**,瞬间感觉题目复杂了很多。 +# 701.二叉搜索树中的插入操作 + +链接:https://leetcode-cn.com/problems/insert-into-a-binary-search-tree/ + +给定二叉搜索树(BST)的根节点和要插入树中的值,将值插入二叉搜索树。 返回插入后二叉搜索树的根节点。 输入数据保证,新值和原始二叉搜索树中的任意节点值都不同。 + +注意,可能存在多种有效的插入方式,只要树在插入后仍保持为二叉搜索树即可。 你可以返回任意有效的结果。 + +![701.二叉搜索树中的插入操作](https://img-blog.csdnimg.cn/20201019173259554.png) +  +提示: + +* 给定的树上的节点数介于 0 和 10^4 之间 +* 每个节点都有一个唯一整数值,取值范围从 0 到 10^8 +* -10^8 <= val <= 10^8 +* 新值和原始二叉搜索树中的任意节点值都不同 + +# 思路 + +其实这道题目其实是一道简单题目,**但是题目中的提示:有多种有效的插入方式,还可以重构二叉搜索树,一下子吓退了不少人**,瞬间感觉题目复杂了很多。 其实**可以不考虑题目中提示所说的改变树的结构的插入方式。** 如下演示视频中可以看出:只要按照二叉搜索树的规则去遍历,遇到空节点就插入节点就可以了。 -例如插入元素10 ,需要找到末尾节点插入便可,一样的道理来插入元素15,插入元素0,插入元素6,**需要调整二叉树的结构么? 并不需要。**只需要遍历二叉搜索树,找到空节点 插入元素就可以了, 那么这道题其实就非常简单了。 - +例如插入元素10 ,需要找到末尾节点插入便可,一样的道理来插入元素15,插入元素0,插入元素6,**需要调整二叉树的结构么? 并不需要。**。 + +只要遍历二叉搜索树,找到空节点 插入元素就可以了,那么这道题其实就简单了。 + 接下来就是遍历二叉搜索树的过程了。 +## 递归 + +递归三部曲: + +* 确定递归函数参数以及返回值 + +参数就是根节点指针,以及要插入元素,这里递归函数要不要有返回值呢? + +可以有,也可以没有,但递归函数如果没有返回值的话,实现是比较麻烦的,下面也会给出其具体实现代码。 + +**有返回值的话,可以利用返回值完成新加入的节点与其父节点的赋值操作**。(下面会进一步解释) + +递归函数的返回类型为节点类型TreeNode * 。 + 代码如下: -## C++代码 - -### 递归 ``` -/** - * 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) {} - * }; - */ +TreeNode* insertIntoBST(TreeNode* root, int val) +``` + +* 确定终止条件 + +终止条件就是找到遍历的节点为null的时候,就是要插入节点的位置了,并把插入的节点返回。 + +代码如下: + +``` +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; +``` + +**到这里,大家应该能感受到,如何通过递归函数返回值完成了新加入节点的父子关系赋值操作了,上一层将加入节点返回,本层用root->left或者root->right将其接住**。 + + +整体代码如下: + +``` class Solution { public: TreeNode* insertIntoBST(TreeNode* root, int val) { @@ -46,12 +107,67 @@ public: }; ``` -### 迭代 +可以看出代码并不复杂。 -在来看看迭代法 +刚刚说了递归函数不用返回值也可以,找到插入的节点位置,直接让其父节点指向插入节点,结束递归,也是可以的。 + +那么递归函数定义如下: + +``` +TreeNode* parent; // 记录遍历节点的父节点 +void traversal(TreeNode* cur, int val) +``` + +没有返回值,需要记录上一个节点(parent),遇到空节点了,就让parent左孩子或者右孩子指向新插入的节点。然后结束递归。 + +代码如下: + +``` +class Solution { +private: + TreeNode* parent; + void traversal(TreeNode* cur, int val) { + if (cur == NULL) { + TreeNode* node = new TreeNode(val); + if (val > parent->val) parent->right = node; + else parent->left = node; + return; + } + parent = cur; + if (cur->val > val) traversal(cur->left, val); + if (cur->val < val) traversal(cur->right, val); + return; + } + +public: + TreeNode* insertIntoBST(TreeNode* root, int val) { + parent = new TreeNode(0); + if (root == NULL) { + root = new TreeNode(val); + } + traversal(root, val); + return root; + } +}; +``` + +可以看出还是麻烦一些的。 + +我之所以举这个例子,是想说明通过递归函数的返回值完成父子节点的赋值是可以带来便利的。 + +**网上千变一律的代码,可能会误导大家认为通过递归函数返回节点 这样的写法是天经地义,其实这里是有优化的!** + + +## 迭代 + +再来看看迭代法,对二叉搜索树迭代写法不熟悉,可以看这篇:[二叉树:二叉搜索树登场!](https://mp.weixin.qq.com/s/vsKrWRlETxCVsiRr8v_hHg) 在迭代法遍历的过程中,需要记录一下当前遍历的节点的父节点,这样才能做插入节点的操作。 +在[二叉树:搜索树的最小绝对差](https://mp.weixin.qq.com/s/Hwzml6698uP3qQCC1ctUQQ)和[二叉树:我的众数是多少?](https://mp.weixin.qq.com/s/KSAr6OVQIMC-uZ8MEAnGHg)中,都是用了记录pre和cur两个指针的技巧,本题也是一样的。 + +代码如下: + ``` class Solution { public: @@ -75,4 +191,16 @@ public: }; ``` +# 总结 + +首先在二叉搜索树中的插入操作,大家不用恐惧其重构搜索树,其实根本不用重构。 + +然后在递归中,我们重点讲了如果通过递归函数的返回值完成新加入节点和其父节点的赋值操作,并强调了搜索树的有序性。 + +最后依然给出了迭代的方法,迭代的方法就需要记录当前遍历节点的父节点了,这个和没有返回值的递归函数实现的代码逻辑是一样的。 + +**就酱,学到了就转发给身边需要的同学吧!** + + + > 更多算法干货文章持续更新,可以微信搜索「代码随想录」第一时间围观,关注后,回复「Java」「C++」 「python」「简历模板」「数据结构与算法」等等,就可以获得我多年整理的学习资料。