简单算法

184 阅读7分钟
  • 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))