一、数组
二、字符串
三、链表(大多数组的思维可解)
删除:可利用找要删除的上一个进行 next.next或将当前保存成下一个在进行移除下一个
反转:可利用建立新的节点 原 head.next.next=head head.next=null
四:二叉树
(1)前序遍历:根左右
(2)中序遍历:左根右
(3)后序遍历:左右根
(4)层序遍历:一层层(封装一个每个节点遍历的方法)
// 前序后序中序的思路:按先后遍历的顺序递归
var inorderTraversal = function(root) { // 中序遍历:左根右
if(root){
return [...inorderTraversal(root.left),root.val,
...inorderTraversal(root.right)]
}else{
return []
}
};
// [1,null,2,3] [1,3,2]
// 层序遍历(动态规划:保存上一层的依次遍历)
var levelOrder = function(root) {
if(!root) {
return []
}
let res = []
dfs(root, 0) // 当前遍历到的节点、层数
return res
function dfs(root, index){//每层透传当前节点,层数,和输出数组
if(root){
if(!res[index]) res[index] = [] //初始化当前层数组
res[index].push(root.val)//当前节点加入当前层数组
dfs(root.left, index + 1)//index+1,递归左节点
dfs(root.right, index + 1)//index+1,递归右节点
}
}
};
(5)相同的树(利用自己递归,特殊一般情况考虑)
(6)对称二叉树(递归,左的左=右的右,自己定一个一层层判断左右是否相同)
(7)平衡二叉树(定义一个求左右子树高的函数,每个节点判断是否是平衡二叉树)
(8)翻转二叉树(递归左右,左右交换)
// 相同的树(左右都同,递归&&的关系)
var isSameTree = function(p, q) { // 完全相同节点的值及左右都同
if(p===null&&q===null){
return true
}
if(p===null&&q!==null){
return false
}
if(p!==null&&q===null){
return false
}
if(p.val!==q.val){
return false
}
return isSameTree(p.left,q.left)&&isSameTree(p.right,q.right)
};
// 对称二叉树(一棵树,每个节点的左=右) 写一个函数判断两函数是否相等
var isSymmetric = function(root) {
if(root===null){ // 空树
return true
}
return backFun(root.left,root.right)
function backFun(left,right){
// 还要进行特殊情况考虑,万一是空节点呢
if(left===null&&right===null){
return true
}
if(left===null&&right!==null){
return false
}
if(left!==null&&right===null){
return false
}
if(left.val!==right.val){
return false
}
return backFun(left.left,right.right)&&backFun(left.right,right.left)
}
};
// 平衡二叉树(一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1)
var isBalanced = function(root) { // 统计每个节点的左子树及右子树的高之差
if(root===null){ // 空树
return true
}
// 判断每个节点的最高深度
function maxDeep(node){
if(node===null){
return true
}
let left=maxDeep(node.left)
let right=maxDeep(node.right)
return Math.max(left,right)+1 // 跟节点也要计算
}
if(Math.abs(maxDeep(root.left)-maxDeep(root.right))>1){
return false
}else{
return isBalanced(root.left)&&isBalanced(root.right)
}
};
// 翻转二叉树也是镜像二叉树(递归拿到左子树和右子树存值,互换)
var invertTree = function(root) { // 每个节点的左右都交换
if(root===null){
return null
}
let left=invertTree(root.left) // 左树
let right=invertTree(root.right)
root.left=right
root.right=left
return root
};
(9)二叉树的最大深度
(10)二叉树的最小深度(要特殊情况考虑,因为取小的,只有左右都为null才算叶子节点)
// 二叉树的最大深度(只统计一次,找左右大者,每一层加一)
var maxDepth = function(root) {
if(root===null){
return 0
}
let leftDeep=maxDepth(root.left)
let rightDeep=maxDepth(root.right)
let deep=leftDeep>rightDeep?leftDeep:rightDeep
return deep+1
};
// 二叉树的最小深度
var minDepth = function(root) {
if(root===null){
return 0
}
if(root.left===null&&root.right===null){
return 1
}
if(root.left===null&&root.right!==null){
return minDepth(root.right)+1
}
if(root.right===null&&root.left!==null){
return minDepth(root.left)+1
}
return Math.min(minDepth(root.left),minDepth(root.right))+1
};
(11)将有序数组转为二叉搜索树(升序数组转为高度平衡的二叉搜索树)
(12)路径总和(判断根到叶子节点有无等于目标值的路径)
(13)二叉树的所有路径(递归:把它指向成左右根的连接)
(14)左叶子之和
// 将有序数组转为高度平衡的二叉搜索树(注意中间根节点不能重复)
var sortedArrayToBST = function(nums) { // 注意返回的是树形结构
// 特殊情况考虑
if(nums.length===0){
return null
}
if(nums.length===1){
return new TreeNode(nums[0])
}
let mid=parseInt(nums.length/2)
let root=new TreeNode(nums[mid])
root.left=sortedArrayToBST(nums.slice(0,mid))
root.right=sortedArrayToBST(nums.slice(mid+1))
return root
};
// 路径总和I(每遍历一层减去当前的值,遇到叶子节点判断是否为所需要值)
var hasPathSum = function(root, targetSum) {
// 思路:如果碰到叶子节点求一次判断
if(root===null){ // 空树
return false
}
if(root.left===null&&root.right===null){ // 是叶子节点
return root.val===targetSum
}
return hasPathSum(root.left,targetSum-root.val)||hasPathSum(root.right,targetSum-root.val)
};
// 二叉树的左右路径
var binaryTreePaths = function(root) {
if(root===null){
return []
}
if(root.left===null&&root.right===null){// 只有根节点
return [root.val.toString()]
}
// 一般情况
let left=binaryTreePaths(root.left)
let right=binaryTreePaths(root.right)
return left.concat(right).map((item)=>root.val+'->'+item)
};
// 左叶子之和(公共函数,每个节点的左是叶子节点)
var sumOfLeftLeaves = function(root) { // 左叶子指:它是做节点但自己没有左右节点
// 每个节点的左且是叶子节点
if(root===null){ // 空树
return 0
}
if(root.left===null&&root.right===null){ // 只有 根节点
return 0
}
let result=0
backFun(root)
return result
function backFun(root){
if(root===null){
return
}
if(root.left!==null&&root.left.right===null&&root.left.left===null){
result+=root.left.val
}
backFun(root.left)
backFun(root.right)
}
return result
};
(15)二叉搜索树中的众数(出现频率最高的,可能存在多个,按任意顺序数组返回)
(16)二叉搜索树中最小绝对差
(17)二叉树的直径
(18)二叉树的坡度
// 二叉搜索树中的众数(写一个函数遍历并将值存放在对象中)
var findMode = function(root) {
let obj=new Map()
function backFun(root){
if(!root){ // null值
return
}
if(obj[root.val]){
obj[root.val]++
}else{
obj[root.val]=1
}
backFun(root.left)
backFun(root.right)
}
backFun(root)
let max=0,result=[]
// 从对象中判断众数
for(let key in obj){
max=Math.max(Number(obj[key]),max)
}
for(let key in obj){
if(Number(obj[key])===max){
result.push(Number(key))
}
}
return result
};
// 二叉搜索树中最小绝对差(定义函数遍历所有节点值到数组,根据数组双循环拿到)
var getMinimumDifference = function(root) {
// 思路将节点值都拿出来,然后 数组动态规划判断
let item=[]
function backFun(root){
if(!root){
return
}
item.push(root.val)
backFun(root.left)
backFun(root.right)
}
backFun(root)
// 数组中最小绝对值差值
let minNumber=Number.MAX_VALUE
for(let i=0;i<item.length;i++){
for(let j=i+1;j<item.length;j++){
let cur=Math.abs(item[i]-item[j])
minNumber=Math.min(minNumber,cur)
}
}
return minNumber
};
// 二叉树直径
var diameterOfBinaryTree = function(root) {
let res = 0
depth(root)
return res
function depth (node) { // 返回子树的深度
if (!node) return 0 // 节点不存在返回0
let left = depth(node.left) // left为左子树的深度
let right = depth(node.right)//right 为右子树的深度
res = Math.max(left + right, res) //计算l+r 更新res
return Math.max(left, right)+1 //返回该节点为根的子树的深度
}
};
// 二叉树坡度()
var findTilt = function(root) {
// 累加整个坡度。定义一个函数求该节点的坡度 (左右差的绝对值)
let total=0
backFun(root)
return total
function backFun(root){
if(!root){
return 0
}
let left=backFun(root.left)
let right=backFun(root.right)
total+=Math.abs(left-right)
return left+right+root.val
}
};
(19)另一颗树的子树
(20)根据二叉树创建字符串
(21)合并二叉树
(22)二叉树的层平均值
// 另一颗树的子树(转成字符串进行判断是否包含)
var isSubtree = function(root, subRoot) {
return JSON.stringify(root).includes(JSON.stringify(subRoot))
};
// 根据二叉树创建字符串(递归前序遍历实现)
var tree2str = function(root) { // 前序遍历 跟左右,根据题意右为空括号可以删除
if(!root){ // 空树
return ''
}
if(root.left===null&&root.right===null){ // 只有跟节点
return `${root.val}`
}
if(root.left===null&&root.right!==null){ // 左 子树 为空
return `${root.val}()(${tree2str(root.right)})`
}
if(root.right===null&root.left!==null){
return `${root.val}(${tree2str(root.left)})`
}
// 正常左右都存在
return `${root.val}(${tree2str(root.left)})(${tree2str(root.right)})`
};
// 合并二叉树(只更新,值,左、右,将新的放在其中一个树中,更新)
var mergeTrees = function(root1, root2) {
// 特殊情况。
if(root1===null||root2===null){
return root1||root2
}
// 一般情况考虑 左右子树都存在,将合并到1上(更新值及左右)
root1.val=root1.val+root2.val
root1.left=mergeTrees(root1.left,root2.left)
root1.right=mergeTrees(root1.right,root2.right)
return root1
};
// 二叉树的层平均值(先层级编辑再求平均值)
var averageOfLevels = function(root) {
// 先进性层序遍历取值,在进行 求平均值
let res=[]
average(root,0)
function average(root,index){
if(root){
if(!res[index]){ // 初始化层级数组
res[index]=[]
}
res[index].push(root.val)
average(root.left,index+1)
average(root.right,index+1)
}
}
let result=[]
for(let i=0;i<res.length;i++){ // 求每项总和的平均值
let sum=res[i].reduce((a,b)=>a+b)
let ave=sum/(res[i].length)
result.push(ave)
}
return result
};
(23)两数之和IV(二叉搜索树中存在两数之和为目标值)
(24)二叉树中第二小的节点
(25)二叉搜索树中的搜索
(26)二叉搜索树中节点最小距离
// 两数之和(思路:写函数前序遍历,只拿有值,在进行遍历找值)
var findTarget = function(root, k) { // 思路前序遍历在遍历 求值
let result=[]
function iterator1(root){
if(root){
return [root.val,...iterator1(root.left),...iterator1(root.right)]
}else{
return []
}
}
result=iterator1(root)
for(let i=0;i<result.length;i++){
for(let j=i+1;j<result.length;j++){
if(result[i]+result[j]===k){
return true
break
}
}
}
return false
};
// 二叉树中第二小的节点
var findSecondMinimumValue = function(root) {
let result=[]
function backFun(root){
if(root){
return [root.val,...backFun(root.left),...backFun(root.right)]
}else{
return []
}
}
result=backFun(root)
result=Array.from(new Set(result))
if(result.length<2){
return -1
}else{
result.sort((a,b)=>a-b)
return result[1]
}
};
// 二叉搜索树中的搜索(实现找出目标节点的子树,根据搜索树的特性进行值大小怎么遍历判断)
var searchBST = function(root, val) { // 实质遍历找到相同的 值,返回该节点
// 特殊条件判断
if(!root){
return null
}
if(root.val===val){
return root
}
// 一般情况判断
return searchBST(val>root.val?root.right:root.left,val)
};
// 二叉搜索树中节点最小距离(任意两节点最小绝对差。遍历去重找最小差值)
var minDiffInBST = function(root) {
let result=[]
function backFun(root){
if(!root){
return []
}else{
return [root.val,...backFun(root.left),...backFun(root.right)]
}
}
result=backFun(root)
// 找绝对值最小的
let min=Number.MAX_VALUE
for(let i=0;i<result.length;i++){
for(let j=i+1;j<result.length;j++){
let cur=Math.abs(result[i]-result[j])
min=Math.min(min,cur)
}
}
return min
};
(28)叶子相似的树
(29)递增顺序搜索树
(30)二叉搜索树的范围和
(31)单值二叉树
// 叶子相似的树(两树拿到的叶子节点同)
var leafSimilar = function(root1, root2) { // 遍历叶子节点,从左到右塞入数组
// 分别后续遍历叶子节点
let result1=[],result2=[]
function backFun(root,arr){
if(!root){ // 空节点
return
}
if(root.left===null&&root.right===null){ // 是叶子 节点
arr.push(root.val)
}
backFun(root.left,arr)
backFun(root.right,arr)
}
backFun(root1,result1)
backFun(root2,result2)
return result1.join()===result2.join()
};
// 递增顺序搜索树(按中序遍历使最左是跟,每个节点只有右节点的递增树)
var increasingBST = function(root) { // 中序遍历所有的节点然后一次连接树(光右节点)
let result=[] // 中序遍历的数组
function backFun(root){
if(root){
backFun(root.left)
result.push(root.val)
backFun(root.right)
}
}
backFun(root)
let newRoot=new TreeNode(0) // 新的节点
let cur=newRoot
for(let i=0;i<result.length;i++){
cur.right=new TreeNode(result[i])
cur=cur.right
}
return newRoot.right
};
// 二叉搜索树的范围和(给出范围求在范围内的节点值和:先树遍历再求和)
var rangeSumBST = function(root, low, high) { // 包含临界值
// 思路先遍历
let result=[]
function backFun(root){
if(root){
if(root.val>=low&&root.val<=high){
result.push(root.val)
}
backFun(root.left)
backFun(root.right)
}
}
backFun(root)
let sum=result.reduce((a,b)=>a+b)
return sum
};
// 单值二叉树(所有节点值都相同)
var isUnivalTree = function(root) { // 思路:遍历过滤后只有一项
let result=[]
function backFun(root){
if(root){
result.push(root.val)
backFun(root.left)
backFun(root.right)
}
}
backFun(root)
let arr=Array.from(new Set(result))
return arr.length===1
};
(32)二叉树的堂兄弟节点
(33)从根到叶的二进制数之和
(34)找出克隆二叉树中的相同节点
(35)判断根结点是否等于子结点之和
(36)开幕式焰火
// 二叉树的堂兄弟节点(同一层级但父不同:层序遍历存储)
var isCousins = function(root, x, y) { // 利用层序遍历存储层和父节点
const getHeight = (root, prev, height) => {
if (!root) {
return
}
if (root.val === x) {
first.parent = prev
first.height = height
}
if(root.val===y){
second.parent = prev
second.height = height
}
// 递归左子树
getHeight(root.left, root, height + 1)
// 递归右子树
getHeight(root.right, root, height + 1)
}
let first = {}
let second = {}
getHeight(root, null, 0)
return first.height === second.height && first.parent !== second.parent
};
// 从根到叶的二进制数之和(难点:都要从根出发)
var sumRootToLeaf = function(root) {
let res=0 // 总的求和
const backFun=(root,sum)=>{
let tem=(sum<<1)+root.val // 二进制相加
if(root.left===null&&root.right===null){ // 到达叶子节点(存储当前分支答案)
res+=tem
return
}
// 没到叶子节点,继续往下遍历
if(root.left){
backFun(root.left,tem)
}
if(root.right){
backFun(root.right,tem)
}
}
backFun(root,0)
return res
};
// 找出克隆二叉树中的相同节点 (结构相同,同时遍历)
var getTargetCopy = function(original, cloned, target) {
// 思路遍历副本,找到值相同的节点
// 特殊情况考虑
if(!original||!cloned){
return null
}
if(original===target){
return cloned
}
return getTargetCopy(original.left,cloned.left,target)||getTargetCopy(original.right,cloned.right,target)
};
// 判断根结点是否等于子结点之和(根的值=左+右)
var checkTree = function(root) {
return root.val===root.left.val+root.right.val
};
// 开幕式焰火
var numColor = function(root) {
// 思路:遍历统计 或者先遍历出来去重 利用前序遍历 Set数据结构去重
let obj=new Map()
const backFun=(root)=>{
if(root){
if(obj[root.val]){
obj[root.val]++
}else{
obj[root.val]=1
}
backFun(root.left)
backFun(root.right)
}
}
backFun(root)
return Object.keys(obj).length
};
(37)二叉搜索树的最近公共祖先(给出其中两个节点,找最近的公共祖先)利用搜索树特性
// 二叉搜索树的最近公共祖先
//如果 p 和 q 都比当前节点小,那么显然 p 和 q 都在左子树,那么 LCA 在左子树
//如果 p 和 q 都比当前节点大,那么显然 p 和 q 都在右子树,那么 LCA 在右子树
//一旦发现 p 和 q 在当前节点的两侧,说明当前节点就是 LCA
var lowestCommonAncestor = function(root, p, q) {
if(root===null){ // 空树的情况
return null
}
if(p.val>q.val){ // 保证p<=q方便后续讨论判断
return lowestCommonAncestor(root,q,p)
}
// 一般情况讨论
// 1 在跟两侧
if(root.val>=p.val&&root.val<=q.val){ // 根节点在中心,说明就是最近祖先
return root
}
// 2、都在左侧
if(root.val>q.val){
return lowestCommonAncestor(root.left,p,q)
}
// 3、都在右侧
if(root.val<p.val){
return lowestCommonAncestor(root.right,p,q)
}
};