Commit b9adeab7 authored by 24OI-bot's avatar 24OI-bot
Browse files

style: format markdown files with remark-lint

parent 371b9d80
Loading
Loading
Loading
Loading
+2 −3
Original line number Diff line number Diff line
@@ -8,4 +8,3 @@
算法流程就是记录每一个数出现了多少次,然后从小到大依次输出。

一般考虑的是某一范围内的整数,但是计数排序也可以和[离散化](/misc/discrete)一起使用,来对浮点数、大整数进行排序。
+11 −11
Original line number Diff line number Diff line
@@ -74,7 +74,7 @@ $$

#### Problem

[AtCoder Grand Contest 032 B](<https://atcoder.jp/contests/agc032/tasks/agc032_b>)
[AtCoder Grand Contest 032 B](https://atcoder.jp/contests/agc032/tasks/agc032_b)

#### Solution

@@ -94,7 +94,7 @@ $$

#### Problem

[BZOJ4971:[Lydsy1708月赛]记忆中的背包](<https://www.lydsy.com/JudgeOnline/problem.php?id=4971>)
[BZOJ4971:\[Lydsy1708 月赛\] 记忆中的背包](https://www.lydsy.com/JudgeOnline/problem.php?id=4971)

#### Solution

+64 −78
Original line number Diff line number Diff line
@@ -14,7 +14,7 @@

1.  如何给一堆数字排序?答:分成两半,先排左半边再排右半边,最后合并就行了,至于怎么排左边和右边,请重新阅读这句话。
2.  孙悟空身上有多少根毛?答:一根毛加剩下的毛。
3. 你今年几岁? 答:去年的岁数加一岁,1999 年我出生。
3.  你今年几岁?答:去年的岁数加一岁1999 年我出生。

递归代码最重要的两个特征:结束条件和自我调用。自我调用是在解决子问题,而结束条件定义了最简子问题的答案。

@@ -25,8 +25,6 @@ int func(传入数值) {
}
```



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

那么数学归纳法和递归有什么联系?我们刚才说了,递归代码必须要有结束条件,如果没有的话就会进入无穷无尽的自我调用,直到内存耗尽。而数学证明的难度在于,你可以尝试有穷种情况,但是难以将你的结论延伸到无穷大。这里就可以看出联系了——无穷。
@@ -58,7 +56,6 @@ void sort(Comparable[] a, int lo, int hi) {
    sort(a, mid + 1, hi);
    merge(a, lo, mid, hi);
}

```

看起来简洁漂亮是一方面,关键是 **可解释性很强** :把左半边排序,把右半边排序,最后合并两边。而非递归版本看起来不知所云,充斥着各种难以理解的边界计算细节,特别容易出 bug 且难以调试,人生苦短,我更倾向于递归版本。
@@ -79,8 +76,6 @@ public int size(Node head) {
}
```



### 写递归的技巧

我的一点心得是: **明白一个函数的作用并相信它能完成这个任务,千万不要试图跳进细节。** 千万不要跳进这个函数里面企图探究更多细节,否则就会陷入无穷的细节无法自拔,人脑能压几个栈啊。
@@ -100,18 +95,14 @@ void traverse(TreeNode* root) {
```cpp
void traverse(TreeNode* root) {
  if (root == nullptr) return;
    for (child : root->children)
        traverse(child);
  for (child : root->children) traverse(child);
}
```

至于遍历的什么前、中、后序,那都是显而易见的,对于 N 叉树,显然没有中序遍历。



以下 **详解 LeetCode 的一道题来说明** :给一棵二叉树,和一个目标值,节点上的值有正有负,返回树中和等于目标值的路径条数,让你编写 pathSum 函数:

```
    /* 来源于 LeetCode PathSum III: https://leetcode.com/problems/path-sum-iii/ */
    root = [10,5,-3,3,2,null,11,3,-2,null,1], sum = 8

@@ -128,7 +119,6 @@ Return 3. The paths that sum to 8 are:
    1.  5 -> 3
    2.  5 -> 2 -> 1
    3. -3 -> 11
```

```cpp
/* 看不懂没关系,底下有更详细的分析版本,这里突出体现递归的简洁优美 */
@@ -138,7 +128,8 @@ int pathSum(TreeNode root, int sum) {
}
int count(TreeNode node, int sum) {
  if (node == null) return 0;
    return (node.val == sum) + count(node.left, sum - node.val) + count(node.right, sum - node.val);
  return (node.val == sum) + count(node.left, sum - node.val) +
         count(node.right, sum - node.val);
}
```

@@ -158,7 +149,8 @@ int pathSum(TreeNode root, int sum) {
  if (root == null) return 0;
  int pathImLeading = count(root, sum);  // 自己为开头的路径数
  int leftPathSum = pathSum(root.left, sum);  // 左边路径总数(相信他能算出来)
    int rightPathSum = pathSum(root.right, sum); // 右边路径总数(相信他能算出来)
  int rightPathSum =
      pathSum(root.right, sum);  // 右边路径总数(相信他能算出来)
  return leftPathSum + rightPathSum + pathImLeading;
}
int count(TreeNode node, int sum) {
@@ -179,16 +171,12 @@ int count(TreeNode node, int sum) {

LeetCode 有递归专题练习,[点这里去做题](https://leetcode.com/explore/learn/card/recursion-i/)



### 递归优化

比较 naive 的递归实现可能递归次数太多,容易超时。

怎么优化呢?详见[搜索优化](/search/optimization)[记忆化搜索](/dp/memo/)



## 分治算法

 **归并排序** ,典型的分治算法;分治,典型的递归结构。
@@ -214,6 +202,4 @@ void merge_sort(一个数组) {

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



本文主要贡献者:[fudonglai](https://github.com/fudonglai)
+23 −26
Original line number Diff line number Diff line
@@ -69,8 +69,7 @@ fclose(stdin);
fclose(stdout);
```

!!! 注
    `printf/scanf/cin/cout`等函数默认使用 `stdin/stdout`,将 `stdin/stdout`重定向后,这些函数将输入 / 输出到被定向的文件
!!! 注 `printf/scanf/cin/cout` 等函数默认使用 `stdin/stdout` ,将 `stdin/stdout` 重定向后,这些函数将输入/输出到被定向的文件

### 模板

@@ -123,8 +122,6 @@ fclose(stdin);
fclose(stdout);
```



## C++ 的 `ifstream/ofstream` 文件输入输出流

### 使用方法
+5 −6
Original line number Diff line number Diff line
@@ -23,7 +23,7 @@

用排序法常见的情况是输入一个包含几个(一般一到两个)权值的数组,通过排序然后遍历模拟计算的方法求出最优值。

有些题的排序方法非常显然,如 [Luogu P1209 [USACO1.3]修理牛棚 Barn Repair](https://www.luogu.org/problemnew/show/P1209) 就是将输入数组差分后排序模拟求值。
有些题的排序方法非常显然,如[Luogu P1209 \[USACO1.3\] 修理牛棚 Barn Repair](https://www.luogu.org/problemnew/show/P1209)就是将输入数组差分后排序模拟求值。

然而有些时候很难直接一下子看出排序方法,比如[NOIP 2012 国王游戏](https://vijos.org/p/1779)就很容易凭直觉而错误地以 $a$ 或 $b$ 为关键字排序,过样例之后提交就发现 WA 了 QAQ。一个~~众所周知的~~常见办法就是尝试交换数组相邻的两个元素来 **推导** 出正确的排序方法。我们假设这题输入的俩个数用一个结构体来保存

@@ -89,7 +89,6 @@ struct uv {
~~看上去是不是很简单呢(这题高精度卡常……)~~  
如果看懂了就可以尝试下一道类似的题:[Luogu P2123 皇后游戏](https://www.luogu.org/problemnew/show/P2123)


## 后悔法

??? note " 例题[Luogu P2949\[USACO09OPEN\]工作调度 Work Scheduling](https://www.luogu.org/problemnew/show/P2949)"
Loading