diff --git a/README.md b/README.md index 89bceef7..d568aeed 100644 --- a/README.md +++ b/README.md @@ -107,6 +107,7 @@ * [二叉树:以为使用了递归,其实还隐藏着回溯](https://mp.weixin.qq.com/s/ivLkHzWdhjQQD1rQWe6zWA) * [二叉树:做了这么多题目了,我的左叶子之和是多少?](https://mp.weixin.qq.com/s/gBAgmmFielojU5Wx3wqFTA) * [二叉树:我的左下角的值是多少?](https://mp.weixin.qq.com/s/MH2gbLvzQ91jHPKqiub0Nw) + * [二叉树:构造二叉树登场!](https://mp.weixin.qq.com/s/7r66ap2s-shvVvlZxo59xg) @@ -264,6 +265,7 @@ |[0116.填充每个节点的下一个右侧节点指针](https://github.com/youngyangyang04/leetcode/blob/master/problems/0116.填充每个节点的下一个右侧节点指针.md) |二叉树 |中等|**广度优先搜索**| |[0117.填充每个节点的下一个右侧节点指针II](https://github.com/youngyangyang04/leetcode/blob/master/problems/0117.填充每个节点的下一个右侧节点指针II.md) |二叉树 |中等|**广度优先搜索**| |[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) |链表 |中等|**快慢指针/双指针**| |[0144.二叉树的前序遍历](https://github.com/youngyangyang04/leetcode/blob/master/problems/0144.二叉树的前序遍历.md) |树 |中等|**递归** **迭代/栈**| |[0145.二叉树的后序遍历](https://github.com/youngyangyang04/leetcode/blob/master/problems/0145.二叉树的后序遍历.md) |树 |困难|**递归** **迭代/栈**| diff --git a/pics/42.接雨水1.png b/pics/42.接雨水1.png new file mode 100644 index 00000000..26624f4b Binary files /dev/null and b/pics/42.接雨水1.png differ diff --git a/pics/42.接雨水2.png b/pics/42.接雨水2.png new file mode 100644 index 00000000..94eda97e Binary files /dev/null and b/pics/42.接雨水2.png differ diff --git a/pics/42.接雨水3.png b/pics/42.接雨水3.png new file mode 100644 index 00000000..1e4b528a Binary files /dev/null and b/pics/42.接雨水3.png differ diff --git a/pics/654.最大二叉树.png b/pics/654.最大二叉树.png new file mode 100644 index 00000000..e8fc63aa Binary files /dev/null and b/pics/654.最大二叉树.png differ diff --git a/problems/0017.电话号码的字母组合.md b/problems/0017.电话号码的字母组合.md index d1358c70..e5e3dac9 100644 --- a/problems/0017.电话号码的字母组合.md +++ b/problems/0017.电话号码的字母组合.md @@ -97,4 +97,8 @@ public: }; ``` +# 拓展 + +请问为什么 getCombinations(const string& digits, int index, const string& s)函数里的string& s 前要加const,不加的报错 + > 更多算法干货文章持续更新,可以微信搜索「代码随想录」第一时间围观,关注后,回复「Java」「C++」 「python」「简历模板」「数据结构与算法」等等,就可以获得我多年整理的学习资料。 diff --git a/problems/0042.接雨水.md b/problems/0042.接雨水.md index 349d4102..4a166ae3 100644 --- a/problems/0042.接雨水.md +++ b/problems/0042.接雨水.md @@ -6,41 +6,103 @@ // 找左面最大的, 找右边最大的,找左右边际的时候容易迷糊。我已开始还找左大于右的。 (还不够) // 每次记录单条,不要记录整个面积 -# C++代码 +## 暴力解法 + +这道题目暴力解法并不简单,我们来看一下思路。 + +首先要明确,要按照行来计算,还是按照列来计算。如图所示: + + + + + +一些同学在实现暴力解法的时候,很容易一会按照行来计算一会按照列来计算,这样就会越写越乱。 + +我个人倾向于按照列来计算,比较容易理解,接下来看一下按照列如何计算。 + +首先,**如果按照列来计算的话,宽度一定是1了,我们再把每一列的雨水的高度求出来就可以了。** + +可以看出每一列雨水的高度,取决于,该列 左侧最高的柱子和右侧最高的柱子中最矮的那个柱子的高度。 + +这句话可以有点绕,来举一个理解,例如求列4的雨水高度,如图: + + + +列4 左侧最高的柱子是列3,高度为2(以下用lHeight表示)。 + +列4 右侧最高的柱子是列7,高度为3(以下用rHeight表示)。 + +列4 柱子的高度为1(以下用height表示) + +那么列4的雨水高度为 列3和列7的高度最小值减列4高度,即: min(lHeight, rHeight) - height。 + +列4的雨水高度求出来了,宽度为1,相乘就是列4的雨水体积了。 + +此时求出了列4的雨水体积。 + +一样的方法,只要从头遍历一遍所有的列,然后求出每一列雨水的体积,相加之后就是总雨水的体积了。 + +首先从头遍历所有的列,并且**要注意第一个柱子和最后一个柱子不接雨水**,代码如下: +``` +for (int i = 0; i < height.size(); i++) { + // 第一个柱子和最后一个柱子不接雨水 + if (i == 0 || i == height.size() - 1) continue; +} +``` + +在for循环中求左右两边最高柱子,代码如下: + +``` +int rHeight = height[i]; // 记录右边柱子的最高高度 +int lHeight = height[i]; // 记录左边柱子的最高高度 +for (int r = i + 1; r < height.size(); r++) { + if (height[r] > rHeight) rHeight = height[r]; +} +for (int l = i - 1; l >= 0; l--) { + if (height[l] > lHeight) lHeight = height[l]; +} +``` + +最后,计算该列的雨水高度,代码如下: + +``` +int h = min(lHeight, rHeight) - height[i]; +if (h > 0) sum += h; // 注意只有h大于零的时候,在统计到总和中 +``` + +整体代码如下: -按照列来 ``` class Solution { public: int trap(vector& height) { int sum = 0; -// for (int i = 0; i < height.size(); i++) { -// cout << height[i] << " "; -// } -// cout << endl; for (int i = 0; i < height.size(); i++) { + // 第一个柱子和最后一个柱子不接雨水 if (i == 0 || i == height.size() - 1) continue; - int lIndex, rIndex; - int rValue = height[i]; - int lValue = height[i]; + int rHeight = height[i]; // 记录右边柱子的最高高度 + int lHeight = height[i]; // 记录左边柱子的最高高度 for (int r = i + 1; r < height.size(); r++) { - if (height[r] > rValue) { - rValue = height[r]; - rIndex = r; - } + if (height[r] > rHeight) rHeight = height[r]; } for (int l = i - 1; l >= 0; l--) { - if (height[l] > lValue) { - lValue = height[l]; - lIndex = l; - } + if (height[l] > lHeight) lHeight = height[l]; } - int h = min(lValue, rValue) - height[i]; - // 我为啥要算 (rIndex - lIndex + 1);就是按照行 按照列 区分不清啊 + int h = min(lHeight, rHeight) - height[i]; if (h > 0) sum += h; } return sum; } }; ``` + +因为每次遍历列的时候,还要向两边寻找最高的列,所以时间复杂度为O(n^2)。 +空间复杂度为O(1)。 + + +# 单调栈 + +单调栈究竟如何做呢,得画一个图,不太好理解 + +按照列来算的,遇到相同的怎么办。 diff --git a/problems/0141.环形链表.md b/problems/0141.环形链表.md index 4198ba4a..bdb12c5b 100644 --- a/problems/0141.环形链表.md +++ b/problems/0141.环形链表.md @@ -19,6 +19,11 @@ fast和slow各自再走一步, fast和slow就相遇了 这是因为fast是走两步,slow是走一步,**其实相对于slow来说,fast是一个节点一个节点的靠近slow的**,所以fast一定可以和slow重合。 +动画如下: + + + + ## C++代码如下 diff --git a/problems/0142.环形链表II.md b/problems/0142.环形链表II.md index 35b323d4..27c61bf9 100644 --- a/problems/0142.环形链表II.md +++ b/problems/0142.环形链表II.md @@ -43,6 +43,11 @@ fast和slow各自再走一步, fast和slow就相遇了 这是因为fast是走两步,slow是走一步,**其实相对于slow来说,fast是一个节点一个节点的靠近slow的**,所以fast一定可以和slow重合。 +动画如下: + + + + ## 如果有环,如何找到这个环的入口 @@ -83,6 +88,11 @@ fast指针走过的节点数:` x + y + n (y + z)`,n为fast指针在环内走 让index1和index2同时移动,每次移动一个节点, 那么他们相遇的地方就是 环形入口的节点。 +动画如下: + + + + 那么 n如果大于1是什么情况呢,就是fast指针在环形转n圈之后才遇到 slow指针。 其实这种情况和n为1的时候 效果是一样的,一样可以通过这个方法找到 环形的入口节点,只不过,index1 指针在环里 多转了(n-1)圈,然后再遇到index2,相遇点依然是环形的入口节点。 diff --git a/problems/0654.最大二叉树.md b/problems/0654.最大二叉树.md index 0ea30f35..64efda5c 100644 --- a/problems/0654.最大二叉树.md +++ b/problems/0654.最大二叉树.md @@ -1,19 +1,35 @@ ## 题目地址 https://leetcode-cn.com/problems/maximum-binary-tree/ +> 用数组构建二叉树都是一样的套路 + +# 654.最大二叉树 + +给定一个不含重复元素的整数数组。一个以此数组构建的最大二叉树定义如下: + +* 二叉树的根是数组中的最大元素。 +* 左子树是通过数组中最大值左边部分构造出的最大二叉树。 +* 右子树是通过数组中最大值右边部分构造出的最大二叉树。 + +通过给定的数组构建最大二叉树,并且输出这个树的根节点。 + +示例 : + + + +提示: + +给定的数组的大小在 [1, 1000] 之间。 + ## 思路 最大二叉树的构建过程如下: - + -典型的递归问题,依然按照递归三部曲来分析: +构造树一般采用的是前序遍历,因为先构造中间节点,然后递归构造左子树和右子树。 -* 确定递归函数的参数和返回值 -* 确定终止条件 -* 确定单层递归的逻辑 - -### 确定递归函数的参数和返回值 +* 确定递归函数的参数和返回值 参数就是传入的是存放元素的数组,返回该数组构造的二叉树的头结点,返回类型是指向节点的指针。 @@ -23,9 +39,11 @@ https://leetcode-cn.com/problems/maximum-binary-tree/ TreeNode* constructMaximumBinaryTree(vector& nums) ``` -### 确定终止条件 +* 确定终止条件 -题目中说了输入的数组大小一定是大于等于1的,所以我们不用考虑小于1的情况,那么当递归遍历的时候,如果传入的数组大小为1,说明遍历到了具体节点了,那么应该定义一个新的节点,并把这个数组的数值赋给新的节点,然后返回这个节点。 这表示一个数组大小是1的时候,构造了一个新的节点,并返回。 +题目中说了输入的数组大小一定是大于等于1的,所以我们不用考虑小于1的情况,那么当递归遍历的时候,如果传入的数组大小为1,说明遍历到了叶子节点了。 + +那么应该定义一个新的节点,并把这个数组的数值赋给新的节点,然后返回这个节点。 这表示一个数组大小是1的时候,构造了一个新的节点,并返回。 代码如下: @@ -36,11 +54,12 @@ if (nums.size() == 1) {     return node; } ``` -### 确定单层递归的逻辑 + +* 确定单层递归的逻辑 这里有三步工作 -1. 先要找到数组中最大的值和对应的下表, 最大的值就是根节点 +1. 先要找到数组中最大的值和对应的下表, 最大的值构造根节点,下表用来下一步分割数组。 代码如下: ``` @@ -82,8 +101,6 @@ if (maxValueIndex < (nums.size() - 1)) { ``` 这样我们就分析完了,整体代码如下:(详细注释) -## C++代码 - ``` class Solution { public: @@ -105,7 +122,7 @@ public: node->val = maxValue; // 最大值所在的下表左区间 构造左子树 if (maxValueIndex > 0) { - vector newVec(nums.begin(), nums.begin() + maxValueIndex); + vector newVec(nums.begin(), nums.begin() + maxValueIndex); node->left = constructMaximumBinaryTree(newVec); } // 最大值所在的下表右区间 构造右子树 @@ -117,4 +134,86 @@ public: } }; ``` + +以上代码比较冗余,效率也不高,每次还要切割的时候每次都要定义新的vector(也就是数组),但逻辑比较清晰。 + +和文章[二叉树:构造二叉树登场!](https://mp.weixin.qq.com/s/7r66ap2s-shvVvlZxo59xg)中一样的优化思路,就是每次分隔不用定义新的数组,而是通过下表索引直接在原数组上操作。 + +优化后代码如下: + +``` +class Solution { +private: + // 在左闭右开区间[left, right),构造二叉树 + TreeNode* traversal(vector& nums, int left, int right) { + if (left >= right) return nullptr; + + // 分割点下表:maxValueIndex + int maxValueIndex = left; + for (int i = left + 1; i < right; ++i) { + if (nums[i] > nums[maxValueIndex]) maxValueIndex = i; + } + + TreeNode* root = new TreeNode(nums[maxValueIndex]); + + // 左闭右开:[left, maxValueIndex) + root->left = traversal(nums, left, maxValueIndex); + + // 左闭右开:[maxValueIndex + 1, right) + root->right = traversal(nums, maxValueIndex + 1, right); + + return root; + } +public: + TreeNode* constructMaximumBinaryTree(vector& nums) { + return traversal(nums, 0, nums.size()); + } +}; +``` + +# 拓展 + +可以发现上面的代码看上去简洁一些,**主要是因为第二版其实是允许空节点进入递归,所以不用在递归的时候加判断节点是否为空** + +第一版递归过程:(加了if判断,为了不让空节点进入递归) +``` + +if (maxValueIndex > 0) { // 这里加了判断是为了不让空节点进入递归 + vector newVec(nums.begin(), nums.begin() + maxValueIndex); + node->left = constructMaximumBinaryTree(newVec); +} + +if (maxValueIndex < (nums.size() - 1)) { // 这里加了判断是为了不让空节点进入递归 + vector newVec(nums.begin() + maxValueIndex + 1, nums.end()); + node->right = constructMaximumBinaryTree(newVec); +} +``` + +第二版递归过程: (如下代码就没有加if判断) + +``` +root->left = traversal(nums, left, maxValueIndex); + +root->right = traversal(nums, maxValueIndex + 1, right); +``` + +第二版代码是允许空节点进入递归,所以没有加if判断,当然终止条件也要有相应的改变。 + +第一版终止条件,是遇到叶子节点就终止,因为空节点不会进入递归。 + +第二版相应的终止条件,是遇到空节点,也就是数组区间为0,就终止了。 + + +# 总结 + +这道题目其实和 [二叉树:构造二叉树登场!](https://mp.weixin.qq.com/s/7r66ap2s-shvVvlZxo59xg) 是一个思路,比[二叉树:构造二叉树登场!](https://mp.weixin.qq.com/s/7r66ap2s-shvVvlZxo59xg) 还简单一些。 + +**注意类似用数组构造二叉树的题目,每次分隔尽量不要定义新的数组,而是通过下表索引直接在原数组上操作,这样可以节约时间和空间上的开销。** + +一些同学也会疑惑,什么时候递归函数前面加if,什么时候不加if,这个问题我在最后也给出了解释。 + +其实就是不同代码风格的实现,**一般情况来说:如果让空节点(空指针)进入递归,就不加if,如果不让空节点进入递归,就加if限制一下, 终止条件也会相应的调整。** + + + > 更多算法干货文章持续更新,可以微信搜索「代码随想录」第一时间围观,关注后,回复「Java」「C++」 「python」「简历模板」「数据结构与算法」等等,就可以获得我多年整理的学习资料。 diff --git a/video/141.环形链表.gif b/video/141.环形链表.gif new file mode 100644 index 00000000..5e5308aa Binary files /dev/null and b/video/141.环形链表.gif differ diff --git a/video/141.环形链表.mp4 b/video/141.环形链表.mp4 new file mode 100644 index 00000000..19a7f223 Binary files /dev/null and b/video/141.环形链表.mp4 differ diff --git a/video/142.环形链表II.mp4 b/video/142.环形链表II.mp4 new file mode 100644 index 00000000..b6ced104 Binary files /dev/null and b/video/142.环形链表II.mp4 differ diff --git a/video/142.环形链表II1.mp4 b/video/142.环形链表II1.mp4 new file mode 100644 index 00000000..317ce246 Binary files /dev/null and b/video/142.环形链表II1.mp4 differ diff --git a/video/654.最大二叉树.gif b/video/654.最大二叉树.gif new file mode 100644 index 00000000..3baa8158 Binary files /dev/null and b/video/654.最大二叉树.gif differ