数据结构之链表——面试基础篇

121 阅读4分钟

在前端中,排开数组,我们最熟悉的数据结构莫过于链表。众所周知,考研也好,就业也罢,链表的地位都是不可动摇的。今天就让我们来搞定它!

本篇以基础为主,面向想要学习链表的小伙伴们,难度螺旋向上(当然也不会很难...

链表的删除

虽然比较简单,但还是想跟大伙唠唠。

给定要删除的目标节点 node(非头尾节点),头结点 head

删除一个给定的节点 node,就必须找到其前驱节点 pre

image.png

pre.next = node,我们只需要让 pre.next = node.next 就能完成删除操作。

精髓部分已经完成,剩下的,只需要从 head 依次遍历寻找到 pre 前驱节点即可。

// 初始化节点
let pre = head
let cur = pre.next

// 一直往后移,直到找到目标节点
while (cur !== node) {
    pre = cur
    cur = cur.next
}

// 删除目标节点
pre.next = cur.next

1. 移除链表元素

题目地址:leetCode 203.移除链表元素

题目描述:

给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点 。

image.png

根据题意,依旧是找到指定节点,只不过这里的节点是根据 val 而定。

知识扩充:对于链表类题型,我们通常会创建一个锚节点 Dummy ,使其 next 指向头结点 head,便于对链表整体的把控。

// 初始化锚节点
let dummy = new ListNode()
dummy.next = head

由于第一个节点也可能会成为被删除节点,所以锚节点的创建是必然的。

细节请直接看代码。

var removeElements = function(head, val) {
    // 如果头结点不存在,直接返回
    if (!head) {
        return null
    }
    // 初始化锚节点
    let dummy = new ListNode()
    dummy.next = head
    // 初始化前驱节点
    let pre = dummy
    // 初始化当前节点
    let cur = head
    // 循环遍历链表
    while (cur) {
        // 如果找到需要删除的节点,直接删除
        if (cur.val === val) {
            // 使pre指向被删除节点的下一个节点,即删除cur节点
            pre.next = cur.next
            // 继续让cur成为pre的后继节点
            cur = pre.next
        } 
        // 如果没有找到,则换下一个节点
        else {
            pre = cur
            cur = cur.next
        }
    }
    return dummy.next
};

环形链表

这个环形链表,大伙应该都不陌生,如果陌生的话,也不要慌,只是还没人给你引上道。

当我们中国人第一次登月,在月球上插上我们的国旗时,小伙伴们的内心一定是激情澎湃的(跟我一样)。环形链表也如此,我们每到一个节点,就插入一面旗帜,表示我们已经来过了。如果一直遍历链表,又碰到了这面旗帜,那就说明,有环。反之,则无。

1. 环形链表

题目地址:leetCode 141.环形链表

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

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递。仅仅是为了标识链表的实际情况。

如果链表中存在环 ,则返回 true 。 否则,返回 false 。

image.png

大伙应该已经顿悟!那我就不客气了,直接上代码,具体细节请看注释

var hasCycle = function (head) {
    // 循环遍历链表
    while (head) {
        // 如果不存在flag
        if (!head.flag) {
            // 则插入这个属性到节点中
            head.flag = 1
        }
        // 如果存在,说明有环
        else {
            return true
        }
        head = head.next
    }
    return false
};

快慢指针也可以实现,有兴趣的小伙伴们可以尝试一下

链表的排序与翻转

1. 排序链表

题目地址:leetCode 148.排序链表

题目描述:

给你链表的头结点 head ,请将其按 升序 排列并返回 排序后的链表 。

image.png

小伙伴们:啊这...这题?可恶!怎么不是数组?!

注意了,拿起笔划重点啦,三剑客登场。

pre(前驱节点) cur(当前节点) next(后继节点)

处理这类排序翻转比较负责的问题时,就需要用到三剑客,小伙伴们需要察言观色啊(多做题...

实现翻转效果

cur.next = next.next
next.next = pre.next
pre.next = next

image.png

image.png

image.png

image.png

请看代码

var sortList = function (head) {
    // 初始化锚节点
    let dummy = new ListNode()
    dummy.next = head
    // 初始化前驱结点
    let pre = dummy
    // 初始化当前节点
    let cur = head
    // 循环遍历链表
    while (cur) {
        // 初始化后继节点
        let next = cur.next
        // 如果后继节点存在 且值要小于当前节点 则需要换位置
        if (next && next.val < cur.val) {
            // 遍历链表 找到比next.val要大的值 
            while (pre.next.val < next.val) {
                pre = pre.next
            }
            // 翻转
            cur.next = next.next
            next.next = pre.next
            pre.next = next
            // 重置pre 从头开始
            pre = dummy
        } else {
            // 回至原来的位置
            cur = next
        }
    }
    return dummy.next
};

小结:

未完待更...