0、基础
(1)异或
相异为1,相同为0。
性质
- N^0=N N^N=0
- 满足交换律和结合律
(2)与
1&1=1,其他都=0。
//利用临时变量实现交换
function swap(arr, i, j){
let temp = arr[i];
arr[i] = arr[j];
arr[j] = arr[i];
}
//利用异或实现交换
//前提:i和j地址值不同(相当于自己地址值里的内容跟自己地址值里的内容异或=0)。
function swap(arr, i, j){
arr[i] = arr[i]^arr[j];
arr[j] = arr[i]^arr[j];
arr[i] = arr[i]^arr[j];
}
//利用解构赋值实现交换
function swap(arr, i, j){
[arr[i], arr[j]] = [arr[j], arr[i]];
}
//数组中有一个数只出现奇数次,其余都出现偶数次。请找出这个数。
//leetcode-136
//时间复杂度O(n),空间复杂度O(1)
var singleNumber = function(nums) {
let i = 0;
nums.forEach(n=>{
i ^= n;
});
return i;
};
//数组中有两个数只出现奇数次,其余都出现偶数次。请找出这两个数。
//leetcode-260
//时间复杂度O(n),空间复杂度O(1)
var singleNumber = function(nums) {
let eor = 0;
nums.forEach(n=>{
eor ^= n;
});
//此时eor=a^b,且不等于0
//找出eor二进制表示中为1的最低位
let right = eor & (~eor+1);
let one = 0;//得到a或b
//根据right位,可以将所有数分成两部分:others1和a,others2和b
nums.forEach(n=>{
//将和right位相同的数异或,得到的结果就是a或b
if(n & right){
one ^= n;
}
});
return Array.of(one, eor^one);
};
(3)生成随机数
//0-1
Math.random()
//0-x
Math.around(Math.random()*x)
//1-10
Math.around(Math.random()*9+1)
//x-y
Math.around(Math.random()*(y-x) + x)
(4)master公式
T(N) = a*T(N/b) + O(N^d)
- log b a < d,时间复杂度为O(N^d)
- log b a > d,时间复杂度为O(N^(log b a))
- log b a = d,是件复杂度为O(N^d * logN)
(5)比较器
//返回负数时,a排在前;返回正数时,b排在前;返回0,不变。
function cmp(a, b){
//按id升序
/*
if(a.id < b.id){
return -1;
}else if(a.id > b.id){
return 1;
}else{
return 0;
}
*/
return a.id - b.id;
//按id降序
//return b.id - a.id;
}
arr.sort(cmp);
(6)定义节点
//单链表
function Node(value){
this.value = value;
this.next = null;
}
//二叉树
function Node(value){
this.value = value;
this.left = null;
this.right = null;
}
1、排序
- 快选快排;空间少选堆排序;稳定选归并排序。
- sort(),基础类型使用快排,非基础类型使用归并:是为了稳定性。
基于比较的排序
(1)选择排序
- 时间复杂度O(n^2)
- 空间复杂度O(1)
- 不稳定
- 每趟从后面未排序序列中选出一个最小的。
function selectSort(arr){
if(arr === null || arr.length < 2) return;
//长度n,下标0~n-1
//遍历i~n-2
for(let i = 0; i < arr.length-1; i++){
//以i为标杆
let minIndex = i;
//遍历i+1~n-1
for(let j = i+1; j < arr.length; j++){
minIndex = arr[j] < arr[minIndex] ? j : minIndex;
}
swap(arr, i, minIndex);
}
}
(2)冒泡排序
- 时间复杂度O(n^2)
- 空间复杂度O(1)
- 稳定
- 从前往后,两两比较,大的往后放,每趟比较后冒出一个最大的元素。
function bubbleSort(arr){
if(arr === null || arr.length < 2) return;
//长度n,下标0~n-1
//i放每趟排序后冒出来的元素。
for(let i = arr.length - 1; i > 0; i--){
//遍历0~i-1
for(let j = 0; j < i; j++){
if(arr[j] > arr[j+1]){
swap(arr, j, j+1);
}
}
}
}
(3)插入排序
- 时间复杂度最差O(n^2),最佳(n)
- 空间复杂度O(1)
- 稳定
- 将元素从后往前插入已排序序列中,比较直到前一个元素比元素小结束。
function insertSort(arr){
if(arr === null || arr.length < 2) return;
//长度n,下标0~n-1
//i指向待插入元素。
for(let i = 0; i < arr.length; i++){
//j指向i前面元素,比较j和j+1
for(let j = i - 1; j > 0 && arr[j] > arr[j+1];j--){
swap(arr, j, j+1);
}
}
}
(4)归并排序
- 时间复杂度O(NlogN)
- 空间复杂度O(N)
- 稳定
- 分成左右两序列,使左右序列分别有序;合并左右序列,小的插入result,指针后移。
function mergeSort(arr){
if(arr === null || arr.length < 2) return arr;
process(arr, 0, arr.length-1);
}
function process(arr, L, R){
if(L === R) return;
//mid = (R-L)/2;为了防止溢出,mid = L + (R-L)/2;位运算更快。
let mid = L + (R-L)>>1;
process(arr, L, mid);
process(arr, mid+1, R);
merge(arr, L, mid, R);
}
function merge(arr, L, M, R){
let result = new Array(R-L+1);
let i = 0;//指向result当前要放入元素的位置
let p1 = L;//指向左序列当前遍历到哪里
let p2 = M+1;//指向右序列当前遍历到哪里
//当指针p1、p2还未超出范围时
while(p1 <= M && p2 <= R){
//注意是<=(相等的时候把左边的先插入)
result[i++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++];
}
//遍历结束还有元素未插入
while(p1 <= M){
result[i++] = arr[p1++];
}
while(p2 <= R){
result[i++] = arr[p2++];
}
for(let i = 0; i < result.length; i++){
arr[L+i] = result[i];
}
}
//应用
//小和问题:在一个数组中,遍历每个数,把每个数左边比当前数小的数累加起来,叫做这个数组的小和。
function smallSum(arr){
if(arr === null || arr.length < 2) return 0;
return process(arr, 0, arr.length-1);
}
function process(arr, l, r){
if(l === r) return 0;
let mid = l + ((r - l)>>1);
//左边处理完的累积小和+右边处理完的累积小和+这趟处理产生的累积小和。
return process(arr, 1, mid)
+ process(arr, mid+1, r)
+ merge(arr, 1, mid, r);
}
function merge(arr, l, m, r){
let result = new Array(r-l+1);
let i = 0;
let p1 = l;
let p2 = m+1;
let res = 0;
while(p1 <= m && p2 <= r){
res += arr[p1] < arr[p2] ? (r-p2+1)*arr[p1] : 0;
//注意是<
result[i++] = arr[p1] < arr[p2]? arr[p1++] : arr[p2++];
}
while(p1 <= m){
result[i++] = arr[p1++];
}
while(p2 <= r){
result[i++] = arr[p2++];
}
for(let j = 0; j < result.length; j++){
arr[l+i] = result[i];
}
return res;
}
//逆序对问题:在一个数组中,左边的数如果比右边的数大,则这两个数构成一个逆序对,请求出逆序对的总数。
//leetcode-剑指offer51
var reversePairs = function(nums) {
if(nums === null || nums.length < 2) return 0;
return process(nums, 0, nums.length-1);
};
function process(arr, l, r){
if(l === r) return 0;
let mid = l + ((r-l)>>1);
return process(arr, l, mid)
+ process(arr, mid+1, r)
+ merge(arr, l , mid, r);
}
function merge(arr, l, m, r){
let result = new Array(r-l+1);
let i = 0;
let p1 = l;
let p2 = m+1;
let res = 0;
while(p1<=m && p2<=r){
//注意比较符号
res += arr[p1] > arr[p2] ? r-p2+1 : 0;
result[i++] = arr[p1] > arr[p2]? arr[p1++] : arr[p2++];
}
while(p1<=m){
result[i++] = arr[p1++];
}
while(p2<=r){
result[i++] = arr[p2++];
}
for(let i = 0; i < result.length; i ++){
arr[l+i] = result[i];
}
return res;
}
(5)快速排序
- 时间复杂度O(NlogN)
- 空间复杂度O(logN)
- 不稳定
//问题1:给定一个数组arr和一个数num,请把小于等于num的数放在数组的左边,大于num的数放在数组的右边。
//时间复杂度O(N),空间复杂度O(1)
function divide(arr, num){
//指向≤区的最后一个元素。
let i = -1;
let p = 0;
while(p < arr.length){
//≤num,和i的下一个数交换(插入≤区,≤区右扩)。
if(arr[p] <= num){
swap(arr, ++i, p);
}
//>num,不处理;
p++;
}
return arr;
}
//荷兰国旗问题:给定一个数组arr和一个数num,请把小于num的数放在数组的左边,等于num的数放在数组的中间,大于num的数放在数组的右边。
//时间复杂度O(N),空间复杂度O(1)
function dutchNationalFlag(arr, num){
let i = -1;
let j = arr.length;
let p = 0;
while(p < j){
//<num,和i的下一个数交换(≤区右扩),p右移。
if(arr[p]<num){
swap(arr, ++i, p++);
}
//=num,p右移。
else if(arr[p]===num){
p++;
}
//>num,和j的前一个数交换(>区左扩),p不动(交换后p还未被比较过)。
else if(arr[p]>num){
swap(arr, p, --j);
}
}
}
//快排1.0:最后一个数为num,以问题1为partition算法,不断递归。
//快排2.0:最后一个数为num,以荷兰国旗问题为partition算法,不断递归。
//快排3.0:随机一个数为num,以荷兰国旗问题为partition算法,不断递归。
function quickSort(arr){
if(arr === null || arr.length < 2)return;
process(arr, 0, arr.length-1);
}
function process(arr, L, R){
if(L < R){
//从R-L之间随机选一个数和R交换,作为标杆。
swap(arr, L + Math.around(Math.random()*(R-L)), R);
//p[0]是=区的左边界,p[1]是=区的右边界。
let p = [...partition(arr, L, R)];
//<区再做快排
process(arr, L, p[0]-1);
//>区再做快排
process(arr, p[1]+1, R);
}
}
function partition(arr, L, R){
let i = L-1;
let j = R;//R位置是标杆
let p = L;
while(p < j){
if(arr[p] < arr[R]){
swap(arr, ++i, p++);
}else if(arr[p] === arr[R]){
p++;
}else if(arr[p] > arr[R]){
swap(arr, p, --j);
}
}
//最后将>区的左边界和标杆换位置。
swap(arr, j, R);
//返回=区的左右边界。
return Array.of(++i, j);
}
//优化:样本数<60使用插入排序;其余使用快排。
(6)堆排序
- 时间复杂度O(NlogN)
- 空间复杂度O(1)
- 不稳定
- 优先级队列结构就是堆结构。
//完全二叉树中,(结点下标为i),i结点的父结点为(i-1)/2,左孩子2i+1,右孩子2i+2。
//给了个大根堆,从index位置开始往上调整成大根堆。
//不断跟父节点比较,比父节点大上浮。
//时间复杂度O(logN)
function heapInsert(arr, index){
//比父亲小 或 我的父亲就是我自己 时跳出循环。
while(arr[index] > arr[(index-1)/2]){
swap(arr, index, (index-1)/2);
index = (index - 1) / 2;
}
}
//给了个大根堆,从index位置开始往下调整成大根堆。
//不断跟子节点较大的比较,比子节点小下沉。
//时间复杂度O(logN)
function heapify(arr, index, heapSize){
let lchild = index*2+1;
while(lchild < heapSize){
//左右孩子进行比较
let bigOne = lchild+1 < heapSize && arr[lchild] < arr[lchild+1]? lchild+1 : lchild;
//左右孩子较大的和父节点进行比较
bigOne = arr[bigOne] > arr[index]? bigOne : index;
if(bigOne === index) break;
swap(arr, bigOne, index);
index = bigOne;
lchild = index*2+1;
}
}
//给了个大根堆,把index位置改成某个数num,要求调整成大根堆。
//num和arr[index]进行比较,如果num<arr[index]往下调整;如果num>arr[index]往上调整。
//堆排序
function heapSort(arr){
if(arr === null || arr.length < 2) return;
//从0位置开始往上调整大根堆。
//for(let i = 0; i < arr.length; i++){
// heapInsert(arr, i);
//}
//倒序遍历,向下调整大根堆。
for(let i = arr.length - 1; i >= 0; i--){
heapify(arr, i, arr.length);
}
//不断把最大的摘下来,放在最后。
let heapSize = arr.length;
swap(arr, 0, --heapSize);
while(heapSize > 0){
heapify(arr, 0, heapSize);
swap(arr, 0, --heapSize);
}
}
//已知一个几乎有序的数组,选择合适的算法对数组进行排序,要求数组排好顺序后,每个元素移动的距离可以不超过k,并且k相对于数组来说比较小。
function sortedArrDistanceLessK(arr, k){
let heap = new Array();
let index = 0;
//把数组前k个数放进堆里,调整堆。
//求出数组长度和k中的最小值(防止k>>数组长度)
for(; index < Math.min(arr.length, k); index++){
heap.push(arr[index]);
heapInsert(heap, index);
}
let i = 0;
//不断把数组中k+1个数放进堆里,调整堆,弹出堆顶。
for(; index < arr.length; i++, index++){
heap[index] = arr[index];
heapInsert(heap, index);
//弹出堆顶,放在i位置上。
arr[i] = heap[0];
heap[0] = heap[heap.length-1];
heap.pop();
heapify(heap, 0, heap.length);
}
//堆里还有剩余
while(heap.length>0){
arr[i++] = heap[0];
heap[0] = heap[heap.length-1];
heap.pop();
heapify(heap, 0, heap.length);
}
}
//数据流,实时找出中位数
//建一个大根堆和小根堆。
//对于每个数字cur:(1)cur<=大根堆堆顶,加入大根堆;否则加入小根堆。(2)比较大小根堆的大小,如果相差>=2,摘下较多的堆的对顶加入另一个堆。
非基于比较的排序
(1)计数排序
- 建立数组,下标即为数字,统计数字出现的频率。
(2)桶排序(基数排序)
function radixSortMain(arr){
if(arr === null || arr.length < 2)return;
radixSort(arr, 0, arr.length - 1, maxbits(arr));
}
//求最多有几位十进制数。
function maxbits(arr){
let max = Number.MAX_VALUE;
for(let i = 0; i < arr.length; i++){
max = Math.max(max, arr[i]);
}
let res = 0;
while(max !== 0){
res++;
max /= 10;
}
return res;
}
function radixSort(arr, L, R, digit){
const radix = 10;
let i = 0, j = 0;
let bucket = new Array(R-L+1);
for(let d = 1; d <= digit; d++){
//原count数组,count[i]表示d位上,i出现的次数。
//现count数组,count[i]等于count[i]前面的数字累加,表示d位上<=i的有几个(每个数字的片加起来);每个数该放在bucket数组下标=count[i]-1。
let count = new Array(radix);
//统计出现的次数
for(i = L; i <= R; i++){
j = getDigit(arr[i], d);
count[j]++;
}
//累加
for(i = 1; i < radix; i++){
count[i] = count[i] + count[i-1];
}
//从右往左遍历数组(保证先入桶的先出桶)
for(i = R; i >= L; i--){
j = getDigit(arr[i], d);
bucket[count[j]-1] = arr[i];
count[j]--;
}
for(i = L, j = 0; i <= R; i++, j++){
arr[i] = bucket[j];
}
}
}
function getDigit(x, d){
return ((x / (Math.pow(10, d-1)))%10);
}
2、二分查找
有序数组,找某个数是否存在。
- 时间复杂度O( logN)
有序数组,找>=某个数最左侧的位置。
- 时间复杂度O(logN)
局部最小值
无序数组,相邻数一定不相等,求局部最小值。
- 时间复杂度O(N)
3、哈希表
哈希表
- 增删改查操作的时间复杂度可以认为是O(1),但是常数时间比较大。
- 放入哈希表的东西,如果是基础类型,内部按值传递,内存占用的是这个东西的大小;如果不是基础类型,内部按引用传递,内存占用的是这个东西内存地址的大小。
- JS中的Object
有序表
- 有序表和哈希表的区别是,有序表把key按照顺序组织起来,而哈希表完全不组织。
- 放入有序表的东西,如果是基础类型,内部按值传递,内存占用的是这个东西的大小;如果不是基础类型, 必须提供比较器,内部按引用传递,内存占用的是这个东西内存地址的大小。
- 红黑树、AVL树、size-balance-tree和跳表都属于有序表。
- JS中的Map、Set
4、链表
//反转单链表
//leetcode-206
//指针法
var reserveList = function(head){
let prev = null;
let curr = head;
let next = head;
while(curr !== null){
next = curr.next;
curr.next = prev;
prev = curr;
curr = next;
}
return prev;
};
//指针法-简写(利用解构赋值)
var reverseList = function(head){
let prev = null;
let curr = head;
while(curr !== null){
[curr.next, prev, curr] = [prev, curr, curr.next];
}
return prev;
};
//递归法
var reverseList = function(head){
if(head === null || head.next === null){
return head;
}
//last用来指向反转后的头节点
const last = reserveList(head.next);
head.next.next = head;
head.next = null;
return last;
};
//反转双链表
var reserve = function(head){
let prev = null;
let curr = head;
while(curr!==null){
curr.prev = curr.next;
curr.next = prev;
prev = curr;
curr = curr.prev;
//[curr.prev, curr.next, prev, curr] = [curr.next, prev, curr, curr.prev];
}
return prev;
};
//打印两个有序链表的公共部分
//牛客网-程序员bla-CD48(os:牛客网真难用)
//谁小谁移动,相等打印并移动,有一个越界停。
var print = function(head1, head2){
let p1 = head1, p2 = head2;
while(p1!==null && p2!==null){
if(p1.data < p2.data){
p1 = p1.next;
}else if(p1.data > p2.data){
p2 = p2.next;
}else{
console.log(p1.data);
p1 = p1.next;
p2 = p2.next;
}
}
}
//分割链表
//分成<,=,>区版本
//定义六个指针变量sH/sT/eH/eT/bH/bT
var partition = function(head, x){
let sH = null;
let sT = null;
let eH = null;
let eT = null;
let bH = null;
let bT = null;
let next = null;
while(head !== null){
//next记录下一个节点。
next = head.next;
//摘下头节点。
head.next = null;
if(head.val < x){
if(sH === null){
sH = head;
sT = head;
}else{
sT.next = head;
sT = head;
}
}else if(head.val === x){
if(eH === null){
eH = head;
eT = head;
}else{
eT.next = head;
eT = head;
}
}else{
if(bH === null){
bH = head;
bT = head;
}else{
bT.next = head;
bT = head;
}
}
head = next;
}
//s区非空,连接s区和e区
if(sH !== null){
sT.next = eH;
//判断e区是否为空
eT = eT === null? sT : eT;
}
//e区非空,连接e区和b区
if(eH !== null){
eT.next = bH;
}
return sH !== null ? sH : (eH !== null ? eH : bH);
};
//分成<,>=区版本
//leetcode-86
var partition = function(head, x){
let sH = null;
let sT = null;
let eH = null;
let eT = null;
let next = null;
while(head !== null){
//next记录下一个节点。
next = head.next;
//摘下头节点。
head.next = null;
if(head.val < x){
if(sH === null){
sH = head;
sT = head;
}else{
sT.next = head;
sT = head;
}
}else{
if(eH === null){
eH = head;
eT = head;
}else{
eT.next = head;
eT = head;
}
}
head = next;
}
//s区非空,连接s区和e区
if(sH !== null){
sT.next = eH;
}
return sH !== null ? sH : eH;
};
//判断回文链表
//leetcode-234
var isPalindrome = function(head){
let slow = head;
let fast = head;
while(fast !== null && fast.next !== null){
//慢指针一个一个走;快指针走双倍。
//如果链表长度是奇数,最后slow走到中间,fast走到end。
//如果链表长度是偶数,最后slow走到中间后一个,fast走到end+1(null)。
slow = slow.next;
fast = fast.next.next;
}
//链表长度是奇数,slow还需要往后走一个。
if(fast !== null){
slow = slow.next;
}
//分成左右两块开始遍历
let left = head;
//将slow后面的链表反转
let right = reverse(slow);
while(right !== null){
if(left.val !== right.val)
return false;
left = left.next;
right = right.next;
}
return true;
};
var reverse = function(head){
let prev = null;
let curr = head;
while(curr !== null){
[curr.next, prev, curr] = [prev, curr, curr.next];
}
return prev;
};
//复制带随机指针的链表
//leetcode-138
//用哈希表
var copyRandomList = function(head){
if(!head) return head;
let map = new Map();
let cur = head;
while(cur !== null){
//key为老节点,value为新节点
map.set(cur, new Node(cur.val));
cur = cur.next;
}
cur = head;
while(cur !== null){
//根据map.get来查map,从而设置新节点。
map.get(cur).next = map.get(cur.next) || null;
map.get(cur).random = map.get(cur.random) || null;
cur = cur.next;
}
return map.get(head);
}
//骚操作
var copyRandomList = function(head){
if(!head) return head;
let cur = head;
let next = null;
while(cur !== null){
next = cur.next;
//把每个节点的克隆节点放在每个节点的后面。
cur.next = new Node(cur.val);
//克隆节点串上原来的next
cur.next.next = next;
cur = next;
}
cur = head;
//拷贝random指向
//遍历新节点
let curCopy = null;
while(cur !== null){
//遍历老节点
next = cur.next.next;
curCopy = cur.next;
curCopy.random = cur.random !== null ? cur.random.next : null;
cur = next;
}
const res = head.next;
cur = head;
//拷贝next
while(cur !== null){
next = cur.next.next;
curCopy = cur.next;
//修改新旧节点的next指向
cur.next = next;
curCopy.next = next !== null ? next.next : null;
cur = next;
}
return res;
}
//无环或有环链表相交节点
function getIntersectNode(head1, head2){
if(head1 === null || head2 === null){
return null;
}
let loop1 = getLoopNode(head1);
let loop2 = getLoopNode(head2);
if(loop1 === null && loop2 === null){
return noLoop(head1, head2);
}
if(loop1 !== null && loop2 !== null){
return bothLoop(head1, loop1, head2, loop2);
}
return null;
}
function getLoopNode(head){
if(head === null || head.next === null || head.next.next === null){
return null;
}
//如果链表有环,快慢指针会相遇;且快慢指针在环中转的圈数不会大于两圈,因为快指针比慢指针快了一倍。
let slow = head.next;
let fast = head.next.next;
while(slow !== fast){
if(fast.next === null || fast.next.next === null){
return null;
}
fast = fast.next.next;
slow = slow.next;
}
//当快慢指针相遇,快指针回到头,快慢指针一起一步一步走,相遇时则为环的入节点。
fast = head;
while(fast !== slow){
fast = fast.next;
slow = slow.next;
}
return fast;
}
//求两个无环链表相交节点
function noLoop(head1, head2){
if(head1 === null || head2 === null){
return null;
}
let cur1 = head1;
let cur2 = head2;
let n = 0;
while(cur1.next !== null){
n++;
cur1 = cur1.next;
}
while(cur2.next !== null){
n--;
cur2 = cur2.next;
}
if(cur1 !== cur2){
return null;
}
//求出差值n,让cur1指向长链表
cur1 = n > 0? head1:head2;
cur2 = cur1 === head1? head2:head1;
n = Math.abs(n);
//长链表往前走差值步数
while(n !== 0){
n--;
cur1 = cur1.next;
}
while(cur1 !== cur2){
cur1 = cur1.next;
cur2 = cur2.next;
}
return cur1;
}
//一个有环,一个无环是不可能相交的。
//求两个有环链表相交节点
function bothLoop(head1, loop1, head2, loop2){
let cur1 = null;
let cur2 = null;
//首环节点相同
if(loop1 === loop2){
cur1 = head1;
cur2 = head2;
let n = 0;
while(cur1 !== loop1){
n++;
cur1 = cur1.next;
}
while(cur2 !== loop2){
n--;
cur2 = cur2.next;
}
cur1 = n > 0? head1:head2;
cur2 = cur1 === head1? head2: head1;
n = Math.abs(n);
while(n !== 0){
n--;
cur1 = cur1.next;
}
while(cur1 !== cur2){
cur1 = cur1.next;
cur2 = cur2.next;
}
return cur1;
}else{
//首环节点不相同
cur1 = loop1.next;
while(cur1 !== loop1){
if(cur1 === cur2){
return loop1;
}
cur1 = cur1.next;
}
return null;
}
}
5、二叉树
//递归遍历(每个节点遍历3次)
function recursion(head){
//1
if(head === null){
return;
}
//1
recursion(head.left);
//2
//2
recursion(head.right);
//3
//3
}
//先序遍历(在1的时候打印)
function preOrderRecur(head){
if(head === null){
return;
}
console.log(head.value);
preOrderRecur(head.left);
preOrderRecur(head.right);
}
//中序遍历(在2的时候打印)
function inOrderRecur(head){
if(head === null){
return;
}
inOrderRecur(head.left);
console.log(head.value);
inOrderRecur(head.right);
}
//后序遍历(在3的时候打印)
function posOrderRecur(head){
if(head === null){
return;
}
posOrderRecur(head.left);
posOrderRecur(head.right);
console.log(head.value);
}
//非递归版本
//先序遍历:根左右
function preOrderUnRecur(head){
if(head !== null){
let stack = new Array();
stack.push(head);
while(stack.length > 0){
//弹出节点,处理
head = stack.pop();
console.log(head.value);
//右节点压入
if(head.right !== null){
stack.push(head.right);
}
//左节点压入
if(head.left !== null){
stack.push(head.left);
}
}
}
}
//中序遍历:左根右
function inOrderUnRecur(head){
if(head !== null){
let stack = new Array();
while(stack.length > 0 || head !== null){
//左边界进栈
if(head !== null){
stack.push(head);
head = head.left;
}else{
//弹出节点,走右树
head = stack.pop();
console.log(head.value);
head = head.right;
}
}
}
}
//后序遍历:左右根(利用收集栈)
function posOrderUnRecur(head){
if(head !== null){
let stack1 = new Array();
let stack2 = new Array();
stack1.push(head);
while(stack1.length > 0){
//弹出节点,压入收集栈
head = stack1.pop();
stack2.push(head);
//左节点压入
if(head.left !== null){
stack1.push(head.left);
}
//右节点压入
if(head.right !== null){
stack1.push(head.right);
}
}
while(stack2.length > 0){
console.log(stack2.pop().value);
}
}
}
//层序遍历(广度优先)
//用队列
function floorTraversing(head){
if(head !== null){
let queue = new Array();
queue.push(head);
while(queue.length > 0){
//队头出队
let cur = queue.shift();
console.log(cur.value);
//左孩子进队
if(cur.left !== null){
queue.push(cur.left);
}
//右孩子进队
if(cur.right !== null){
queue.push(cur.right);
}
}
}
}
//求二叉树的最大宽度
//哈希表
function maxWidth(head){
if(head !== null){
let queue = new Array();
let levelMap = new Map();
queue.push(head);
levelMap.set(head, 1);
let curLevel = 1;//当前层数
let curLevelNodes = 0;//当层节点个数
let max = 0;//最大层数
while(queue.length > 0){
let cur = queue.shift();
let curNodeLevel = levelMap.get(cur);
if(curNodeLevel === curLevel){
curLevelNodes++;
}else{
//该更新了
max = Math.max(max, curLevelNodes);
curLevel++;
curLevelNodes = 1;
}
if(cur.left !== null){
queue.push(cur.left);
levelMap.set(cur.left, curNodeLevel+1);
}
if(cur.right !== null){
queue.push(cur.right);
levelMap.set(cur.right, curNodeLevel+1);
}
}
//更新最后一层
max = Math.max(max, curLevelNodes);
return max;
}
}
//判断搜索二叉树
//中序遍历依次升序就是搜索二叉树
//1
var preValue = Number.MIN_VALUE;
function isBST(head){
if(head === null){
return true;
}
if(!isBST(head.left)){
return false;
}
if(head.value <= preValue){
return false;
}else{
preValue = head.value;
}
return isBST(head.right);
}
//2
function checkBST(head){
let list = new Array();
process(head, list);
for(let i = 0; i < list.length - 1; i++){
if(list[i] > list[i+1]){
return false;
}
}
return true;
}
function process(head, list){
if(head === null){
return;
}
process(head.left, list);
list.add(head);
process(head.right, list);
}
//3
function isBST(head){
if(head !== null){
let preValue = Number.MIN_VALUE;
let stack = new Array();
while(stack.length > 0 || head !== null){
//左边界进栈
if(head !== null){
stack.push(head);
head = head.left;
}else{
//弹出节点,走右树
head = stack.pop();
if(head.value <= preValue){
return false;
}else{
preValue = head.value;
}
head = head.right;
}
}
}
}
//判断完全二叉树
function isCBT(head){
if(head === null){
return true;
}
let queue = new Array();
let leaf = false;//是否遇到左右两个孩子不双全的节点
let l = null;
let r = null;
queque.push(head);
while(queue.length > 0){
head = queue.shift();
l = head.left;
r = head.right;
//左孩子为空,右孩子不为空 or leaf开启后遇到的不是叶节点
if(l === null && r !== null
|| leaf && (l!==null || r!==null)
){
return false;
}
if(l !== null){
queue.push(l);
}
if(r !== null){
queue.push(r);
}
//遇到左右两个孩子不双全时(要么l有r空,要么l有r有)
if(l === null || r === null){
leaf = true;
}
}
return true;
}
//判断满二叉树
//可以用以下套路
//判断平衡二叉树
//树型dp套路
function isBT(head){
//>=0返回高度是平衡二叉树;<0不是平衡二叉树。
return process(head) >= 0;
}
function process(head){
if(head === null){
return 0;
}
let lh = process(head.left);
let rh = process(head.right);
//如果这棵子树是平衡二叉树
if(lh >= 0
&& rh >= 0
&& Math.abs(lh - rh) < 2
){
return Math.max(lh, rh) + 1;
}
return -1;
}
//给定两个二叉树的节点,找到他们的最低公共祖先节点。
//leetcode-236
//1,超时了好吧
function lca(head, n1, n2){
//构造fatherMap
let fatherMap = new Map();
fatherMap.set(head, head);
process(head, fatherMap);
let set = new Set();
//n1往上遍历
let cur = n1;
//当等于自己(根节点),跳出
while(cur !== fatherMap.get(cur)){
set.add(cur);
cur = fatherMap.get(cur);
}
set.add(head);
//n2往上遍历
cur = n2;
while(!set.has(fatherMap.get(cur))){
cur = fatherMap.get(cur);
}
return cur;
}
function process(head, map){
if(head === null){
return;
}
map.set(head.left, head);
map.set(head.right, head);
process(head.left, map);
process(head.right, map);
}
//2
function lca(head, n1, n2){
if(head === null || head === n1 || head === n2){
return head;
}
let left = lca(head.left, n1, n2);
let right = lca(head.right, n1, n2);
//左右都不为空
if(left!==null && right!==null){
return head;
}
//左右一个为空,返回不空的
return left !== null ? left : right;
}
//求中序遍历后继节点
function getSuccessorNode(node){
if(node === null){
return node;
}
//有右子树,后继节点为右子树中最左边的节点
if(node.right !== null){
return getLeftMost(node.right);
}else{
let parent = node.parent;
//找到第一个为父亲的左子树的节点,该父亲就是后继节点
while(parent!==null && parent.left!==node){
node = parent;
parent = node.parent;
}
return parent;
}
}
function getLeftMost(node){
if(node === null){
return node;
}
while(node.left !== null){
node = node.left;
}
return node;
}
//二叉树的序列化和反序列化
//leetcode-297
//序列化,按先序
function serialByPre(head){
if(head === null){
return "#_";
}
let res = head.value + '_';
res += serialByPre(head.left);
res += serialByPre(head.right);
return res;
}
//反序列化,按先序
function reconByPre(str){
let values = str.split('_');
return process(values);
}
function process(queue){
let value = queue.shift();
if(value === '#'){
return null;
}
let head = new Node(Number(value));
head.left = process(queue);
head.right = process(queue);
return head;
}
//纸条的折痕
//二叉树中序遍历就是纸条从左到右的折痕顺序,根节点是凹,左孩子是凹,右孩子是凸。
function printAllFolds(n){
//节点层数;总层数;是否为凹
printProcess(1, n, true);
}
function printProcess(i, n, down){
if(i > n){
return;
}
printProcess(i+1, n, true);
console.log(down?"凹":"凸");
printProcess(i+1, n, false);
}
6、图
(略了略了,面试少遇到)
7、前缀树
class TrieNode{
constructor(){
this.past = 0;//经过几次
this.end = 0;//以该节点为叶节点的字符有几个
this.nexts = new Array(26);//走向26个字母的路
//字符种类很多的时候用map
}
}
function main(words){
const root = new TrieNode();
words.forEach(w=>{
insert(w, root);
});
search('ab', root);
prefixNumber('a', root);
}
//建立前缀树
function insert(word, root){
if(word === null){
return;
}
let node = root;
node.pass++;
let index = 0;
for(let i = 0; i < word.length; i++){
index = word[i] - 'a';
if(node.nexts[index] === null){
node.nexts[index] = new TrieNode();
}
node = node.nexts[index];
node.pass++;
}
node.end++;
}
//搜索字符串出现的次数
function search(word, root){
if(word === null){
return 0;
}
let node = root;
let index = 0;
for(let i = 0; i < word.length; i++){
index = word[i] - 'a';
if(node.nexts[index] === null){
return 0;
}
node = node.nexts[index];
}
return node.end;
}
//搜索以pre为前缀的字符串出现的次数
function prefixNumber(word, root){
if(word === null){
return 0;
}
let node = root;
let index = 0;
for(let i = 0; i < word.length; i++){
index = word[i] - 'a';
if(node.nexts[index] === null){
return 0;
}
node = node.nexts[index];
}
return node.pass;
}
//删除字符串
function delete(word, root){
//查询到有这个字符串才能进行删除操作
if(search(word)){
let node = root;
let index = 0;
for(let i = 0; i < word.length; i ++){
index = word[i] - 'a';
//下级的pass=0,删除整条下级
if(--node.nexts[index].pass === 0){
node.nexts[index] = null;
return;
}
node = node.nexts[index];
}
node.end--;
}
}
8、贪心算法
局部最优解=>答案
- 根据某标准建立一个比较器来排序
- 根据某标准建立一个比较器来组成堆
//合理地安排最多的会议
//类似leetcode-435
function cmp(a, b){
return a.end - b.end;
}
function bestArrange(programs, timePoint){
//按结束时间升序排列
programs.sort(cmp);
let res = 0;
//先安排结束时间最早的
for(let i = 0; i < programs.length; i++){
//如果开始时间比时间线晚,即可安排
if(timePoint <= programs[i].start){
res ++;
timePoint = programs[i].end;
}
}
return res;
}
//n皇后问题:在n*n的棋盘上摆n个皇后,要求任何2个皇后不同行、不同列,也不在同一条斜线上。
//给定一个整数n,返回n皇后的摆法有多少种。
function nqueen(n){
if(n === 0) return 0;
//record[0] = 3表示第0行的第3列放着一个皇后。
let record = new Array(n);
return process(0, record, n);
}
function process(i, record, n){
//来到终止行,返回1表示有一种方案
if(i === n) return 1;
let res = 0;
//处理第i行该放在哪一列
for(let j = 0; j < n; j++){
if(isValid(record, i, j)){
record[i] = j;
res += process(i+1, record, n);
}
}
return res;
}
function isValid(record, i , j){
//看第i行之前的皇后放在哪
for(let k = 0; k < i; k++){
//判断是否同一列or在同一斜线(列差=行差)
//不必判断是否同一行(因为处理是一行一行处理的,自然不会在同一行。
if(j === record[k] || Math.abs(record[k] - j) === Math.abs(k - i)){
return false;
}
}
return true;
}
//请不要超过32皇后问题(用位运算加速)
//bbq,不做了,再见。