开始学算法

314 阅读13分钟

复杂度

1.时间复杂度计算

1. O(1)

let i=1;
i+=1;

2.O(n)

for(let i=0;i<n;i+=1){
  console.log(i)
}

O(1)*O(n)=O(n)
3. O(n)*O(n)=O(n^2)

for(let i=0;i<n;i++){
  for(let j=0;j<n;j++){
    console.log(i,j)
  }
}

4. O(logn)

let i=1;
while(i<n){
  console.log(i);
  i*=2;
}

空间复杂度计算

算法在运行过程中临时占用存储空间大小的量度
1.O(1)

let i=1;
i+=1;
//只声明了单个变量

2. O(n)

const list=[];
for(let i=0;i<n;i++){
  list.push(i)
}

3. O(n^2)

const matrix=[];
for(let i=0;i<n;i++){
  martix.push([]);
  for(let j=0;j<n;j++){
    martix[i].push(j);
  }
}

image.png

image.png

image.png

一个后进先出的数据结构,JavaScript中没有栈,但是可以用array实现栈中的功能

栈的应用场景

十进制转二进制、判断字符串的括号是否有效、函数调用堆栈...

leetcode 20 有效的括号

题目描述
1.给定一个只包括 '(',')','{','}','[',']' 的字符串 s ,判断字符串是否有效。
有效字符串需满足:
左括号必须用相同类型的右括号闭合
左括号必须以正确的顺序闭合。
解题步骤
1.新建一个栈
2.扫描字符串,遇左括号入栈,遇到和栈顶括号(最后进入的括号)类型匹配的右括号就出栈,类型不匹配直接判定为不合法
3.最后栈空了就合法,否则不合法

var isValid = function(s) {
    if(s.length%2===1){
        return false;
    }//如果是奇数,则返回错误,没有必要进入循环了
    const stack=[];
    for(let i=0;i<s.length;i+=1){
        const c=s[i];
        if(c==='[' || c==='{' || c==='('){
            stack.push(c);//推入栈中
        }
        else{
            const t=stack[stack.length-1];//获取栈顶元素
            if(
                (t==='(' && c===')')||
                (t==='[' && c===']')||
                (t==='{' && c==='}')   //暴力枚举
            ){
                stack.pop();
            }else{
                return false;
            }
        }
    }
    return stack.length===0;
};

时间复杂度:O(n) 空间复杂度O(n)

前端与栈的结合点

JS中的函数调用堆栈

const fun1=()=>{
    fun2();
}
const fun2=()=>{
    fun3();
}
const fun3=()=>{}
fun1();

image.png 接着往上走,fun2消失,之后fun1
在工作中遇到关于函数的复杂情况,可以打个断点看它们的执行顺序
栈常用操作 push() pop() stack[stack.length-1]//获取栈顶元素

队列

一个先进先出的数据结构,JavaScript中没有队列,可以使用array来实现队列

什么场景用队列

js异步中的任务队列

js是单线程,无法同时处理异步中的并发任务

setTimeout(()=>console.log(1),0);
console.log(2);
//先打印2,后打印1

事件循环与任务队列 image.png

计算最近请求次数(leetcode 993)

解题步骤

  • 有新请求就入队,3000ms前发出的请求出队
  • 队列的长度就是最近请求次数 leetcode-cn.com/problems/nu…
    有while循环 时间复杂度:O(n)
    新建了一个队列 空间复杂度为O(n)

链表

多个元素组成的列表
元素存储不连续,用next指针连在一起

image.png

数组vs链表
数组:增删非首尾元素时往往需要移动元素。
链表:增删非首尾元素,不需要移动元素,只需要更改next的指向即可

JavaScript中没有链表,可以用object来模拟链表

const a={val:'a'};
const b={val:'b'};
const c={val:'c'};
const d={val:'d'};
a.next=b;
b.next=c;
c.next=d;
//遍历链表
let p=a;
while(p){
    console.log(p.val);
    p=p.next;
}
//插入
const e={val:'e'};
c.next=e;
e.next=d;
//删除
c.next=d;

删除链表中的节点 LeetCode 237

解题思路

  • 无法直接获取被删除节点的上个节点
  • 将被删除节点转移到上个节点
    阶梯步骤
  • 将被删除节点的值改为下个节点的值
  • 删除下个节点 leetcode-cn.com/problems/de…

反转链表 LeetCode206

反转两个节点:将n+1的next指向n
反转多个节点:双指针遍历链表,重复上述操作
解题步骤

  • 双指针一前一后遍历链表
  • 反转双指针
var reverseList = function(head) {
    let p1=head;
    let p2=null;
    while(p1){
        const tmp=p1.next;
        p1.next=p2;  //改变指针箭头的方向
        p2=p1;       //向后走
        p1=tmp;      //向后走
    }
    return p2;
};

image.png 时间复杂度O(n) 空间复杂度O(1)

两数相加 LeetCode2

解题步骤

  • 新建一个空链表
  • 遍历被相加的两个链表,模拟相加操作,将个位数追加到新链表上,将十位数留到下一位去相加 复杂度
  • 时间复杂度:O(\max(m,n))O(max(m,n)),其中 mm 和 nn 分别为两个链表的长度。我们要遍历两个链表的全部位置,而处理每个位置只需要 O(1) 的时间。
  • 空间复杂度:O(1)。注意返回值不计入空间复杂度
var addTwoNumbers = function(l1, l2) {
    let addOne=0;//进位
    let sum = new ListNode('0') // 创建一个头链表用于保存结果
    let head = sum // 保存头链表的位置用于最后的链表返回
    while (addOne || l1 || l2) {//在两个链表之中有一个存在的前提下执行下面的逻辑
        let val1 = l1?l1.val:0;
        let val2 = l2?l2.val:0;//取出左右两边数的最低位,还要判断它是否为空0
        let r1 = val1 + val2+addOne;//求和
        addOne=r1>=10?1:0;//如果求和大于等于10,那么进位为1,否则为0
        sum.next = new ListNode(r1 % 10)//sum的下一个节点
        sum = sum.next //sum指向下一个节点
       if (l1) l1 = l1.next //l1指向下一个节点,以便计算第二个节点的值
        if(l2) l2 = l2.next //l2指向下一个节点,以便计算第二个节点的值
    }
    return head.next //返回计算结果,之所以用head.next是因为head中保存的第一个节点是刚开始定义的“0”
};

删除链表中的重复元素LeetCode83(简单)

解题步骤

  • 遍历链表,如果当前元素和下一个元素的值相等,则删除下一个元素值
  • 遍历结束后,返回原来的链表
var deleteDuplicates = function(head) {
    let p=head;
    while(p && p.next){
        if(p.val===p.next.val){
            p.next=p.next.next;
        }else{
            p=p.next;
        }
    }
    return head
};

时间复杂度O(n) 空间复杂度O(1)

环形链表

解题思路

  • 用一快一慢两个指针遍历链表,如果指针能够相逢,就返回true
  • 遍历结束,没有相逢就返回false
var hasCycle = function(head) {
    let p1=head;
    let p2=head;
    while(p1 && p2 && p2.next){
        p1=p1.next;
        p2=p2.next.next;
        if(p1=p2){
            return true;
        }          
    }
    return false
};

时间复杂度O(n) 空间复杂度O(1)

前端与链表 JS中的原型链

原型链

  • 原型链的本质是链表。
  • 原型链上的节点是各种原型对象,比如Function.prototype、Object.prototype......
  • 原型链通过__proto___属性连接各种原型对象。 面试常考点
  • 如果A能沿着原型链找到B.prototype,则A instanceof B为true
  • 如果A对象上没有找到x属性,那么会顺着原型链找x属性 使用链表指针获取JSON的节点值
const json={
  a:{b:{c:1}};
  d:{e:1};
};
const path=['a','b'];
let p=json;
path.forEach(k=>{
  p=p[k];
});
console.log(p);

集合

  • 无序且唯一的数据结构
  • ES6中有集合,名为set
  • 集合的常用操作:去重、判断某元素是否在集合中、求交集

去重、判断元素是否在集合中、求交集

//去重
const arr1=[1,1,2,2];
const arr2=[...new Set(arr1)];
//判断元素是否在集合中
const set=new Set(arr);
const has=new has(1)
//求交集
const set2=new Set([2,3]);
const set3=new Set([...set].filter(item=>set2.has(item))

两个数组的交集 LeetCode349

解题步骤

  • 用集合对nums1去重
  • 遍历nums1,筛选出nums2也包含的值
var intersection = function(nums1, nums2) {
    return [...new Set(nums1)].filter(n=>nums2.includes(n));
};
/把nums1放进数组中,对其进行筛选nums2中是否有n值与nums1中的数值相等
//如果用n=>new Set(nums2).has(n)执行用时和内存消耗比较慢(把nums2变成了一个集合,调用这个集合需要耗时)

时间复杂度O(m*n)
因为filter占一个循环,内部还有一循环嵌套includes
空间复杂度O(m)

前端与集合 使用ES6中的set

使用set对象:new、add、delete、has、size

add()方法

let mySet=new Set();
mySet.add(1);
mySet.add(5);
mySet.add(5);
//set 具有唯一性,无论添加多少次,都只有一个5
mySet.add('some');//set可以添加字符串
let o={a:1,b:2}
mySet.add(o);//set还可以添加对象
mySet.add({a:1,b:2});//这两个对象看似一样,但是在内存中存储的位置是不同的

has()方法 delete()方法

//接上面的代码
const has=mySet.has(5);
### 迭代set:多种迭代方法、setarray互转、求交集/差集
mySet.delete(5);

如何迭代set
for(let [key, value] of mySet.entries()) console.log(key, value);
set与数组的互相转化

const myArr=Array.from(mySet);
const mySet2=new set([1,2,3,4])

求交集和差集

const intersection=new Set([...mySet].filter(x=>mySet2.has(x)));
const difference=new Set([...mySet].filter(x=>!mySet2.has(x)));

字典

  • 与集合类似,字典也是一种存储唯一值的数据结构,但他是以键值对的形式来存储
  • ES6中有字典,叫Map

字典的常用操作:键值对的增删改查

const m=new Map();
m.set('a','aa');//增
m.delete('a');
m.clear();//删除键的两种方式,clear是全部清空
m.set('a','aaa')//改:把a的值改为aaa

两个数组的交集 LeetCode349

解题步骤

  • 新建一个字典,遍历nums1,填充字典
  • 遍历nums2,遇到字典里的值就选出,并从字典中删除
var intersection = function(nums1, nums2) {
    const map=new Map();
    nums1.forEach(n=>{
      map.set(n,true)
    })
    const res=[];
    nums2.forEach(n=>{
      if(map.get(n)){
        res.push(n);
        map.delete(n);
      }
    })
    return res;
};

时间复杂度O(m+n)
空间复杂度O(m)

有效的括号 LeetCode20

var isValid = function(s) {
    if(s.length%2===1){
        return false;
    }//如果是奇数,则返回错误,没有必要进入循环了
    const stack=[];
    const map=new Map();
    map.set('{','}');
    map.set('[',']');
    map.set('(',')');
    for(let i=0;i<s.length;i+=1){
        const c=s[i];
        if(map.has(c)){
            stack.push(c);//推入栈中
        }
        else{
            const t=stack[stack.length-1];//获取栈顶元素
            if(map.get(t)===c){
                stack.pop();
            }else{
                return false;
            }
        }
    }
    return stack.length===0;
};

时间复杂度o(n) 空间复杂度o(n)

两数之和 LeetCode1

解题思路

  • 把nums想象成相亲者
  • 把target想象成匹配条件
  • 用字典建立一个婚姻介绍所,存储相亲者的数字和下标 解题步骤
  • 新建一个字典作为婚姻介绍所
  • nums的值,逐个来介绍所找对象,没有合适的就先登记着,有合适的就牵手成功
var twoSum = function(nums, target) {
    const map=new Map();
    for(let i=0;i<nums.length;i++){
        const n=nums[i];
        const n2=target-n;
        if(map.has(n2)){
            return [map.get(n2),i];
        }else{
            map.set(n,i)
        }
    }
};

时间复杂度o(n)
空间复杂度o(n)

无重复字符的最长子串 LeetCode1

解题思路

  • 用双指针维护一个滑动窗口,用来剪切子串
  • 不断移动右指针,遇到重复字符,就把左指针移动到重复字符的下一位
  • 移动过程中,记录所有窗口的长度,并返回最大值
var lengthOfLongestSubstring = function(s) {
    let l = 0;
    let res = 0;
    const map = new Map();
    for(let r = 0; r < s.length; r += 1){
        if(map.has(s[r]) && map.get(s[r]) >= 1){
            l = map.get(s[r]) + 1;
        }
        res = Math.max(res, r - l + 1);
        map.set(s[r], r);
    }
    return res;
};

上面这个解法不知道为什么一直报错,呜呜呜,,,

下面是官网给出的其他解法:

var lengthOfLongestSubstring = function(s) {
    // 哈希集合,记录每个字符是否出现过
    const occ = new Set();
    const n = s.length;
    // 右指针,初始值为 -1,相当于我们在字符串的左边界的左侧,还没有开始移动
    let r = -1, ans = 0;
    for (let i = 0; i < n; ++i) {
        if (i != 0) {
            // 左指针向右移动一格,移除一个字符
            occ.delete(s.charAt(i - 1));
        }
        while (rk + 1 < n && !occ.has(s.charAt(rk + 1))) {
            // 不断地移动右指针
            occ.add(s.charAt(rk + 1));
            ++rk;
        }
        // 第 i 到 rk 个字符是一个极长的无重复字符子串
        ans = Math.max(ans, rk - i + 1);
    }
    return ans;
};

最小覆盖子串 LeetCode76

解题步骤

  • 用双指针维护一个滑动窗口
  • 移动右指针,找到包含T的子串,移动左指针,尽量减少包含T的子串的长度
var minWindow = function(s, t) {
    let l = 0;
    let r = 0;
    const need = new Map();
    for (let c of t) {
        need.set(c, need.has(c) ? (need.get(c)+1) : 1);//新建一个字典,用来表示需要的字符以及个数
    }
    let needType = need.size;
    let res = '';
    while(r < s.length){
        const c = s[r];//获取右指针当前的字符
        if(need.has(c)){
            need.set(c, need.get(c)-1);//如果右指针有当前字符,则需要的个数减一
             if(need.get(c)===0) needType -= 1;//如果t中某个需要的字符在s中没有了,则needType的值减一
        }
        while(needType===0){
            const newRes=s.substring(l,r+1);
            if(!res || newRes.length<=res.length) res=newRes;
            const c2=s[l];
            if(need.has(c2)){
                need.set(c2,need.get(c2)+1);
                if(need.get(c2)===1) needType+=1;
            }
            l+=1;
        }
        r+=1;//移动右指针
    }
    return res
};

一种分层数据的抽象模型
前端工作中常见的树包括:DOM树、级联选择、树形控件...
JS中没有树,但是可以用Object和Array构建树

数的常用操作 深度、广度优先遍历,先中后序优先遍历

深度、广度优先遍历

深度优先遍历:尽可能深的访问数的分支
广度优先遍历:先访问离根节点最近的节点

深度优先遍历算法口诀

  • 访问根节点
  • 对根节点的children挨个进行深度优先遍历 广度优先遍历算法口诀
  • 新建一个队列,并把根节点入队
  • 把队头出队并访问
  • 把队头的children挨个入队
  • 重复第二三步,直到队列为空

二叉树的先中后序遍历

二叉树:树中每个节点最多只能有两个子节点

先序遍历算法口诀

  • 访问根节点
  • 对根节点的左子树进行先序遍历
  • 对根节点的右子树进行先序遍历
const bt = require('./bt');
const precoder = (root) =>{
    if(! root){ return; }
   console.log(root.val);
   precoder(root.left);
   precoder(root.right);

}
precoder(bt)

中序遍历算法口诀

  • 对根节点的左子树进行先序遍历
  • 访问根节点
  • 对根节点的右子树进行先序遍历

后序遍历算法口诀 左右后

非递归版先中后序遍历

先序遍历

const precoder = (root) =>{
  if(!root){return:}
  const stack=[root];
  while(stack.length){
    const n=stack.pop(); //将根节点从栈里弹出来,下一步访问根节点的值
    console.log(n.val);
    if(n.right) stack.push(n.right);
    if(n.left) stack.push(n.left);
   }
 };

中序遍历

const inrorder = (root) =>{
  if(!root) {return;}
  const stack=[];
  let p=root;  //此时需要用到指针
  while(stack.length || p) {
    while(p){
      stack.push(p);
      p=p.left;
      }
      const n=stack.pop();
      console.log(n.val);
      p = n.right;
    }
}

后序遍历

const postorder = (root) =>{
  if(!root){return;}
  const outputStack = [];
  const stack = [root];
  while (stack.length){
    const n = stack.pop();
    outputStack.push(n);
    if(n.left) stack.push(n.left);
    if(n.right) stack.push(n.right);
  }
   while(outputStack.length){
        const n = outputStack.pop();
        console.log(n.val);
    }
};

二叉树的最大深度 LeetCode104

解题思路

  • 求最大深度。考虑使用深度优先遍历
  • 在深度优先遍历过程中,记录每个节点所在的层级。找出最大的层级即可 解题步骤
  • 新建一个变量,记录最大深度
  • 深度优先遍历整棵树,并记录每个节点的层级,同时不断刷新最大深度这个变量
  • 遍历结束返回最大深度这个变量
var maxDepth = function(root) {
    let res=0;  //用一个变量来记录最大深度
    const dfs = (n,l) => {     //深度优先遍历整棵树
        if (!n) {return;}
        if(!res.left && !res.right){
            res = Math.max(res,l); 
        }//这一步判断是否为叶子节点,如果是的话,就刷新树的深度
        dfs(n.left,l+1);
        dfs(n.right,l+1);
    }
    dfs(root,1);
    return res;
};

复杂度分析

  • 时间复杂度:O(n),其中 n为二叉树节点的个数。每个节点在递归中只被遍历一次。
  • 空间复杂度:O(height),其中height 表示二叉树的高度。递归函数需要栈空间,而栈空间取决于递归的深度,因此空间复杂度等价于二叉树的高度。

二叉树的最小深度

解题思路

  • 求最小深度,建议用广度优先遍历
  • 在遍历中,遇到叶子结点即返回当前叶子节点的层级 解题步骤
  • 广度优先遍历整棵树,记录每个节点的层级
  • 遇见叶子结点,返回节点层级,停止遍历
var minDepth = function(root) {
    if(!root){ return 0; }
    const q=[[root,1]];//广度优先遍历需要用到队列
    while(q.length){
        const [n,l]=q.shift();
        if(!n.left && !n.right){
            return l;
        }//判断叶子结点
         if(n.left) q.push([n.left,l+1]);
        if(n.right) q.push([n.right,l+1]);//把当前节点的孩子节点推入到队列中
    }
};
  • 时间复杂度:O(N),其中 N 是树的节点数。对每个节点访问一次。
  • 空间复杂度:O(N),其中 N 是树的节点数。空间复杂度主要取决于队列的开销,队列中的元素个数不会超过树的节点数。

二叉树的层级遍历

解题步骤

  • 广度优先遍历二叉树
  • 遍历过程中,记录每个节点的层级,并将其添加到不同的数组中
var levelOrder = function(root) {
    if(!root){
        return []
    };
    const q=[[root,0]];
    const res=[];
    while(q.length){
        const [n,level]=q.shift();
        if(!res[level]){
            res.push([n.val]);//数组为空,给个[]
        }else{
            res[level].push(n.val);//把值推入到该数组中
        }
        if(n.left) q.push([n.left,level+1]);
        if(n.right) q.push([n.right,level+1]);
    }
    return res;
};

记树上所有节点的个数为 n

  • 时间复杂度:每个点进队出队各一次,故渐进时间复杂度为 O(n)
  • 空间复杂度:队列中元素的个数不超过 n 个,故渐进空间复杂度为 O(n)

中序遍历LeetCode:94

简单的递归算法

var inorderTraversal = function(root) {
    const res=[];
    const rec = (n) =>{
        if(!n){return;}
        rec(n.left);
        res.push(n.val);
        rec(n.right);
    }
    rec(root);
    return res;
};

较为复杂的迭代算法

var inorderTraversal = function(root) {
    const res=[];
    const stack=[];
    let p=root;
    while(stack.length || p){
        while(p){
            stack.push(p);//把指针所指的节点推到堆栈中
            p=p.left;//遍历所有的子节点
        }
        const n=stack.pop();//访问节点,其实就是一个出栈的过程
        res.push(n.val);
        p=n.right;//p指针指向它的右节点
    }
    return res;
};

路径总和 LeetCode112

解题步骤

  • 深度优先遍历二叉树,在叶子结点处,判断当前路径的节点值的和是否等于目标值。是就返回true
  • 遍历结束,如果没有匹配,就返回false
var hasPathSum = function(root, targetSum) {
    if(!root) return false;
    let res=false;
    const dfs = (n,s) =>{
        if(!n.left && !n.right && s===targetSum){
            res = true;
        }
        if(n.left) dfs(n.left,s + n.left.val);
        if(n.right) dfs(n.right,s + n.right.val);

    }
    dfs(root,root.val);
    return res;
};
  • 时间复杂度 O(n)
  • 空间复杂度 O(n) 递归调用一个函数调用堆栈

遍历JSON的所有节点值

const json = {
    a: { b: { c: 1 } },
    d: [1, 2],
};

const dfs = (n, path) => {
    console.log(n, path);
    Object.keys(n).forEach(k => {
        dfs(n[k], path.concat(k));
    });
};

dfs(json, []);

图是网络结构的抽象模型,是一组由连接的节点
JS中没有图,但是可以用object和array来构建图

图的深度优先、广度优先遍历

深度优先遍历算法

  • 访问根节点
  • 对根节点没有访问过的相邻节点挨个进行深度优先遍历
const graph = {
    0: [1, 2],
    1: [2],
    2: [0, 3],
    3: [3]
};  //邻接表表示法
const visited = new Set();
const dfs = (n) => {
    console.log(n);
    visited.add(n);
    graph[n].forEach(c => {
        if(!visited.has(c)){
            dfs(c);
        }
    });
};
dfs(2);

广度优先遍历

  • 新建一个队列,把根节点入队
  • 把队头出队并访问
  • 把队头没访问过的相邻节点入队
  • 重复第二三步,直到队列为空
const visited = new Set();
const q = [2];
visited.add(2);
while(q.length){
    const n = q.shift();
    //shift删除原数组第一项,并返回删除元素的值
    graph[n].forEach(c=>{
        if(!visited){
            q.push(c);
            //push接收参数,把他们添加到数组末尾
            visited.add(n);
        }
    })
}

有效的数字LeetCode65(难

太平洋大西洋水流问题 leetcode417

用的是图的深度优先遍历

解题步骤

  • 新建两个矩阵,分别记录能流到两个大洋的坐标
  • 从海岸线,多管齐下,同时深度优先遍历图,过程中填充上述矩阵
  • 遍历两个矩阵,找到能流到两个大洋的坐标 leetcode-cn.com/problems/pa…
    时间复杂度和空间复杂度都是m*n

克隆图LeetCode133

堆是一种特殊的完全二叉树

  • 所有的节点都大于等于(最大堆)或者小于等于(最小堆)他的子节点
  • JS中一般用数组表示堆
  • 左侧子节点的位置是2*index+1
  • 右侧子节点的位置是2*index+2
  • 父节点位置是(index-1)*2

image.png

js实现最小堆类

  • 步骤
  1. 在类里,声明一个数组,用来装元素
  2. 主要方法:插入、删除堆项、获取堆顶,获取堆大小
  • 插入
  1. 将值插入堆的底部,即数组的尾部。
  2. 然后上移:将这个值和它的父节点进行交换,直到父节点小于等于这个插入的值。
  3. 大小为k的堆中插入元素的时间复杂度为o(logk)
class minHeap{
    constructor(){
        this.heap = [];
    }
    //获取父节点
    getParentIndex(i){
        return (i-1) >> 1;//取商的一个方法
    }
    swap(i1,i2){
        const temp = this.heap[i1];//声明临时变量,来存储i1的值
        this.heap[i1] = this.heap[i2];
        this.heap[i2] = temp;
    }
    shiftUp(index){
        if(index == 0){ return;}//上移到堆顶,就不要再上移了
        const ParentIndex =this.getParentIndex(index);//获取父节点
        if(this.heap[ParentIndex] > this.heap[index]){
            this.swap(ParentIndex,index);  //如果父节点的值大于子节点的值,进行交换
            this.shiftUp()
        }
    }
    insert(value){
        this.heap.push(value); //把它放在数组的最后一位
        this.shiftUp(this.heap.length-1);//上移操作
    }
}
const h =new minHeap();
h.insert(3);//检查一下

删除堆顶

  • 用数组尾部元素替换堆顶(直接删除堆顶会破坏堆结构)。
  • 然后下移:将新堆顶和它的子节点进行交换,直到子节点大于等于这个新堆顶。
  • 大小为k 的堆中删除堆顶的时间复杂度为O(logk)。
 shiftdown(index){
        const leftindex = this.getLeftIndex(index);
        const rightindex = this.getRightIndex(index);
        if(this.heap[leftindex] < this.heap[index]){
            this.swap(leftindex,index);
            this.shiftdown(leftindex);
        }
        if(this.heap[rightindex] < this.heap[index]){
            this.swap(rightindex,index);
            this.shiftdown(rightindex);
        }
    }
    pop(){
        this.heap[0] = this.heap.pop();
        this.shiftdown()
    }

数组中的第k个最大元素 LeetCode215

leetcode-cn.com/problems/kt…

前k个高频元素 LeetCode347

var topKFrequent = function(nums, k) {
    const map = new Map();
    nums.forEach(n=>{
        map.set(n,map.has(n) ? map.get(n) + 1 : 1);//建立映射关系
        
    })
    const list = Array.from(map).sort((a,b)=> b[1] - a[1]);//sort方法的应用
    return list.slice(0,k).map(n=>n[0]);//本来输出的是频率,但加上map之后就是这个元素值
};

但是题目说时间复杂度要优于O(nlogn),所以我们现在就要用到 leetcode-cn.com/problems/to…

进阶算法之搜索排序

排序 把某个乱序的数组变成升序或者降序的数组 sort()方法 搜索indexOf()方法

算法设计思想之“分而治之”

算法设计思想之“动态规划”

算法设计思想之“贪心算法”

算法设计思想之“回溯算法”