链表与LeetCode链表题目详解

176 阅读4分钟

链表基础知识介绍

链表是一种基本的数据结构,用于组织和存储数据。数组和链表是常见的数据结构,它们在实现上有一些区别。数组由一组连续的内存空间组成,元素按顺序排列,并且可以通过索引值进行随机访问。但数组的大小固定,插入或删除元素需要进行大量的数据移动,效率较低。而链表则是由节点组成,每个节点包含一个数据元素和指向下一个节点的指针。链表的大小可以动态扩充或缩减,插入或删除元素只需修改指针,效率较高。但链表不支持随机访问,只能从头节点开始依次访问。相比数组,链表具有动态的大小,不需要预先分配内存空间,更容易进行插入和删除操作。

数组 vs. 链表

数组

  • 有序的数据集合。
  • 存储在连续的内存块中。
  • 随机访问速度快,时间复杂度为 O(1)。
  • 插入和删除的平均时间复杂度为 O(n),因为需要移动元素。

链表

  • 由节点组成,每个节点包含数据和指向下一个节点的指针。
  • 内存不连续,动态分配。
  • 随机访问相对较慢,时间复杂度为 O(n)。
  • 插入和删除的平均时间复杂度为 O(1),因为只需调整指针。

链表题目详解

1. 删除排序链表中的重复元素

LeetCode原题链接

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

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

示例 1:

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

示例 2:

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

题目解析

遍历链表,比较当前节点和下一个节点的值。如果相等,将当前节点的指针指向下一个节点的下一个节点,即跳过重复元素。

JavaScript 实例

/**
 * 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 deleteDuplicates = function(head) {
    // if(head == null) return head
    let cur = head
    while(cur!==null && cur.next){
        if(cur.val === cur.next.val){
            cur.next = cur.next.next
        }else{
            cur = cur.next
        }
    }
    return head
};

2. 合并两个有序链表

LeetCode原题链接

21. 合并两个有序链表

合并两个有序链表,返回合并后的有序链表。

示例 1:

输入: l1 = [1,2,4], l2 = [1,3,4]
输出: [1,1,2,3,4,4]

示例 2:

输入: l1 = [], l2 = []
输出: []

示例 3:

输入: l1 = [], l2 = [0]
输出: [0]

题目解析

创建一个新链表作为合并后的链表,使用两个指针分别遍历两个有序链表,比较节点值大小,逐个合并节点。

JavaScript 实例

/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} list1
 * @param {ListNode} list2
 * @return {ListNode}
 */
var mergeTwoLists = function(list1, list2) {
    const head = new ListNode();// val = 0, next = null
    let cur = head;
    while(list1 && list2){
        if(list1.val <= list2.val){
            cur.next = list1;
            list1 = list1.next;
        }else{
            cur.next = list2;
            list2 = list2.next;
        }
        cur = cur.next;
    }
    if(list1) cur.next = list1;
    if(list2) cur.next = list2;

    return head.next
};

思路详解1:

我们可以通过具体的链表题目来理解链表的应用。首先是题目「83. 删除排序链表中的重复元素」。给定一个已排序的链表,要求删除所有重复的元素,使每个元素只出现一次,并返回已排序的链表。我们可以使用双指针法来解决这个问题。定义一个指针cur指向头节点,然后遍历链表。如果当前节点的值等于下一个节点的值,说明有重复元素,将当前节点的next指针指向下一个节点的next,即跳过重复元素。如果不相等,将cur指针移动到下一个节点。最后返回头节点,即可得到删除重复元素后的链表。

思路详解2:

接下来是题目「21. 合并两个有序链表」。给定两个有序链表,要求将它们合并成一个有序链表并返回。我们可以使用归并排序的思想来解决这个问题。定义一个新的链表头节点head,并用cur指针指向它。然后比较两个链表的当前节点值,将较小的节点接在cur的next指针上,并将对应链表的指针移动到下一个节点。重复这个过程,直到遍历完其中一个链表。最后将剩余链表的节点接在cur的next指针上,即完成合并。返回head.next即可得到合并后的有序链表。

总结:

通过以上代码和解析,我们了解了链表的基本概念和常见操作方法。链表作为一种灵活的数据结构,在某些场景中具有较高的效率优势。对于涉及插入、删除等频繁操作的情况,链表的性能更好。希望这篇文章能够帮助你理解数组和链表的区别以及链表的应用。