- 1.二分法 k = log2N T(n) = log2N + 1 假如二分法查找1000,最坏情况n是多少,10次
//A数组,x需要查找的值 l:左边界 r:右边界 g:guess
//g猜的位置Math.floor((l+r)/2);l开始,r数组最后一项序列,l第一次是0
//每次循环后,查找的值要么在l 和 r之间,要么不存在
function bsearch(A,x){
let l = 0,//左边界
r = A.length - 1,//右边界
guess
while(l<=r){
guess = Math.floor((l+r)/2)
if(A[guess] === x) return guess
else if(A[guess] > x){ //要查找的数字在guess左边
r = guess - 1
}else l = guess + 1 //要查找的数字在guess右边
}
return -1 //没找到
}
const A = [1,2,3,5,6,8,100]
console.log(bsearch(A,99))
二分法的核心:循环不变式:while左边界小于右边界 ,A[guess]在右边界,则缩小右边界范围 r=guess-1,反之则缩小左边界guess+1
- 选择排序(核心:先选择第一个数,然后不断和这个数后面的数比较,直到找到比这个数还小的,然后再做交换,以此类推)
function swap(A,i,j) {
[A[i],A[j]] = [A[j],A[i]]
}
function bubbor_sort(A) {
for(let i=0;i<A.length;i++) {
for(let j=i+1;j<A.length;j++) {
while(A[i]>A[j]) {
swap(A,i,j)
}
}
}
return A
}
let A = [4,3,66,22,666,23,33,221]
console.log(bubbor_sort(A))
- 2.插入排序 核心:循环不变式,p指向下一个要比较的元素,p+1指向腾出来的空位, while(A[p]>x(插入的元素)) ,则比较的元素A[p]前进一位,把原来A[p]赋值给A[p+1],否则把A[p+1]=x
//A:原数组 x:需要插入的元素
function insert(A,x){
let b = A.find( a => a>x); //第一次大于x的元素
let idx = A.indexOf(b)
A.splice(idx === -1 ? A.length : idx, 0, x) //在第一次找到元素前插入x
return A
}
console.log(insert([1,2,3,4,9,20],8))
//A:原数组 x:需要插入的元素
function insert(A,x){
//p 指向下一个需要比较的元素
//p+1 指向空位
let p = A.length -1
while(p > 0 && A[p] > x) {
//如果比插入数字大,则A[p]的值给A[p+1],p--,等于下一个比较的元素前进一位
A[p+1] = A[p]
p--
}
A[p+1] = x //当插入元素大于比较元素,则插入到比较元素后一个
return A
}
console.log(insert([1,2,3,4,9,20],8))
function in_sort(A) {
for(let i=1; i<A.length; i++) {
insert(A,i,A[i])
}
return A
}
function insert(A,i,x) {
let p = i-1;
while(p>0 && A[p] > x) {
A[p+1] = A[p]
p--
}
A[p+1] = x
}
console.log(in_sort([1,2,30,9,20,3,8]))
- 3.冒泡排序(核心:每一轮都把最大的数移动到最后面)
- 4.合并(归并)排序
冒泡和插入时间复杂度是O(n2),合并排序是O(nlgn)
3(i)和9(j)比较,3小,放回A中k的位置...然后,9和27比较 一直比下去,i和j谁小就++,最后的正无穷是判断条件
merge_sort中,核心就是if(r-p < 2) return 当原数组不断拆分,直到长度为1,接下来的q就是右半边开始
二叉树的高度H=[lgn] 13的h是4
- 5.快速排序 O(nlgn)
- 核心是找中心点,通过和最后一个值比较,如果大于,则和倒数第二个值交换,一直循环,最后把中心点换到中间,拿到中间点后就拆分数组,类似归并排序,不同点是,拆分的过程已经是排序了
<=中心点区间,最开始是空,大于中心的也是空,然后逐步把区间变大
确立中心点
整个快排实现
lo 左边开始,hi右边开始
循环不变式,当i === j的时候也就是都交换完了
swap(A,j,hi-1)全部执行完后把中心点交换,换到中间,return j(中心点)
斐波那契数列
function f(n) {
return n===1 || n===2 ? 1 : f(n-1) + f(n-2)
}
时间复杂度太高
改进
function f(n) {
let [a,b] = [0,1]
for(let i=0;i<n;i++) {
[a,b] = [b, a+b]
}
return b
}
function f(n){
return Array(n).fill().reduce(([a,b]) => {
return [b,a+b]
},[0,1])[1]
}
- 链表
linkedList链表中,head指向listNode,其中包含 key和next指针
this.head = node意思是H指向新插入的节点
拍平数组,并且去除,排序
let arr = [ [1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14] ] ] ], 10];
function flat(A) {
let arr = A.reduce((pre,cur) => Array.isArray(cur) ? [...pre,...flat(cur)] : [...pre,cur],[])
return arr.sort((a,b) => {return a-b})
}
function unique(arr) {
let x = new Set(arr)
return [...x]
}
console.log(unique(flat(arr)))
eval(`[${arr+ ''}]`)
栈:LIFO 后进先出 push操作,有一个栈顶指向最后一个地址,push操作时,指针移向下一个地址,并把值赋进去
队列:FIFO 有两个指针,是一个循环
红色:尾指针 蓝:头指针
找出数组中连续子序列
希尔排序
判断一个数是不是素数
function is_prime(n){
if(n <=1) return false
let N = Math.floor(Math.sqrt(n)) //N^2 <= n <= (N+1)^2
let is_prime = true
for(let i=2;i<=N;i++) {
if(n % i === 0) {
is_prime = false
break//跳出循环
}
}
return is_prime
}
shift 把数组第一个元素删除,unshift 添加第一个元素
[1,2,3].every(x => x>0) true
判断[([()])]左右符号是否平衡,匹配就出栈,不匹配就入栈
function is_balance(str) {
const [first,...others] = str
const stack = [first]
while(others.length > 0) {
const c = stack[stack.length -1]
const n = others.shift()
//每次都是判断栈的最新一个和others的第一个
if(!match(n,c)) {
stack.push(n)
}else {
stack.pop()
}
}
return stack.length === 0
}
function match(n,c) {
return (c==='[' && n === ']') || (c=== '(' && n === ')')
}
console.log(is_balance('[([])]'))
DOM节点的绝对位置
function get_layout(ele) {
const layout = {
with: ele.offsetWidth,
height: ele.offsetHeight,
left: ele.offsetLeft,
top: ele.offsetTop
}
if(ele.offsetParent) {
const parentLayout = get_layout(ele.offsetParent)
layout.left += parentLayout.left
layout.top += parentLayout.top
}
return layout
}
如果指定的属性存在于对象上,则返回其属性描述符对象(property descriptor),否则返回 undefined。
- 树
树的递归表示
class Tree {
constructor(v, children) {
this.v = v
this.children = children || null
}
}
const tree = new Tree(10, [
new Tree(5),
new Tree(3, [
new Tree(7),
new Tree(11)
]),
new Tree(2)
])
子节点优先打印(后续)
10第一个遍历(0) 先序,10第三个遍历(5,3,2,10)后序 10第二个遍历(5,10,3,2)中序
//如果没有子节点先打印,如果子节点的序号为1,则打印父节点的v,3是1, 11是1
5没有子节点,先打印5,接着遍历子节点(3,7,11),3的序号为1,所以打印tree.v,10,节点遍历(7,11),7没有子节点,先打印7,然后11序号为1,打印父节点3,然后打印11,最后打印2 (5,10,7,3,11,2)
ord 0 线序,1中序 3之后是后序
function tree_transverse(tree,ord,callback) {
let transvered = false
if(!tree.children) {
callback(tree.v)
return
}
tree.children.forEach((child,i) => {
//i: 0 1 0 1 2
if(i === ord) {
transvered = true
callback(tree.v)
}
tree_transverse(child,ord,callback)
})
!transvered && callback(tree.v)
}
tree_transverse_m(tree,5,(v)=>{
console.log(v)
})
- 字符串
//1. 翻转每个单词,要保留空格 Let's take LeetCode contest => s'teL ekat edoCteeL tsetnoc
export default (str) => {
let arr = str.split(' ');
let result = arr.map((v) => (
v.split('').reverse().join('')
))
// [ 's\'teL', 'ekat', 'edoCteeL', 'tsetnoc' ]
return result.join(' ')
}
export default (str) => {
return str.split(' ').map((v) => (
v.split('').reverse().join('')
)).join(' ')
}
export default (str) => {
return str.split(/\s/g).map((v) => (
v.split('').reverse().join('')
)).join(' ')
}
export default (str) => {
//正则[]是可选项,\w是字符 +至少一次
return str.match(/[\w']+/g).map((v) => (
v.split('').reverse().join('')
)).join(' ')
}
//找连续的子串,1和0 00110011,输出 6 有6个连续的 0011 01 1100 10 0011 01
export default (str) => {
let result = []
let match = (str) => {
let j = str.match(/^(1+|0+)/)[0]//拿到最开始连续的0或者1
let o = (j[0] ^ 1).toString().repeat(j.length)//通过^1取反,复制j,
let reg = new RegExp(`^(${j}${o})`)
if(reg.test(str)) {
return RegExp.$1 //返回正则对象匹配的第一个结果
}else {
return ''
}
}
for(let i=0; i<str.length-1;i++) {
let sub = match(str.slice(i))
if(sub){
result.push(sub)
}
}
return result
}
- 数组
//给定一个仅包含2-9个数字的字符串,返回它所有可能的组合,给定的数字到字母的映射和电话号码相同,1不对应任何字母
export default (str) => {
let map = ['', 1, 'abc', 'def', 'ghi', 'jkl', 'mno', 'pqrs', 'tuv', 'wxyz']//电话号码映射
let num = str.split('')
let code = []
num.forEach(v => {
if (map[v]) {
code.push(map[v])
}
})
let concat = (A)=> {
let r = []
for (let i = 0; i < A[0].length; i++) {
for (let j = 0; j < A[1].length; j++) {
r.push(`${A[0][i]}${A[1][j]}`)
}
}
A.splice(0, 2, r)//关键,把前两项删除,加入组合后的新一项
if (A.length > 1) {
concat(A)
} else {
return A[0]
}
}
return concat(code)
}
- 卡牌分组
let sort = (arr) => {
// 对这副牌进行排序,升序、降序都可以
arr.sort((a, b) => a - b)
let min = Number.MAX_SAFE_INTEGER
let dst = []
let result = true
for (let i = 0, len = arr.length, tmp = []; i < len; i++) {
tmp.push(arr[i])
for (let j = i + 1; j <= len; j++) {
if (arr[i] === arr[j]) {
tmp.push(arr[j])
} else {
if (min > tmp.length && min > 1) {
min = tmp.length
}
dst.push([].concat(tmp))
tmp.length = 0
i = j - 1
break
}
}
}
dst.forEach(item => {
if (item.length % min !== 0) {
result = false
return false
}
})
return result
}
let A = [1, 1, 2, 2, 3, 3, 3, 3]
时间复杂度看的是运行次数,空间复杂度看的是占用的内存
let oddEven = (A) => {
A.sort((a,b) => (a-b))
let temp = []
let odd = 1;
let even = 0;
A.forEach( v => {
if(v % 2 === 1) { // 基数
temp[odd] = v
odd += 2
}else{
temp[even] = v
even += 2
}
})
return temp
}
let findMax = (A) => {
A.sort((a, b) => a - b)
let max = Number.MIN_SAFE_INTEGER
let i = 1
while (i < A.length) {
max = Math.max(max, (A[i] - A[i-1]))
i++
}
return max
}
let A = [3, 6, 9, 1,14]
// 冒泡排序原理是每一轮都把最大值排到最后,如果找到第k个大的,就是冒泡k轮就行了
function swap(A,i,j) {
[A[i],A[j]] = [A[j],A[i]]
}
let findK = (A,k) => {
let len = A.length - 1
for(let i=len; i>len-k ;i--) {
for(let j=1;j<=i;j++) {
A[j] < A[j-1] && swap(A,j-1,j)
}
}
return A[len-(k-1)]
}
let A = [3,2,1,5,6,4]
console.log(findK(A,2))
//找第一个缺失的正数,通过选择排序,查找第一次,
//如果是不是1,则返回1,如果是1,继续查找下一个,看是否是2,依次类推
//不直接用sort的原因,不需要把所有都排序好,才去找差值
function swap(A,i,j) {
[A[i],A[j]] = [A[j],A[i]]
}
function bubbor_sort(A) {
let k = 1
for(let i=0,len=A.length;i<len;i++) {
for(let j=i+1;j<len;j++) {
while(A[i]>A[j]) {
swap(A,i,j)
}
}
//选择排序至少排了两次,才可以比较差值
if(i > 0) {
if(A[i] - A[i-1] > 1) {
return A[i-1] + 1
}
}else if(A[i] !== 1){
return 1
}
}
return A
}
let A = [1,2,3,9,8,5,6]
//给定一个字符串,复原所有可能ip地址
let teleRecur = (str) => {
let r = []
//cur剩下的字符串 ,sub每次取的字符串
let search = (cur, sub) => {
if (cur.length === 4 && cur.join('') === str) {
r.push(cur.join('.'))
} else {
for (let i = 0, len = Math.min(3, sub.length), temp; i < len; i++) {
temp = sub.substr(0, i + 1)
if (temp < 256) {
if (cur.concat([temp]).length > 4) return
search(cur.concat([temp]), sub.substr(i + 1))
}
}
}
}
search([], str)
return r
}
console.log(teleRecur('25525511135'))
let concatArr = (str,words) => {
let result = []
let len = words.length
//r 依次取出的字母 _arr剩下的字母
let range = (r,_arr) => {
//如果取出拼接后的数组长度和传进来的长度一样,则push
if(r.length === len) {
result.push(r)
}else{
//递归取出字母,和剩下字母
_arr.forEach((item,idx) => {
let temp = [].concat(_arr)
temp.splice(idx,1)
range(r.concat(item),temp)
})
}
}
range([],words)
return result.map((v) => {
return str.indexOf(v.join(''))
}).filter( v => v > -1).sort()
}
console.log(concatArr('barfoothefoobarman',['foo','bar']))
栈(堆栈) 后进先出
let A = ['5', '2', 'C', 'D', "+"]
let ballScore = (score) => {
let result = []
let pre,pre1;
score.forEach(v => {
switch (v) {
case 'C':
if (result.length) {
result.pop()
}
break;
case 'D':
pre = result.pop()
result.push(pre, 2 * pre)
break;
case "+":
pre = result.pop()
pre1 = result.pop()
result.push(pre1,pre,pre+pre1)// 堆栈 后进先出,pre是先出的,所以要后进
break;
default:
result.push(v*1)
}
})
return result.reduce((pre,cur) => pre+cur)
}
console.log(ballScore(A))
队列 先进先出 622leetCode
//循环队列
//循环队列
class MycircularQueue {
constructor(k) {
this.list = Array(k)
this.front = 0 //首指针
this.rear = 0 //尾指针
this.max = k
}
// 向循环队列插入一个元素。如果成功插入则返回真。
enQueue(num) {
if (this.isFull()) {
return false
} else {
this.list[this.rear] = num
this.rear = (this.rear + 1) % this.max
console.log(this.list)
return true
}
}
//从循环队列中删除一个元素。如果成功删除则返回真。
deQueue() {
let v = this.list[this.front]
this.list[this.front] = ''
//删完后首指针要指向下一个
this.front = (this.front + 1) % this.max
return v
}
// 从队首获取元素。如果队列为空,返回 -1 。
Front() {
return this.isEmpty() ? -1 : this.list[this.front]
}
// 获取队尾元素。如果队列为空,返回 -1 。
Rear() {
//尾指针总是指向最后一个数的下一个
let rear = this.rear - 1
return this.isEmpty() ? -1 : this.list[rear > 0 ? rear : this.max - 1]
}
// 检查循环队列是否为空。如果为空,首指针和尾指针指向同一个地方,并且为空
isEmpty() {
return this.rear === this.front && !this.list[this.front]
}
//检查循环队列是否已满,如果满,首指针和尾指针指向同一个地方,并且不为空
isFull() {
return this.rear === this.front && !!this.list[this.front]
}
}
let circul = new MycircularQueue(3)
// 任务调度器 leetCode621
//总结: 利用队列 选取一个值push进新的队列,一直循环,不断截取最后两个值newN,对tasks做循环
//取newN中不包含的第一个值,放进队列,并记录i,接着跳出循环,删除i所在的值,len--
let leastInterval = (tasks, n) => {
let len = tasks.length
//存储cpu执行的任务
let q = []
//[ 'B', 'B', 'B', 'C', 'C', 'A' ]
tasks = tasks.sort().join('').match(/(\w)\1+|\w/g).sort((a, b) => b.length - a.length).join('').split('')
while (len > 0) {
//第一次存储任务
if (!q.length) {
q.push(tasks.shift())
len--
} else {
let newN = q.slice(-n)
let is = -1
for (let i = 0, l = tasks.length; i < l; i++) {
if (!newN.includes(tasks[i])) {
q.push(tasks[i])
is = i
break;
}
}
//找到非冷却时间内的任务,也就是和前一个不相同
if (is !== -1) {
tasks.splice(is, 1)
len--
} else {
q.push('-')
}
}
}
return q
}
let tasks = ["C", "C", "A", "B", "B", "B"], n = 2;
console.log(leastInterval(tasks, n))
买卖股票
var maxProfit = function(prices) {
let min = prices[0]
let max = 0
let i = 1
while(i < prices.length) {
min = Math.min(min,prices[i])
max = Math.max(max,prices[i] - min)
i++
}
return max
};
//[7,1,5,3,6,4] => 7
reduce实现map
Array.prototype.myMap = function (callback, context) {
if (typeof callback !== 'function') return;
return this.reduce(function (prev, current, index, srcArray) {
let result = callback.call(context, current, index, srcArray);
return prev.concat(result);
}, []);
}
怎么实现 a===1 && a ===2
let xx = {}
let ll = false
Object.defineProperty(xx,'a',{
get(){
if(!ll){ll = true; console.log(2); return 1}
return 2
}
})
with(xx){
a===1 && a===2
}
实现promise.all
let p1 = Promise.resolve(1)
let p2 = Promise.resolve(2)
let p3 = Promise.reject(3)
function pAll(promises) {
return new Promise((resolve,reject) => {
let index = 0;
let result = []
if(promises.length === 0) {
resolve(result)
}else {
setTimeout(() => {
function processValue(i,data) {
result[i] = data
if(++index === promises.length) {
resolve(result)
}
}
for(let i=0;i<promises.length;i++) {
Promise.resolve(promises[i]).then(data => {
processValue(i,data)
},(err) => {
reject(err);
return
})
}
})
}
})
}
// pAll([p1,p2,p3]).then(result => {
// console.log(result)
// }).catch(e => {
// console.log(e)
// })
pAll([p1,p2]).then(result => {
console.log(result)
})
实现Promise.finally
Promise.prototype.finally = function (callback) {
let P = this.constructor;
return this.then(
value => P.resolve(callback()).then(() => value),
reason => P.resolve(callback()).then(() => { throw reason })
);
};
实现函数科里化
function curry(fn, args = []) {
return function () {
let rest = [...args, ...arguments];
if (rest.length < fn.length) {
return curry.call(this, fn, rest);
} else {
return fn.apply(this, rest);
}
}
}
function sum(a, b, c) {
return a + b + c;
}
let sumFn = curry(sum);
sumFn(1)(2)(3); //6
console.log(sumFn(1)(2, 3)); //6
数组去重
function unique(arr) {
let newA = []
let index = []
let len = arr.length
for(let i=0;i<len;i++) {
for(let j=i+1;j<len;j++) {
if(arr[i] === arr[j]){
i++;
j=i;
}
}
newA.push(arr[i])
}
return newA
}
let newArr = [3,3,44,3,2,4,2,66,77,44,77]
console.log(unique(newArr))
解析query字符串
function parse(str) {
return str.split('&').reduce((o, kv) => {
const [key, value] = kv.split('=')
if (!value) {
return o
}
// o[key] = value
deep_set(o, key.split(/[\[\]]/g).filter(x => x), value)
return o
}, {})
}
function deep_set(o, path, value) {
// {} [ 'a', 'name' ] fox
// { a: { name: 'fox' } } [ 'a', 'short' ] ali
// { a: { name: 'fox', short: 'ali' } } [ 'a', 'com' ] tencent
let i = 0;
// path: [a, name] [a, short] ...
for (; i < path.length - 1; i++) {
if (o[path[i]] === undefined) {
if (path[i + 1].match(/^\d+$/)) {
o[path[i]] = []
} else {
o[path[i]] = {}
}
}
o = o[path[i]]
}
o[path[i]] = value
}
const url = 'a=1&b=2&f=hello'
const url1 = 'a[name][short]=fox&a[com]=ali&b=yy'
const url2 = 'a[name]=fox&&a[short]=ali&a[com]=tencent&b=yy'
const url3 = 'a[0]=1&a[1]=2'
console.log(parse(url2))