5、哈希表、映射、集合的实现与特性。
哈希表,也加散列表,是根据关键码值而直接进行访问的数据结构。 哈希表,哈希函数 - 查询、插入、删除都是O(1) 时间复杂度。 复杂度 search O(1) 添加 O(1) 删除 O(1) 红黑树都是O(log(n))
- 有效的字母异位词 leetcode-cn.com/problems/va…
1、普通方式
function isAnagram(s, t){
if(s.length !== t.length) return false
s = s.split('').sort().join('')
t = t.split('').sort().join('')
return s === t
}
2、哈希表形式
从26个字符角度统计
function isAnagram(s, t){
if(s.length !== t.length){
return false;
}
const tableArr = new Array(26).fill(0)
let _flagCodeNum = 'a'.codePointAt(0)
for(let i=0;i<s.length;i++){
tableArr[s[i].codePointAt(0) - _flagCodeNum]++;
}
for(let i=0;i<t.length;i++){
tableArr[t[i].codePointAt(0) - _flagCodeNum]--;
if(tableArr[t[i].codePointAt(0) - _flagCodeNum] < 0){
return false
}
}
return true
}
function twoNum(arr, target){
let hash = {}
for(let i=0;i<arr.length;i++){
let another = target - arr[i]
// 可能为0 查询差值
if(hash[another] !== undefined){
return [hash[another], i]
}
// 存入初始值
hash[arr[i]] = i
}
}
- 字母异位词分组 leetcode-cn.com/problems/gr…
// 1
var groupAnagrams = function(strs, h = {}) {
for (var str of strs) {
var key = Array.from(str).sort().join()
h[key] ? h[key].push(str) : h[key] = [str]
}
return Object.values(h)
}
// 2 charCodeAt字母的ASCII码
var groupAnagrams = function(strs, h = {}) {
for (var str of strs) {
for (var i = str.length, p = new Array(26).fill(0); i--;)
p[str[i].charCodeAt() - 97]++
key = p.toString()
h[key] ? h[key].push(str) : h[key] = [str]
}
return Object.values(h)
}
// 3
var groupAnagrams = function(strs) {
let hash = new Map()
for(let i = 0; i < strs.length; i++) {
let str = strs[i].split('').sort().join()
if(hash.has(str)) {
let temp = hash.get(str)
temp.push(strs[i])
hash.set(str, temp)
} else {
hash.set(str, [strs[i]])
}
}
return [...hash.values()]
};
tips: 养成收藏精选代码的习惯
6、树、二叉树、二叉搜索树
链表是特殊化的树,树(只有两个子节点)是特殊化的图(有环)
- 前序遍历 根-左-右
- 中序遍历 左-根-右
- 后续遍历 左-右-根
二叉搜索树,也称为有序二叉树,排序二叉树,是指一个空树,或者具有下列性质的二叉树,
1、左子树上所有结点的值均小于它的根结点的值。
2、右子树上所有结点的值均大于它的根结点的值。
3、以此类推
查询、插入、删除都是log(n)。极端情况下是O(n)。所以加速了。二分查找,log(2n)
// 遍历理论值
var treeArr = [1, null, 2,3, null,4,5,6,7]
1、前 根-左-右 1、2、4、5、3、6、7
2、中 左-根-右 4、2、5、1、6、3、7
3、后 左-右-根 4、5、2、6、7、3、1
- 二叉树前序遍历 (leetcode-cn.com/problems/bi…
1、递归
const preorder = (root) => {
const res = []
const preOrder = (root) => {
if(root === null) return
// arr 对应位置,即:前中后
res.push(root.val)
preOrder(root.left)
preOrder(root.right)
}
preOrder(root)
return res
}
const preorder = (root, array = []) => {
if(root){
array.push(root.val)
preorder(root.left, array)
preorder(root.right, array)
}
}
2、栈、迭代
// 0.取根节点为目标节点,开始遍历
// 1.访问目标节点
// 2.左孩子入栈 -> 直至左孩子为空的节点
// 3.节点出栈,以右孩子为目标节点,再依次执行1、2、3
const preorder2 = (root) => {
const stack = []
const arr = []
while(root || stack.length){
while(root){
arr.push(root.val)
stack.push(root)
root = root.left
}
root = stack.pop()
root = root.right
}
return arr
}
- 二叉树的中序遍历 (leetcode-cn.com/problems/bi…)
1、递归
var inorderTraver = function(root){
const arr = []
const inorder = (root)=>{
if(!root) return;
// arr 对应前中后
inorder(root.left)
arr.push(root.val)
inorder(root.right)
}
inorder(root)
return arr;
}
2、栈、迭代
// 取跟节点为目标节点,开始遍历
// 1.左孩子入栈 -> 直至左孩子为空的节点
// 2.节点出栈 -> 访问该节点
// 3.以右孩子为目标节点,再依次执行1、2、3
var inorderTraver = function(root){
const arr = []
const skt = []
while(root || skt.length){
while(root){
skt.push(root)
root = root.left
}
root = skt.pop()
arr.push(root.val)
root = root.right
}
return arr
}
- 二叉树的后序遍历 (leetcode-cn.com/problems/bi…
# 取跟节点为目标节点,开始遍历
# 1.左孩子入栈 -> 直至左孩子为空的节点
# 2.栈顶节点的右节点为空或右节点被访问过 -> 节点出栈并访问他,将节点标记为已访问
# 3.栈顶节点的右节点不为空且未被访问,以右孩子为目标节点,再依次执行1、2、3
var postorder = function(root){
const arr = []
const skt = []
let prev = null
while(root || skt.length){
while(root){
skt.push(root)
root = root.left
}
root = skt.pop()
if(root.right === null || root.right === prev){
arr.push(root.val)
prev = root
root = null
} else {
skt.push(root)
root = root.right
}
}
return arr
}
var postorderTraversal2 = function (root) {
const result = [];
const stack = [];
let last = null; // 标记上一个访问的节点
let current = root;
while (current || stack.length > 0) {
while (current) {
stack.push(current);
current = current.left;
}
current = stack[stack.length - 1];
if (!current.right || current.right == last) {
current = stack.pop();
result.push(current.val);
last = current;
current = null; // 继续弹栈
} else {
current = current.right;
}
}
return result;
}
- N 树后序遍历 (leetcode-cn.com/problems/n-… 给定一个 N 叉树,返回其节点值的后序遍历。
// 递归算法
var postorder = function(root) {
if (!root) return []
function houxu (root, res) {
if (!root) {
return []
}
if (root.children) {
root.children.map(child => houxu(child, res))
}
res.push(root.val)
return res
}
return houxu(root, [])
};
// 利用栈迭代算法
var postorder = function(root) {
if (!root) return []
let stack = [root]
let result = []
while (stack.length) {
let node = stack.pop()
if (node) {
result.unshift(node.val)
}
if (node.children) {
stack.push(...node.children)
}
}
return result
};
- N 叉树的层序遍历 (leetcode-cn.com/problems/n-…) 给定一个 N 叉树,返回其节点值的层序遍历。
二叉树节点插入js 实现
function Node(value) {
this.value = value
this.left = null
this.right = null
}
function BinaryTree() {
this.root = null // 树根
this.queue = [] // 存储会被使用的父节点
this.insertNum = 0 // 记录插入操作的次数
}
BinaryTree.prototype.insert = function (value) {
this.insertNum++ // 插入次数加1
let node = new Node(value)
if (!this.root) { // 判断根节点是否存在
this.root = node // 插入根节点
this.queue.push(this.root) // 新节点入列
} else { // 插入非根节点
let parent = this.queue[0] // 被插入的父节点
if (!(this.insertNum % 2)) { // 通过插入次数判断左右
parent.left = node // 插入左边
this.queue.push(parent.left) // 新节点入列
} else {
parent.right = node // 插入右边
this.queue.push(parent.right) // 新节点入列
this.queue.shift() // 当前父节点parent 已经不可能再插入子节点,故出列
}
}
return this
}
let binaryTree = new BinaryTree()
binaryTree.insert('A')
.insert('B')
.insert('C')
.insert('D')
.insert('E')
.insert('F')
console.log(JSON.stringify(binaryTree.root, null, 4))
function BinaryTree(){
// 定义节点
var Node = function(key){
this.key = key;
this.left = null;
this.right = null;
}
// 初始化根节点
var root = null;
// 插入节点
this.insert = function(key){
// 实例化node节点
var newNode = new Node(key);
// 根节点为空,便将newNode赋给root节点
if (root === null) {
root = newNode;
} else {
// 根节点存在,插入新节点
insertNode(root, newNode);
};
}
// 插入节点(中序遍历)
var insertNode = function(node, newNode){
// node 当前节点
// newNode 新节点
// 若newNode小于node,则插入到node的左侧
if(newNode.key < node.key){
// 若node的左孩子为空,则插入为node的左孩子
if(node.left === null){
node.left = newNode;
} else {
// 若node的左孩子不为空,则继续去和node的左孩子比较进行插入
insertNode(node.left, newNode);
}
}else {
if(node.right === null){
node.right = newNode;
}else{
insertNode(node.right, newNode);
}
}
}
}
var nodes = [2, 5, 4, 1, 3, 6];
var binaryTree = new BinaryTree(); // 将数组中的每个元素插入到二叉树中
nodes.forEach(function(key){
binaryTree.insert(key);
})
function Node(data,left,right) {
this.data = data;
this.left = left;
this.right = right;
this.show = show;
}
function show() {
return this.data;
}
function BST() {
this.root = null;
this.insert = insert;
this.inOrder = inOrder;
this.getMin = getMin;
this.getMax = getMax;
this.find = find;
this.remove = remove;
}
function insert(data) {
var n = new Node(data,null,null);
if(this.root == null) {
this.root = n;
}else {
var current = this.root;
var parent;
while(current) {
parent = current;
if(data < current.data) {
current = current.left;
if(current == null) {
parent.left = n;
break;
}
}else {
current = current.right;
if(current == null) {
parent.right = n;
break;
}
}
}
}
}
// 中序遍历
function inOrder(node) {
if(!(node == null)) {
inOrder(node.left);
console.log(node.show());
inOrder(node.right);
}
}
// 先序遍历
function preOrder(node) {
if(!(node == null)) {
console.log(node.show());
preOrder(node.left);
preOrder(node.right);
}
}
// 后序遍历
function postOrder(node) {
if(!(node == null)) {
postOrder(node.left);
postOrder(node.right);
console.log("后序遍历"+node.show());
}
}
// 二叉树查找最小值
function getMin(){
var current = this.root;
while(!(current.left == null)) {
current = current.left;
}
return current.data;
}
// 二叉树上查找最大值
function getMax() {
var current = this.root;
while(!(current.right == null)) {
current = current.right;
}
return current.data;
}
// 查找给定值
function find(data) {
var current = this.root;
while(current != null) {
if(current.data == data) {
return current;
}else if(data < current.data) {
current = current.left;
}else {
current = current.right;
}
}
return null;
}
function remove(data) {
root = removeNode(this.root,data);
}
function getSmallest(node) {
if (node.left == null) {
return node;
}
else {
return getSmallest(node.left);
}
}
function removeNode(node,data) {
if(node == null) {
return null;
}
if(data == node.data) {
// 没有子节点的节点
if(node.left == null && node.right == null) {
return null;
}
// 没有左子节点的节点
if(node.left == null) {
return node.right;
}
// 没有右子节点的节点
if(node.right == null) {
return node.left;
}
// 有2个子节点的节点
var tempNode = getSmallest(node.right);
node.data = tempNode.data;
node.right = removeNode(node.right,tempNode.data);
return node;
}else if(data < node.data) {
node.left = removeNode(node.left,data);
return node;
}else {
node.right = removeNode(node.right,data);
return node;
}
}
代码初始化如下:
var nums = new BST();
nums.insert(23);
nums.insert(45);
nums.insert(16);
nums.insert(37);
nums.insert(3);
nums.insert(99);
nums.insert(22);
var min = nums.getMin();
console.log(min);
var max = nums.getMax();
console.log(max);
var value = nums.find("45");
console.log(value);
nums.remove(23);
分治(尝试分步去解决一个问题)、回溯的的思路
分治模板
Javascript
const divide_conquer = (problem, params) => {
// recursion terminator
if (problem == null) {
process_result
return
}
// process current problem
subproblems = split_problem(problem, data)
subresult1 = divide_conquer(subproblem[0], p1)
subresult2 = divide_conquer(subproblem[1], p1)
subresult3 = divide_conquer(subproblem[2], p1)
...
// merge
result = process_result(subresult1, subresult2, subresult3)
// revert the current level status
}
数据结构 - 堆
堆的底层实际上是一棵完全二叉树,可以用数组实现
- 每个的节点元素值不小于其子节点 - 最大堆
- 每个的节点元素值不大于其子节点 - 最小堆 堆在处理某些特殊场景时可以大大降低代码的时间复杂度,例如在庞大的数据中找到最大的几个数或者最小的几个数,可以借助堆来完成这个过程。
function Heap(type = 'min') {
this.type = type;
this.value = [];
}
Heap.prototype.create = function () {
const length = this.value.length;
for (let i = Math.floor((length / 2) - 1); i >= 0; i--) {
this.ajust(i, length);
}
}
Heap.prototype.ajust = function (index, length) {
const array = this.value;
for (let i = 2 * index + 1; i < length; i = 2 * i + 1) {
if (i + 1 < length) {
if ((this.type === 'max' && array[i + 1] > array[i]) ||
(this.type === 'min' && array[i + 1] < array[i])) {
i++;
}
}
if ((this.type === 'max' && array[index] < [array[i]]) ||
(this.type === 'min' && array[index] > [array[i]])) {
[array[index], array[i]] = [array[i], array[index]];
index = i;
} else {
break;
}
}
}
Heap.prototype.add = function (element) {
const array = this.value;
array.push(element);
if (array.length > 1) {
let index = array.length - 1;
let target = Math.floor((index - 1) / 2);
while (target >= 0) {
if ((this.type === 'min' && array[index] < array[target]) ||
(this.type === 'max' && array[index] > array[target])) {
[array[index], array[target]] = [array[target], array[index]]
index = target;
target = Math.floor((index - 1) / 2);
} else {
break;
}
}
}
}
Heap.prototype.pop = function () {
const array = this.value;
let result = null;
if (array.length > 1) {
result = array[0];
array[0] = array.pop();
this.ajust(0, array.length);
} else if (array.length === 1) {
return array.pop();
}
return result;
}
var heap = new Heap('max');
heap.add(6)
heap.add(10)
console.log(heap.value);
console.log(heap.pop());
console.log(heap.value);