问题:其中actor_id表示用户id,referrer_id表示推荐人id。
给定一个用户ID,如何查找这个用户的“最终推荐人”?
1. 如何理解“递归”?
字面意思:去的过程,递,回来的过程,归。
2. 递归需要满足的三个条件
- 1.一个问题的解可以分解为几个子问题的解
- 2.这个问题与分解之后的子问题,除了数据规模不同,求解思路完全一样
- 3.存在递归终止条件
3.如何编写递归代码?
写出递推公式,找到终止条件。
例子:假如这里有n个台阶,每次你可以跨1个台阶或者2个台阶,请问走这n个台阶有多少种走法?
递推公式:
分成两类,可以是一步一个台阶,也可以是一步两个台阶。
f(n) = f(n-1) + f(n-2)
终止条件
如果是一个台阶,只有一种走法,f(1) = 1, 如果是两个台阶,f(2) = f(1) + f(0), f(0) = 1, 不符合情况。所以终止条件是f(1) = 1,f(2) = 2.
提示:
如果一个问题A可以分解为若干子问题B、C、D,你可以假设子问题B、C、D已经解决,在此基础上思考如何解决问题A。而且,你只需要思考问题A与子问题B、C、D两层之间的关系即可,不需要一层一层往下思考子问题与子子问题,子子问题与子子子问题之间的关系。屏蔽掉递归细节,这样子理解起来就简单多了。
4. 递归代码要警惕堆栈溢出
系统栈或者虚拟机栈空间一般都不大。如果递归求解的数据规模很大,调用层次很深,一直压入栈,就会有堆栈溢出的风险。
如何避免出现堆栈溢出:限制递归调用的最大深度的方式
5. 递归代码要警惕重复计算
可以用散列表保存已经求解的值。
另外的问题:函数调用的数量较大,有客观的时间成本;空间复杂度高。
6. 怎么将递归代码改写为非递归代码?
递归的都可以改成非递归。
之前迈台阶的:其实就是模拟递归。
int f(int n) {
if (n == 1) return 1;
if (n == 2) return 2;
int ret = 0;
int pre = 2;
int prepre = 1;
for (int i = 3; i <= n; ++i) {
ret = pre + prepre;
prepre = pre;
pre = ret;
}
return ret;
}
7. 解答开篇
long findRootReferrerId(long actorId) {
Long referrerId = select referrer_id from [table] where actor_id = actorId;
if (referrerId == null) return actorId;
return findRootReferrerId(referrerId);
}
问题:
- 数据量大,存在堆栈溢出问题
- 脏数据问题,A->B->C->A. 环的存在,导致死循环。