前端常用数据结构与算法整理--链表

1,029 阅读7分钟

概念:

  1. 由多个元素组成的列表。
  2. 元素存储不连续,用next指针将其连在一起。
  3. JavaScript中没有链表这个数据结构,但可以用object去模拟

数组与链表比较:

  1. 数组在修改非头尾元素时会移动其中的元素位置(index被改变)
  2. 链表中对数据的增删不需要移动其中元素,只需要改变next指针的指向即可

数据结构概念代码验证:

创建链表

const a = {val: 'a'}
const b = {val: 'b'}
const c = {val: 'c'}

a.next = b
b.next = c
//用next将链表串起来

遍历链表:

定义一个指针p,当p为true时打印当前指针p指向的数据,然后将p向后移动一位,即指向p.next。

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

链表中插入新数据i:(a和b之间插入i)

将前一个数据(a)的next指向i,将i的next指向下一个(b)即可

const i = { val: 'i'}

a.next = i
i.next = b

链表中删除一个数据(将i删除):

将i的前一个元素(a)的next指针指向i的下下个元素(i.next.next)即b 即让a的next重新指向b即可

a.next = b

leetcode相关算法题:

t237. 删除链表中的节点

请编写一个函数,使其可以删除某个链表中给定的(非末尾)节点。传入函数的唯一参数为 要被删除的节点 。 现有一个链表 -- head = [4,5,1,9],它可以表示为:

示例 1:

输入:head = [4,5,1,9], node = 5 输出:[4,1,9] 解释:给定你链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9.

来源:力扣(LeetCode) 链接:leetcode-cn.com/problems/de… 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

解题思路:
常规删除链表中节点需要将上一个节点指向自己的下一个节点即可,但这个题只能得知自己的下一个节点,无法得知自己的上一个节点。
那么我们可以转变思路,不去删除自己,而是去删除自己的下一个节点,这样可以得知node 和 node.next.next。这两个就是node.next的上一个节点和下一个节点。由此可知,这题我们无法删除自己,但可以删除自己的下一个节点,我们可以把node变成node.next的样子,然后将node.next删除掉即可,这样node就被删除了,并且所有数据的val都不会被影响。
一句话总结就是:
把自己变成另一个节点的样子,然后再让另一个节点消失,这样虽然短时间内会出现两个node.next,但是只要删掉一个,就只剩下一个了。两行代码即可实现

/**
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */
/**
 * @param {ListNode} node
 * @return {void} Do not return anything, modify node in-place instead.
 */
var deleteNode = function(node) {
    node.val = node.next.val
    node.next = node.next.next
};

T206 反转链表:

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

  • 示例 1:
  • 输入:head = [1,2,3,4,5]
  • 输出:[5,4,3,2,1]
  • 示例 2:
  • 输入:head = [1,2]
  • 输出:[2,1]
  • 来源:力扣(LeetCode)

链接:leetcode-cn.com/problems/re… 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
解题思路:
首先思考如何反转只有两个数据的链表,即a和b,反转这个链表只需要将b的next指向a即可。
将这个问题拆分即可知反转整个长链表实际上也就是不断的把遍历到的数据的next指向上一个即可,所以我们可以用两个指针,i和j来遍历整个列表,i先于j,所以每次遍历时将i.next指向j即可,然后让i和j一起向右移动一位即可,由于反转后原本的头应该指向null,所以开始将i设为传入的head,j设为null。

/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} head
 * @return {ListNode}
 */
var reverseList = function(head) {
    let i = head,j=null
    while(i){
        temp = i.next
        i.next = j
        j = i
        i =temp
    }
    return j
};

T2 两数相加:

给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。

请你将两个数相加,并以相同形式返回一个表示和的链表。

你可以假设除了数字 0 之外,这两个数都不会以 0 开头。

  • 输入:l1 = [2,4,3], l2 = [5,6,4]
  • 输出:[7,0,8]
  • 解释:342 + 465 = 807.
  • 示例 2:
  • 输入:l1 = [0], l2 = [0]
  • 输出:[0]
  • 示例 3:
  • 输入:l1 = [9,9,9,9,9,9,9], l2 = [9,9,9,9]
  • 输出:[8,9,9,9,0,0,0,1]

来源:力扣(LeetCode) 链接:leetcode-cn.com/problems/ad… 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
解题思路;
先创建一个空链表,然后定义两个指针,分别遍历两个链表,然后将值相加后加到新的链表,如果有进位则把进位用变量保存参与下一轮相加,如果最后遍历完还有进位,则将进位加入链表

/**
 * @param {ListNode} l1
 * @param {ListNode} l2
 * @return {ListNode}
 */
var addTwoNumbers = function(l1, l2) {
    let l3 = new ListNode(0)
    let p1 = l1,p2 = l2,p3 = l3
    let carry = 0
    while(p1 || p2){
        let v1 = p1?p1.val:0
        let v2 = p2?p2.val:0
        let v3 = v1+v2+carry
        carry = Math.floor(v3 /10)
        p3.next = new ListNode(v3%10)
        if(p1) p1 = p1.next
        if(p2) p2 = p2.next
        p3 = p3.next
    }
    if(carry){
        p3.next = new ListNode(carry)
    }
    return l3.next
};

T83:删除排序列表中的重复元素

  • 存在一个按升序排列的链表,给你这个链表的头节点 head ,请你删除所有重复的元素,使每个元素 只出现一次 。
  • 返回同样按升序排列的结果链表。
  • 示例 1:
  • 输入:head = [1,1,2]
  • 输出:[1,2]
  • 示例 2:
  • 输入:head = [1,1,2,3,3]
  • 输出:[1,2,3]

来源:力扣(LeetCode) 链接:leetcode-cn.com/problems/re… 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
解题思路:
题目所给的是一个有序链表,所以重复的相同值一定相邻,重复的值之间和p和p.next的关系,所以遍历链表,比较p和p.next是否相同即可,相同则删除,不相同则继续向下遍历

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
};

T141.环形链表

给定一个链表,判断链表中是否有环。

  • 如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。
  • 如果链表中存在环,则返回 true 。 否则,返回 false 。
  • 进阶:
  • 你能用 O(1)(即,常量)内存解决此问题吗?
  • 示例 1:
  • 输入:head = [3,2,0,-4], pos = 1
  • 输出:true
  • 解释:链表中有一个环,其尾部连接到第二个节点。
  • 示例 2:
  • 输入:head = [1,2], pos = 0
  • 输出:true
  • 解释:链表中有一个环,其尾部连接到第一个节点。
  • 示例 3:
  • 输入:head = [1], pos = -1
  • 输出:false
  • 解释:链表中没有环。

来源:力扣(LeetCode) 链接:leetcode-cn.com/problems/li… 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
解题思路:
定义一快一慢两个指针,如果两个指针相遇,则证明有环(类似圆形操场跑步,环形跑道只要有人跑得快,总能追上跑得慢的,并且超过一圈,但如果跑直线,无论如何也不会和最慢的人相遇)

/**
 * @param {ListNode} head
 * @return {boolean}
 */
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
};

JavaScript与链表:

js中的原型链实际上就是一个链表

p._proto_就相当于向上遍历 等于p.next