万字总结 JS 数据结构与常用的算法

本文详述了JavaScript中的数据结构(栈、队列、链表、集合、字典、树、图、堆)与常见算法(排序、搜索、分而治之、动态规划、贪心算法、回溯算法),并通过实际例子进行解析,如二进制转换、有效括号、去重、交集计算等。同时介绍了时间复杂度和空间复杂度的基本概念。

一、前言

首先,为什么我会学习数据结构与算法呢,其实主要是有两方面

  • 第一,是我在今年的flag里明确说到我会学这个东西

  • 第二,学了这些,对自己以后在工作或者面试也会带来许多好处

然后,本文是最近学习的一个 总结文章 ,文中有不足的地方也希望大家在评论区进行指正,本文较长,设有目录。可直接通过目录跳转阅读。

文中的算法题, 大部分都是leetcode中 的,如不太理解题意,可直接去leetcode中找到对应的题。

二、基本概念

常常听到算法的时候,就会有人说到 时间复杂度 ,  空间复杂度 。那么这俩玩意是啥呢,下面我就来一一解释

1. 时间复杂度

其实就是一个函数,用大 O 表示, 比如 O(1)、 O(n)...

它的作用就是用来 定义描述算法的运行时间

  • O(1)

    let i = 0
    i += 1
复制代码
  • O(n):如果是 O(1) + O(n) 则还是 O(n)

    for (let i = 0; i < n; i += 1) {
      console.log(i)
    }
复制代码
  • O(n^2):O(n) * O(n), 也就是双层循环,自此类推:O(n^3)...

    for (let i = 0; i < n; i += 1) {
      for (let j = 0; j < n; j += 1) {
        console.log(i, j)
      }
    }
复制代码
  • O(logn):就是求 log 以 2 为底的多少次方等于 n

    // 这个例子就是求2的多少次方会大于i,然后就会结束循环。 这就是一个典型的 O(logn)
    let i = 1
    while (i < n) {
      console.log(i)
      i *= 2
    }
复制代码

2. 空间复杂度

和时间复杂度一样,空间复杂度也是用大 O 表示,比如 O(1)、 O(n)...

它用来 定义描述算法运行过程中临时占用的存储空间大小

占用越少 代码写的就越好

  • O(1):单个变量,所以占用永远是 O(1)

    let i = 0
    i += 1
复制代码
  • O(n):声明一个数组, 添加 n 个值, 相当于占用了 n 个空间单元

    const arr = []
    for (let i = 0; i < n; i += 1) {
      arr.push(i)
    }
复制代码
  • O(n^2):类似一个矩阵的概念,就是二维数组的意思

    const arr = []
    for (let i = 0; i < n; i += 1) {
      arr.push([])
      for (let j = 0; j < n; j += 1) {
        arr[i].push(j)
      }
    }
复制代码

三、数据结构

1. 栈

一个 后进先出 的数据结构

按照常识理解就是有序的挤公交, 最后上车 的人会在门口,然后门口的人会 最先下车

image.png

js中没有栈的数据类型,但我们可以通过 Array来模拟一个

const stack = [];

stack.push(1); // 入栈
stack.push(2); // 入栈

const item1 = stack.pop();  //出栈的元素
复制代码

1)十进制转二进制

// 时间复杂度 O(n) n为二进制的长度
// 空间复杂度 O(n) n为二进制的长度
const dec2bin = (dec) => {
  // 创建一个字符串
  let res = "";

  // 创建一个栈
  let stack = []

  // 遍历数字 如果大于0 就可以继续转换2进制
  while (dec > 0) {
    // 将数字的余数入栈
    stack.push(dec % 2);

    // 除以2
    dec = dec >> 1;
  }

  // 取出栈中的数字
  while (stack.length) {
    res += stack.pop();
  }

  // 返回这个字符串
  return res;
};
复制代码

2)判断字符串的有效括号

// 时间复杂度O(n) n为s的length
// 空间复杂度O(n)
const isValid = (s) => {

  // 如果长度不等于2的倍数肯定不是一个有效的括号
  if (s.length % 2 === 1) return false;

  // 创建一个栈
  let stack = [];

  // 遍历字符串
  for (let i = 0; i < s.length; i++) {

    const c = s[i];

    // 如果是左括号就入栈
    if (c === '(' || c === "{" || c === "[") {
      stack.push(c);
    } else {

      // 如果不是左括号 且栈为空 肯定不是一个有效的括号 返回false
      if (!stack.length) return false

      // 拿到最后一个左括号
      const top = stack[stack.length - 1];

      // 如果是右括号和左括号能匹配就出栈
      if ((top === "(" && c === ")") || (top === "{" && c === "}") || (top === "[" && c === "]")) {
        stack.pop();
      } else {

        // 否则就不是一个有效的括号
        return false
      }
    }

  }
  return stack.length === 0;
};
复制代码

2. 队列

和栈相反 先进先出 的一个数据结构

按照常识理解就是银行排号办理业务, 先去 领号排队的人,  先办理 业务

image.png

同样 js中没有栈的数据类型,但我们可以通过 Array来模拟一个

const queue = [];

// 入队
queue.push(1);
queue.push(2);

// 出队
const first = queue.shift();
const end = queue.shift();
复制代码

1)最近的请求次数

var RecentCounter = function () {
  // 初始化队列
  this.q = [];
};

// 输入 inputs = [[],[1],[100],[3001],[3002]] 请求间隔为 3000ms
// 输出 outputs = [null,1,2,3,3]   

// 时间复杂度 O(n) n为剔出老请求的长度
// 空间复杂度 O(n) n为最近请求的次数
RecentCounter.prototype.ping = function (t) {
  // 如果传入的时间小于等于最近请求的时间,则直接返回0
  if (!t) return null

  // 将传入的时间放入队列
  this.q.push(t);

  // 如果队头小于 t - 3000 则剔除队头
  while (this.q[0] < t - 3000) {
    this.q.shift();
  }

  // 返回最近请求的次数
  return this.q.length;
};
复制代码

3. 链表

多个元素组成的列表,元素存储不连续, 通过 next 指针来链接 , 最底层为 null

就类似于 父辈链接关系 吧, 比如:你爷爷的儿子是你爸爸,你爸爸的儿子是你,而你假如目前还没有结婚生子,那你就暂时木有儿子

image.png

js中类似于链表的典型就是原型链, 但是js中没有链表这种数据结构,我们可以通过一个 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;

// const linkList = {
//    val: "a",
//    next: {
//        val: "b",
//        next: {
//            val: "c",
//            next: {
//                val: "d",
//                next: null
//            }
//        }
//    }
// }

// 遍历链表
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;
复制代码

1)手写instanceOf

const myInstanceOf = (A, B) => {
  // 声明一个指针
  let p = A;
  
  // 遍历这个链表
  while (p) {
    if (p === B.prototype) return true;
    p = p.__proto__;
  }

  return false
}

myInstanceOf([], Object)
复制代码

2)删除链表中的节点

// 时间复杂和空间复杂度都是 O(1)
const deleteNode = (node) => {
  // 把当前链表的指针指向下下个链表的值就可以了
  node.val = node.next.val;
  node.next = node.next.next
}
复制代码

3)删除排序链表中的重复元素

// 1 -> 1 -> 2 -> 3 -> 3 
// 1 -> 2 -> 3 -> null

// 时间复杂度 O(n) n为链表的长度
// 空间复杂度 O(1)
const deleteDuplicates = (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
}
复制代码

4)反转链表

// 1 -> 2 -> 3 -> 4 -> 5 -> null
// 5 -> 4 -> 3 -> 2 -> 1 -> null

// 时间复杂度 O(n) n为链表的长度
// 空间复杂度 O(1)
var reverseList = function (head) {

  // 创建一个指针
  let p1 = head;

  // 创建一个新指针
  let p2 = null;

  // 遍历链表
  while (p1) {

    // 创建一个临时变量
    const tmp = p1.next;

 
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值