1. 栈实现斐波那契
function fibStack (n) {
let arr = [n]
let val = 0
while (arr.length) {
let cur = arr.pop()
if (cur === 1 || cur === 2) {
val++
}
if (cur > 2) {
arr.push(cur - 1, cur - 2)
}
}
return val
}
2. KMP算法
2.1什么是KMP
字符串匹配,获取子字符串索引
2.2 为什么不用回退?
如果回溯后,不能保证前面的元素全部匹配就没有意义,如果回溯后,前面的内容全部匹配了,那么肯定可以通过移动j的方式实现。
i
a a a b c d e f g
a a b d
j
- 凡是j可以回溯的地方都被记录下来
- 任何一个可跳跃的地方next[j],都可以保证str[0, next[j]] 都是匹配的,next[j]的位置可以保证匹配的个数最多
2.3 当移动j的时候,如果不移动到next,可能匹配得更多吗?
不可能
首先必须明确,必须从子字符串的头开始的匹配才有意义, 移动到非next[j]的位置,假设有一部分前面的元素是匹配的(比如下面的例子,此时前面的123是匹配的),但是此时也是无意义的,因为此时整个字符串的末尾肯定最开始不匹配的的右边,123后面肯定有不匹配的内容,如果123后面的内容全部匹配,那么next[] 里面肯定包含对应的回溯位置
1 2 3 4 5 6 7 1 2 3 a b c d e f g h
1 2 3 4 5 6 7 1 2 3 a b d
1 2 3 4 5 6 7 1 2 3 a b d --> 移动后
2.4 实现代码
// 获取下一个要跳转的位置
function getNext (ps) {
var next = []
next[0] = -1 // 初始化为-1
let j = 0
let k = -1
while (j < ps.length - 1) {
if (k == -1 || ps[j] == ps[k]) {
next[j + 1] = k + 1
j++
k++
} else {
// 如果 ps[j] 和 ps[k] 不相等,要想求 j + 1的跳转值,先将指针移到next[k]的位置,
// 如果k移动到next[k]后,ps[j] == ps[k],那么立马就可以得出 next[j + 1]的位置
// 如果和第一个元素比较以后还是不相等,那么 k = next[k] 将等于 -1
k = next[k]
}
}
return next
}
// ts 主串;ps 子字符串
function KMP (ts, ps) {
let i = 0 // 主串的位置
let j = 0 // 模式串的位置
let next = getNext(ps) // next只与ps有关
while (i < ts.length && j < ps.length) {
if (j == -1 || ts[i] == ps[j]) {
// 当j为-1时,要移动的是i,当然j也要归0
i++
j++
} else {
j = next[j] // j回到指定位置,如果赋值后等于0,那么就比较ps[0]与ts[j]是否相等
}
}
if (j == ps.length) {
return i - j // 返回ps第一个字符所在的位置
} else {
return -1
}
}
3. 递归算法的空间复杂度
递归算法的空间复杂度 = 每次递归的空间复杂度 * 递归深度
理解:递归算法需要的额外空间就是递归所形成的栈,每次调用一个函数就会将新的内容压入栈内,单次递归需要的空间大小是相同的,所以是O(1),如果递归深度n,那么空间复杂度就是O(n)
4. 中序二叉树的线索化
// 线索二叉树
typedef struct ThreadNode {
ElemType data;
struct ThreadNode* lchild, * rchild; // 0 表示指向孩子 1表示指向前驱
int ltag, rtag;
} ThreadNode, *ThreadTree;
void InThread(ThreadTree p, ThreadTree pre) {
if (p != NULL) {
InThread(p->lchild, pre); // 递归,序列化左子树
}
if (p->lchild == NULL) {
p->lchild = pre;
p->ltag = 1;
}
if (pre != NULL && pre->rchild == NULL) {
pre->rchild = p; // 建立前驱结点的关系
pre->rtag = 1;
}
pre = p;
InThread(p->rchild, pre); // 递归,序列化右子树
}
void CreateInThread(ThreadTree T) {
ThreadTree pre = NULL;
if (T != NULL) {
InThread(T, pre);
pre->rchild = NULL; // 处理遍历的最后一个结点
pre->rtag = 1;
}
}
整体思路
- 按照递归中序的方式进行序列化,最左边的肯定在整个序列的最前面,所以它最先与pre确认关系。这儿的前驱后驱是指的中序遍历时对应的关系;
- 当左子树为 NULL 以后,就将当前节点设置为 p,然后确定与pre的关系;
- 然后设置当前p为pre,序列化右子树,因为是中序,所以当前的p肯定是右序的前驱;
- 通过rtag、ltag区分是子树还是线索