【记录】算法 - JavaScript版

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,不做了,再见。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值