# 概念
简单来说,当我们看到一个函数反复调用它自己的时候,递归就发生了。“递归”就意味着“反复”。
# 模版
- 递归终止条件 
- 处理当前层逻辑 
- 下探到下一层(层数标记) 
- 清理当前层状态(可选) 
# 思维要点
- 不要人肉进行递归(最大误区) 
- 找到最近最简方法,将其拆解成可重复解决到问题(找最近重复子问题) 
- 数学归纳法思维 
# 斐波那契数列(简单) (opens new window)
- 递归
var fib =function (n){
    if(n<2){
        return n
    }
    return fib(n-1)+fib(n-2)
}
//优化 
var fib =function (n,memory=[]){
    if(n<2){
        return n
    }
    if(!memory[n]){
        memory[n]=fib(n-1,memory)+fib(n-2,memory)
    }
    return memory[n]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- 动态规划解法
var = fib =function (n){
    if(n<=1){
        return n
    }
    let i =1
    let pre =0 
    let current =1
    let result =0
    while(i++ <n){
        result =pre +current
        pre = current
        current =result
    }
    return result
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 青蛙跳台阶(简单) (opens new window)
- 递归
var numWays = function(n) {
if (n <= 1) return 1;
  if (n === 2) return 2;
  return (numWays(n - 1) + numWays(n - 2)) % 1000000007;
};
1
2
3
4
5
2
3
4
5
- 计算备忘录时间
var numWays = function (n) {
  if (!n || n === 1) return 1;
  let a = 1; //临时保存n-2的值
  let b = 2; //临时保存n-1的值
  let result = n === 2 ? 2 : 0;
  for (let i = 3; i <= n; i++) {
    result = (a + b) % 1000000007;
    a = b;
    b = result;
  }
  return result;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
# 扁平化嵌套列表迭代器(中等) (opens new window)
- DFS
嵌套的整型列表是一个树形结构,树上的叶子节点对应一个整数,非叶节点对应一个列表。
在这棵树上深度优先搜索的顺序就是迭代器遍历的顺序。
我们可以先遍历整个嵌套列表,将所有整数存入一个数组,然后遍历该数组从而实现 next 和 hasNext 方法
var NestedIterator = function(nestedList) {
    vals = [];
    const dfs = (nestedList) => {
        for (const nest of nestedList) {
            if (nest.isInteger()) {
                vals.push(nest.getInteger());
            } else {
                dfs(nest.getList());
            }
        }
    }
    dfs(nestedList);
};
NestedIterator.prototype.hasNext = function() {
    return vals.length > 0;
};
NestedIterator.prototype.next = function() {
    const val = vals[0];
    vals = vals.slice(1);
    return val;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 字符串解码(中等) (opens new window)
n[string] 表示解析 [] 模板里面的内容,然后重复 n 次,即得到 n 个 string 拼接起来的字符串。
根据题意,[] 里面还可以再嵌套 [] ,例如 n[m[string]]。这种情况下,我们得先解析最内层的模板,重复 m 次,然后将 m * string 的结果作为外层模板的解析内容,再重复 n 次。
如果嵌套的层数更多,我们也是得先找到最内层的 [],就像洋葱一样,一层层剥开,然后再从内到外一层层解析和拼接。这种层层嵌套的描述很容易就让人想到了递归。
按照常规,写递归时不要过多考虑前后的递归函数,想好当前递归函数需要处理些什么就好了。
- 在每个递归函数里我们处理一段没有嵌套模版的字符串。
- 遇到英文字母时,进行简单的拼接。
- 遇到数字时,累加数字。
- 遇到 [ 时,说明接下来是一个嵌套模版,开始递归处理。递归处理嵌套模版结束后,我们回到了当前递归层级,需要的信息有两个:
- 嵌套模版的解码字符串
- 嵌套模版的结束坐标
- 获得嵌套模版的解码字符串后,重复 n 次拼接到当前层级的解码字符串中,接着从嵌套模版的结束坐标开始继续处理当前层级的字符串。
- 遇到 ] 时,说明处理当前层级模版的递归可以结束了,返回上一种情况需要的信息,解码字符串以及模版结束的坐标。
const decodeString =function (s,cur =0){
    let k = 0;
    let decoded=''
    while (cur< s.length){
        if(s[cur]==='['){
            const [str ,pos] =decodeString(s,cur+1);
            decoded += str.repeat(k);
            k=0;
            cur=pos;
        }else if(s[cur]===']'){
            return [decoded , cur +1]
        } else if (/[0-9]/.test(s[cur])) {
            k = k * 10 + parseInt(s[cur++]);
        } else {
            decoded +=s[cur++];
        }       
    }
    return decoded
}
//Time: O(N)O(N),N 是解码后字符串的长度。
//Space: O(N)O(N)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
- 循环+栈
/**
 * @param {string} s
 * @return {string}
 */
var decodeString = function (s) {
    const reg = /[a-zA-Z]+|[0-9]+|\[|\]/g;
    const stack = [];
    const peek = () => stack[stack.length - 1]; // p.s. 这是一个不正经的栈
    while (reg.lastIndex < s.length) {
        let token = reg.exec(s)[0];
        if (token !== ']') {
            // 数字,字母,左括号通通入栈
            stack.push(token);
        } else {
            // 遇到右括号就开始出栈
            let str = '';
            // [] 中间的就是要重复的模式,把它们全部出栈,拼接起来
            while (peek() !== '[') {
                str = stack.pop() + str;
            }
            // 丢掉左括号
            stack.pop();
            // 左括号前面的一定是模式重复的次数
            const num = +stack.pop();
            // 把复制操作后的字符串放回栈中,作为外层 [] 模式的一部分
            stack.push(str.repeat(num));
        }
    }
    return stack.join('');
};
//Time: O(N)O(N),N 是解码后字符串的长度。
//Space: O(N)O(N)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35