最近少更了,入职了新公司,熟悉各类环境和业务需求,短暂时间内都没有抽出时间去总结和分享了;今天给你们分享下,我是如何复习算法编程题
来进行手撕代码
;java
谈起算法,它是解决某个问题的计算方法、步骤。为何许多公司都喜欢让手撕代码呢,更多考察的是写代码的能力和思惟吧;不过多赘述,开启咱们的算法复习之途吧;node
也许对于一些人来讲,是“学习”,由于本身在大学期间是有修过一些算法和数据结构的课程,切入点也是比较容易的;下面总结下如何切入到深刻;算法
其实我本身开始是在leetcode上进行大量的练习,典型的乱序练习,可是本身感受仍是记不住哈哈哈哈,老了,趁着元旦的三天假期,本身安排了复习计算,决定本身先本地总结一波,常见理论知识和常见题目,本地练习;编程
这里就略过概念的介绍了直接进入咱们经常使用到的算法编程题markdown
主要是本身进行本地练习,因此定义了前置的一些条件数据结构
class NodeTree{
constructor(val){
this.left = null //左子树
this.right = null //右子树
this.value= val; //存储的值
return this;
}
}
复制代码
根据根节点建立 根据节点建立 粗略符合左右树的差别oop
function createTree(){
this.root = null;
}
createTree.prototype.insertNode=function(node,newNode){
//判断新节点和根节点的大小
if(node.value > newNode.value){
if(node.left ==null){
node.left = newNode
}else{
//遍历左节点 进行查找
this.insertNode(node.left,newNode)
}
}else{
if(node.right == null){
node.right = newNode
}else{
this.insertNode(node.right,newNode)
}
}
}
createTree.prototype.insert=function(val){
let node = new NodeTree(val);
//若是没有根节点 则直接插入到根节点中
if(!this.root){
this.root = node;
}else{
//不然插入到左右元素中
this.insertNode(this.root,node)
}
}
复制代码
let array = [10,7,6,8,9,13,5,11,14]
let tree = new createTree();
array.forEach(item=>{
tree.insert(item)
})
复制代码
DLR--前序遍历(根在前,从左往右,一棵树的根永远在左子树前面,左子树又永远在右子树前面 )post
前序遍历的标准是,先遍历根节点,在遍历左子树,在遍历右子树,若是左子树中节点存在左节点,则存储当前左节点,继续遍历左节点的左子树则继续进行遍历;学习
function preorderTraversal(root){
//先遍历根节点
preOrderArray.push(root.value);
//遍历左子树
root.left && preorderTraversal(root.left)
//遍历右子树
root.right && preorderTraversal(root.right)
}
复制代码
function preorderTraversalNot(root){
let preOrderArrDir=[],stack =[];
let curr = root;
//先遍历根 左子树 左子树的子节点
while(curr!=null || stack.length>0){
//当前节点存在 则压入栈中
while(curr!=null){
//压入当前元素
stack.push(curr);
//存储当前的元素
preOrderArrDir.push(curr.value)
//将curr指向当前的左节点
curr = curr.left;
}
//判断是否为空,若是不为空 弹出已经遍历的元素,进行遍历右子树
if(stack.length !=0){
curr = stack.pop();
curr = curr.right;
}
}
return preOrderArrDir
}
复制代码
预期结果:优化
[5,6,7,8,9,10,11,13,14]
复制代码
let inOrderArray = []
function inorderTraversal(nodeTree){
if(!nodeTree) return;
//先遍历左节点
nodeTree.left && inorderTraversal(nodeTree.left)
//存储当前根节点
inOrderArray.push(nodeTree.value)
//在遍历右节点
nodeTree.right && inorderTraversal(nodeTree.right)
}
inorderTraversal(tree.root)
console.log(inOrderArray)
复制代码
function inorderTraversalNot(root){
let inOrderQueue = [];//存储遍历结果
let curr = root,stack =[];
while(curr!=null || stack.length>0){
//先遍历左子树 到叶子节点
while(curr!=null){
//逐个压入左节点
stack.push(curr);
curr = curr.left;
}
//进行出栈
if(stack.length>0){
//取出栈的最后一个元素 逐个是左子树排列的元素
curr = stack.pop();
//压入当前的元素,当前元素排列为左子树
inOrderQueue.push(curr.value)
//遍历当前节点的右子树
curr = curr.right;
}
}
return inOrderQueue
}
复制代码
实际输出
[ 5, 6, 7, 8, 9, 10, 11, 13, 14 ]
[ 5, 6, 7, 8, 9, 10, 11, 13, 14 ]
复制代码
预期输出
[5,6,9,8,7,11,14,13,10]
复制代码
let postOrderTrversal=[]
function postorderTraversal(root){
if(!root) return
//先左
if( root.left )
root.left && postorderTraversal(root.left)
//后右
root.right && postorderTraversal(root.right)
//在根
postOrderTrversal.push(root.value)
}
复制代码
定义两个栈,压入根元素,在压入左子树,在压入右子树,而后从后开始取出元素,后面的就是右子树,左子树和根元素
function postorderTraversalNot(root){
let postOrderArray =[];
let curr = root,stack1 = [],stack2=[];
if(curr){
stack1.push(curr)
}
// stack1 存储当前子树的目录结构
while(stack1.length > 0){
let curr = stack1.pop();//弹出当前元素
//压入stack2中 stack2每次都存入的是根节点 而后是左子树的根节点 一直到叶子节点
stack2.push(curr)
//存储当前的左子树
if(curr.left){
stack1.push(curr.left)
}
//存储当前的右子树
if(curr.right){
stack1.push(curr.right)
}
}
console.log(stack2)
//遍历stack2的变量 此时stack 存储的数据结构正好是 节点的 根左右节点z
while(stack2.length>0){
let curr = stack2.pop();
postOrderArray.push(curr || curr.value)
}
return postOrderArray;
}
let result = postorderTraversalNot(tree.root);
console.log(result)
复制代码
后续遍历的这个过程不是很好理解,所以画了一个图
属于广度优先搜索BFS,一层一层遍历,遍历的顺序以下图 弹出一个节点,访问,若左子节点或右子节点不为空,将其压入队列。
// 非递归
function bfsTree(root){
let quque =[],bfsQueue=[]; //存储遍历的元素
let curr = null;
quque.push(root)
while(quque.length>0){
curr = quque.pop();//取出最后一个元素
bfsQueue.push(curr.value)
//查看当前左边是否存在元素 存在则压入栈前面
if(curr.left) {
quque.unshift(curr.left)
}
//查看当前元素右边是否存在元素
if(curr.right){
quque.unshift(curr.right)
}
//queue存储的元素都是当前层的左子树的子树和右子树的子树
}
return bfsQueue;
}
let result = bfsTree (tree.root);
复制代码
给定一个二叉树,求节点的个数
求二叉树的个数,其实就是对二叉树的节点进行遍历的过程;
采用先序中序 后序的均可以
let node = 1;
function comNodeSum(root){
node ++ ;
root.left && comNodeSum(root.left)
root.right && comNodeSum(root.right)
}
复制代码
层次遍历进行计算
function codeNodeNotSum(root){
let node = 1;
let queue =[],curr = root;
queue.unshift(root) //存储根节点
while(queue.length>0){
curr = queue.pop(); //取出队列中的最后一个
node++ ;//计数当前的节点数量
if(curr.left){
queue.unshift(curr.left); //将左节点存入到前面
}
if(curr.right){
queue.unshift(curr.right); //右节点入队
}
}
return node;
}
let result =codeNodeNotSum (tree.root);
console.log(result)
复制代码
//递归计算树的深度
function maxDepTree(root){
let num = 0;
if(!root) return 0;
//取 左子树和右子树的最大高度 而后加上当前节点的1
num = Math.max(maxDepTree(root.left),maxDepTree(root.right)) +1;
return num;
}
let result = maxDepTree(tree.root)
console.log(result)
复制代码
//非递归 计算树的深度 层序遍历
function dpComTreeHeight(root){
if(!root) return 0
let currentNum = 1,//当前层的节点
nextNum = 0,//下一层的节点数目
depth = 0;//树的深度
let queue = [],curr=null;
queue.push(root);
while(queue.length >0){
curr = queue.pop();
currentNum --;
//收集左节点
if(curr.left){
queue.unshift(curr.left)
nextNum++;
}
//收集右节点
if(curr.right){
queue.unshift(curr.right)
nextNum ++ ;
}
//若是是当前该层的最后一个节点
if(currentNum == 0){
depth++;
currentNum = nextNum;
nextNum = 0;
}
}
return depth;
}
let depth =dpComTreeHeight(tree.root);
console.log(depth)
复制代码
思路
/** * 层序遍历 * @param {*} root 当前的树 * @param {*} key 当前底基层 */
function findKeyNumber(root,k){
if(!root || k==0) return 0
let currentNum = 1,
nextCurr =0,h=1;
let queue = [],
curr = null;
queue.push(root);
while(queue.length > 0){
if(k == h) return currentNum;
curr = queue.pop();
currentNum --
if(curr.left){
queue.unshift(curr.left)
nextCurr ++ ;
}
if(curr.right){
queue.unshift(curr.right)
nextCurr ++
}
//判断是否遍历完
if(currentNum == 0){
h++; //计数遍历的第几层
currentNum = nextCurr;
nextCurr = 0; //开始下一步步骤
}
}
return 0
}
let re = findKeyNumber(tree.root,4)
console.log(re)
复制代码
思路:
//递归
function isSametree(tree1,tree2){
if(!tree1 && !tree2){
return true;
}
if(!tree1 || !tree2){
return false
}
//两个值不相同
if(tree1.value != tree2.value){
return false;
}
//比较左子树和右子树
return isSametree(tree1.left,tree2.left) && isSametree(tree2.right,tree2.right)
}
复制代码
function isSametreeNot(tree1,tree2){
if(!tree1 && !tree2){
return true;
}else if(!tree1 || !tree2){
return false
}
let stack1 = [],stack2 =[];
let curr1 = tree1,curr2= tree2;
stack2.push(tree2);
stack1.push(tree1)
while(stack1.length >0 && stack2.length >0 ){
curr1 = stack1.pop() //取出对应节点1
curr2 = stack2.pop()
//比较是否相同
if(curr1==null && curr2==null){
continue
}else if(curr2!=null && curr1!=null && curr1.value ==curr2.value){
//当前节点相同,比较下一个
stack2.push(curr2.left)
stack2.push(curr2.right)
stack1.push(curr1.left)
stack1.push(curr1.right)
}else{
return false;
}
}
return true;
}
let result3 = isSametreeNot(tree2,tree)
console.log(result3)
console.log(isSametreeNot(tree2,null))
复制代码
它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,而且左右两个子树都是一棵平衡二叉树
思路
//递归
function isAsl (root){
if(!root) return true;
//计算左子树和右子树高度
let leftH = maxDepTree(root.left)
let rightH = maxDepTree(root.right)
//左右子树的因子大于1即树的高度太高
if(Math.abs(leftH-rightH)>1) return false;
//在进行比较左右子树
return isAsl(root.left) && isAsl(root.right)
}
复制代码
function createMirrorTree(root){
if(!root) return;
let newNode = new NodeTree(root.value);
//新节点左子树等于右子树
newNode.left = createMirrorTree(root.right)
//新节点右子树等于左子树
newNode.right = createMirrorTree(root.left)
return newNode
}
复制代码
function isMirrorTree(root1,root2){
if(root1==null && root2==null ) return true;
if(!root2 || !root1 ) return false;
if(root1.value != root2.value) return false;
return isMirrorTree(root1.left,root2.right) && isMirrorTree(root1.right,root2.left)
}
复制代码
function isMirrorTreeNot(root1,root2){
if(!root1 && !root2){
return true;
}else if(!root1 || !root2){
return false
}
let stack1=[],stack2 =[];
let curr1 = null,curr2= null;
stack1.push(root1);
stack2.push(root2)
while(stack2.length > 0 && stack1.length >0){
curr1 = stack1.pop();//取出当前节点的数据
curr2 = stack2.pop();
if(!curr1 && !curr2){
continue ;
}else if(curr2 && curr1 && curr1.value == curr2.value){
stack1.push(curr1.left) //现存root1左
stack1.push(curr1.right) //存root1 右
stack2.push(curr2.right) //先存root2 左
stack2.push(curr2.left) //存root2 左
}else{
return false;
}
//存栈的数据
}
return true;
}
复制代码
二分查找树特色 就是中序遍历是一个有序的列表
function isValidBST(root,pre){
//进行中序遍历
if(!root) return true;
let verLeft = isValidBST(root.left,pre)
if(!verLeft){
return false
}
//当前节点比前一个小,则不是递增的,则错误
if(root.value <=pre){
return false;
}
pre = root.value;
let verRight = isValidBST(root.right,pre)
if(!verRight) return false;
return true;
}
复制代码
给定两个树,判断B是不是A的子树
function hasSubTree(root1,root2){
if(!root1 || !root2) return false;
return isSubTree(root1,root2) || isSubTree(root1.left,root2) || isSubTree(root1.right,root2)
}
function isSubTree(root1,root2){
if(!root2) return true;
if(!root1) return false;
return root1.value !=root2.value ? false:
isSubTree(root1.left,root2.left) && isSubTree(root1.right,root2.right)
}
console.log(JSON.stringify(tree.root))
let result = hasSubTree(tree.root,null)
console.log(result)
复制代码
/* * function TreeNode(x) { * this.val = x; * this.left = null; * this.right = null; * } */
/** * * @param root TreeNode类 * @param o1 int整型 * @param o2 int整型 * @return int整型 */
function lowestCommonAncestor( root , o1 , o2 ) {
// write code here
if(!root) return -1;
/** 关键仍是找到最近公共节点的特征: 1. 若是该节点不是O1也不是O2,那么O1与O2必然分别在该节点的左子树和右子树中 2. 若是该节点就是O1或者O2,那么另外一个节点在它的左子树或右子树中 稍微能够优化的一点就是,遇到O1或者O2节点就不往下递归了,把O1或者O2节点一层层往上传。 */
if(o1==root.val || o2==root.val) return root.val;
let left = lowestCommonAncestor(root.left,o1,o2);
let right = lowestCommonAncestor(root.right,o1,o2);
if(left==-1) return right;
if(right==-1) return left;
return root.val;
}
module.exports = {
lowestCommonAncestor : lowestCommonAncestor
};
复制代码
本篇文章主要讲述了在算法复习过程当中的知识方法和二叉树的常见算法,学习方式因人而异,可是算法学习是一个锻炼的结果,共勉~