LeetCode【数据结构篇】【链表】

213 阅读6分钟

相交链表

编写一个程序,找到两个单链表相交的起始节点。

如下面的两个链表:

image

在节点 c1 开始相交

难易度: 简单

解题思路

设 A 的长度为 a + c,B 的长度为 b + c,其中 c 为尾部公共部分长度,可知 a + c + b = b + c + a

当访问 A 链表的指针访问到链表尾部时,令它从链表 B 的头部开始访问链表 B;同样地,当访问 B 链表的指针访问到链表尾部时,令它从链表 A 的头部开始访问链表 A。这样就能控制访问 A 和 B 两个链表的指针能同时访问到交点

如果不存在交点,那么 a + b = b + a,以下实现代码中 l1 和 l2 会同时为 null,从而退出循环

var getIntersectionNode = function(headA: LinkedNode, headB: LinkedNode) {
    let l1 = headA, l2 = headB

    while(l1 != l2) {
        l1 = l1 ? l1.next : headB // 如果l1为null 代表l1已经遍历完,将l1尾部链接至l2尾部
        l2 = l2 ? l2.next : headA
    }
    return l1
};

两数相加

给出两个 非空 的链表用来表示两个非负的整数。其中,它们各自的位数是按照 逆 序 的方式存储的,并且它们的每个节点只能存储 一位 数字。

如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们的和。

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

输入:(2 -> 4 -> 3) + (5 -> 6 -> 4)

输出:7 -> 0 -> 8

原因:342 + 465 = 807

  • 难易程度: 中等

解题思路

  1. 平常思维,按序取出链表中的值,得出相加的结果,再利用结果倒序生成新的链表
class LinkedNode {
    value: number
    next: LinkedNode
    constructor(value: number) {
        this.next = null
        this.value = value
    }
}

const a = new LinkedNode(2)
const b = new LinkedNode(4)
const c = new LinkedNode(3)

const d = new LinkedNode(5)
const e = new LinkedNode(6)
const f = new LinkedNode(4)

a.next = b
b.next = c

d.next = e
e.next = f

function getNode(linkedList: LinkedNode, values: number[]) {
    values.push(linkedList.value)
    if (linkedList.next !== null) {
        getNode(linkedList.next, values)
    }
}
// 按序取出链表中的值的数组
function getNodeArr(linkedList: LinkedNode) {
    let values: number[] = []
    getNode(linkedList, values)
    return values
}

const addTwoNumbers = function (l1: LinkedNode, l2: LinkedNode) {
    let value1 = getNodeArr(l1).reverse().join('')
    let value2 = getNodeArr(l2).reverse().join('')

    let res = (+value1) + (+value2) + ''

    // 存储结果的每个节点
    const nodeArr: LinkedNode[] = []
    res.split('').reverse().forEach(r => {
        nodeArr.push(new LinkedNode(+r))
    })

    for (let i = 0, len = nodeArr.length; i < len - 1; i++) {
        // 构建结果链表
        nodeArr[i].next = nodeArr[i + 1]
    }

    return nodeArr[0]
};

console.log(addTwoNumbers(a, d)) // 7 -> 0 -> 8
  1. 两个链表相加,因为是倒序的,则两数相加大于10,向后进位,低位相加。

2 -> 4 -> 3

5 -> 6 -> 4 +

----------------------

7 -> 0 -> 8

因为6+4大于等于10, 所以需要向后进位, 3 + 4 + 1 = 8

function addTwoNumbers2(l1: LinkedNode, l2: LinkedNode): LinkedNode {
    let node = new LinkedNode(0);
    let temp = node;// temp节点
    let add = 0;// 是否进一
    let sum = 0;// 新链表当前未取余的值 = 链表1值 + 链表2值 + add;

    // 遍历, 以最长链表为结束点
    while (l1 || l2) {
        sum = (l1 ? l1.value : 0) + (l2 ? l2.value : 0) + add;
        temp.next = new LinkedNode(sum % 10);// 取余则为新链表的值
        temp = temp.next;
        add = sum >= 10 ? 1 : 0; // 相加大于10,则需向后进位(+1)
        l1 && (l1 = l1.next); // 改变指向,继续循环
        l2 && (l2 = l2.next);
    }
    add && (temp.next = new LinkedNode(add));
    return node.next;
}

反转链表

反转一个单链表。

示例

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

难易度:简单

解题思路

从头逆序

构造一个新的头,新头的next等于head

var reverseList = function (head: LinkedNode) {
    if (!head || !head.next) return head

    let newHead = new LinkedNode(-1)
    while (head) {
        let temp = head.next
        head.next = newHead.next // 实现反转
        newHead.next = head
        head = temp
    }
    return newHead.next
};

递归

function reverseList2 (head: LinkedNode) {
    if (head == null || head.next == null) return head;
    let next = head.next;
    let newHead = reverseList2(next);
    next.next = head;
    head.next = null;
    return newHead;
}

合并链表

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

示例:

输入:1->2->4, 1->3->4

输出:1->1->2->3->4->4

难度:容易

解题思路:

  1. 暴力解法

递归两个链表,取出所有val,对这些val进行排序,将排序之后的val在生成一个新链表输出。

class ListNode {
    val: number = 0
    next: ListNode = null
    constructor(val: number, next: ListNode = null) {
        this.val = val
        this.next = next
    }
}

function getNodeVal(l: ListNode, values: number[]) {
    if (!l) return
    values.push(l.val)
    getNodeVal(l.next, values)
}

/**
* @param {ListNode} l1
* @param {ListNode} l2
* @return {ListNode}
*/
var mergeTwoLists = function (l1: ListNode, l2: ListNode): ListNode {
    if (l1 && l2) {
        let values: number[] = []
        getNodeVal(l1, values)
        getNodeVal(l2, values)

        values.sort((a, b) => a - b)

        let res: ListNode[] = values.map(v => new ListNode(v))
        res.reduce((prev, cur) => prev.next = cur)

        return res[0]

    } else {
        return l1 || l2
    }
};
  1. 不生成新的链表

既然两个链表都是升序排列,那么只需要把两个列表的节点相互比较,然后插入到愿列表中即可

1. 如果l1的当前节点比l2的当前节点大,那么就应该是l1的当前节点插入l2的当前节点之后
2. 反之亦然
3. 递归比较没一个节点
var mergeTwoLists2 = function (l1: ListNode, l2: ListNode): ListNode {
    if (!l1 || !l2) return l1 || l2

    if (l1.val > l2.val) {
        l2.next = mergeTwoLists2(l1, l2.next)
        return l2

    } else {
        l1.next = mergeTwoLists2(l1.next, l2)
        return l1
    }
}

删除有序链表中的重复元素

给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。

示例

输入: 1->1->2->3->3
输出: 1->2->3

难度:容易

解题思路

因为这个链表是有序的,所以如果链表的next 等于 当前位, 那么就可以直接舍弃当前位,让next指向 next.next

var deleteDuplicates = function (head: ListNode): ListNode {
    if (!head || !head.next) return head

    head.next = deleteDuplicates(head.next)

    return head.val == head.next.val ? head.next : head
};

删除链表的倒数第N个节点

给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。

示例:

给定一个链表: 1->2->3->4->5, 和 n = 2.
当删除了倒数第二个节点后,链表变为 1->2->3->5

难度: 中等

解题思路

快慢双指针

快指针先走 n+1 步

慢指针从头开始走,两个指针以同样的速度往后走

直到快指针为 nll 此时,说明快指针已经走完了整个链表

此时慢指针的位置就是倒数第n个节点

删除这个节点

var removeNthFromEnd = function(head: ListNode, n: number): ListNode {
    let fast = head
    // 快指针先走 n+1 步
    while (n > 0) {
        fast = fast.next
        n--
    }
    // 如果快指针走完n+1步,整个链表就走完了,说明只需要删除头节点
    if (fast == null) return head.next 
    let slow = head
    // 慢指针从头开始走,两个指针以同样的速度往后走
    while (fast.next) {
        slow = slow.next
        fast = fast.next
    }

    slow.next = slow.next.next // 删除这个节点
    return head
};

两两交换链表中的节点

给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。

你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。

示例

给定 1->2->3->4, 你应该返回 2->1->4->3.

难度: 中等

解题思路

image

var swapPairs = function (head: ListNode): ListNode {
    let newListNode = new ListNode(-1) // 添加dummy节点
    newListNode.next = head 

    let temp = newListNode

    while(temp.next && temp.next.next) {
        let s = temp.next
        let e = s.next
        // 交换s 和 e 两个节点
        temp.next = e;
        s.next = e.next;
        e.next = s;

        temp = s;// 向后两个节点,继续交换
    }
    return newListNode.next
};