C语言链表分区问题

C语言链表分区问题

在链表相关的问题中,分区问题是一个典型的算法场景。本文将以 partition 函数为例,讲解如何实现将链表分为两部分,其中一部分的节点值小于指定值 x x x,另一部分的节点值大于或等于 x x x。


问题描述

给定一个单链表和一个值 x x x,我们需要重新排列链表,使得:

  • 所有小于 x x x 的节点位于链表前部分;
  • 所有大于或等于 x x x 的节点位于链表后部分;
  • 两部分中的节点相对顺序保持不变。

例如:

输入链表为:
1 → 4 → 3 → 2 → 5 → 2 1 \rightarrow 4 \rightarrow 3 \rightarrow 2 \rightarrow 5 \rightarrow 2 1→4→3→2→5→2

分区值为 x = 3 x=3 x=3,则输出为:
1 → 2 → 2 → 4 → 3 → 5 1 \rightarrow 2 \rightarrow 2 \rightarrow 4 \rightarrow 3 \rightarrow 5 1→2→2→4→3→5


分析与实现

核心思路

  1. 使用两个虚拟链表:
    • 一个链表 small 保存所有小于 x x x 的节点;
    • 一个链表 big 保存所有大于或等于 x x x 的节点。
  2. 遍历原链表:
    • 对每个节点,根据其值将其添加到 smallbig 链表中。
  3. 拼接链表:
    • small 链表的尾部与 big 链表的头部相连。
  4. 返回结果:
    • 返回新的链表头。

代码实现

以下是具体代码实现:

c 复制代码
#include <stdio.h>
#include <stdlib.h>

struct ListNode {
    int val;
    struct ListNode* next;
};

struct ListNode* partition(struct ListNode* head, int x) {
    // 创建两个虚拟头节点
    struct ListNode* small = (struct ListNode*)malloc(sizeof(struct ListNode));
    struct ListNode* big = (struct ListNode*)malloc(sizeof(struct ListNode));
    struct ListNode* temp_small = small;
    struct ListNode* temp_big = big;

    // 初始化虚拟链表
    small->next = NULL;
    big->next = NULL;

    // 遍历原链表
    while (head != NULL) {
        if (head->val < x) {
            // 创建新节点并添加到 small 链表
            temp_small->next = (struct ListNode*)malloc(sizeof(struct ListNode));
            temp_small = temp_small->next;
            temp_small->val = head->val;
        } else {
            // 创建新节点并添加到 big 链表
            temp_big->next = (struct ListNode*)malloc(sizeof(struct ListNode));
            temp_big = temp_big->next;
            temp_big->val = head->val;
        }
        head = head->next;
    }

    // 结束 big 链表
    temp_big->next = NULL;

    // 拼接 small 和 big 链表
    temp_small->next = big->next;

    // 释放 big 的头节点
    free(big);

    // 返回新链表的头节点
    struct ListNode* result = small->next;
    free(small);
    return result;
}

详细解释

虚拟头节点的作用

虚拟头节点的引入使得链表操作更为简洁,避免了单独处理链表头的复杂逻辑。以下是两个虚拟头节点的初始化:

c 复制代码
struct ListNode* small = (struct ListNode*)malloc(sizeof(struct ListNode));
struct ListNode* big = (struct ListNode*)malloc(sizeof(struct ListNode));
small->next = NULL;
big->next = NULL;

虚拟头节点使得我们可以从任意位置开始构建链表,而无需额外判断是否是第一个节点。


遍历原链表

通过遍历原链表,将节点分配到对应的 smallbig 链表中:

c 复制代码
while (head != NULL) {
    if (head->val < x) {
        temp_small->next = (struct ListNode*)malloc(sizeof(struct ListNode));
        temp_small = temp_small->next;
        temp_small->val = head->val;
    } else {
        temp_big->next = (struct ListNode*)malloc(sizeof(struct ListNode));
        temp_big = temp_big->next;
        temp_big->val = head->val;
    }
    head = head->next;
}

在每次分配时,记得使用 malloc 创建新的节点,否则会破坏链表的结构。


拼接链表

完成遍历后,将 smallbig 两部分链表拼接起来:

c 复制代码
temp_big->next = NULL;      // 确保 big 链表以 NULL 结尾
temp_small->next = big->next; // 拼接 small 和 big 链表
free(big);                  // 释放 big 的虚拟头节点

注意在拼接之前,我们需要确保 big 链表以 NULL 结尾。


内存管理

在使用动态内存分配时,要注意避免内存泄漏。本文代码在最后释放了 smallbig 的虚拟头节点:

c 复制代码
struct ListNode* result = small->next; // 获取结果链表
free(small);                          // 释放虚拟头节点
return result;

时间与空间复杂度

  1. 时间复杂度:

    遍历原链表一次,时间复杂度为 O ( n ) O(n) O(n)。

  2. 空间复杂度:

    额外分配了两个链表(smallbig),每个节点仅分配一次,空间复杂度为 O ( n ) O(n) O(n)。


测试用例

以下是测试用例及其输出结果:

输入链表

plaintext 复制代码
1 -> 4 -> 3 -> 2 -> 5 -> 2

分区值

plaintext 复制代码
x = 3

输出链表

plaintext 复制代码
1 -> 2 -> 2 -> 4 -> 3 -> 5
相关推荐
大丈夫立于天地间6 小时前
ISIS协议中的数据库同步
运维·网络·信息与通信
Dream Algorithm6 小时前
路由器的 WAN(广域网)口 和 LAN(局域网)口
网络·智能路由器
似水এ᭄往昔6 小时前
【C语言】文件操作
c语言·开发语言
IT猿手6 小时前
基于CNN-LSTM的深度Q网络(Deep Q-Network,DQN)求解移动机器人路径规划,MATLAB代码
网络·cnn·lstm
吴盐煮_7 小时前
使用UDP建立连接,会存在什么问题?
网络·网络协议·udp
owde7 小时前
顺序容器 -list双向链表
数据结构·c++·链表·list
hyshhhh7 小时前
【算法岗面试题】深度学习中如何防止过拟合?
网络·人工智能·深度学习·神经网络·算法·计算机视觉
蒙奇D索大7 小时前
【数据结构】第六章启航:图论入门——从零掌握有向图、无向图与简单图
c语言·数据结构·考研·改行学it
A旧城以西7 小时前
数据结构(JAVA)单向,双向链表
java·开发语言·数据结构·学习·链表·intellij-idea·idea
Hellc0078 小时前
轮询、WebSocket 和 SSE:实时通信技术全面指南(含C#实现)
网络