LeetCode JS 常用辅助数据结构

LeetCode JS 常用辅助数据结构 + 本地模拟代码

刷题高频辅助:链表、栈、队列、单调栈、双端队列、哈希表、Set、堆 (优先队列)、前缀和数组、并查集,逐个给出「本地可直接运行模拟代码」。

LeetCode 环境自带 ListNode本地需要自己定义,其余 JS 原生自带 Set/Map/Array,堆需要手动封装。

一、链表(ListNode,链表题必备)

1. 本地定义 ListNode

复制代码
// 本地必须手写,LC内置不用写
function ListNode(val, next = null) {
  this.val = val;
  this.next = next;
}

// 数组转链表(造测试用例)
function arrToList(arr) {
  return arr.reduceRight((next, val) => new ListNode(val, next), null);
}
// 链表转数组(打印调试)
function listToArr(head) {
  const res = [];
  let p = head;
  while(p) {
    res.push(p.val);
    p = p.next;
  }
  return res;
}

// 测试
let head = arrToList([1,2,3,4]);
console.log(listToArr(head)); // [1,2,3,4]

二、栈 Stack(数组模拟:尾进尾出)

场景:有效括号、字符串反转、单调栈、二叉树迭代遍历

复制代码
class Stack {
  #arr = []
  push(v) {this.#arr.push(v)}
  pop() {return this.#arr.pop()}
  peek() {return this.#arr.at(-1)}
  isEmpty() {return this.#arr.length === 0}
  size() {return this.#arr.length}
}

// 测试:有效括号
const s = "()[]{}";
const st = new Stack();
const map = {')':'(', ']':'[', '}':'{'};
let flag = true;
for(let c of s) {
  if(['(','[','{'].includes(c)) st.push(c)
  else {
    if(st.isEmpty() || st.pop() !== map[c]) flag = false
  }
}
console.log(flag);

三、队列 Queue(数组模拟:尾进头出)

BFS 层序遍历、滑动窗口、任务队列

复制代码
class Queue {
  #arr = []
  enqueue(v) {this.#arr.push(v)}
  dequeue() {return this.#arr.shift()} // 低效,大数据用双端队列
  front() {return this.#arr[0]}
  isEmpty() {return this.#arr.length===0}
}

// BFS二叉树示例(简略)
// 本地TreeNode定义
function TreeNode(val,left=null,right=null){
  this.val=val; this.left=left; this.right=right;
}
const root = new TreeNode(1,new TreeNode(2),new TreeNode(3))
const q = new Queue()
q.enqueue(root)
while(!q.isEmpty()){
  let cur = q.dequeue()
  console.log(cur.val)
  if(cur.left) q.enqueue(cur.left)
  if(cur.right) q.enqueue(cur.right)
}

四、双端队列 Deque(优化队列,首尾 O (1) 增删,滑动窗口最大值)

单调队列、滑动窗口经典题

复制代码
class Deque {
  #head = 0
  #arr = []
  // 队尾入
  push(v) {this.#arr.push(v)}
  // 队首出
  shift() {
    if(this.#head >= this.#arr.length) return undefined
    return this.#arr[this.#head++]
  }
  // 队尾出
  pop() {return this.#arr.pop()}
  // 队首值
  front() {return this.#arr[this.#head]}
  // 队尾值
  back() {return this.#arr.at(-1)}
  isEmpty() {return this.#head >= this.#arr.length}
}

// 滑动窗口最大值示例 leetcode239
function maxSlidingWindow(nums,k){
  const dq = new Deque(), res=[]
  for(let i=0;i<nums.length;i++){
    while(!dq.isEmpty() && nums[dq.back()] <= nums[i]) dq.pop()
    dq.push(i)
    while(dq.front() <= i-k) dq.shift()
    if(i >= k-1) res.push(nums[dq.front()])
  }
  return res
}
console.log(maxSlidingWindow([1,3,-1,-3,5,3,6,7],3))

五、哈希集合 Set / 哈希表 Map(原生自带,不用封装)

去重、两数之和、判环、查找

复制代码
// Set 去重
const arr = [1,2,2,3]
const s = new Set(arr)
console.log([...s])

// Map 两数之和
function twoSum(nums,target){
  const map = new Map()
  for(let i=0;i<nums.length;i++){
    let need = target - nums[i]
    if(map.has(need)) return [map.get(need),i]
    map.set(nums[i],i)
  }
}
console.log(twoSum([2,7,11,15],9))

六、优先队列(最小 / 大堆,JS 无原生,本地封装,topK、数据流中位数)

LeetCode topK、贪心、Dijkstra 最短路径

复制代码
// 小顶堆
class MinHeap {
  #heap = []
  push(val) {
    this.#heap.push(val)
    this.bubbleUp(this.#heap.length-1)
  }
  bubbleUp(idx) {
    while(idx>0){
      let p = (idx-1)>>1
      if(this.#heap[p] <= this.#heap[idx]) break
      [this.#heap[p],this.#heap[idx]] = [this.#heap[idx],this.#heap[p]]
      idx = p
    }
  }
  pop() {
    let top = this.#heap[0], last = this.#heap.pop()
    if(this.#heap.length) {
      this.#heap[0] = last
      this.sinkDown(0)
    }
    return top
  }
  sinkDown(idx){
    let len = this.#heap.length
    while(true){
      let l = idx*2+1, r = idx*2+2, min = idx
      if(l<len && this.#heap[l]<this.#heap[min]) min=l
      if(r<len && this.#heap[r]<this.#heap[min]) min=r
      if(min===idx) break
      [this.#heap[idx],this.#heap[min]] = [this.#heap[min],this.#heap[idx]]
      idx = min
    }
  }
  top(){return this.#heap[0]}
  size(){return this.#heap.length}
}

// topK 取前3大
function topK(arr,k){
  const heap = new MinHeap()
  for(let v of arr){
    heap.push(v)
    if(heap.size()>k) heap.pop()
  }
  let ans = []
  while(heap.size()) ans.unshift(heap.pop())
  return ans
}
console.log(topK([3,2,1,5,6,4],3))

七、并查集 UnionFind(连通分量、岛屿数量、朋友圈)

复制代码
class UnionFind {
  parent = []
  constructor(n){
    for(let i=0;i<n;i++) this.parent[i]=i
  }
  find(x){
    if(this.parent[x]!==x) this.parent[x]=this.find(this.parent[x])
    return this.parent[x]
  }
  union(x,y){
    let fx = this.find(x), fy=this.find(y)
    if(fx!==fy) this.parent[fy]=fx
  }
}
// 岛屿连通模拟
const uf = new UnionFind(5)
uf.union(0,1)
console.log(uf.find(0)===uf.find(1)) // true

八、前缀和数组(子数组和、区间求和)

复制代码
function preSum(arr){
  let pre = [0]
  for(let n of arr) pre.push(pre.at(-1)+n)
  return pre
}
const arr = [1,2,3,4]
let pre = preSum(arr)
// [l,r]区间和:pre[r+1]-pre[l]
console.log(pre[3]-pre[1]) // 2+3=5

本地调试使用规范

  1. VSCode 新建 .js 文件,粘贴上面对应结构代码;
  2. arrToList 快速生成链表用例,listToArr 打印结果;
  3. BFS 用 Queue/Deque,滑动窗口固定 Deque,topK 用 Heap,连通题 UF

1. 为什么 class 里能直接写 #arr = []

这叫 ** 类字段(Class Field)** 语法,是 JS 官方允许的写法。

作用:直接给类实例声明属性,不用写在 constructor 里。

以前老写法(必须写在 constructor):

复制代码
class Stack {
  constructor() {
    this.arr = []
  }
}

现在新写法(直接写在 class 顶层):

复制代码
class Stack {
  arr = []  // 直接写!
}

效果完全一样,只是语法更简洁。


2. 为什么加 ##arr 是什么?

# 代表 私有属性(private field)

  • 公有属性:外部能改、能访问
  • 私有属性(# 开头):外部不能改、不能访问,只能在 class 内部用

这是为了安全 + 封装

复制代码
const stack = new Stack()
stack.#arr = 123 // 报错!私有属性,外部不能碰

刷题时你可以不加 #,也能正常运行:

复制代码
class Stack {
  arr = []
}

3. 为什么能用 this.#arr

因为:

this 永远指向当前创建的实例对象

当你写

复制代码
const s = new Stack()

new Stack() 会创建一个空对象,然后:

  • #arr = [] 会自动绑定到这个对象上
  • 所有方法里的 this 都指向这个对象

所以:

复制代码
push(v) {
  this.#arr.push(v)
}

这里的 this 就是你创建的实例对象 ,所以能访问到 #arr


4. 一句话总结(最关键)

复制代码
class Stack {
  #arr = []      // 1. 声明实例属性(私有)
  push() {
    this.#arr    // 2. this 指向实例,所以能访问
  }
}

class 里直接写变量 = 给实例添加属性 方法里的 this = 指向实例


5. 最简单等价写法(帮助你理解)

下面两种写法完全一样

写法 A(现代简洁)

复制代码
class Stack {
  #arr = []
}

写法 B(传统构造函数)

复制代码
class Stack {
  constructor() {
    this.#arr = []
  }
}

最终总结

  1. class 里直接写变量 → 声明实例属性
  2. # 开头 → 私有属性,外部不能访问
  3. this → 指向当前实例,所以能访问到类里声明的变量

JS Class 字段前缀汇总(# 之外所有常用写法)

原生 JS 只有 # 是符号前缀,剩下靠关键字修饰:static_(约定)、get/set 存取器,分 4 大类讲清楚👇

一、# :原生硬私有(ES2022 唯一符号前缀)

复制代码
class Stack {
  #arr = [] // 实例私有,外部访问报错
  #fn(){}   // 私有方法
}
  • #名字的一部分 ,访问必须写 this.#arr
  • 语言层面锁死,外部拿不到

二、static 关键字前缀(静态成员,不属于实例、属于类)

无特殊符号,关键字修饰,分「公有静态 / 私有静态 #+static」

复制代码
class Stack {
  // 1.公有静态属性:全类共用,Stack.count,实例拿不到
  static count = 0
  static info(){}

  // 2.私有静态:# + static,只能类内部访问
  static #innerCache = []
}

Stack.count = 10
Stack.info()

刷题场景:堆、工具类全局常量、计数器

三、下划线 _name:开发约定私有(非语言强制,业界惯例)

JS语法不生效,只是程序员约定「以下划线开头不要在外部修改」,仍能直接访问:

复制代码
class Stack {
  _arr = [] // 只是规范,stack._arr 外部照样能改
}
const s = new Stack()
s._arr.push(1) // 合法代码
  • 老项目大量使用,不能替代 #真正私有

四、存取器 get /set(属性伪装成方法)

不加符号,get/set修饰,读写拦截:

复制代码
class Stack {
  #arr = []
  get size() { // 读:stack.size
    return this.#arr.length
  }
  set size(n) { // 写:stack.size=5
    this.#arr.length = n
  }
}
const s = new Stack()
console.log(s.size)
s.size = 2

五、补充:TypeScript 才有额外修饰符(JS 原生没有!)

TS 独有:private/protected/public编译后变成普通 JS,无真正私有

复制代码
// TS代码,编译后JS无任何限制
class Stack{
  private arr:number[]=[]
}

纯 JS 不用这三个关键字,刷题写 leetcode 原生 JS 忽略

六、速记表格

表格

写法 标识 作用 外部可访问?
#arr #符号 实例真私有
static arr static 公有静态 ✅ 类名.arr
static #arr static+# 私有静态
_arr 下划线约定 伪私有 ✅(规范不建议改)
get arr(){} get 属性拦截

刷题最优写法(你之前 Stack 改造)

复制代码
class Stack {
  #arr = []
  static totalCreate = 0 // 全局计数
  constructor(){
    Stack.totalCreate++
  }
  push(v){this.#arr.push(v)}
  get size(){return this.#arr.length}
}
相关推荐
丑过三八线1 小时前
【无标题】
前端
yuananyun1 小时前
APP 图标规范与设计全攻略:iOS/Android/Web 一次设计多端合规,快速出图
android·前端·ios
李剑一1 小时前
面试问网络?问到我的软肋了。面试官:讲一下HTTP强缓存与协商缓存
前端·面试
小雨下雨的雨1 小时前
近视度数模拟器鸿蒙PC Electron框架技术实现详解
前端·javascript·electron
喜欢踢足球的老罗1 小时前
逆向 WhatsApp Web:前端 SDK 深度剖析与 Chrome 插件实战指南
前端·chrome
roseonly_h2 小时前
如何将钉钉微应用在浏览器打开
前端·钉钉
小雨下雨的雨2 小时前
鸿蒙PC用Electron框架——Canvas蜡笔抖动效果实现技术深度解析
前端·javascript·华为·electron·鸿蒙系统
ZC跨境爬虫2 小时前
跟着 MDN 学CSS day_49:定位实例练习从入门到精通
前端·css·学习
前端小万2 小时前
用AI两小时开发上架的小程序,单日新增用户173
前端·微信小程序