1. 时间空间复杂度
- 时间复杂度
- 用大O表示,如O(1)、O(n)…
- 定性描述算法的运行时间
- 空间复杂度
运行过程中临时占用存储空间大小的量度
2. 栈:后进先出的结构 stack
push、pop
- 有效的括号: https://leetcode-cn.com/problems/valid-parentheses
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 item = s[i]
if (map.has(item)) {
stack.push(item)
} else {
const p = stack[stack.length - 1]
if (map.get(p) === item) {
stack.pop() // 匹配上的就剔除了
} else {
return false
}
}
}
// 如果最后栈为空,那么它是有效的括号
return stack.length === 0 // 都是单边的情况
};
3. 队列:先进先出的结构 queue
push、shift
- 写一个 RecentCounter 类来计算特定时间范围内最近的请求: https://leetcode-cn.com/problems/number-of-recent-calls
var RecentCounter = function () {
this.q = []
};
/**
* @param {number} t
* @return {number}
*/
RecentCounter.prototype.ping = function (t) {
this.q.push(t)
while (this.q[0] < t - 3000) {
this.q.shift()
}
return this.q.length
};
4. 链表 link
元素存储不连续,用next指针连接。
237. 删除链表中的节点
206. 反转链表
2. 两数相加
83. 删除排序链表中的重复元素
141. 环形链表
实现一个instanceof查找实例是否在原型链上:
// params:A: 实例; B:构造函数
const instanceOf = (A,B) => {
let p = A
while (p) {
// 原型链上的实例是否指向构造函数
if (p === B.prototype) {
return true
}
p = p.__proto__ // 沿着原型链查找
}
return false
}
5. 集合 Set
无序且唯一的数据结构。
- 两个数组的交集:https://leetcode-cn.com/problems/intersection-of-two-arrays
/**
* @param {number[]} nums1
* @param {number[]} nums2
* @return {number[]}
* 时间复杂度:O(m * n);空间复杂度:O(m)。m为nums1的长度
*/
var intersection = function (nums1, nums2) {
// 交集:用集合对num1去重;筛选出num2也包含的值。
return [...new Set(nums1)].filter(item => nums2.includes(item))
};
// 集合的一些操作方法
const s = new Set();
s.add(1); // 添加
s.has(1); // 值是否存在
s.delete(1); // 删除
6. 字典 Map
349. 两个数组的交集
20. 有效的括号
1. 两数之和
3. 无重复字符的最长子串
76. 最小覆盖子串
存储唯一值的数据结构。
const m = new Map()
m.set("a",1) // 增
m.get("a") // 查
s.has("a"); // 值是否存在
m.delete("a") // 删
两数之和:https://leetcode-cn.com/problems/two-sum/
/**
* @param {number[]} nums
* @param {number} target
* @return {number[]}
* 时间复杂度:O(n),空间复杂度:O(n)
*/
var twoSum = function (nums, target) {
const map = new Map()
for (let i = 0; i < nums.length; i++) {
const n = nums[i]
const n1 = target - n
if (map.has(n1)) {
return [map.get(n1), i]
} else {
map.set(n, i)
}
}
};
无重复字符的最长子串: https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/
/**
* @param {string} s
* @return {number}
* 时间复杂度 O(n) 空间复杂度O(m) m为最长无重复字符
* 双指针移动窗口确定字符串
*/
var lengthOfLongestSubstring = function (s) {
const map = new Map()
let l = 0 // 左指针
let res = 0 // 最大值
for (let r = 0; r < s.length; r++) { // 右指针移动
// 当前值 需要在滑动窗口中
if (map.has(s[r]) && map.get(s[r]) >= l) {
// 有值,说明重复了需要移动左指针到新的字符
l = map.get(s[r]) + 1
}
res = Math.max(res, r - l + 1) // 最大值为当前窗口值,或者之前的最大值
map.set(s[r], r) // 存储当前字符的下标
}
return res
};
7. 树 tree
分层数据结构。
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(item => dfs(item)); // 对children逐个递归
}
dfs(tree) // a b d e c f g
- 广度优先遍历:先访问离根节点最近的节点。
const bfs = (root) => {
const q = [root] // 队列
while (q.length > 0) {
const n = q.shift() // 出队
console.log(n.val); // 访问
n.children.forEach((item) => { // 对children推到队列中
q.push(item)
})
}
}
bfs(tree) // a b c d e f g
二叉树:树中每个节点最多只能有两个子节点。
- 先序遍历:根 -> 左 -> 右
- 中序遍历:左->根 -> 右
- 后序遍历:左 -> 右 -> 根
104. 二叉树的最大深度 :深度优先遍历
/**
* @param {TreeNode} root
* @return {number}
* 时间复杂度 O(n),空间复杂度 O(log n) 到 O(n)
*/
var maxDepth = function (root) {
let res = 0; // 记录最大深度
const dfs = (n, l) => {
// l: 深度层级
if (!n) return
// console.log(n.val, l)
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
};
111. 二叉树的最小深度: 广度优先遍历
/**
* @param {TreeNode} root
* @return {number}
* 时间复杂度 O(n) 空间复杂度 O(n)
*/
var minDepth = function (root) {
if (!root) return 0
const q = [[root, 1]]
// 广度优先遍历,并记录节点层级
while (q.length) {
const [n, l] = q.shift()
// console.log(n.val, l)
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])
}
// 遇到叶子节点,返回节点层级,停止遍历
};
102. 二叉树的层序遍历:广度优先遍历
/**
* @param {TreeNode} root
* @return {number[][]}
* 时间复杂度O(n) 空间复杂度O(n)
*/
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()
console.log(n.val)
res[res.length - 1].push(n.val) // 队列中都是同一个层级的
if (n.left) q.push(n.left)
if (n.right) q.push(n.right)
}
}
return res
};
/**
* @param {TreeNode} root
* @return {number[]}
* 中序遍历:左 -> 根 -> 右
*/
// 递归
// 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
// };
// 非递归:栈实现; 时间复杂度、空间复杂度都是O(n)
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
};
遍历JSON的所有字典值
const json = {
a: { b: { c: 1 } },
d: [1, 2]
}
const bfs = (n, path) => {
console.log(n, path); // 节点值,路径
Object.keys(n).forEach((item) => {
bfs(n[item], path.concat(item))
})
}
bfs(json, [])
8. 图 graph
图:网络结构的抽象模型;可以表示任何二元关系,比如道路、航班…
数据:
const graph = {
0: [1, 2],
1: [2],
2: [0, 3],
3: [3]
}
- 深度优先遍历:
// 深度优先遍历
const visited = new Set()
const dfs = (n)=>{
console.log(n)
visited.add(n)
graph[n].forEach(c => {
if (!visited.has(c)) { // 需要遍历不是重复的
dfs(c)
}
});
}
dfs(2) // 2 0 1 3
- 广度优先遍历:
// 广度优先遍历
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) // 防止队列中出现重复的元素
}
});
}
// 2 0 3 1
9. 堆 heap
堆是一种特殊的完全二叉树
特点: 所有节点都大于等于(最大堆)或小于等于(最小堆)它的子节点。
js中: 用数组表示。左侧子节点位置:2index + 1,右侧:2index+2,父节点位置(index-1)/ 2。
应用: 快速找出最大值、最小值。
10. 排序和搜索
- 排序
- 冒泡排序:比较所有相邻元素,如果第一个比第二个大,则交换他们。
时间复杂度:O(n^2)
const arr = [5, 4, 3, 2, 1]
for (let i = 0; i < arr.length - 1; i++) {
for (let j = 0; j < arr.length - 1 - i; j++) {
// 从小到大排序
if (arr[j] > arr[j + 1]) {
const temp = arr[j];
arr[j] = arr[j + 1]
arr[j + 1] = temp
}
}
}
console.log(arr); // [1, 2, 3, 4, 5]
- 选择排序
找到数组中的最小值,选中它并将其放在第一位;第二小值,放第二位。
// 选择排序
const arr = [5, 4, 3, 2, 1]
for (let i = 0; i < arr.length - 1; i++) {
let indexMin = i
for (let j = i; j < arr.length; j++) {
if (arr[j] < arr[indexMin]) {
indexMin = i
}
}
if (indexMin !== i) {
const temp = arr[i]
arr[i] = arr[indexMin]
arr[indexMin] = temp
}
}
console.log(arr); // [1, 2, 3, 4, 5]
-
插入排序
-
归并排序
-
快速排序
- 搜索
-
顺序搜索
-
二分搜索