重新梳理算法,看labuladong 相关文章,有感,记录
算法
- 提到算法,其实主要可以拆解为两个部分
- 数据结构本身,用于解决对于数据的增删改查
- 而所谓的算法,就是更高效,自动化穷举, 比如 .1 递归,动态规划,回溯.
- 穷举的关键点在于无遗漏和无冗余
- 那么常用的数据结构,具体划分其实只有两种 ;1. 数组,链表
- 队列和栈是用数组或者链表模拟实现的
- 常用的树结构,可以通过指针指向,模拟左右子树.
那么从根源上来讲
数组: 从访问的角度,o1 内存连续,可通过索引来定位,,插入,删除的角度,on,因为需要进行后续内容移动
链表: 访问的角度为on 删除的角度为o1 操作,只需要进行指针修改,并且从单个节点角度看,相比于单个数组元素,消耗空间更大,因为还要进行存储前后指针.
那么从数据结构操作的角度来看
- 就是对于数据增删改查--> ?
- 从遍历和访问的角度来看,就是 线性和非线性,
- 循环就是线性,递归一类就是非线性
那既然算法的目标就是穷举
那么到底怎么穷举呢
无遗漏,无冗余
无遗漏,就是对于边界情况要规避
无冗余,就是要重复利用已知条件进行剪枝,比如回溯算法
回溯强调遍历
动态规划强调 分解,
动态规划系列问题的解题过程,无非就是先写出暴力穷举解法(状态转移方程),加个备忘录就成自顶向下的递归解法了,再改一改就成自底向上的递推迭代解法
穷举的核心是数学归纳法,明确函数的定义,分解问题,然后利用这个定义递归求解子问题
比如入门刷的数组,链表算法考察的就是如何高效的穷举
通过双指针,减少遍历时间复杂度
对于搜索: 对于有序数组,通过二分算法来进行搜索从on--ologn
对于滑动窗口: 就是通过快慢指针来解决,避免双重for 循环,在On 下进行遍历
对于二叉树
1 两种思维 :遍历的思维, 分解的思维
对于树的深度, 可以遍历获取,对于树中节点值的加总,可以遍历一遍求解
var maxDepth = function(root) {
// 记录最大深度
let res = 0;
// 记录当前遍历节点的深度
let depth = 0;
// 主函数
function traverse(root) {
if (root === null) {
// 到达叶子节点
res = Math.max(res, depth);
return;
}
// 前序遍历位置
depth++;
traverse(root.left);
traverse(root.right);
// 后序遍历位置
depth--;
}
traverse(root);
return res;
};
那么对于回溯 ,其实核心代码就是遍历一颗多叉树
if (track.length === nums.length) {
res.push(track.slice());
return;
}
for (let i = 0; i < nums.length; i++) {
// 排除不合法的选择
if (used[i]) {
// nums[i] 已经在 track 中,跳过
continue;
}
// 做选择
track.push(nums[i]);
used[i] = true;
// 进入递归树的下一层
backtrack(nums);
// 取消选择
track.pop();
used[i] = false;
}
以一个分解的思路来看二叉树前序遍历
// 定义:输入一棵二叉树的根节点,返回这棵树的前序遍历结果
var preorder = function(root) {
var res = [];
if (root === null) {
return res;
}
// 前序遍历的结果,root.val 在第一个
res.push(root.val);
// 后面接着左子树的前序遍历结果
res = res.concat(preorder(root.left));
// 最后接着右子树的前序遍历结果
res = res.concat(preorder(root.right));
return res;
};