⑤ 数据结构之“链表”

function ListNode(val, next) {
  this.val = (val===undefined ? 0 : val)
  this.next = (next===undefined ? null : next)
}

一、 理论

1. 链表简介

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

2. 数组 vs 链表

  • 数组:增删非首尾元素需要移动元素
  • 链表:增删非首尾元素不需要移动元素,只需要改动元素的next指针

3. js中的链表

  • js中没有链表,使用object模拟链表

3.1 链表实现

// linkList
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

3.2 遍历链表

let p = a
while(p) {
  console.log(p.val)
  p = p.next
}

3.3 插入

const e = {val: 'e' }
c.next = e
e.next = d

3.4 删除

c.next = d

二、刷题

1. 删除链表中的节点(237)

1.1 题目描述

  • 请编写一个函数,用于 删除单链表中某个特定节点
  • 在设计函数时需要注意,你无法访问链表的头节点 head ,只能直接访问 要被删除的节点
  • 题目数据保证需要删除的节点 不是末尾节点

1.2 解题思路

输入:head = [4,5,1,9], node = 1
输出:[4,5,9]

  • 无法直接获取被删除节点的上个节点
  • 将被删除的节点转移到下个节点

1.3 解题步骤

  • 将被删除的节点的值改为下个节点的值
  • 删除下个节点
function deleteNode(node) {
  node.val = node.next.val
  node.next = node.next.next
}

1.4 时间复杂度&空间复杂度

  • 时间复杂度:O(1)
  • 空间复杂度:O(1)

2. 反转链表(206)

2.1 题目描述

  • 给你单链表的头节点 head ,请你反转链表,并返回反转后的链表

2.2 解题思路

输入: 1 -> 2 -> 3 -> 4 -> 5 -> null
输出: 5 -> 4 -> 3 -> 2 -> 1 -> null

  • 反转两个节点:将n+1的next指向n
  • 反转多个节点:双指针遍历链表

2.3 解题步骤

  • 双指针一前一后遍历链表
  • 反转双指针
function reverseList(head) {
  let p1 = head, p2 = null
  while(p1) {
    const tmp = p1.next
    p1.next = p2
    p2 = p1
    p1 = tmp
  }
  return p2
}

2.4 时间复杂度&空间复杂度

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

3. 两数相加(2)

3.1 题目描述

  • 给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字
  • 请你将两个数相加,并以相同形式返回一个表示和的链表
  • 你可以假设除了数字 0 之外,这两个数都不会以 0 开头

3.2 解题思路

输入:(2 -> 4 -> 3) + (5 -> 6 -> 4)
输出:7 -> 0 -> 8
原因:342 + 465 = 807

  • 模拟相加操作
  • 需要遍历链表

3.3 解题步骤

  • 新建空链表
  • 遍历两个被相加的链表,模拟相加操作,将个位数追加到新链表上,将十位数留到下一位去相加
function addTwoNumbers(l1, l2) {
  const l3 = new ListNode(0)
  let p1 = l1, p2 = l2, p3 = l3
  let carry = 0
  while(p1 || p2) {
    const v1 = p1 ? p1.val : 0
    const v2 = p2 ? p2.val : 0
    const val = v1 + v2 + carry
    carry = parseInt(val / 10)
    p3.next = new ListNode(val % 10)
    if(p1) p1 = p1.next
    if(p2) p2 = p2.next
    p3 = p3.next
  }
  if(carry) {
    p3.next = new ListNode(carry)
  }
  return l3.next
}

3.4 时间复杂度&空间复杂度

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

4. 删除排序链表中的重复元素(83)

4.1 题目描述

  • 存在一个按升序排列的链表,给你这个链表的头节点 head ,请你删除所有重复的元素,使每个元素 只出现一次
  • 返回同样按升序排列的结果链表

4.2 解题思路

输入:head = [1,1,2]
输出:[1,2]

  • 因为链表有序,所以重复元素一定相邻
  • 遍历链表,若发现当前元素与下个元素值相同,就删除下个元素值

4.3 解题步骤

  • 遍历链表,若发现当前元素与下个元素值相同,就删除下个元素值
  • 遍历结束后,返回原链表头部
function 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.4 时间复杂度&空间复杂度

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

5. 环形链表(141)

5.1 题目描述

  • 给你一个链表的头节点 head ,判断链表中是否有环

  • 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)

  • 如果 pos 是 -1,则在该链表中没有环

  • 注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况

  • 如果链表中存在环,则返回 true

  • 否则,返回 false

5.2 解题思路

输入:head = [3,2,0,-4], pos = 1
输出:true

  • 两人在操场同起点起跑,速度快的人一定会超过速度慢的人一圈
  • 用一快一慢指针遍历链表,若指针相逢则表示链表有圈

5.3 解题步骤

  • 用一快一慢指针遍历链表,若指针相逢则返回true
  • 遍历结束后,还没相逢则返回false
function hasCycle(head) {
  let p1 = head, p2 = head
  while(p1 && p2 && p2.next) {
    p1 = p1.next
    p2 = p2.next.next
    if(p1 == p2) return true
  }
  return false
}

5.4 时间复杂度&空间复杂度

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

6. js中的原型链(前端与链表)

6.1 简介

  • 原型链的本质是链表
  • 原型链上的节点是各种原型对象,比如Function.prototype、Object.prototype......
  • 原型链通过 __proto__ 属性连接各种原型对象

6.2 原型链

  • obj -> Object.prototype -> null
  • func -> Function.prototype -> Object.prototype -> null
  • arr -> Array.prototype -> Object.prototype -> null

6.3 code part

// proto
const obj = {}
const dunction = () => {}
const arr =[]

6.4 原型链知识点

① 如果A沿着原型链能找到B,则A instanceof B ==> true
obj instanceof Object
func instanceof Function
arr instanceof Array
arr instanceof Object
② 如果在A对象上没有找到x属性,会沿着原型链找x属性
const obj = {}
Object.prototype.x = 'x'
const func = () => {}
Function.prototype.y = 'y'

6.5 面试题

6.5.1 简述instanceof的原理,并用代码实现
  • 知识点:如果A沿着原型链能找到B,则A instanceof B ==> true
  • 解法:遍历A的原型链,如果找到B.prototype,返回true,否则返回false
const instanceOf = (A, B) => {
  let p = A
  while(p) {
    if(p == B.prototype) return true
    p = p.__proto__
  }
  return false
}
instanceOf([], Array)
instanceOf({}, Object)
6.5.2 打印
var foo = {}, F = function() {}
Object.prototype.a = 'value a'
Function.prototype.b = 'value b'
console.log(foo.a) // 'value a'
console.log(foo.b) // undefined
console.log(F.a) // 'value a'
console.log(F.b) // 'value b'
  • 知识点: 如果在A对象上没有找到x属性,会沿着原型链找x属性
  • 解法:明确foo和F变量的原型链,沿着原型链找到a和b的值

7. 使用链表指针获取json的节点值(前端与链表)

// json
const json = {
  a: { b: { c: 1 } },
  d: { e: 2 }
}
const path = ['a', 'b', 'c']
let p = json
path.forEach(k => p = p[k])

三、总结 -- 技术要点

  • 链表里的元素存储不连续,用next指针连在一起
  • js中没有链表,使用object模拟链表
  • 链表常用操作:修改next、遍历链表
  • js中的原型链也是一个链表,沿着 __proto__
  • 使用链表指针可以获取json的节点值
posted on 2022-01-18 10:38  pleaseAnswer  阅读(54)  评论(0编辑  收藏  举报