Unverified Commit 9d1aa27e authored by Angel_Kitty's avatar Angel_Kitty Committed by GitHub
Browse files

Merge pull request #1893 from AngelKitty/master

Format some words
parents 79041bdf 6004d9b5
Loading
Loading
Loading
Loading
+8 −8
Original line number Diff line number Diff line
author: fudonglai
author: fudonglai, AngelKitty

首先简单阐述一下递归,分治算法,动态规划,贪心算法这几个东西的区别和联系,心里有个印象就好。

@@ -27,11 +27,11 @@ int func(传入数值) {
}
```

其实仔细想想, **递归运用最成功的是什么?我认为是数学归纳法。** 我们高中都学过数学归纳法,使用场景大概是:我们推不出来某个求和公式,但是我们试了几个比较小的数,似乎发现了一点规律,然后了一个公式,看起来应该是正确答案。但是数学是很严谨的,你哪怕穷举了一万个数都是正确的,但是第一万零一个数正确吗?这就要数学归纳法发挥神威了,可以假设我们的这个公式在第 k 个数时成立,如果证明在第 k + 1 时也成立,那么我们的这个公式就是正确的。
其实仔细想想, **递归运用最成功的是什么?我认为是数学归纳法。** 我们高中都学过数学归纳法,使用场景大概是:我们推不出来某个求和公式,但是我们试了几个比较小的数,似乎发现了一点规律,然后猜想了一个公式,看起来应该是正确答案。但是数学是很严谨的,你哪怕穷举了一万个数都是正确的,但是第一万零一个数正确吗?这就要数学归纳法发挥神威了,可以假设我们猜想的这个公式在第 k 个数时成立,如果证明在第 k + 1 时也成立,那么我们猜想的这个公式就是正确的。

那么数学归纳法和递归有什么联系?我们刚才说了,递归代码必须要有结束条件,如果没有的话就会进入无穷无尽的自我调用,直到内存耗尽。而数学证明的难度在于,你可以尝试有穷种情况,但是难以将你的结论延伸到无穷大。这里就可以看出联系了——无穷。

递归代码的精髓在于调用自去解决规模更小的子问题,直到到达结束条件;而数学归纳法之所以有用,就在于不断把我们的猜测向上加一,扩大结论的规模,没有结束条件,从而把结论延伸到无穷无尽,也就完成了猜测正确性的证明。
递归代码的精髓在于调用自去解决规模更小的子问题,直到到达结束条件;而数学归纳法之所以有用,就在于不断把我们的猜测向上加一,扩大结论的规模,没有结束条件,从而把结论延伸到无穷无尽,也就完成了猜测正确性的证明。

### 为什么要写递归

@@ -137,13 +137,13 @@ int count(TreeNode node, int sum) {

题目看起来很复杂吧,不过代码却极其简洁,这就是递归的魅力。我来简单总结这个问题的 **解决过程**

首先明确,递归求解树的问题必然是要遍历整棵树的,所以 **二叉树的遍历框架** (分别对左右孩子递归调用函数本身)必然要出现在主函数 pathSum 中。那么对于每个节点,们应该干什么呢?们应该看看,自己和脚底下的小弟们包含多少条符合条件的路径。好了,这道题就结束了。
首先明确,递归求解树的问题必然是要遍历整棵树的,所以 **二叉树的遍历框架** (分别对左右孩子递归调用函数本身)必然要出现在主函数 pathSum 中。那么对于每个节点,们应该干什么呢?们应该看看,自己和脚底下的小弟们包含多少条符合条件的路径。好了,这道题就结束了。

按照前面说的技巧,根据刚才的分析来定义清楚每个递归函数应该做的事:

PathSum 函数:给一个节点和一个目标值,返回以这个节点为根的树中,和为目标值的路径总数。
PathSum 函数:给一个节点和一个目标值,返回以这个节点为根的树中,和为目标值的路径总数。

count 函数:给一个节点和一个目标值,返回以这个节点为根的树中,能凑出几个以该节点为路径开头,和为目标值的路径总数。
count 函数:给一个节点和一个目标值,返回以这个节点为根的树中,能凑出几个以该节点为路径开头,和为目标值的路径总数。

```cpp
/* 有了以上铺垫,详细注释一下代码 */
@@ -167,7 +167,7 @@ int count(TreeNode node, int sum) {
}
```

还是那句话, **明白每个函数能做的事,并相信们能够完成。** 
还是那句话, **明白每个函数能做的事,并相信们能够完成。** 

总结下,PathSum 函数提供的二叉树遍历框架,在遍历中对每个节点调用 count 函数,看出先序遍历了吗(这道题什么序都是一样的);count 函数也是一个二叉树遍历,用于寻找以该节点开头的目标值路径。好好体会吧!

@@ -200,6 +200,6 @@ void merge_sort(一个数组) {
}
```

好了,这个算法也就这样了,完全没有任何难度。记住之前说的,相信函数的能力,传给半个数组,那么这半个数组就已经被排好了。而且你会发现这不就是个二叉树遍历模板吗?为什么是后序遍历?因为我们分治算法的套路是 **分解 -> 解决(触底)-> 合并(回溯)** 啊,先左右分解,再处理合并,回溯就是在退栈,就相当于后序遍历了。至于 `merge` 函数,参考两个有序链表的合并,简直一模一样。
好了,这个算法也就这样了,完全没有任何难度。记住之前说的,相信函数的能力,传给半个数组,那么这半个数组就已经被排好了。而且你会发现这不就是个二叉树遍历模板吗?为什么是后序遍历?因为我们分治算法的套路是 **分解 -> 解决(触底)-> 合并(回溯)** 啊,先左右分解,再处理合并,回溯就是在退栈,就相当于后序遍历了。至于 `merge` 函数,参考两个有序链表的合并,简直一模一样。

LeetCode 上有分治算法的专项练习, [点这里去做题](https://leetcode.com/tag/divide-and-conquer/)