- 栈

1.栈的应用场景

场景一:十进制转二进制

  • 后出来的余数反而要排到前面
  • 把余数依次入栈,就可以实现倒序输出

场景二:有效的括号

  • 越靠前的左括号,对应的左括号越靠前。
  • 左括号入栈,右括号出栈,最后栈空就是合法的。

场景三:函数调用堆栈

  • 最后调用的函数,最先执行完。
  • js计解释器使用栈来控制调用顺序。

2.栈的实战

1. 有效括号

题目链接:20. 有效的括号 - 力扣(LeetCode) (leetcode-cn.com)

思路

  • 对于没有闭合的左括号,越靠后的左括号,对应的右括号越靠前
  • 满足后进先出,考虑用栈

解题步骤

  • 新建一个栈。
  • 扫描字符串,遇到左括号入栈,遇到和栈定括号类型匹配的右括号就出栈,类型不能匹配直接判定不合法。
  • 最后栈空了就合法,否则不合法
var isValid = function(s) {//优化点:如果字符串长度为奇数,直接返回falseif(s.length % 2 === 1) return false//1. 创建一个栈const stack = []for(let i=0;i<s.length;i++){const c = s[i]//2.1 判断是否为左括号,就进栈if(c === '(' || c === '{' || c === '['){stack.push(c);} else{//2.2 否则是右括号的话是否匹配相同的括号,匹配相同就出栈,不相同就进栈const t = stack[stack.length - 1]if((t === '(' && c ===')') ||(t === '{' && c ==='}') ||(t === '[' && c ===']') ){stack.pop()} else{return false}}}//3. 判断是否为空栈,如果是空栈就返回true,否则就返回falsereturn stack.length === 0;
};
复制代码

3. 栈的总结

  • 栈是一个后进先出的数据结构
  • JavaScript中没有栈,但可以用Array的所有功能。
  • 栈常用的操作:push(进栈)、pop出栈、stack[stack.length-1]

- 队列

  • 一个先进先出的数据结构。
  • JavaScript中没有队列,但可以用Array实现所有功能

1. 队列的应用场景

场景一:食堂排队打饭

  • 食堂只留一个窗口,排队打饭似春运。
  • 先进先出,保证有序。

场景二:js异步中的任务队列

  • js是单线程,无法同时处理异步中的并发任务。
  • 使用任务队列先后处理异步任务。

场景三: 计算最近请求次数

  • 有请求就入队,3000ms前发出的请求出队
  • 队列的长度就是最近请求次数
输入:inputs = [[],[1],[100],[3001],[3002]]
输出:[null,1,2,3]
复制代码

2. 队列实战

1. 最近请求的次数

题目链接:933. 最近的请求次数 - 力扣(LeetCode) (leetcode-cn.com)

思路

  • 越早发出的请求越早不在最近3000ms内
  • 满足先进先出,考虑用队列

解题步骤

  • 先建一个队列

  • 有请求就入队,把3000ms前发出的请求出队

  • 队列的长度就是最近请求次数

var RecentCounter = function() {this.queue = []
};/** * @param {number} t* @return {number}*/
RecentCounter.prototype.ping = function(t) {this.queue.push(t)while(this.queue[0] < t - 3000){this.queue.shift()}return this.queue.length;
};
复制代码

3. 队列总结

  • 队列是先进先出的一个数据结构。
  • JavaScript中没有队列,但可以Array实现的所有功能
  • 队列操作:push、shift、queue[0]

- 链表

1. 链表是什么

  • 多个元素组成的列表
  • 元素存储不连续,用next指针连在一起。

2. 数组和链表的区别

  • 数组:增删非首尾元素往往需要移动元素
  • 链表:增删非首尾元素,不需要移动元素,只需要改next的指向即可。

3. JS中的链表

  • JavaScript中没有链表
  • 可以用Object模拟链表
const a = {val:'a'}
const b = {val:'b'}
const c = {val:'c'}
const d = {val:'d'}
a.next = b;
b.next = c
c.next = d//遍历链表
let p = a;
while(p){console.log(p.val);p = p.next
}
复制代码

4.链表实战

1. 链表删除

题目链接:237. 删除链表中的节点 - 力扣(LeetCode) (leetcode-cn.com)

解题思路

  • 无法直接获取被删除的是上个节点。
  • 被删除节点转移到下一个节点。
var deleteNode = function(node) {node.val = node.next.val;node.next = node.next.next;
};
复制代码

2. 反转链表

题目链接:206. 反转链表 - 力扣(LeetCode) (leetcode-cn.com)

解题思路

  • 反转两个节点:将n+1的next指向n
  • 反转多个节点:双指针遍历链表,重复上述操作。

解题步骤

  • 双指针一前一后遍历链表
  • 反转双指针
var reverseList = function(head) {let p1 = headlet p2 = null  while(p1){const temp = p1.nextp1.next = p2p2 = p1p1 = temp}return p2
};
复制代码

3. 两数相加

题目链接:2. 两数相加 - 力扣(LeetCode) (leetcode-cn.com)

思路

  • 小学数学题,模拟相加操作
  • 需要遍历链表

解题步骤

  • 新建一个空链表
  • 遍历被相加的两个链表,模拟相加操作,将个位数追加到新链表上,将十位数追加到新链表上,将十位数留到下一位相加。
var addTwoNumbers = function (l1, l2)
{const l3 = new ListNode(0)let p1 = l1let p2 = l2let p3 = l3let carry = 0;while(p1 || p2){const v1 = p1 ? p1.val :0const v2 = p2 ? p2.val :0const val = v1 + v2 + carry;carry = Math.floor(val / 10)p3.next = new ListNode(val % 10);if(p1) p1 = p1.nextif(p2) p2 = p2.nextp3 = p3.next}if(carry){p3.next = new ListNode(carry)}return l3.next
};
复制代码

4. 删除排序链表的重复元素

题目链接:83. 删除排序链表中的重复元素 - 力扣(LeetCode) (leetcode-cn.com)

思路

  • 因为链表是有序的,所有重复元素一定会相邻
  • 遍历链表,如果发现当前元素和下个元素值相同,就删除下个元素值

解题步骤

  • 遍历链表发现当前元素和下个元素相同,就删除下个元素值
  • 遍历结束后,返回原链表头部
var deleteDuplicates = function(head) {let p = headwhile(p&&p.next){if(p.val === p.next.val){//移除掉后一项p.next = p.next.next} else{p = p.next}}return head
};
复制代码

5. 环形链表

题目链接:141. 环形链表 - 力扣(LeetCode) (leetcode-cn.com)

思路

  • 两个人在圆形操场上的起点同时起跑,速度快的人一定会超过慢的人一圈
  • 用一块一慢指针遍历链表,如果指针能够相逢,那么链表就是有圈。

解题步骤

  • 用一快一慢两个指针遍历链表,如果指针能够相逢,就返回true
  • 遍历结束后,还没有相逢就返回false
var hasCycle = function(head) {let p1 = head;let p2 = head;while(p1 && p2 && p2.next){p1 = p1.next;p2 = p2.next.next;if(p1 === p2) return true}return false
};
复制代码

6. js中的原型链

1. 原型链的简介

  • 原型链的本质是链表
  • 原型链的节点是各种原型对象(prototype)
  • 原型链通过__ proto __属性链接各种原型对象

2. 原型链长啥样

->代表__ proto __ 链接

  • obj -> Object.prototype -> null
  • func -> Fuction.prototype -> Object.prototype -> null
  • arr -> Array.prototype -> Object.prototype -> null

知识点

  1. 如果A沿着原型链能找到B.prototype,那么A instanceof B 为true

  2. 如果在A对象上没有找到x属性,那么会沿着原型链找x属性。

Object.prototype.x = 'x'
Function.prototype.y = 'y'
const func = () => {};
console.log(func.x);//x
console.log(func.y);//y
复制代码

手写一个instanceof

本题考查上述知识点1

解法:遍历A的原型链,如果找到B.prototype,返回true,否则返回false

const Myinstanceof = (A,B) =>{let p = Awhile(p){if(p === B.prototype){return true;}p = p.__proto__;}return false;
}
复制代码

7. 前端与链表:使用链表指正获取JSON的节点值

const json ={a:{b:{c:1}},d:{e:2}
}
const path = ['a','b','c']
let p = json
path.forEach(k=>{p = p[k];
})
//p:1
复制代码

5. 链表总结

  • 链表的元素存储不是连续的,之间通过next连接
  • JavaScript中没有链表,可以用Object模拟链表
  • 链表常用的操作:修改next、遍历链表
  • JS原型链也是一个链表,之间通过__ proto __连接
  • 使用链表可以获取JSON的节点值

- 集合

1. 集合是什么

  • 一种无序且唯一的数据结构。
  • ES6中有集合,名为Set。
  • 集合的常用去重操作:去重、去判断某元素是否在集合中、求交集

2. 集合实战

1. 集合的交集

题目链接:349. 两个数组的交集 - 力扣(LeetCode) (leetcode-cn.com)

思路

  • 求交集且无序唯一
  • 使用集合

解题步骤

  • 用集合对nums1去重
  • 遍历nums1,筛选nums2也包含的值
var intersection = function(nums1, nums2) {let set = new Set(nums1)let set2 = new Set(nums2)return [...set].filter(item => set2.has(item))
};
复制代码

3. Set操作

  • 使用Set对象:new、add、delete、has、size
  • 迭代Set:多种迭代方法、Set与Array互转、求交集/差集

具体操作可看如下代码:

let MySet = new Set();// add方法
MySet.add(1)
MySet.add(5)
MySet.add(5)
MySet.add('some text')
let o = {a: 1, b: 2}
MySet.add(o)
MySet.add({a:1,b:2})// has方法
const has = MySet.has('some text')// delete方法
MySet.delete(5)// 迭代方法
for(let item of MySet) console.log(item);
for(let item of MySet.keys()) console.log(item);
for(let item of MySet.values()) console.log(item);
for(let [key,value] of MySet.entries()) console.log(key,value);//Array和Set互转//Set转换Array
// 1. 赋值解构
const myArr1 = [...MySet]// 2. 数组Array.from方法
const myArr2 = Array.from(MySet)//Array转换Set
const MySet2 = new Set([1,2,3,4])//求交集和差集
//交集
const intersection = new Set([...MySet].filter(x => MySet2.has(x)))//差集
const difference = new Set([...MySet].filter(x => !MySet2.has(x)))复制代码

- 字典

1. 字典是什么?

  • 与集合类似,字典也是一种存储唯一值的数据结构,但它是以键值对的形式来存储
  • ES6中有字典,名为Map.
  • 字典的常用操作:键值对的增删改查。
const m = new Map()//增
m.set('a','aa')
m.set('b','bb')//删
m.delete('b') //根据键来删除
// m.clear() //全删//改
m.set('a','aaa')//查
m.get('a')
复制代码

2. 字典实战

1. 两个数组的交集

题目链接:349. 两个数组的交集 - 力扣(LeetCode) (leetcode-cn.com)

思路

  • 求nums1和nums2都有的值
  • 用字典建立一个映射关系,记录nums1里面的值。
  • 遍历nums2,找到nums1里面也有的值。

解题步骤

  • 新建立一个字典,遍历nums1,填充字典。
  • 遍历nums2,遇到字典的值就选出,并从字典中删除。
var intersection = function(nums1, nums2) {let map = new Map()nums1.forEach(n => {map.set(n,true)})const res = []nums2.forEach(n => {if(map.get(n)){res.push(n)map.delete(n)}})return res
}//时间复杂度O(m + n)
//空间复杂度为O(m)
复制代码

2. 有效括号

题目链接:20. 有效的括号 - 力扣(LeetCode) (leetcode-cn.com)

字典优化栈的判断

var isValid = function(s) {if(s.length % 2 === 1) return false;const stack = [];const map = new Map();map.set('(',')');map.set('{','}');map.set('[',']');for(let i = 0;i < s.length;i++){const c = s[i]if(map.has(c)){stack.push(c)} else{const t = stack[stack.length - 1];if(map.get(t) === c){stack.pop();} else{return false}}}return stack.length === 0
}
复制代码

3. 两数相加

题目链接:1. 两数之和 - 力扣(LeetCode) (leetcode-cn.com)

  • 把nums想象成相亲者。
  • 把target想象成匹配条件。
  • 用字典建立一个婚姻介绍所,存储相亲者的数字和下标

解题机构

  • 新建一个字典作为结婚介绍所。
  • nums里的值,逐个来介绍所找对象,没有合适的就先登记着,有合适的就牵手成功。
var twoSum = function(nums, target) {const map = new Map();for (let i = 0; i < nums.length; i++) {const a = target - nums[i]if (map.has(a)) {return [map.get(a),i]}else{map.set(nums[i],i)}}
};
//时间复杂度O(n) 空间复杂度也是O(n)
复制代码

4. 无重复的字符最长子串

题目链接:3. 无重复字符的最长子串 - 力扣(LeetCode) (leetcode-cn.com)

思路

  • 先找出所有的不包含重复字符的子串
  • 找出长度最大那个子串,返回其长度即可

步骤

  • 用双指针维护一个滑动窗口,用来剪切子串
  • 不断移动右指针,遇到重复字符,就把左指针移动到重复字符的下一位
  • 过程中,记录所有窗口的长度,并返回最大值。
var lengthOfLongestSubstring = function(s) {let left = 0let res = 0const map = new Map()for(let right = 0;right<s.length;right++){if(map.has(s[right]) && map.get(s[right]) >= left){left = map.get(s[right]) + 1 //相同就滑动一位}res = Math.max(res,right - left + 1)map.set(s[right],right)}return res
};
//时间复杂度 O(n) 空间复杂度O(m) m为不重复子串的长度
复制代码

5. 最小覆盖子串

题目链接:76. 最小覆盖子串 - 力扣(LeetCode) (leetcode-cn.com)

思路

  • 先找出所有的包含T的子串
  • 找出长度最小那个子串,返回即可

步骤

  • 用双指针维护一个滑动窗口
  • 移动右指针,找到包含T的子串,移动左指针,尽量减少包含T的子串的长度
var minWindow = function(s, t) {let l = 0;let r = 0;const need = new Map()for(let c of t){need.set(c , need.has(c)? need.get(c) + 1 : 1) //判断T字符需要多少} let needType = need.size; //设置需要都是种类let res = '';while(r < s.length){const c = s[r];if(need.has(c)){ // 如果找到需要的字符串need.set(c,need.get(c) - 1); // 找到就减一if(need.get(c) === 0) needType-- // 找到为零的话就类型减一}while(needType === 0){ // 如果是类型数已找完整,就执行以下操作const newRes = s.substring(l , r+1); // 得到需要的字符串if(!res || newRes.length < res.length) res = newResconst c2 = s[l];if(need.has(c2)){need.set(c2 , need.get(c2) + 1);if(need.get(c2) === 1) needType++}l++}r++}return res
};
// 时间复杂度O(n+m) 空间复杂度O(m)
复制代码

3. 字典总结

  • 与结婚类似,字典也是一种存储唯一值的数据结构,但它是以键值对的形式
  • ES6有字典,名为Map
  • 字典的常规操作:键值对的增删改查

- 树

1. 树是什么?

  • 一种分层的数据的抽象模型
  • 前端工作中常见的树包括:DOM树,级联选择、树形控件......
  • js中没有树,但是可以用Object中Array构建树。

2. 什么是深度/广度优先遍历?

  • 深度优先遍历:尽可能深的搜索树的分支
  • 广度优先遍历:先访问离根节点最近的节点

深度优先遍历口诀:

  • 访问根节点
  • 对根节点的children挨个进行深度优先遍历
const tree = {val:'a',children: [{val:'b',children:[{val:'d',children:[]},{val:'e',children:[]}],},{val:'c',children:[{val:'f',children:[]},{val:'g',children:[]}],}]
}const dfs = (root) => {console.log(root.val);// root.children.forEach((child) => {dfs(child)})root.children.forEach(dfs)
}
dfs(tree) // a b d e c f g
复制代码

广度优先遍历口诀

  • 新建一个队列,把根节点入队
  • 把头部出队访问。
  • 把对头的children挨个入队
  • 重复第二、三步,直到队列为空。
const tree = {val:'a',children: [{val:'b',children:[{val:'d',children:[]},{val:'e',children:[]}],},{val:'c',children:[{val:'f',children:[]},{val:'g',children:[]}],}]
}const bfs = (root) =>{const q = [root];while(q.length > 0){const n = q.shift()console.log(n.val);n.children.forEach(child =>{q.push(child);});}
}bfs(tree); // a b c d e f g
复制代码

3. 二叉树是什么?

  • 树中每一个节点最多只能有两个子节点。
  • 在js中通常Object来模拟二叉树。
const bt = {val: 1,left: {val:2,left: {val: 4,left: null,right: null},right: {val: 5,left: null,right: null},},right: {val: 3,left: {val: 6,left: null,right: null},right: {val: 7,left: null,right: null},},}module.exports = bt;
复制代码

先序遍历算法口诀

  • 访问根节点
  • 对根节点的左子树进行先序遍历
  • 对根节点的右子树进行先序遍历

递归版:

const bt = require('./bt');const preorder = (root) => {if(!root) return;console.log(root.val);preorder(root.left);preorder(root.right);
}preorder(bt); // 根左右: 1 2 4 5 3 6 7
复制代码

非递归版:

const preorder = (root) => {if(!root) return;const stack = [root]while(stack.length){const n = stack.pop();console.log(n.val);if(n.right) stack.push(n.right)if(n.left) stack.push(n.left)}
}
复制代码

中序遍历算法口诀

  • 对根节点的子树进行中序遍历。
  • 访问节点
  • 对根节点的子树进行中序遍历。
const bt = require('./bt')
const inorder = (root) => {if(!root) return inorder(root.left)console.log(root.val);inorder(root.right)
}
inorder(bt) //4 2 5 1 6 3 7
复制代码

非递归版:

const inorder = (root) => {if(!root) return const stack = []let p = root;while(stack.length || p){while(p){stack.push(p)p = p.left;}const n = stack.pop()console.log(n.val);p = n.right;}}
复制代码

后序遍历算法口诀

  • 对根节点的左子树进行后序遍历
  • 对根节点的右子树进行后序遍历
  • 访问根节点
const bt = require('./bt')const postorder = (root) =>{if(!root) return;postorder(root.left)postorder(root.right)console.log(root.val);
}
postorder(bt); // 4 5 2 6 7 3 1
复制代码

非递归版:

const postorder = (root) => {// 1.左右根 ---> 根右左if(!root) return;const outputStack = [];const stack = [root];while(stack.length){const n = stack.pop()outputStack.push(n)if(n.left) stack.push(n.left)if(n.right) stack.push(n.right)}while(outputStack.length){const n = outputStack.pop()console.log(n.val);}
}
复制代码

4. 树的实战

1. 树的最大深度

题目链接:104. 二叉树的最大深度 - 力扣(LeetCode) (leetcode-cn.com)

思路

  • 最大深度,考虑使用深度优先遍历
  • 在深度优先遍历过程中,记录没个节点所在的层级,找到最大的层级即可

解题步骤

  • 新建一个变量,记录最大深度
  • 深度优先遍历整颗树,并记录每个节点的层级,同时不断刷新最大深度这个变量
  • 遍历结束后最大深度这个变量
var maxDepth = function(root) {let res = 0;const dfs = (n,l) => {if(!n) return;if(!n.left && !n.right) res = Math.max(res,l)dfs(n.left,l+1)dfs(n.right,l+1)}dfs(root,1)return res
};
// 空间复杂度最好情况O(log(n)),最坏的情况O(n)
复制代码

2. 树 的最小深度

思路

  • 求最小深度,考虑广度优先遍历。
  • 在广度优先遍历过程中,遇到叶子节点停止遍历,返回节点层级。

解题步骤

  • 广度优先遍历整颗树,并记录每一个节点的层级。
  • 遇到叶子节点,返回节点层级,停止遍历。
var minDepth = function(root) {if(root === null) return 0const q = [[root,1]];while(q.length){const [n,l] = q.shift();if(!n.left && !n.right) return l;if(n.left) q.push([n.left,l+1]);if(n.right) q.push([n.right,l+1]);}
};
// 时间复杂度O(n),空间复杂度O(n)
复制代码

3. 二叉树的层序遍历

题目链接:102. 二叉树的层序遍历 - 力扣(LeetCode) (leetcode-cn.com)

思路

  • 层序遍历顺序就是广度优先遍历。

  • 不过在遍历时候需要记录当前节点所在的层级,方便将其添加不同的数组中。

解题步骤

  • 广度优先遍历二叉树
  • 遍历过程中,记录每个节点的层级,并将其添加不同的数组中。
 var levelOrder = function(root) {if(!root) return [];const q = [root]const res = []while(q.length){let len = q.length;res.push([]);while(len--){ // 保证在同一层级const n = q.shift()res[res.length -1].push(n.val);if(n.left) q.push(n.left)if(n.right) q.push(n.right)}}return res
};
复制代码

4. 二叉树的中序遍历

题目链接:94. 二叉树的中序遍历 - 力扣(LeetCode) (leetcode-cn.com)

 // 迭代版var inorderTraversal = function(root) {const res = []const stack = []let p = root;while(stack.length || p){while(p){stack.push(p)p = p.left}const n = stack.pop()res.push(n.val)p = n.right}return res;
};
//递归版
var inorderTraversal = function(root) {const res = []const rec = (n) =>{if(!n) return;rec(n.left)res.push(n.val)rec(n.right);};rec(root)return res
};
复制代码

5. 路径的总和

题目的链接:112. 路径总和 - 力扣(LeetCode) (leetcode-cn.com)

思路

  • 在深度优先遍历过程中,记录当前路径的节点值的和。
  • 在叶子节点处,判断当前路径的节点值是否等于目标值。

解题步骤

  • 深度优先遍历二叉树,在叶子节点处,判断当前路径的节点值的和是否等于目标值,是就是返回true

  • 遍历结束,如果没有匹配,就返回false

var hasPathSum = function(root, targetSum) {if(!root) return false;let res = falseconst dfs = (n,s)=>{if(!n.left && !n.right && s=== targetSum)  res = trueif(n.left) dfs(n.left,s+n.left.val)if(n.right) dfs(n.right,s+n.right.val)}dfs(root,root.val)return res
};
// 时间复杂度O(n) 空间复杂度为O(n) 因为使用递归
复制代码

6. 遍历JSON的所有节点值

const json = {a: {b: {c:1}},d:[1,2]
}
const dfs = (n,path) =>{console.log(n,path);Object.keys(n).forEach(k =>{dfs(n[k],path.concat(k));})
}
dfs(json,[])
复制代码

输出:

{ a: { b: { c: 1 } }, d: [ 1, 2 ] } []
{ b: { c: 1 } } [ 'a' ]
{ c: 1 } [ 'a', 'b' ]
1 [ 'a', 'b', 'c' ]
[ 1, 2 ] [ 'd' ]
1 [ 'd', '0' ]
2 [ 'd', '1' ]
复制代码

5. 前端与树:渲染Antd的树组件

react+antd组件树的js部分

const {Tree} = antd;const {TreeNode} = Tree;const json = [{title:'一',key:'1',children:[{title:'三' , key:'3', children:[]}]},{title:'二',key:'2',children:[{title:'四',key:'4',children:[]}]}
]class Demo extends React.Component {dfs = (n) =>{return (<TreeNode title={n.title} key={n.key}>{n.children.map(this.dfs)}</TreeNode>)}render(){return(<Tree>{json.map(this.dfs)}</Tree>)}
}ReactDOM.render(<Demo />,mountNode)
复制代码

6. 章节总结

  • 树是一种分层数据的抽象模型,在前端广泛应用
  • 树的常规用操作:深度/广度优先遍历、先中后序遍历......

- 图

1. 图是什么

  • 图是网络结构的抽象模型,是一组由连接的节点
  • 图可以表示任何二元关系:比如道路、航班.....
  • js没有图,但是可以用Object和Array构建图
  • 图的表示法:邻接矩阵、邻接表、关联矩阵.....

邻接矩阵:

邻接表

  • 图常用的操作:深度优先遍历、广度优先遍历

2. 图的深度优先遍历和广度优先遍历

什么是深度/广度优先遍历?

  • 深度优先遍历:尽可能深的搜索图的分支
  • 广度优先遍历:先访问离根节点最近的节点

// 描绘图
const graph = {0: [1,2],1:[2],2:[0,3],3:[3]
}module.exports = graph
复制代码

深度优先遍历算法口诀

  • 访问根节点。
  • 对根节点的没访问过的相邻节点挨个进行深度优先遍历。

深度优先实现代码

const graph = require('./graph')
const visted = new Set();
const dfs = (n) =>{console.log(n);visted.add(n);graph[n].forEach(c => {if(!visted.has(c)){dfs(c)}});
}
dfs(2)
复制代码

广度优先遍历算法口诀

  • 新建一个队列,把根节点入队
  • 把队头并访问。
  • 把队头的没有访问过的相邻节点入队
  • 重复第二、三步,直到队列为空
const graph = require('./graph')const visited = new Set()
visited.add(2);
const q = [2];
while(q.length){const n = q.shift();console.log(n);graph[n].forEach(c => {if(!visited.has(c)){q.push(c)visited.add(c)}});
}
复制代码

3. 图的实战

1. 有效数字

题目链接:65. 有效数字 - 力扣(LeetCode) (leetcode-cn.com)

思路

解题步骤

  • 构建一个表示状态的图
  • 遍历字符串,并沿着图走,如果到某个节点无路可走就返回false
  • 遍历结束,如果走到3/5/6,就返回true,否则返回false。
var isNumber = function(s) {const graph = {0: {'blank':0,'sign':1,'.':2,'digit':6},1: {'digit':6,'.':2},2: {'digit':3},3: {'digit':3,'e':4},4: {'digit':5,'sign':7},5: {'digit':5},6: {'digit':6,'.':3,'e':4},7: {'digit':5}};let state = 0;for(c of s.trim()){if(c >= '0' && c <= '9'){c = 'digit';} else if(c === ' '){c = 'blank';} else if(c ==='+' || c ==='-'){c ='sign';} else if(c === 'E'){c = 'e'}state = graph[state][c];if(state === undefined) return false}if(state === 3 || state === 5 || state === 6){return true}return false
};
//时间复杂度O(n) 空间复杂度O(1)
复制代码

2. 太平洋大西洋流水问题

题目链接:417. 太平洋大西洋水流问题 - 力扣(LeetCode) (leetcode-cn.com)

思路

  • 把矩阵想象成图。
  • 从海岸线逆流而上遍历图,所到之处就可以流到某个太平洋的坐标

解题步骤

  • 新建两个矩阵,分别记录能流到两个大洋的坐标
  • 从海岸线,多管齐下,同时深度遍历图,过程中填充上述矩阵。
  • 遍历两个矩阵,找到流到两个大洋坐标
var pacificAtlantic = function(heights) {if(!heights||!heights[0]) return []const m = heights.lengthconst n = heights[0].lengthconst flow1 = Array.from({length:m},()=> new Array(n).fill(false))const flow2 = Array.from({length:m},()=> new Array(n).fill(false))const dfs = (r,c,flow) =>{flow[r][c] = true;[[r-1,c],[r+1,c],[r,c-1],[r,c+1]].forEach(([nr,nc])=>{if(//保证在矩阵中nr >= 0 && nr < m &&nc >= 0 && nc < n &&//防止死循环!flow[nr][nc] &&//保证逆流而上heights[nr][nc] >= heights[r][c]){dfs(nr,nc,flow)}})};//沿着海岸线逆流而上for(let r = 0;r<m;r++){dfs(r,0,flow1)dfs(r,n-1,flow2)}for(let c = 0;c<n;c++){dfs(0,c,flow1)dfs(m-1,c,flow2)}//收集能流到两个大洋的坐标const res = []for(let r = 0;r<m;r++){for(let c = 0;c<n;c++){if(flow1[r][c]&&flow2[r][c]){res.push([r,c])}}}return res
};
复制代码

3. 克隆图

题目链接:133. 克隆图 - 力扣(LeetCode) (leetcode-cn.com)

思路:

  • 拷贝所有节点
  • 拷贝所有的边

解题思路

  • 深度或者广度优先遍历所有的节点
  • 拷贝所有的节点,存储起来
  • 将拷贝的节点,按照原图的方法进行连接

图的深度优先遍历:

var cloneGraph = function (node){if(!node) return;const visited  = new Map()const dfs = (n)=>{const nCopy = new Node(n.val)visited.set(n,nCopy);(n.neighbors || []).forEach(ne =>{if(!visited.has(ne)){dfs(ne)}nCopy.neighbors.push(visited.get(ne))})};dfs(node);return visited.get(node)
}
// 时间复杂度O(n) 空间复杂度O(n)
复制代码

图的广度优先遍历

var cloneGraph = function (node){if(!node) return;const visited  = new Map()visited.set(node,new Node(node.val))const q = [node];while(q.length){const n = q.shift()n.neighbors.forEach(ne =>{if(!visited.has(ne)){q.push(ne)visited.set(ne,new Node(ne.val))}visited.get(n).neighbors.push(visited.get(ne))})}return visited.get(node)
}
复制代码

4. 图的总结

  • 图是网络结构的抽象模型,是一组由连接的节点
  • 图可以表示任何二元关系,比如道路、航班.....。
  • js没有图,但可以用Object和Array构建图。
  • 图的表示法:邻接矩阵、邻接表......
  • 图的常用操作:深度优先/广度优先遍历

- 堆

1. 什么是堆?

  • 堆是一颗特殊的完全二叉树.
  • 所有的节点都大于等于(最大堆)或小于等于(最小堆)它的子节点。
  • js常用数组表示堆

  • 左侧子节点的位置是2*index + 1。

  • 右侧子节点的位置是2*index + 2.

  • 父节点位置(index - 1)/2

  • 堆能高效、快速地找到最大值和最小值,时间复杂度:O(1)

  • 找到第K个最大(小)元素。

2. 最小堆类

  • 在类里,声明一个数组,用来装元素。
  • 主要方法:插入、删除堆顶、获取堆顶、获取堆大小。

- 插入

  • 将值插入堆的底部,即数组的尾部。
  • 然后上移:将这个值和它的父节点进行交换,直到父节点小于等于这个插入的值。
  • 大小为k的堆插入元素的时间复杂度为O(logk)

- 删除堆顶

  • 用数组尾部元素替换堆顶(直接删除堆顶会破坏堆结构)
  • 然后下移:将新堆顶赫尔它的子节点进行交换,直到子节点大于等于这个新堆顶。
  • 大小为k的堆中删除堆顶的时间复杂度为O(logk)。

- 获取堆顶和堆的大小

  • 获取堆顶:返回数组的头部。
  • 获取堆的大小:返回数组的长度
class MinHeap {constructor(){this.heap = []}swap(i1,i2){ //交换操作const temp = this.heap[i1]this.heap[i1] = this.heap[i2]this.heap[i2] = temp}getPartIndex(i){ // 获取父节点操作return Math.floor((i - 1) / 2) // return (i - 1) >> 1;}getLeftIndex(i){return i * 2 + 1;}getRightIndex(i){return i * 2 + 2;}shiftUp(index){ // 上移算法if(index == 0) { return; }const parentIndex = this.getPartIndex(index) // 获取子节点的下标if(this.heap[parentIndex] > this.heap[index]){ //如果父节点大于子节点this.swap(parentIndex, index)}}shiftDown(index){ // 下移算法const leftIndex = this.getLeftIndex(index);const RightIndex = this.getRightIndex(index);if(this.heap[leftIndex] < this.heap[index]){this.swap(leftIndex,index)this.shiftDown(leftIndex)}if(this.heap[RightIndex] < this.heap[index]){this.swap(RightIndex,index)this.shiftDown(RightIndex)}}insert(value){ //插入操作this.heap.push(value); this.shiftUp(this.heap.length -1); //向上移this.shiftUp(parentIndex);}pop(){ //删除操作this.heap[0] = this.heap.pop();this.shiftDown(0)}peek() { //获取头部操作return this.heap[0];}size() { // 获取大小操作return this.heap.length;}
}
复制代码

3. 第K个最大元素问题

  • 构建一个最小堆,并将元素依次插入堆中。
  • 当堆的容量超过K,就删除对顶。
  • 插入结束后,堆顶就是第K个最大元素。

题目链接:215. 数组中的第K个最大元素 - 力扣(LeetCode) (leetcode-cn.com)

思路

  • 看到“第k个最大元素”
  • 考虑选择使用最小堆

解题步骤

  • 构建最小堆,并依次把数组的值插入堆中
  • 当堆的容量超过K,就会删除堆顶。
  • 插入结束后,堆顶就是第K个最大元素
var findKthLargest = function(nums, k) {const h = new MinHeap();nums.forEach(n => {h.insert(n)if(h.size() > k) h.pop();})return h.peek();
};
// 时间复杂度O(nlogk) 空间复杂度O(k)
复制代码

4. 前K个高频元素

题目链接:347. 前 K 个高频元素 - 力扣(LeetCode) (leetcode-cn.com)

暴力破解

var topKFrequent = function(nums, k) {const map = new Map()nums.forEach(n => {map.set(n,map.has(n) ? map.get(n) + 1 : 1)})const list = Array.from(map).sort((a,b) => b[1]- a[1])return list.slice(0,k).map(n => n[0])
};
复制代码

堆的解法

class MinHeap {constructor(){this.heap = []}swap(i1,i2){ //交换操作const temp = this.heap[i1]this.heap[i1] = this.heap[i2]this.heap[i2] = temp}getPartIndex(i){ // 获取父节点操作return Math.floor((i - 1) / 2) // return (i - 1) >> 1;}getLeftIndex(i){return i * 2 + 1;}getRightIndex(i){return i * 2 + 2;}shiftUp(index){ // 上移算法if(index == 0) { return; }const parentIndex = this.getPartIndex(index) // 获取子节点的下标if(this.heap[parentIndex] && this.heap[parentIndex].value > this.heap[index].value){ //如果父节点大于子节点this.swap(parentIndex, index)this.shiftUp(parentIndex);}}shiftDown(index){ // 下移算法const leftIndex = this.getLeftIndex(index);const RightIndex = this.getRightIndex(index);if(this.heap[leftIndex] && this.heap[leftIndex].value < this.heap[index].value){this.swap(leftIndex,index)this.shiftDown(leftIndex)}if(this.heap[RightIndex] && this.heap[RightIndex].value < this.heap[index].value){this.swap(RightIndex,index)this.shiftDown(RightIndex)}}insert(value){ //插入操作this.heap.push(value); this.shiftUp(this.heap.length -1); //向上移}pop(){ //删除操作this.heap[0] = this.heap.pop();this.shiftDown(0)}peek() { //获取头部操作return this.heap[0];}size() { // 获取大小操作return this.heap.length;}
}
var topKFrequent = function(nums, k) {const map = new Map()nums.forEach(n => {map.set(n,map.has(n) ? map.get(n) + 1 : 1)})const h = new MinHeap();map.forEach((value,key) => {h.insert({value,key});if(h.size() > k){h.pop()}})return h.heap.map(a => a.key)
};
// 时间复杂度O(n*logK) 空间复杂度O(n)
复制代码

- 排序

1. 排序和搜索是什么?

  • 排序:把某个乱序的数组变成升序或者降序的数组。
  • 搜索:找出数组中某个元素的下标。
  • js中的排序:数组的sort方法。
  • js中的搜索:数组的indexOf方法。

2. 前端最主要的排序和搜索算法

- 排序算法

  • 冒泡排序O(n^2)

  • 归并排序O(n^2)

  • 选择排序O(n^2)

  • 快速排序O(n*logN)

  • 插入排序O(n*logN)

    .......

- 搜索算法

  • 顺序搜索

  • 二分搜索

    ......

3. 冒泡排序

  • 比较所有相邻的元素,如果第一个比第二个大,就交换它们。
  • 一轮下来,可以保证最后一个数是最大的。
  • 执行n - 1轮,就可以完成排序。
Array.prototype.bubbleSort = function () {for (let i = 0; i < this.length - 1; i++) {for (let j = 0; j < this.length - 1 - i; j++) {if(this[j]>this[j+1]){const temp = this[j];this[j] = this[j+1]this[j+1] = temp}} }
};const arr = [5,4,3,2,1];
arr.bubbleSort();复制代码

冒泡排序的时间复杂度

  • 两个嵌套循环
  • 时间复杂度O(n^2)

4. 选择排序

  • 找到数字中的最小值,选中它将其放在第二位。
  • 接着找到第二小的值,选中它并将其放置在第二位。
  • 以此类推n-1轮。
Array.prototype.selectionSort = function () {for (let i = 0; i < this.length; i++) {let indexMin = i;for (let j = 0; j < this.length; j++) {if (this[j] < this[indexMin]) {indexMin = j}}if (indexMin !== i) {const temp = this[0];this[0] = this[indexMin];this[indexMin] = temp;}}
}
// 选择排序的时间复杂度O(n^2)
const arr = [5, 4, 3, 2, 1]
arr.selectionSort();
复制代码

5. 插入排序

  • 从第二个数开始往前比
  • 比它大就往后排
  • 以此类推进行到最后一个数
Array.prototype.insertionSort = function () {for (let i = 1; i < this.length; i++) {const temp = this[i]let j = i;while (j > 0) {if (this[j - 1] > temp) {this[j] = this[j - 1];} else {break;}j--}this[j] = temp}
};const arr = [5, 4, 3, 2, 1]
arr.insertionSort();
// 时间复杂度 O(n^2)
复制代码

6. 归并排序

  • 分:把数组劈成两半,再递归对子数组进行“分”操作,直到分成一个个单独的数。
  • 合:把两个数合并成有序数组,再对有序数组合并,直到全部子数组合并为完整的数组
  • 新建一个res空数组,用于存放排序后的数组。
  • 比较两个有序数组的头部,较小者出队推入res中。
  • 如果两个数组还有值,就重复第二步。
Array.prototype.megeSort = function () {const rec = (arr) => {if (arr.length === 1) return;const mid = Math.floor(arr.length / 2);const left = arr.slice(0, mid);const right = arr.slice(mid, arr.length);const orderLeft = rec(left);const orderRight = rec(right);const res = []while (orderLeft.length || orderRight.length) {if (orderLeft.length && orderRight.length) {res.push(orderLeft[0] < orderRight[0] ? orderLeft.shift() : orderRight.shift());} else if (orderLeft.length) {res.push(orderLeft.shift());} else if (orderRight.length) {res.push(orderRight.shift());}}return res;}const res = rec(this)//拷贝res.forEach((n, i) => {this[i] = n;})
}
// 分时间复杂度O(logN)
// 合时间复杂度O(n)
// 时间复杂度:O(n*logN)
复制代码

7. 快速排序

  • 分区:从数组任意一个“基准”,所以比基准小的元素前面,比基准大的元素放在基准的后面。
  • 递归:递归地对基准前后的子数组进行分区。
Array.prototype.quickSort = function () {const rec = (arr) => {if (arr.length === 1) return arr;const left = []const right = [];const mid = arr[0]for (let i = 1; i < arr.length; i++) {if (arr[i] < mid) {left.push(arr[i])} else {right.push(arr[i]);}}return [...res(left), mid, ...rec(right)]}const res = rec(this)res.forEach((n, i) => {this[i] = n})
}
// 时间复杂度
// 递归时间复杂度O(logN)
// 分区操作O(n)
// 总时间复杂度O(nlogN)
复制代码

8. 顺序搜索

  • 遍历数组
  • 找到跟目标的元素,就返回它的下标
  • 没有找到返回-1
Array.prototype.sequentialSearch = function (item) {for (let i = 0; i < this.length; i++) {if (this[i] === item) {return i;}}return -1
}
// 遍历数组是一个循环操作
// 时间复杂度:O(n)
复制代码

9. 二分搜索

  • 从数组的中间元素开始,如果中间元素正好是目标值,则搜索结束。
  • 如果目标值大于或者小于中间元素,则在大于或小于中间元素的那一半数组中搜索。
Array.prototype.binarySearch = function (item) {let low = 0;let high = this.length - 1;while (low <= high) {const mid = Math.floor((low + high) / 2);const element = this[mid];if (element < item) {low = mid + 1;} else if (element > item) {high = mid - 1;} else {return mid;}}return -1;
}//每一次比较范围都是缩小一半
//时间复杂度:O(logN).
复制代码

10. 力扣实战

1. 合并两个链表

题目链接:21. 合并两个有序链表 - 力扣(LeetCode) (leetcode-cn.com)

思路

  • 与归并排序中的合并两个有序数组很相似。
  • 与数组替换成链表就能解此题。

解题步骤

  • 新建一个新链表,作为返回结果。
  • 用指针遍历两个有序链表,并比较两个链表当前节点,较小者接入新链表,并将指针后移一步。
  • 链表遍历结束,返回新链表。
var mergeTwoLists = function(l1, l2) {const res = new ListNode(0);let p = res;let p1 = l1;let p2 = l2;while(p1 && p2){if(p1.val < p2.val){p.next = p1p1 = p1.next} else{p.next = p2;p2 = p2.next;}p = p.next;}if(p1){p.next = p1}if(p2){p.next = p2}return res.next
};
//时间复杂度O(m + n) 空间复杂度O(1)
复制代码

2. 猜数字大小

思路

  • 二分搜索
  • 调用guess函数,来判断中间元素是否是目标值。

解题步骤

  • 从数组的中间元素开始,如果中间元素正好是目标值,则搜索过程结束。
  • 如果目标值大于或者小于中间元素,则在数组大于或者小于中间元素的那一半查找。
 var guessNumber = function(n) {let low = 1;let high = n;while(low<=high){const mid = Math.floor((low + high) / 2);const res = guess(mid);if(res === 0){return mid;} else if(res === 1){low = mid + 1;} else {high = mid - 1;}}
};
复制代码

- 分而治之

1. 什么是分而治之?

  • 分而治之是算法设计的一种方法。
  • 它将一个问题成多个和原问题相似的小问题,递归解决小问题,再将结果并以解决原来的问题。

场景一:归并排序

  • 分:把数组从中间一分为二。
  • 解:递归地对两个子数组进行归并排序。
  • 合:合并有序子数组。

场景二:快速排序

  • 分:选基准把数组分成两个子数组。
  • 解:递归地两个子数组进行快速排序。
  • 合:对两个子数组进行合并。

2. 猜数字大小(二分搜索)

题目链接:374. 猜数字大小 - 力扣(LeetCode) (leetcode-cn.com)

思路

  • 二分搜索,同样具备“分、解、合”的特性。
  • 考虑选择分而治之。

解题步骤

  • 分:计算中间元素,分割数组。
  • 解:递归地在较大或者较小数组进行二分搜索。
  • 合:不需要此步,因为在子数组中搜索到就返回。
var guessNumber = function(n) {const rec = (low,high) =>{if(low>high) return;const mid = Math.floor((low + high) / 2)const res = guess(mid);if(res === 0 ){return mid} else if(res === 1){return rec(mid + 1, high);}else{return rec(1,mid-1);}};return rec(1,n);
};
// 事件复杂度O(logn) 空间复杂度O(logn)
复制代码

3. 翻转二叉树(那一年HomeBrew大佬翻车)

  • 先翻转左右子树,再将子树换个位置。

  • 符合“分、解、合”特性。

  • 考虑分而治之

解题步骤

  • 分:获取左右子树。
  • 解:递归地翻转左右子树。
  • 合:将翻转后的左右子树换个位置放到根节点上。
var invertTree = function(root) {if(!root) return null;return {val:root.val,left:invertTree(root.right),right:invertTree(root.left)}
};
// 事件复杂度(访问次数)O(n),空间复杂度O(h),h是树的高度
复制代码

4. 相同的树

题目链接:100. 相同的树 - 力扣(LeetCode) (leetcode-cn.com)

思路:

  • 两个树:根节点的值相同,左子树相同,右子树相同。
  • 符合“分、解、合”特性。
  • 考虑选择分而治之。

解题步骤

  • 分:获取两个树的左子树和右子树。
  • 解:递归地判断两个树的左子树是否相同,右子树是否相同。
  • 合:将上述结果合并,如果根节点的值也相同,树就相同。
var isSameTree = function(p, q) {if(!p && !q) return true;if(p && q && p.val === q.val && isSameTree(p.left,q.left)&& isSameTree(p.right,q.right)) {return true;}return false
}
// 时间复杂度O(n) 空间复杂度O(n)
复制代码

5. 对称二叉树

题目链接:101. 对称二叉树 - 力扣(LeetCode) (leetcode-cn.com)

思路:

  • 转化为:左右子树是否为镜像。
  • 分解为:树1的左子树和树2的右子树是否为镜像,树1的右子树和树2的左子树是否镜像。
  • 符合“分、解、合”特性,考虑选择分而治之。

解题步骤

  • 分:获取两个树的左子树和右子树。

  • 解:递归地判断树1的左子树和树2的右子树是否为镜像,树1的右子树和树2的左子树是否镜像。

  • 合:如果上述都成立,且根节点值也相同,两个树就镜像。

var isSymmetric = function(root) {if(!root) return true;const isMirror = (l,r) => {if(!l && !r) return true;if(l && r && l.val === r.val &&isMirror(l.left,r.right) &&isMirror(l.right,r.left) ){return true;}return false;};return isMirror(root.left,root.right);
};
//时间复杂度O(n) 空间复杂度O(n)树的高度
复制代码

- 动态规划

1.动态规划是什么?

  • 动态规划是算法设计的一种方法
  • 它将一个问题分解为相互重叠的子问题,通过反复求解子问题,来解决原来的问题

  • 定义子问题:F(n) = F(n - 1) + F(n - 2)
  • 反复执行:从2循环到n,执行上述公式。

2. 爬楼梯问题

题目链接:70. 爬楼梯 - 力扣(LeetCode) (leetcode-cn.com)

思路

  • 爬到第n阶可以在第n-1阶爬1个台阶,或者在第n-2阶爬2个台阶。
  • F(n) = F(n-1) + F(n-2)
  • 使用动态规划。

解题步骤:70. 爬楼梯 - 力扣(LeetCode) (leetcode-cn.com)

  • 定义子问题:F(n) = F(n-1) + F(n-2)
  • 反复执行:从2循环到n,执行上述公式。
var climbStairs = function(n) {if(n < 2) return 1;const dp = [1,1];for(let i = 2;i <= n;i++){dp[i] = dp[i - 1] + dp[i - 2]}return dp[n]
};
// 时间复杂度O(n) 临时变量数组空间复杂度O(n)//空数组改为两个变量,将空间复杂度下降至O(1)
var climbStairs = function(n) {if(n < 2) return 1;let dp0 = 1;let dp1 = 1;for(let i = 2;i <= n;i++){const  temp = dp0;dp0 = dp1;dp1 = dp0 + temp;}return dp1
};
复制代码

3. 打家劫舍

题目链接:198. 打家劫舍 - 力扣(LeetCode) (leetcode-cn.com)

思路:

  • f(k) = 从前K个房屋中能窃取的最大数额。

  • Ak = 第k个房屋的钱数。

  • f(k) = max(f(k-2)+Ak,f(k-1))。

  • 考虑动态规划

解题步骤

  • 定义子问题:f(k) = max(f(k-2)+Ak,f(k-1))。
  • 反复执行:从2循环到n,执行上述公式。
var rob = function(nums) {if(nums.length === 0) return 0const dp = [0,nums[0]];for(let i =2;i<=nums.length;i++){dp[i] = Math.max(dp[i-2] + nums[i - 1],dp[i - 1])}return dp[dp.length - 1]
};
复制代码

- 贪心算法

1. 贪心算法是什么?

  • 贪心算法是算法设计中的一种方法。
  • 期盼通过每个阶段的局部最优选择,从而达到全局的最优。
  • 结果并不一定是最优

2. 零钱兑换

题目链接:455. 分发饼干 - 力扣(LeetCode) (leetcode-cn.com)

思路

  • 局部最优:既能满足孩子,还消耗最小。
  • 先将“较小的饼干”分给“胃口最小”的孩子。

解题步骤

  • 对饼干数组和胃口数组升序排序。
  • 遍历饼干数组,找到能满足第一个孩子的饼干
  • 然后继续遍历饼干,找到满足第二、三、.....、n个孩子的饼干。
var findContentChildren = function(g, s) {const sortFunc = function(a,b) {return a -b}g.sort(sortFunc);s.sort(sortFunc);let i = 0;s.forEach(n => {if(n >= g[i]){i++}})return i;
};
复制代码

3. 买股票问题

题目链接:122. 买卖股票的最佳时机 II - 力扣(LeetCode) (leetcode-cn.com)

思路

  • 前提:上帝视角,知道未来的价格。
  • 局部最优:见好就收,见差不动,不做任何长远打算

解题步骤

  • 新建一个变量,用来统计总利润。
  • 遍历价格数组,如果当前价格
var maxProfit = function(prices) {let profit = 0;for(let i =1;i < prices.length; i++){if(prices[i] > prices[i - 1]){profit += prices[i] - prices[i - 1];}}return profit
};
复制代码

- 回溯算法

1. 回溯算法是什么

  • 回溯算法是算法设计的一种方法。
  • 回溯算法是一种渐进式寻找构建问题解决的策略。
  • 回溯算法会先从一个可能的动作开始解问题,如果不行,就回溯并选择另一个动作,直到问题解决。

2. 什么问题适合回溯算法解决?

  • 有很多路。
  • 这些路里,有死路,也有出路
  • 通常需要递归模拟所有的路。

3. 全排列

  • 用递归模拟出所有的情况。
  • 遇到包含重复元素的情况,就回溯
  • 收集所有到达递归终点的情况,就返回

题目链接:46. 全排列 - 力扣(LeetCode) (leetcode-cn.com)

思路

  • 要求:1、所有排列情况 2、没有重复元素
  • 有出路、有死路
  • 考虑回溯算法

解题步骤

  • 有递归模拟所有的情况。
  • 遇到包含重复元素的情况,就回溯。
  • 收集所有到达递归的情况,并返回
var permute = function(nums) {const res = [];const backtrack = (path) =>{ //返回函数if(path.length === nums.length){ // 长度相等就返回res.push(path);return;}nums.forEach(n => {if(path.includes(n)){ // 不能包含一模一样的数字return;}backtrack(path.concat(n));})}backtrack([])return res;
};
// 时间复杂度O(n!) 空间复杂度O(n)
复制代码

4. 子集问题

题目链接:78. 子集 - 力扣(LeetCode) (leetcode-cn.com)

思路:

  • 要求:1、所有子集;2、没有重复元素
  • 有出路、有死路。
  • 考虑使用回溯算法。

解题步骤

  • 用递归模拟出所有情况
  • 保证接的数字都是后面的数字。
  • 收集所有到达递归终点的情况,并返回。
var subsets = function(nums) {const res =[]const backtrack = (path,l,start) =>{if(path.length === l){res.push(path)return;} for(let i = start;i<nums.length;i++){backtrack(path.concat(nums[i]),l,i+1)}};for(let i =0;i<=nums.length;i++){backtrack([],i,0)}return res
};
复制代码

- 完结

- 知识体系结构

  • 数据结构:栈、队列、链表、集合、字典、树、图、堆。
  • 算法:链表/树/图的遍历、数组的排序和搜索
  • 算法设计思想:分而治之、动态规划、贪心、回溯。

- 重点难点

  • 数据结构:所有数据结构都很重要,跟前端最相关的是链表和树。
  • 算法:链表/树/图的遍历、数组的排序和搜索.....
  • 设计思想:分而治之、动态规划常考,贪心、回溯次之。

- 经验心得

  • 搞清楚数据结构与算法的特点应用场景
  • 用JS实现一遍,最好能用第二第三语言再实现一遍。
  • 学会分析时间/空间复杂度
  • 提炼前端和算法结合点,用于工作实战

- 拓展建议

  • 刷题,最好能保证300道以上。
  • 多总结各种套路模板
  • 多遇到源码,比如React、Lodash、V8......
  • 实战,将数据结构与算法用于工作。

JavaScript数据结构与算法 基础相关推荐

  1. 学习JavaScript数据结构与算法(一):栈与队列

    本系列的第一篇文章: 学习JavaScript数据结构与算法(一),栈与队列 第二篇文章:学习JavaScript数据结构与算法(二):链表 第三篇文章:学习JavaScript数据结构与算法(三): ...

  2. JavaScript数据结构和算法

    前言 在过去的几年中,得益于Node.js的兴起,JavaScript越来越广泛地用于服务器端编程.鉴于JavaScript语言已经走出了浏览器,程序员发现他们需要更多传统语言(比如C++和Java) ...

  3. JavaScript数据结构和算法 1

    前言 在过去的几年中,得益于Node.js的兴起,JavaScript越来越广泛地用于服务器端编程.鉴于JavaScript语言已经走出了浏览器,程序员发现他们需要更多传统语言(比如C++和Java) ...

  4. JavaScript数据结构和算法简述——数组

    为什么先讲数组 数据结构可以简单的被分为线性结构和非线性结构. 线性结构大致包括: 数组(连续存储): 链表(离散存储): 栈(线性结构常见应用,由链表或数组增删和改进功能实现): 队列(线性结构常见 ...

  5. JavaScript数据结构和算法简述——前言

    为什么要使用数据结构和算法(程序=数据结构+算法)         数据结构是对在计算机内存中(有时在磁盘中)的数据的一种安排.包括数组.链表.栈.二叉树.哈希表等.        算法是对这些结构中 ...

  6. 《学习JavaScript数据结构与算法》第三章 数组

    文章目录 前言 一.创建 && 初始化数组 二.操作数组 push-添加元素于末尾 unshift-添加元素于开头 pop-从数组末尾开始删除元素 shift-从数组开头开始删除元素 ...

  7. 读《学习JavaScript数据结构与算法》 第二章

    第二章 ECMAScript和TypeScript概述 文章目录 第二章 ECMAScript和TypeScript概述 前言 一.let const 二.模板字面量 支持换行 拼接方式 三.函数的默 ...

  8. JavaScript数据结构与算法(2)(集合、字典、哈希表、二叉树、图)(ES6)

    注意:原教学视频:JavaScript(ES6)数据结构和算法 | JavaScript数据结构与算法 (都是CoderWhy老师的教学) 原作者(笔记)链接:JavaScript 数据结构与算法 | ...

  9. javascript数据结构与算法---检索算法(二分查找法、计算重复次数)

    javascript数据结构与算法---检索算法(二分查找法.计算重复次数) /*只需要查找元素是否存在数组,可以先将数组排序,再使用二分查找法*/ function qSort(arr){if (a ...

最新文章

  1. spark+数据倾斜+解决方案
  2. 进程间数据传递:Queue,Pipe 进程间数据共享:Manager
  3. 使用Java创建内存泄漏
  4. 瞄准千亿个护市场,纸业龙头们下半场战役已经打响
  5. ElasticSearch Java Api(二) -检索索引库
  6. matlab中的terminator模块,2.2 Ground 及 Terminator模块
  7. ruby hash添加数据_如何在Ruby中向Hash添加元素?
  8. 不要假装努力,结果不会陪你演戏!
  9. python开源项目贡献_如何通过为开源项目做贡献来发展自己的职业
  10. 美国歌手西恩·列侬以3200美元WETH出售NFT艺术品
  11. js 音乐播放器(audio属性及其使用方法)
  12. (扫盲)WebSocket 教程
  13. DDCTFMulTzor-xoortol的使用
  14. 实对称阵的正交相似对角化
  15. windows下bat文件一直循环一句话如何解决
  16. 《从零构建前后分离的web项目》:前端完善 - 手把手教你快速构建网站布局
  17. mysql zombodb_zombodb安装试用
  18. 计算机网络基础教程实训总结,实训总结
  19. 【JZOJ1758】过河
  20. Angular+arcgisjs之平面地图测距、测面积、搜索

热门文章

  1. app软件开发现状及前景
  2. python判断手机号运营商_匹配手机号码及运营商校验
  3. PHP 获取手机号运营商
  4. K8S使用habor作为私有仓库
  5. Python+经济学:资本边际效率与IRR
  6. Ubuntu 安装rar文件解压工具
  7. Spring Data Redis 实践
  8. Multi-UA V Cooperative Exploringfor the Unknown Indoor EnvironmentBased on Dynamic Target Tracking翻译
  9. 西西里2286. Stack Implementation
  10. 目光远大,不要被小许诺所牵绊,唯有自我提升是真理