diff --git a/README.en.md b/README.en.md index b72e58a14..06467a36c 100644 --- a/README.en.md +++ b/README.en.md @@ -112,7 +112,9 @@ The data structures mainly includes: #### Easy +- [0001.TwoSum](./problems/1.TwoSum.md)🆕 - [0020.Valid Parentheses](./problems/20.validParentheses.md) +- [0021.MergeTwoSortedLists](./problems/21.MergeTwoSortedLists.md) 🆕 - [0026.remove-duplicates-from-sorted-array](./problems/26.remove-duplicates-from-sorted-array.md) - [0053.maximum-sum-subarray](./problems/53.maximum-sum-subarray-en.md) 🆕 - [0088.merge-sorted-array](./problems/88.merge-sorted-array.md) @@ -136,9 +138,10 @@ The data structures mainly includes: - [0263.ugly-number](./problems/263.ugly-number.md) - [0283.move-zeroes](./problems/283.move-zeroes.md) - [0342.power-of-four](./problems/342.power-of-four.md) +- [0371.sum-of-two-integers](./problems/371.sum-of-two-integers.md) - [0349.intersection-of-two-arrays](./problems/349.intersection-of-two-arrays.md) - [0437.path-sum-iii](./problems/437.path-sum-iii.md) 🆕 -- [0371.sum-of-two-integers](./problems/371.sum-of-two-integers.md) +- [0455.AssignCookies](./problems/455.AssignCookies.md) 🆕 - [0501.find-mode-in-binary-search-tree](./problems/501.Find-Mode-in-Binary-Search-Tree.md) 🆕 - [0575.distribute-candies](./problems/575.distribute-candies.md) @@ -149,7 +152,9 @@ The data structures mainly includes: - [0005.longest-palindromic-substring](./problems/5.longest-palindromic-substring.md) - [0011.container-with-most-water](./problems/11.container-with-most-water.md) - [0015.3-sum](./problems/15.3-sum.md) +- [0017.Letter-Combinations-of-a-Phone-Number](./problems/17.Letter-Combinations-of-a-Phone-Number.md) 🆕 - [0019. Remove Nth Node From End of List](./problems/19.removeNthNodeFromEndofList.md) +- [0022.GenerateParentheses](./problems/22.GenerateParentheses.md) 🆕 - [0024. Swap Nodes In Pairs](./problems/24.swapNodesInPairs.md) - [0029.divide-two-integers](./problems/29.divide-two-integers.md) - [0031.next-permutation](./problems/31.next-permutation.md) @@ -227,6 +232,7 @@ The data structures mainly includes: - [0025.reverse-nodes-in-k-group](./problems/25.reverse-nodes-in-k-groups-en.md) 🆕 - [0032.longest-valid-parentheses](./problems/32.longest-valid-parentheses.md) 🆕 - [0042.trapping-rain-water](./problems/42.trapping-rain-water.md) +- [0052.N-Queens-II](./problems/52.N-Queens-II.md) 🆕 - [0124.binary-tree-maximum-path-sum](./problems/124.binary-tree-maximum-path-sum.md) - [0128.longest-consecutive-sequence](./problems/128.longest-consecutive-sequence.md) - [0145.binary-tree-postorder-traversal](./problems/145.binary-tree-postorder-traversal.md) diff --git a/README.md b/README.md index 64acac7e6..2b1e402e9 100644 --- a/README.md +++ b/README.md @@ -122,7 +122,9 @@ leetcode 题解,记录自己的 leetcode 解题之路。 #### 简单难度 +- [0001.TwoSum](./problems/1.TwoSum.md) 🆕 - [0020.Valid Parentheses](./problems/20.validParentheses.md) +- [0021.MergeTwoSortedLists](./problems/21.MergeTwoSortedLists.md) 🆕 - [0026.remove-duplicates-from-sorted-array](./problems/26.remove-duplicates-from-sorted-array.md) - [0053.maximum-sum-subarray](./problems/53.maximum-sum-subarray-cn.md) 🆕 - [0088.merge-sorted-array](./problems/88.merge-sorted-array.md) @@ -147,8 +149,9 @@ leetcode 题解,记录自己的 leetcode 解题之路。 - [0283.move-zeroes](./problems/283.move-zeroes.md) - [0342.power-of-four](./problems/342.power-of-four.md) - [0349.intersection-of-two-arrays](./problems/349.intersection-of-two-arrays.md) -- [0437.path-sum-iii](./problems/437.path-sum-iii.md) 🆕 - [0371.sum-of-two-integers](./problems/371.sum-of-two-integers.md) +- [0437.path-sum-iii](./problems/437.path-sum-iii.md) 🆕 +- [0455.AssignCookies](./problems/455.AssignCookies.md) 🆕 - [0501.find-mode-in-binary-search-tree](./problems/501.Find-Mode-in-Binary-Search-Tree.md)🆕 - [0575.distribute-candies](./problems/575.distribute-candies.md) - [0874.walking-robot-simulation](./problems/874.walking-robot-simulation.md) 🆕 @@ -162,7 +165,9 @@ leetcode 题解,记录自己的 leetcode 解题之路。 - [0005.longest-palindromic-substring](./problems/5.longest-palindromic-substring.md) - [0011.container-with-most-water](./problems/11.container-with-most-water.md) - [0015.3-sum](./problems/15.3-sum.md) +- [0017.Letter-Combinations-of-a-Phone-Number](./problems/17.Letter-Combinations-of-a-Phone-Number.md) 🆕 - [0019. Remove Nth Node From End of List](./problems/19.removeNthNodeFromEndofList.md) +- [0022.GenerateParentheses](./problems/22.GenerateParentheses.md) 🆕 - [0024. Swap Nodes In Pairs](./problems/24.swapNodesInPairs.md) - [0029.divide-two-integers](./problems/29.divide-two-integers.md) - [0031.next-permutation](./problems/31.next-permutation.md) @@ -265,6 +270,7 @@ leetcode 题解,记录自己的 leetcode 解题之路。 - [0030.substring-with-concatenation-of-all-words](./problems/30.substring-with-concatenation-of-all-words.md) - [0032.longest-valid-parentheses](./problems/32.longest-valid-parentheses.md) - [0042.trapping-rain-water](./problems/42.trapping-rain-water.md) +- [0052.N-Queens-II](./problems/52.N-Queens-II.md) 🆕 - [0084.largest-rectangle-in-histogram](./problems/84.largest-rectangle-in-histogram.md) 🆕 - [0085.maximal-rectangle](./problems/85.maximal-rectangle.md) - [0124.binary-tree-maximum-path-sum](./problems/124.binary-tree-maximum-path-sum.md) diff --git a/problems/1.TwoSum.md b/problems/1.TwoSum.md new file mode 100644 index 000000000..2f8299473 --- /dev/null +++ b/problems/1.TwoSum.md @@ -0,0 +1,50 @@ +## 题目地址 +https://leetcode-cn.com/problems/two-sum + +## 题目描述 +``` +给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。 + +你可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。 + +示例: + +给定 nums = [2, 7, 11, 15], target = 9 + +因为 nums[0] + nums[1] = 2 + 7 = 9 +所以返回 [0, 1] +``` +## 思路 +最容易想到的就是暴力枚举,我们可以利用两层 for 循环来遍历每个元素,并查找满足条件的目标元素。不过这样时间复杂度为 O(N^2),空间复杂度为 O(1),时间复杂度较高,我们要想办法进行优化。我们可以增加一个 Map 记录已经遍历过的数字及其对应的索引值。这样当遍历一个新数字的时候去 Map 里查询,target 与该数的差值是否已经在前面的数字中出现过。如果出现过,那么已经得出答案,就不必再往下执行了。 + +## 关键点 + +- 求和转换为求差 +- 借助 Map 结构将数组中每个元素及其索引相互对应 +- 以空间换时间,将查找时间从 O(N) 降低到 O(1) + +## 代码 +* 语言支持:JS + +```js +/** + * @param {number[]} nums + * @param {number} target + * @return {number[]} + */ +const twoSum = function (nums, target) { + const map = new Map(); + for (let i = 0; i < nums.length; i++) { + const diff = target - nums[i]; + if (map.has(diff)) { + return [map.get(diff), i]; + } + map.set(nums[i], i); + } +} +``` + +***复杂度分析*** + +- 时间复杂度:O(N) +- 空间复杂度:O(N) diff --git a/problems/17.Letter-Combinations-of-a-Phone-Number.md b/problems/17.Letter-Combinations-of-a-Phone-Number.md new file mode 100644 index 000000000..3845eb7e5 --- /dev/null +++ b/problems/17.Letter-Combinations-of-a-Phone-Number.md @@ -0,0 +1,77 @@ +## 题目地址 +https://leetcode-cn.com/problems/letter-combinations-of-a-phone-number + +## 题目描述 +``` +给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。 + +给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。 + +示例: + +输入:"23" +输出:["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"]. + +说明: +尽管上面的答案是按字典序排列的,但是你可以任意选择答案输出的顺序。 + +``` + +## 思路 +使用回溯法进行求解,回溯是一种通过穷举所有可能情况来找到所有解的算法。如果一个候选解最后被发现并不是可行解,回溯算法会舍弃它,并在前面的一些步骤做出一些修改,并重新尝试找到可行解。究其本质,其实就是枚举。 + +如果没有更多的数字需要被输入,说明当前的组合已经产生。 + +如果还有数字需要被输入: +- 遍历下一个数字所对应的所有映射的字母 +- 将当前的字母添加到组合最后,也就是 str + tmp[r] + +## 关键点 +利用回溯思想解题,在for循环中调用递归。 + +## 代码 +* 语言支持:JS + +```js +/** + * @param {string} digits + * @return {string[]} + */ +const letterCombinations = function (digits) { + if (!digits) { + return []; + } + const len = digits.length; + const map = new Map(); + map.set('2', 'abc'); + map.set('3', 'def'); + map.set('4', 'ghi'); + map.set('5', 'jkl'); + map.set('6', 'mno'); + map.set('7', 'pqrs'); + map.set('8', 'tuv'); + map.set('9', 'wxyz'); + const result = []; + + function generate(i, str) { + if (i == len) { + result.push(str); + return; + } + const tmp = map.get(digits[i]); + for (let r = 0; r < tmp.length; r++) { + generate(i + 1, str + tmp[r]); + } + } + generate(0, ''); + return result; +}; +``` + +***复杂度分析*** + +N + M 是输入数字的总数 + +- 时间复杂度:O(3^N * 4^M) +- 空间复杂度:O(3^N * 4^M) + diff --git a/problems/21.MergeTwoSortedLists.md b/problems/21.MergeTwoSortedLists.md new file mode 100644 index 000000000..86b5ce76d --- /dev/null +++ b/problems/21.MergeTwoSortedLists.md @@ -0,0 +1,63 @@ +## 题目地址 +https://leetcode-cn.com/problems/merge-two-sorted-lists + +## 题目描述 +``` +将两个升序链表合并为一个新的升序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。  + +示例: + +输入:1->2->4, 1->3->4 +输出:1->1->2->3->4->4 + +``` + +## 思路 +使用递归来解题,将两个链表头部较小的一个与剩下的元素合并,并返回排好序的链表头,当两条链表中的一条为空时终止递归。 + + +## 关键点 + +- 掌握链表数据结构 +- 考虑边界情况 + +## 代码 +* 语言支持:JS + +```js +/** + * Definition for singly-linked list. + * function ListNode(val) { + * this.val = val; + * this.next = null; + * } + */ +/** + * @param {ListNode} l1 + * @param {ListNode} l2 + * @return {ListNode} + */ +const mergeTwoLists = function (l1, l2) { + if (l1 === null) { + return l2; + } + if (l2 === null) { + return l1; + } + if (l1.val < l2.val) { + l1.next = mergeTwoLists(l1.next, l2); + return l1; + } else { + l2.next = mergeTwoLists(l1, l2.next); + return l2; + } +}; +``` + +***复杂度分析*** + +M、N 是两条链表 l1、l2 的长度 + +- 时间复杂度:O(M+N) +- 空间复杂度:O(M+N) + diff --git a/problems/22.GenerateParentheses.md b/problems/22.GenerateParentheses.md new file mode 100644 index 000000000..352f265e5 --- /dev/null +++ b/problems/22.GenerateParentheses.md @@ -0,0 +1,72 @@ +## 题目地址 +https://leetcode-cn.com/problems/generate-parentheses + +## 题目描述 +``` +数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。 + +示例: + +输入:n = 3 +输出:[ + "((()))", + "(()())", + "(())()", + "()(())", + "()()()" + ] + +``` + +## 思路 + +深度优先搜索(回溯思想),从空字符串开始构造,做加法。 + + +## 关键点 + +- 当 l < r 时记得剪枝 + + +## 代码 +* 语言支持:JS + +```js +/** + * @param {number} n + * @return {string[]} + * @param l 左括号已经用了几个 + * @param r 右括号已经用了几个 + * @param str 当前递归得到的拼接字符串结果 + * @param res 结果集 + */ +const generateParenthesis = function (n) { + const res = []; + + function dfs(l, r, str) { + if (l == n && r == n) { + return res.push(str); + } + // l 小于 r 时不满足条件 剪枝 + if (l < r) { + return; + } + // l 小于 n 时可以插入左括号,最多可以插入 n 个 + if (l < n) { + dfs(l + 1, r, str + '('); + } + // r < l 时 可以插入右括号 + if (r < l) { + dfs(l, r + 1, str + ')'); + } + } + dfs(0, 0, ''); + return res; +}; +``` + + +***复杂度分析*** + +- 时间复杂度:O(2^N) +- 空间复杂度:O(2^N) diff --git a/problems/455.AssignCookies.md b/problems/455.AssignCookies.md new file mode 100644 index 000000000..a0e38ca49 --- /dev/null +++ b/problems/455.AssignCookies.md @@ -0,0 +1,82 @@ +## 题目地址 +https://leetcode-cn.com/problems/assign-cookies + +## 题目描述 +``` +假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。对每个孩子 i ,都有一个胃口值 gi ,这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j ,都有一个尺寸 sj 。如果 sj >= gi ,我们可以将这个饼干 j 分配给孩子 i ,这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子,并输出这个最大数值。 + +注意: + +你可以假设胃口值为正。 +一个小朋友最多只能拥有一块饼干。 + +示例 1: + +输入: [1,2,3], [1,1] + +输出: 1 + +解释: + +你有三个孩子和两块小饼干,3个孩子的胃口值分别是:1,2,3。 +虽然你有两块小饼干,由于他们的尺寸都是1,你只能让胃口值是1的孩子满足。 +所以你应该输出1。 + +示例 2: + +输入: [1,2], [1,2,3] + +输出: 2 + +解释: + +你有两个孩子和三块小饼干,2个孩子的胃口值分别是1,2。 +你拥有的饼干数量和尺寸都足以让所有孩子满足。 +所以你应该输出2. +``` + +## 思路 +贪心算法+双指针求解 + +给一个孩子的饼干应当尽量小并且能满足孩子,大的留来满足胃口大的孩子。因为胃口小的孩子最容易得到满足,所以优先满足胃口小的孩子需求。按照从小到大的顺序使用饼干尝试是否可满足某个孩子。 + + +## 关键点 + +将需求因子 g 和 s 分别从小到大进行排序,使用贪心思想,配合双指针,每个饼干只尝试一次,成功则换下一个孩子来尝试。 + + +## 代码 +* 语言支持:JS + +```js +/** + * @param {number[]} g + * @param {number[]} s + * @return {number} + */ +const findContentChildren = function (g, s) { + g = g.sort((a, b) => a - b); + s = s.sort((a, b) => a - b); + let gi = 0; // 胃口值 + let sj = 0; // 饼干尺寸 + let res = 0; + while (gi < g.length && sj < s.length) { + // 当饼干 sj >= 胃口 gi 时,饼干满足胃口,更新满足的孩子数并移动指针 + if (s[sj] >= g[gi]) { + gi++; + sj++; + res++; + } else { + // 当饼干 sj < 胃口 gi 时,饼干不能满足胃口,需要换大的 + sj++; + } + } + return res; +}; +``` + +***复杂度分析*** + +- 时间复杂度:O(NlogN) +- 空间复杂度:O(1) diff --git a/problems/52.N-Queens-II.md b/problems/52.N-Queens-II.md new file mode 100644 index 000000000..e3175ea80 --- /dev/null +++ b/problems/52.N-Queens-II.md @@ -0,0 +1,83 @@ +## 题目地址 +https://leetcode-cn.com/problems/n-queens-ii + +## 题目描述 +``` +n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。 + +给定一个整数 n,返回 n 皇后不同的解决方案的数量。 + +示例: + +输入: 4 +输出: 2 +解释: 4 皇后问题存在如下两个不同的解法。 + +[ + [".Q..",  // 解法 1 +  "...Q", +  "Q...", +  "..Q."], + + ["..Q.",  // 解法 2 +  "Q...", +  "...Q", +  ".Q.."] +] +``` + +## 思路 +使用深度优先搜索配合位运算,二进制为 1 代表不可放置,0 相反 + +利用如下位运算公式: + +- x & -x :得到最低位的 1 代表除最后一位 1 保留,其他位全部为 0 +- x & (x-1):清零最低位的 1 代表将最后一位 1 变成 0 +- x & ((1 << n) - 1):将 x 最高位至第 n 位(含)清零 + +## 关键点 + +- 位运算 +- DFS(深度优先搜索) + +## 代码 +* 语言支持:JS + +```js +/** + * @param {number} n + * @return {number} + * @param row 当前层 + * @param cols 列 + * @param pie 左斜线 + * @param na 右斜线 + */ +const totalNQueens = function (n) { + let res = 0; + const dfs = (n, row, cols, pie, na) => { + if (row >= n) { + res++; + return; + } + // 将所有能放置 Q 的位置由 0 变成 1,以便进行后续的位遍历 + // 也就是得到当前所有的空位 + let bits = (~(cols | pie | na)) & ((1 << n) - 1); + while (bits) { + // 取最低位的1 + let p = bits & -bits; + // 把P位置上放入皇后 + bits = bits & (bits - 1); + // row + 1 搜索下一行可能的位置 + // cols | p 目前所有放置皇后的列 + // (pie | p) << 1 和 (na | p) >> 1) 与已放置过皇后的位置 位于一条斜线上的位置 + dfs(n, row + 1, cols | p, (pie | p) << 1, (na | p) >> 1); + } + } + dfs(n, 0, 0, 0, 0); + return res; +}; +``` +***复杂度分析*** + +- 时间复杂度:O(N!) +- 空间复杂度:O(N) diff --git a/thinkings/basic-data-structure.md b/thinkings/basic-data-structure.md index 0523f1bdb..402774be9 100644 --- a/thinkings/basic-data-structure.md +++ b/thinkings/basic-data-structure.md @@ -124,7 +124,7 @@ React 将`如何确保组件内部hooks保存的状态之间的对应关系`这 为了解决`HTTP/1.1`中的服务端队首阻塞,`HTTP/2`采用了`二进制分帧` 和 `多路复用` 等方法。 `二进制分帧`中,帧是`HTTP/2`数据通信的最小单位。在`HTTP/1.1`数据包是文本格式,而`HTTP/2`的数据包是二进制格式的,也就是二进制帧。采用帧可以将请求和响应的数据分割得更小,且二进制协议可以更高效解析。`HTTP/2`中,同域名下所有通信都在单个连接上完成,该连接可以承载任意数量的双向数据流。每个数据流都以消息的形式发送,而消息又由一个或多个帧组成。多个帧之间可以乱序发送,根据帧首部的流标识可以重新组装。 -`多路复用`用以替代原来的序列和拥塞机制。在`HTTP/1.1`中,并发多个请求需要多个 TCP 链接,且单个域名有 6-8 个 TCP 链接请求限制。在`HHTP/2`中,同一域名下的所有通信在单个链接完成,仅占用一个 TCP 链接,且在这一个链接上可以并行请求和响应,互不干扰。 +`多路复用`用以替代原来的序列和拥塞机制。在`HTTP/1.1`中,并发多个请求需要多个 TCP 链接,且单个域名有 6-8 个 TCP 链接请求限制。在`HTTP/2`中,同一域名下的所有通信在单个链接完成,仅占用一个 TCP 链接,且在这一个链接上可以并行请求和响应,互不干扰。 > [此网站](https://http2.akamai.com/demo)可以直观感受`HTTP/1.1`和`HTTP/2`的性能对比。 @@ -378,11 +378,11 @@ return, children, sibling 也都是一个 fiber,因此 fiber 看起来就是 #### AVL -是最早被发明的自平衡二叉查找树。在 AVL 树中,任一节点对应的两棵子树的最大高度差为 1,因此它也被称为高度平衡树。查找、插入和删除在平均和最坏情况下的时间复杂度都是 {\displaystyle O(\log {n})} O(\log{n})。增加和删除元素的操作则可能需要借由一次或多次树旋转,以实现树的重新平衡。AVL 树得名于它的发明者 G. M. Adelson-Velsky 和 Evgenii Landis,他们在 1962 年的论文 An algorithm for the organization of information 中公开了这一数据结构。 节点的平衡因子是它的左子树的高度减去它的右子树的高度(有时相反)。带有平衡因子 1、0 或 -1 的节点被认为是平衡的。带有平衡因子 -2 或 2 的节点被认为是不平衡的,并需要重新平衡这个树。平衡因子可以直接存储在每个节点中,或从可能存储在节点中的子树高度计算出来。 +是最早被发明的自平衡二叉查找树。在 AVL 树中,任一节点对应的两棵子树的最大高度差为 1,因此它也被称为高度平衡树。查找、插入和删除在平均和最坏情况下的时间复杂度都是 O(logn)。增加和删除元素的操作则可能需要借由一次或多次树旋转,以实现树的重新平衡。AVL 树得名于它的发明者 G. M. Adelson-Velsky 和 Evgenii Landis,他们在 1962 年的论文 An algorithm for the organization of information 中公开了这一数据结构。 节点的平衡因子是它的左子树的高度减去它的右子树的高度(有时相反)。带有平衡因子 1、0 或 -1 的节点被认为是平衡的。带有平衡因子 -2 或 2 的节点被认为是不平衡的,并需要重新平衡这个树。平衡因子可以直接存储在每个节点中,或从可能存储在节点中的子树高度计算出来。 #### 红黑树 -在 1972 年由鲁道夫·贝尔发明,被称为"对称二叉 B 树",它现代的名字源于 Leo J. Guibas 和 Robert Sedgewick 于 1978 年写的一篇论文。红黑树的结构复杂,但它的操作有着良好的最坏情况运行时间,并且在实践中高效:它可以在 {\displaystyle O(\log {n})} O(\log{n})时间内完成查找,插入和删除,这里的 n 是树中元素的数目 +在 1972 年由鲁道夫·贝尔发明,被称为"对称二叉 B 树",它现代的名字源于 Leo J. Guibas 和 Robert Sedgewick 于 1978 年写的一篇论文。红黑树的结构复杂,但它的操作有着良好的最坏情况运行时间,并且在实践中高效:它可以在 O(logn) 时间内完成查找,插入和删除,这里的 n 是树中元素的数目 ### 字典树(前缀树) diff --git a/thinkings/dynamic-programming.md b/thinkings/dynamic-programming.md index cf34290d1..aa88db8b3 100644 --- a/thinkings/dynamic-programming.md +++ b/thinkings/dynamic-programming.md @@ -186,6 +186,6 @@ f(n) = f(n-1) + f(n-2) 就是【状态转移公式】 本篇文章总结了算法中比较常用的两个方法 - 递归和动态规划。 -如果你只能借助一句话,那么请记住:`递归是从问题的结果倒推,直到问题的规模缩小到寻常。 动态规划是从寻常入手, 逐步扩大规模到最优子结构。` +如果你只能记住一句话,那么请记住:`递归是从问题的结果倒推,直到问题的规模缩小到寻常。 动态规划是从寻常入手, 逐步扩大规模到最优子结构。` 另外,大家可以去 LeetCode 探索中的 [递归 I](https://leetcode-cn.com/explore/orignial/card/recursion-i/) 中进行互动式学习。