力扣热题 100:链表专题经典题解析(后7道)

文章目录

    • 前言
    • [一、删除链表的倒数第 N 个节点(题目 19)](#一、删除链表的倒数第 N 个节点(题目 19))
      • [1. 题目描述](#1. 题目描述)
      • [2. 示例](#2. 示例)
      • [3. 解题思路](#3. 解题思路)
      • [4. 代码实现(Java)](#4. 代码实现(Java))
      • [5. 复杂度分析](#5. 复杂度分析)
    • [二、两两交换链表中的节点(题目 24)](#二、两两交换链表中的节点(题目 24))
      • [1. 题目描述](#1. 题目描述)
      • [2. 示例](#2. 示例)
      • [3. 解题思路](#3. 解题思路)
      • [4. 代码实现(Java)](#4. 代码实现(Java))
      • [5. 复杂度分析](#5. 复杂度分析)
    • [三、K 个一组翻转链表(题目 25)](#三、K 个一组翻转链表(题目 25))
      • [1. 题目描述](#1. 题目描述)
      • [2. 示例](#2. 示例)
      • [3. 解题思路](#3. 解题思路)
      • [4. 代码实现(Java)](#4. 代码实现(Java))
      • [5. 复杂度分析](#5. 复杂度分析)
    • [四、随机链表的复制(题目 138)](#四、随机链表的复制(题目 138))
      • [1. 题目描述](#1. 题目描述)
      • [2. 示例](#2. 示例)
      • [3. 解题思路](#3. 解题思路)
      • [4. 代码实现(Java)](#4. 代码实现(Java))
      • [5. 复杂度分析](#5. 复杂度分析)
    • [五、排序链表(题目 147)](#五、排序链表(题目 147))
      • [1. 题目描述](#1. 题目描述)
      • [2. 示例](#2. 示例)
      • [3. 解题思路](#3. 解题思路)
      • [4. 代码实现(Java)](#4. 代码实现(Java))
      • [5. 复杂度分析](#5. 复杂度分析)
    • [六、合并 K 个升序链表(题目 23)](#六、合并 K 个升序链表(题目 23))
      • [1. 题目描述](#1. 题目描述)
      • [2. 示例](#2. 示例)
      • [3. 解题思路](#3. 解题思路)
      • [4. 代码实现(Java)](#4. 代码实现(Java))
      • [5. 复杂度分析](#5. 复杂度分析)
    • [七、LRU 缓存(题目 146)](#七、LRU 缓存(题目 146))
      • [1. 题目描述](#1. 题目描述)
      • [2. 示例](#2. 示例)
      • [3. 解题思路](#3. 解题思路)
      • [4. 代码实现(Java)](#4. 代码实现(Java))
      • [5. 复杂度分析](#5. 复杂度分析)

前言

在力扣(LeetCode)平台上,链表相关的题目一直是考察重点,也是许多开发者提升算法能力的必刷内容。今天,我们就来详细解析链表专题中的几道经典题目,帮助大家更好地理解解题思路和技巧。

一、删除链表的倒数第 N 个节点(题目 19)

1. 题目描述

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

2. 示例

示例 1:

输入:head = [1, 2, 3, 4, 5], n = 2

输出:[1, 2, 3, 5]

解释:删除倒数第 2 个节点后,链表变为 [1, 2, 3, 5]

3. 解题思路

这道题主要考察双指针的应用。我们可以使用两个指针,一个指针先移动 n 步,然后两个指针同时移动,直到先移动的指针到达链表末尾。具体步骤如下:

  1. 初始化两个指针 fastslow,都指向链表的头节点。
  2. fast 指针先移动 n 步。
  3. 同时移动 fastslow 指针,直到 fast 指针到达链表末尾。
  4. 此时,slow 指针指向倒数第 n 个节点的前一个节点,删除该节点。

4. 代码实现(Java)

java 复制代码
public class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode dummy = new ListNode(0);
        dummy.next = head;
        ListNode fast = dummy;
        ListNode slow = dummy;
        for (int i = 0; i < n; i++) {
            fast = fast.next;
        }
        while (fast.next != null) {
            fast = fast.next;
            slow = slow.next;
        }
        slow.next = slow.next.next;
        return dummy.next;
    }
}

5. 复杂度分析

  • 时间复杂度 :O(n),其中 n 是链表的长度。我们只需要遍历链表一次。
  • 空间复杂度 :O(1),我们只使用了常数级别的额外空间。

二、两两交换链表中的节点(题目 24)

1. 题目描述

给你一个链表,两两交换其中相邻的节点,并返回交换后的链表。你需要在不修改节点值的情况下完成本题。

2. 示例

示例 1:

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

输出:[2, 1, 4, 3]

解释:两两交换后,链表变为 [2, 1, 4, 3]

3. 解题思路

这道题主要考察链表节点的操作。我们可以使用迭代的方法来交换相邻的节点。具体步骤如下:

  1. 初始化一个虚拟头节点 dummy,用于构建新的链表。
  2. 使用一个指针 prev 指向当前需要交换的节点的前一个节点。
  3. 遍历链表,交换相邻的节点,并更新指针。
  4. 重复步骤 3,直到遍历完整个链表。

4. 代码实现(Java)

java 复制代码
public class Solution {
    public ListNode swapPairs(ListNode head) {
        ListNode dummy = new ListNode(0);
        dummy.next = head;
        ListNode prev = dummy;
        while (prev.next != null && prev.next.next != null) {
            ListNode first = prev.next;
            ListNode second = first.next;
            prev.next = second;
            first.next = second.next;
            second.next = first;
            prev = first;
        }
        return dummy.next;
    }
}

5. 复杂度分析

  • 时间复杂度 :O(n),其中 n 是链表的长度。我们只需要遍历链表一次。
  • 空间复杂度 :O(1),我们只使用了常数级别的额外空间。

三、K 个一组翻转链表(题目 25)

1. 题目描述

给你一个链表,每 k 个节点一组翻转链表,返回翻转后的链表。如果剩余节点少于 k 个,则保持原样。

2. 示例

示例 1:

输入:head = [1, 2, 3, 4, 5], k = 2

输出:[2, 1, 4, 3, 5]

解释:每 2 个节点一组翻转后,链表变为 [2, 1, 4, 3, 5]

3. 解题思路

这道题主要考察链表的分组和翻转操作。我们可以使用递归或迭代的方法来实现。具体步骤如下:

  1. 初始化一个虚拟头节点 dummy,用于构建新的链表。
  2. 使用一个指针 groupPrev 指向当前组的前一个节点。
  3. 遍历链表,每 k 个节点一组,翻转该组节点。
  4. 重复步骤 3,直到遍历完整个链表。

4. 代码实现(Java)

java 复制代码
public class Solution {
    public ListNode reverseKGroup(ListNode head, int k) {
        ListNode dummy = new ListNode(0);
        dummy.next = head;
        ListNode groupPrev = dummy;
        while (true) {
            ListNode kth = groupPrev;
            for (int i = 0; i < k; i++) {
                kth = kth.next;
                if (kth == null) {
                    return dummy.next;
                }
            }
            ListNode groupNext = kth.next;
            ListNode prev = groupNext;
            ListNode curr = groupPrev.next;
            while (curr != groupNext) {
                ListNode temp = curr.next;
                curr.next = prev;
                prev = curr;
                curr = temp;
            }
            groupPrev.next = prev;
            groupPrev = groupNext;
        }
    }
}

5. 复杂度分析

  • 时间复杂度 :O(n),其中 n 是链表的长度。我们只需要遍历链表一次。
  • 空间复杂度 :O(1),我们只使用了常数级别的额外空间。

四、随机链表的复制(题目 138)

1. 题目描述

给你一个长度为 n 的链表,每个节点包含一个额外增加的随机指针,该指针可以指向链表中的任何节点或空节点。返回这个链表的深拷贝。

2. 示例

示例 1:

输入:head = [[1, null], [2, 1], [3, 2], [4, 3], [5, 4]]

输出:[[1, null], [2, 1], [3, 2], [4, 3], [5, 4]]

解释:复制后的链表与原链表相同。

3. 解题思路

这道题主要考察链表的深拷贝和随机指针的处理。我们可以使用哈希表来存储原节点和新节点的映射关系。具体步骤如下:

  1. 遍历链表,创建新节点,并将原节点和新节点的映射关系存储在哈希表中。
  2. 再次遍历链表,设置新节点的 nextrandom 指针。
  3. 返回新链表的头节点。

4. 代码实现(Java)

java 复制代码
public class Solution {
    public RandomListNode copyRandomList(RandomListNode head) {
        if (head == null) {
            return null;
        }
        Map<RandomListNode, RandomListNode> map = new HashMap<>();
        RandomListNode current = head;
        while (current != null) {
            map.put(current, new RandomListNode(current.label));
            current = current.next;
        }
        current = head;
        while (current != null) {
            map.get(current).next = map.get(current.next);
            map.get(current).random = map.get(current.random);
            current = current.next;
        }
        return map.get(head);
    }
}

5. 复杂度分析

  • 时间复杂度 :O(n),其中 n 是链表的长度。我们需要遍历链表两次。
  • 空间复杂度 :O(n),需要使用哈希表存储原节点和新节点的映射关系。

五、排序链表(题目 147)

1. 题目描述

给你一个单链表的头节点 head,请你将其按升序排序并返回排序后的链表。

2. 示例

示例 1:

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

输出:[1, 2, 3, 4]

解释:排序后的链表为 [1, 2, 3, 4]

3. 解题思路

这道题主要考察链表的排序操作。我们可以使用归并排序来实现。具体步骤如下:

  1. 找到链表的中点,将链表分成两部分。
  2. 递归地对两部分链表进行排序。
  3. 合并两个有序链表。

4. 代码实现(Java)

java 复制代码
public class Solution {
    public ListNode sortList(ListNode head) {
        if (head == null || head.next == null) {
            return head;
        }
        ListNode mid = getMid(head);
        ListNode left = sortList(head);
        ListNode right = sortList(mid);
        return merge(left, right);
    }

    private ListNode getMid(ListNode head) {
        ListNode slow = head, fast = head.next;
        while (fast != null && fast.next != null) {
            slow = slow.next;
            fast = fast.next.next;
        }
        ListNode mid = slow.next;
        slow.next = null;
        return mid;
    }

    private ListNode merge(ListNode l1, ListNode l2) {
        ListNode dummy = new ListNode(0);
        ListNode current = dummy;
        while (l1 != null && l2 != null) {
            if (l1.val < l2.val) {
                current.next = l1;
                l1 = l1.next;
            } else {
                current.next = l2;
                l2 = l2.next;
            }
            current = current.next;
        }
        current.next = (l1 != null) ? l1 : l2;
        return dummy.next;
    }
}

5. 复杂度分析

  • 时间复杂度 :O(n log n),其中 n 是链表的长度。我们需要将链表分成两部分,递归地排序,然后合并。
  • 空间复杂度 :O(log n),需要使用递归栈空间。

六、合并 K 个升序链表(题目 23)

1. 题目描述

给你一个由 k 个升序链表组成的数组 lists,请你将它们合并成一个升序链表,并返回合并后的链表。

2. 示例

示例 1:

输入:lists = [[1, 4, 5], [1, 3, 4], [2, 6]]

输出:[1, 1, 2, 3, 4, 4, 5, 6]

解释:合并后的链表为 [1, 1, 2, 3, 4, 4, 5, 6]

3. 解题思路

这道题主要考察多个链表的合并操作。我们可以使用优先队列(最小堆)来实现。具体步骤如下:

  1. 初始化一个优先队列,将每个链表的头节点加入队列。
  2. 使用一个虚拟头节点 dummy,用于构建新的链表。
  3. 每次从队列中取出最小的节点,将其添加到新链表中,并将该节点的下一个节点加入队列。
  4. 重复步骤 3,直到队列为空。

4. 代码实现(Java)

java 复制代码
import java.util.PriorityQueue;

public class Solution {
    public ListNode mergeKLists(ListNode[] lists) {
        PriorityQueue<ListNode> queue = new PriorityQueue<>((a, b) -> a.val - b.val);
        ListNode dummy = new ListNode(0);
        ListNode current = dummy;
        for (ListNode list : lists) {
            if (list != null) {
                queue.offer(list);
            }
        }
        while (!queue.isEmpty()) {
            ListNode node = queue.poll();
            current.next = node;
            current = current.next;
            if (node.next != null) {
                queue.offer(node.next);
            }
        }
        return dummy.next;
    }
}

5. 复杂度分析

  • 时间复杂度 :O(n log k),其中 n 是所有链表的总长度,k 是链表的数量。我们需要将每个节点加入优先队列,并从队列中取出最小的节点。
  • 空间复杂度 :O(k),需要使用优先队列存储每个链表的头节点。

七、LRU 缓存(题目 146)

1. 题目描述

请你设计并实现一个遵循最近最少使用(LRU)缓存机制的数据结构。它应支持以下操作:获取数据(get)和写入数据(put)。

  • 当获取数据时,如果键(key)存在于缓存中,则获取键的值(总是正数),否则返回 -1。
  • 当写入数据时,如果键已经存在,则变更其数据值;如果键不存在,则插入该组「键-值」。当缓存容量达到上限时,它应该在写入新数据之前,删除最久未使用的数据值,从而为新的数据值留出空间。

2. 示例

示例 1:

输入:

"LRUCache", "put", "put", "get", "put", "get", "put", "get", "get", "get"

\[2\], \[1, 1\], \[2, 2\], \[1\], \[3, 3\], \[2\], \[4, 4\], \[1\], \[3\], \[4\]

输出:

null, null, null, 1, null, -1, null, -1, 3, 4

解释:

LRUCache cache = new LRUCache(2);

cache.put(1, 1); // 缓存是 {1=1}

cache.put(2, 2); // 缓存是 {1=1, 2=2}

cache.get(1); // 返回 1

cache.put(3, 3); // 缓存是 {2=2, 3=3}

cache.get(2); // 返回 -1

cache.put(4, 4); // 缓存是 {3=3, 4=4}

cache.get(1); // 返回 -1

cache.get(3); // 返回 3

cache.get(4); // 返回 4

3. 解题思路

这道题主要考察缓存的设计和链表的操作。我们可以使用哈希表和双向链表来实现 LRU 缓存。具体步骤如下:

  1. 使用哈希表存储键和节点的映射关系。
  2. 使用双向链表存储缓存中的节点,最近使用的节点放在链表头部,最久未使用的节点放在链表尾部。
  3. 实现 getput 操作,更新节点的位置。

4. 代码实现(Java)

java 复制代码
import java.util.HashMap;

public class LRUCache {
    private int capacity;
    private HashMap<Integer, DLinkedNode> cache;
    private DLinkedNode head, tail;

    public LRUCache(int capacity) {
        this.capacity = capacity;
        cache = new HashMap<>();
        head = new DLinkedNode();
        tail = new DLinkedNode();
        head.next = tail;
        tail.prev = head;
    }

    public int get(int key) {
        if (cache.containsKey(key)) {
            DLinkedNode node = cache.get(key);
            moveToHead(node);
            return node.value;
        }
        return -1;
    }

    public void put(int key, int value) {
        if (cache.containsKey(key)) {
            DLinkedNode node = cache.get(key);
            node.value = value;
            moveToHead(node);
        } else {
            DLinkedNode node = new DLinkedNode(key, value);
            cache.put(key, node);
            addToHead(node);
            if (cache.size() > capacity) {
                DLinkedNode removed = removeTail();
                cache.remove(removed.key);
            }
        }
    }

    private void addToHead(DLinkedNode node) {
        node.prev = head;
        node.next = head.next;
        head.next.prev = node;
        head.next = node;
    }

    private void moveToHead(DLinkedNode node) {
        removeNode(node);
        addToHead(node);
    }

    private void removeNode(DLinkedNode node) {
        node.prev.next = node.next;
        node.next.prev = node.prev;
    }

    private DLinkedNode removeTail() {
        DLinkedNode node = tail.prev;
        removeNode(node);
        return node;
    }

    private class DLinkedNode {
        int key, value;
        DLinkedNode prev, next;

        public DLinkedNode() {
        }

        public DLinkedNode(int key, int value) {
            this.key = key;
            this.value = value;
        }
    }
}

5. 复杂度分析

  • 时间复杂度 :O(1),每个操作(getput)的时间复杂度都是 O(1)。
  • 空间复杂度 :O(capacity),需要使用哈希表和双向链表存储缓存中的节点。

以上就是力扣热题 100 中与链表相关的经典题目的详细解析,希望对大家有所帮助。在实际刷题过程中,建议大家多动手实践,理解解题思路的本质,这样才能更好地应对各种算法问题。

相关推荐
风象南2 小时前
SpringBoot中6种自定义starter开发方法
java·spring boot·后端
mghio11 小时前
Dubbo 中的集群容错
java·微服务·dubbo
咖啡教室16 小时前
java日常开发笔记和开发问题记录
java
咖啡教室16 小时前
java练习项目记录笔记
java
鱼樱前端16 小时前
maven的基础安装和使用--mac/window版本
java·后端
RainbowSea17 小时前
6. RabbitMQ 死信队列的详细操作编写
java·消息队列·rabbitmq
RainbowSea17 小时前
5. RabbitMQ 消息队列中 Exchanges(交换机) 的详细说明
java·消息队列·rabbitmq
算AI18 小时前
人工智能+牙科:临床应用中的几个问题
人工智能·算法
我不会编程55518 小时前
Python Cookbook-5.1 对字典排序
开发语言·数据结构·python