链表专题五

106 阅读5分钟

这是我参与2022首次更文挑战的第10天,活动详情查看:2022首次更文挑战 | 创作学习持续成长,夺宝闯关赢大奖 - 掘金 (juejin.cn)

题目链接

  1. 两数相加II leetcode-cn.com/problems/ad…
  2. 重排链表 leetcode-cn.com/problems/re…
  3. 环路检测 leetcode-cn.com/problems/li…
  4. 设计链表 leetcode-cn.com/problems/de…
  5. 删除链表的节点 leetcode-cn.com/problems/sh…

题解及分析

两数相加II

给你两个非空链表来代表两个非负整数。数字最高位位于链表开始位置。它们的每个节点只存储一位数字。将这两数相加会返回一个新的链表。
你可以假设除了数字0之外,这两个数字都不会以零开头。
示例1:
输入:l1 = [7,2,4,3], l2 = [5,6,4]
输出:[7,8,0,7]

思路:如果把题目示例1的看成7243+564,那么结果得出7807就能理解题意了。实际上这道题是要求利用链表做加法。

var addTwoNumbers = function(l1, l2) {
    const stack1 = []
    const stack2 = []
    while (l1 || l2) {//两链表入栈
        if (l1) {
            stack1.push(l1.val);
            l1 = l1.next;
        }
        if (l2) {
            stack2.push(l2.val);
            l2 = l2.next;
        }
    }

    let newNodeList = null
    let prefix = 0
    while(stack1.length || stack2.length || prefix !== 0) {
        const newNode1 = stack1.length ? stack1.pop() : 0
        const newNode2 = stack2.length ? stack2.pop() : 0
        const newVal = newNode1 + newNode2 + prefix
        const curNode = new ListNode((newVal % 10))
        curNode.next = newNodeList
        newNodeList = curNode
        prefix = parseInt(newVal / 10)
    }
    return newNodeList
}

重排链表

给定一个单链表 L 的头节点head,单链表L表示为: L0 → L1 → … → Ln - 1 → Ln 请将其重新排列后变为: L0 → Ln → L1 → Ln - 1 → L2 → Ln - 2 → … 不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。

思路一:栈

  • 找出中间节点。(快慢指针)
  • 将中间节点之后的值用栈存起来(反正就是找一个翻转链表的办法)
  • 同时遍历中间节点之前的链表和栈,组装一个新链表
var reorderList = function(head) {
    if(!head || !head.next || !head.next.next) return head

    let fast = head
    let slow = head
    while(fast && fast.next) {
        slow = slow.next
        fast = fast.next.next
    }

    let left = slow.next
    slow.next = null
    const stack = []
    while(left) {
        stack.push(left)
        left = left.next
    }

    while(head && stack.length) {
        const next = head.next
        const newNode = stack.pop()
        newNode.next = next
        head.next = newNode
        head = next
    }

    return head
}

思路二:数组

  • 将链表从每个节点处断开并存入数组
  • 利用快慢指针,重新组转链表
var reorderList = function(head) {
    let arr = []
    let tmp = null
    while (head) 
        tmp = head.next, 
        head.next = null, 
        arr.push(head), 
        head = tmp
    var i = -1, j = arr.length
    while (++i < --j) 
        arr[i].next = arr[j], 
        j !== i + 1 && (arr[j].next = arr[i + 1])
    return arr[0] 
}

环路检测

给定一个链表,如果它是有环链表,实现一个算法返回环路的开头节点。若环不存在,请返回null。
如果链表中有某个节点,可以通过连续跟踪next指针再次到达,则链表中存在环。为了表示给定链表中的环,我们使用整数pos来表示链表尾连接到链表中的位置(索引从0开始)。如果pos是-1,则在该链表中没有环。注意:pos不作为参数进行传递,仅仅是为了标识链表的实际情况。

这道题目和链表折磨专题一 - 掘金 (juejin.cn)中第二题一样,这里不做其他解释

设计链表

设计链表的实现。您可以选择使用单链表或双链表。单链表中的节点应该具有两个属性:val和next。val是当前节点的值,next是指向下一个节点的指针/引用。如果要使用双向链表,则还需要一个属性prev以指示链表中的上一个节点。假设链表中的所有节点都是 0-index 的。
在链表类中实现这些功能:
get(index):获取链表中第index个节点的值。如果索引无效,则返回-1。
addAtHead(val):在链表的第一个元素之前添加一个值为val的节点。插入后,新节点将成为链表的第一个节点。
addAtTail(val):将值为val的节点追加到链表的最后一个元素。
addAtIndex(index,val):在链表中的第index个节点之前添加值为val的节点。如果index等于链表的长度,则该节点将附加到链表的末尾。如果index大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点。
deleteAtIndex(index):如果索引index有效,则删除链表中的第index个节点。

这道题实际上参考了链表折磨专题四 - 掘金 (juejin.cn)第一题,但是条件从获取/添加/删除中间节点变成了获取/添加/删除指定index节点,其他功能实际上一样

单链表解法

  • 获取指定index节点,直接遍历链表找到对应的下标即可
  • 添加指定index节点,需要注意我们应该遍历到第index-1个节点,然后执行操作
    • 要注意链表为空的情景
  • 删除指定index节点和添加注意事项一致
  • 顺带一提,双链表其实也一样,但是需要注意prev指针的指向
var MyLinkedList = function() {
    this.head = null
};

MyLinkedList.prototype.get = function(index) {
    let count = 0
    let cur = this.head
    while(cur) {
        if(count === index) {
            return cur.val
        }
        cur = cur.next
        count++
    }
    return -1
};

MyLinkedList.prototype.addAtHead = function(val) {
    this.head = new ListNode(val, this.head)
};

MyLinkedList.prototype.addAtTail = function(val) {
    let cur = this.head
    if(!cur) {
        this.head = new ListNode(val)
        return
    } 
    while(cur && cur.next) {
        cur = cur.next
    }
    cur.next = new ListNode(val)
};

MyLinkedList.prototype.addAtIndex = function(index, val) {
    if(!index) return this.addAtHead(val)
    let count = 0
    let cur = this.head
    while(cur) {
        if(count === index - 1) {
            let newNode = new ListNode(val, cur.next)
            cur.next = newNode
            break
        }
        cur = cur.next
        count++
    }
};

MyLinkedList.prototype.deleteAtIndex = function(index) {
    if(!index) return this.head = this.head.next
    let count = 0
    let cur = this.head
    while(cur && cur.next) {
        if(count === index - 1) {
            cur.next = cur.next.next || null
            break
        }
        cur = cur.next
        count++
    }
};

删除链表的节点

给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。 返回删除后的链表的头节点。

思路一:单指针

  • 需要注意的是,修改指针的条件是当指针指向符合条件的节点的前一个节点时
var deleteNode = function(head, val) {
    if (head.val == val) return head.next
    let prev = head
    while(prev) {
        if(prev.next.val === val) {
            const next = prev.next
            prev.next = next.next
            return head
        }
        prev = prev.next
    }
    return head
}

思路二:双指针
(没什么能解释的)

var deleteNode = function(head, val) {
    if (head.val == val) return head.next
    let prev = head
    let cur = head.next
    while(cur) {
        if(cur.val === val) {
            const next = cur.next
            prev.next = next
            return head
        }
        prev = prev.next
        cur = cur.next
    }
    return head
}

思路三:递归

  • 利用递归的特性解决
  • 刚好只要判断head即可
var deleteNode = function(head, val) {
    if (head.val == val) return head.next
    head.next = deleteNode(head.next, val)
    return head
}

题目总结

链表的题目,说穿了只有增删改查。但如何找到需要修改的位置才是题目真正考察的核心。

上期文章

链表折磨专题四 - 掘金 (juejin.cn)