面试经典150题[066]:分隔链表(LeetCode 86)

分隔链表(LeetCode 86)

题目链接:分隔链表(LeetCode 86)

难度:中等

1. 题目描述

给你一个链表的头节点 head 和一个特定值 x ,请你对链表进行分隔,使得所有 小于 x 的节点都出现在 大于或等于 x 的节点之前。

你应当 保留 两个分区中每个节点的初始相对位置。

示例:

复制代码
输入: head = [1,4,3,2,5,2], x = 3
输出: [1,2,2,4,3,5]

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

2. 问题分析

2.1 规律

  • 这是一个链表的划分问题,需要将节点根据其值与 x 的关系分成两部分。
  • 关键要求是保持每个分区内部节点的 原始相对顺序。这意味着我们不能简单地通过交换节点值来解决,而需要移动节点。

2.2 双链表(双指针)思路

最直观且高效的方法是创建两个新的链表,一个用来存放所有小于 x 的节点,另一个用来存放所有大于或等于 x 的节点。

  • 创建哑节点(Dummy Nodes)

    • less_head:作为 "小于 x" 链表的头节点。
    • greater_head:作为 "大于等于 x" 链表的头节点。
    • 使用哑节点可以简化边界条件的处理,避免对头节点的特殊判断。
  • 创建遍历指针

    • less_ptr:指向 "小于 x" 链表的尾部。
    • greater_ptr:指向 "大于等于 x" 链表的尾部。
  • 遍历原链表

    • 用一个指针 current 遍历原链表 head
    • 对于每个节点 current
      • 如果 current.val < x,则将其链接到 less_ptr 的后面,并移动 less_ptr
      • 如果 current.val >= x,则将其链接到 greater_ptr 的后面,并移动 greater_ptr
  • 拼接链表

    • 遍历结束后,我们得到了两个独立的链表。
    • 将 "小于" 链表的尾部 (less_ptr) 指向 "大于等于" 链表的头部 (greater_head.next)。
    • 重要 :将 "大于等于" 链表的尾部 (greater_ptr) 的 next 指针设为 None,以防止产生环。
    • 返回 less_head.next作为新链表的头节点。

3. 代码实现

Python

python 复制代码
# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next

class Solution:
    def partition(self, head: ListNode, x: int) -> ListNode:
        # 创建两个哑节点,分别作为两个新链表的头
        less_head = ListNode(-1)
        greater_head = ListNode(-1)
        
        # 创建两个指针,用于构建新链表
        less_ptr = less_head
        greater_ptr = greater_head
        
        current = head
        while current:
            if current.val < x:
                # 将节点加入到 'less' 链表
                less_ptr.next = current
                less_ptr = less_ptr.next
            else:
                # 将节点加入到 'greater' 链表
                greater_ptr.next = current
                greater_ptr = greater_ptr.next
            
            # 移动到下一个节点
            current = current.next
        
        # 将 'greater' 链表的末尾指向 None,断开与原链表的连接
        greater_ptr.next = None
        
        # 将 'less' 链表的末尾连接到 'greater' 链表的头部
        less_ptr.next = greater_head.next
        
        return less_head.next

C++

cpp 复制代码
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* partition(ListNode* head, int x) {
        // 创建哑节点
        ListNode* less_head = new ListNode(-1);
        ListNode* greater_head = new ListNode(-1);
        
        // 创建构建指针
        ListNode* less_ptr = less_head;
        ListNode* greater_ptr = greater_head;
        
        ListNode* current = head;
        while (current != nullptr) {
            if (current->val < x) {
                less_ptr->next = current;
                less_ptr = less_ptr->next;
            } else {
                greater_ptr->next = current;
                greater_ptr = greater_ptr->next;
            }
            current = current->next;
        }
        
        // 断开 'greater' 链表的尾部
        greater_ptr->next = nullptr;
        
        // 拼接两个链表
        less_ptr->next = greater_head->next;
        
        // 释放哑节点内存(可选,但好习惯)
        ListNode* new_head = less_head->next;
        delete less_head;
        delete greater_head;
        
        return new_head;
    }
};

4. 复杂度分析

  • 时间复杂度:O(n),其中 n 是链表的节点数。我们只需要遍历一次原链表。
  • 空间复杂度 :O(1),我们只使用了常数个额外指针(less_head, greater_head, less_ptr, greater_ptr),并没有创建新的节点,只是重新组织了原链表的节点。

5. 总结

  • 链表划分 问题,特别是要求保持相对顺序时,双链表(或双指针+哑节点) 方法是一个非常经典且有效的解决方案。
  • 使用哑节点可以极大地简化代码逻辑,避免对头节点的空指针判断和特殊处理。
  • 最后一定要记得处理好两个链表的拼接以及新链表尾部的 next 指针(设置为 None/nullptr)。

复习

面试经典150题[006]:旋转数组(LeetCode 189)

面试经典150题[036]:旋转图像(LeetCode 48)

面试经典150题[051]:用最少数量的箭引爆气球(LeetCode 452)

相关推荐
Lee川13 小时前
优雅进化的JavaScript:从ES6+新特性看现代前端开发范式
javascript·面试
Lee川17 小时前
从异步迷雾到优雅流程:JavaScript异步编程与内存管理的现代化之旅
javascript·面试
晴殇i19 小时前
揭秘JavaScript中那些“不冒泡”的DOM事件
前端·javascript·面试
绝无仅有19 小时前
Redis过期删除与内存淘汰策略详解
后端·面试·架构
绝无仅有19 小时前
Redis大Key问题排查与解决方案全解析
后端·面试·架构
AAA梅狸猫20 小时前
Looper.loop() 循环机制
面试
AAA梅狸猫20 小时前
Handler基本概念
面试
Wect21 小时前
浏览器缓存机制
前端·面试·浏览器
掘金安东尼21 小时前
Fun with TypeScript Generics:玩转 TS 泛型
前端·javascript·面试
掘金安东尼21 小时前
Next.js 企业级落地
前端·javascript·面试