递归
树的面试题解法一般都是递归
1 节点的定义(树的节点和树本身数据结构的定义就是用递归方式进行的)
2 重复性(不仅树本身,二叉树以及搜索二叉树,比如二叉搜索树,它的左子树都要小于根节点,右子树都要大于根节点,且左右子树具有相同的特征)
def preorder(self, root):
if root:
self.traverse_path.append(root.val)
self.preorder(root.left)
self.preorder(root.right)
def inorder(self, root):
if root:
self.inorder(root.left)
self.traverse_path.append(root.val)
self.preorder(root.right)
def postorder(self, root):
if root:
self.postorder(root.left)
self.postorder(root.right)
self.traverse_path.append(root.val)
递归 Recursion: 递归本身就是循环,通过函数体来调用自己来进行循环
因为计算机本身用的汇编,而汇编有个特点,就是没有所谓的循环嵌套这么一说,很多时候用的
更多的是你之前有一段函数写在什么地方,或者一段指令写在什么地方,就会直接不断地反复跳到
之前的那段指令,不断的执行,其实这就是所谓的递归
循环本身编译处理后的汇编代码,其实和递归本身有异曲同工之处, 所以递归和循环没有明显的边界现实中的递归,现实生活中也有所谓的重复性,想到来自一部电影"盗梦空间" 就有归去来兮的感觉
主线:从飞机开始,然后到城市,再往下不断地递归, 最后在一个雪上的屋子里
每进入一层递归就是 一个新的世界,或者互不干扰的世界,在里面做些事情
再下到另外一层再做些事情, 如果要返回现实世界,必须一层一层再回来
每次下去和回来的时候自身发生改变(把自己的状态带到下一层,发生改变后再带回来)(参数)
但是环境里面的东西是不受影响, 下一层的环境不会影响这一层的环境和人物,那么主角
和主角相关的人是函数的参数盗梦空间 递归要点:
1 向下进入到不同梦境中:向上回到原来一层 (一层一层下, 一层一层回来,不能跳跃 对称性,归去来兮的感觉)
2 通过声音同步回到上一层(所谓同步就是用参数来进行函数不同层之间传递变量)
3 每一层环境和周围的人都是一份拷贝,也就是进入每一层的房子建筑,电脑,无关的人物,其实就是
创造了一份新 的世界,当吧这个世界全部打坏了,去到下一层和上一层它们的建筑都是不受影响的
主角可以穿越不同的梦境同时把自身和自己所有携带的东西,都可以带到不同梦境发送变化且可以把变化携带回来(类似于函数参数),也会有一些全局变量
计算n!
# n! = 1 * 2 * 3…n
def Factorial(n):
if n <=1
return 1;
return n * Factorial(n - 1)
递归运行方式递归栈,系统就给我们做了这样一个调用栈(一层一层展开,更像剥洋葱.类似于栈的形式,一层一层进去,再剥开),而栈本身就是递归调用的时候系统做了这样一个调用栈
递归代码模板
def recursion(level, param1, param2,…):
# recursion terminator
if level > MAX_LEVEL:
process_result
return
# process logic in current level
process(level, data…)
#drill down
self.recursion(level + 1, p1,…)
# reverse the current level status if needed (链表 p.next.next = p)
public void recur(int level, int param) {
// terminator
if (level > MAX_LEVEL) {
// process result
return;
}
// process current logic
process(level, param);
// drill down
recur(level+1, newPara…)
// restore current status
}
最后一部分,在这一层有些东西可能要清理,就清理下,有些时候可能不需要这一部,因为这一层很多时候,本身的环境是拷贝一份出来的,但也有很多时候会有一些全局变量要进行清理,就在最后这部分进行清理即可
public ListNode reverseList(ListNode head) {
// teminator
if (head = null || head.next == null) {
return head;
}
// process logic in current level and drill down
ListNode pre = reverseList(head.next);
// reverse the current level status if needed
head.next.next = head;
head.next = null;
return pre;
}
写递归,
第一部分,递归终止条件, 一定要先把递归终止条件写上(这点不注意就会造成结果是无线递归或者死循环)
第二部分, 处理当前层逻辑
第三部分, 下到下一层去 (用参数标记当前层是哪一层即将level变成 level + 1同时传递相应的参数)
第四部分: 清扫当前层(有些时候不需要清理,因为这一层本身环境是拷贝处理的,但很多时候会有一些全局变量进行清理)递归思维要点:
1 抵制人肉进行递归(最大误区) 应该直接看函数本身写 // 主要是针对熟练的人 初学者要花递归状态树
2 找最近重复性(找到最近最简方法,将其拆解成可重复解决的问题)
3 数学归纳法思维 (最开始条件成立,比如n = 1,n =2 的时候成立,且可以证明当n成立的时候可以推导出n+1也成立)
递归实战
爬楼梯
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
注意:给定 n 是一个正整数。
示例 1:
输入: 2
输出: 2
解释: 有两种方法可以爬到楼顶。
- 1 阶 + 1 阶
- 2 阶
示例 2:
输入: 3
输出: 3
解释: 有三种方法可以爬到楼顶。- 1 阶 + 1 阶 + 1 阶
- 1 阶 + 2 阶
- 2 阶 + 1 阶
傻递归
public int climStairs(int n) {
clim_Stair(0, n);
}
public int clim_Stair(int i, int n) {
// terminator
if (i > n) {return 0;}
if (i == n) {return 1;}
// process current level and drill down
return clim_Stair(i + 1, n) + clim_Stair(i + 2, n);
}
记忆化
public int climStairs(int n) {
int mem[] = new int[n + 1];
clim_Stair(0, n, mem);
}
public int clim_Stair(int i, int n, int[] mem) {
// terminator
if (i > n) {return 0;}
if (i == n) {return 1;}
if (mem[i] > 0) {
return mem[i];
}
// process current level and drill down
mem[i] = clim_Stair(i + 1, n, mem) + clim_Stair(i + 2, n, mem);
return mem[i]
}
动态规划
public int climStairs(int n) {
if (n == 1) {return 1;}
int dp[] = new int[n + 1];
dp[1] = 1;
dp[2] = 2;
for (int i = 3; i <= n; i++) {
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
}
public int climStairs(int n) {
if (n == 1) {return 1;}
int first = 1;
int second = 2;
for (int i = 3; i <= n; i++) {
second = first + second;
first = second - first;
}
return second;
}
#1: 1
#2: 2
#3: f(1) + f(2) mutual exclusive 互斥, complete exhausive 所有可能都包含
#4: f(2) + f(3)
#f(n) : f(n - 1) + f(n - 2) fibonacci
def climbStairs(self, n):
if (n <= 2): return n;
f1, f2, f3 = 1, 2, 3
for i in rang(3, n):
f3 = f2 + f1
f1 = f2
f2 = f3
return f3
括号生成
public List<String> generateParenthese(int n) {
// 总共2n个格子
_generate(0, 0, n, “”);
}
List<String> result = new ArrayList<String>();
private void _generate(int left, int right, int n, String s) {
// terminator
if (left == n && right == n) {
System.out.println(s);
result.add(s);
return;
}
// process current logic: left
if left < n {
// drill down
_generate(left + 1, right, n, s + “(”);
}
// process current logic: right
if (left > right && right < n) {
// drill down
_generate(left, right + 1, n, s + “)”);
}
// reverse states 不需要因为都是局部变量, 本地变量,自己清除
}
// left:随时可以加,只要别超标
// right: 必须之前有左括号,且左个数 > 右个数
验证二叉搜索树, 最近公共祖先, 二叉树的深度
BST(binary search tree) :中序遍历是递增的
理解起来一遍看不明白很正常(要反复看几次)这个过程要掌握
解题与学习过程
错误思路:不看左子树也不看右子树,只看了左节点和右节点,这个是错误的,不仅仅要看它的左儿子和右儿子和根节点的大小,还要看整个左子树右子树的大小
二叉树最大深度是什么(重复项)
最大深度来自两个地方
1 左子树深度 + 根
2 右子树深度 + 跟