PAT真题:反转链表 (25)

 解题思路详解


🌟 题目分析

本题要求对单链表进行分段反转,需注意以下要点:

  1. 剩余不足K个的结点不反转
  2. 结点地址是5位非负整数(需处理补零输出)
  3. 可能存在无效结点(输入中的N不一定是实际链表长度)

🛠️ 实现策略分解
1. 数据存储结构
  • 数组模拟内存:使用Node Nodes[100000],直接用地址作为下标访问结点
  • 结点结构:包含data(数据)和next(下个结点地址)
  • 优势:相比动态链表,数组访问时间复杂度为O(1)
2. 链表有效性处理
  • 计算实际长度:遍历链表直到遇到-1,统计有效结点数Size
  • 意义:避免处理无效结点,且确定可反转的完整段数Size/K
3. 分段反转核心逻辑

for(int i = K; i <= Size; i += K)

  • 段头定位tempBeginAr标记当前段起始地址
  • 段尾定位:通过K次next遍历找到段尾endAr
  • 段内反转:将当前段的指针方向逐个反转
  • 段间连接:通过lastEnd记录前一段尾结点,实现段间衔接
4. 指针调整细节
  • 首段特殊处理:更新全局起始地址beginAr
  • 后续段处理:将前一段尾结点的next指向当前段头
  • 段尾记录更新lastEnd始终指向已处理段的最后一个结点
5. 输出格式化处理
  • display函数:处理地址补零和-1的特殊输出
  • 顺序遍历输出:按照处理后的链表顺序输出每个结点信息

🎯 复杂度分析
  • 时间复杂度:O(N)
    每个结点被遍历不超过3次(计算长度、反转处理、输出)
  • 空间复杂度:O(1)
    除存储结点的固定数组外,仅使用有限变量

 关键技巧总结
  1. 数组模拟法:用空间换时间,实现快速地址访问
  2. 三段式处理:定位段尾 → 反转段内指针 → 连接前后段
  3. 边界处理:通过Size/K控制完整段数,避免处理残余段
  4. 地址格式化:使用多级条件判断实现5位补零输出

具体代码如下:
#include<iostream>
using namespace std;
 
// 链表结点类定义 
class Node{
    public:
        int data;    // 结点存储的数值 
        int next;    // 下个结点的地址(模拟指针)
        Node();      // 构造函数初始化 
}; 
 
// 格式化输出地址函数(保证5位补零)
void display(int num){
    if(num < 0){
        cout<<num;
        return;
    }
    // 通过多级补零保证输出5位地址 
    if(num < 10000) cout<<"0";
    if(num < 1000)  cout<<"0";
    if(num < 100)   cout<<"0";
    if(num < 10)    cout<<"0";
    cout<<num;
}
 
Node::Node(){
    // 初始化结点数据成员 
    data = 0;
    next = 0; 
}
 
int main(){
    Node Nodes[100000];  // 用数组模拟内存空间(地址直接作为下标)
    int beginAr, N, K;   // 链表起始地址、结点总数、反转步长 
    cin>>beginAr>>N>>K;
 
    // 读取所有结点数据存入数组 
    int t = N;
    while(t--){
        int Address, Data, Next;
        cin>>Address>>Data>>Next;
        Nodes[Address].data = Data;
        Nodes[Address].next = Next;
    }
 
    int tempBeginAr = beginAr;  // 当前处理段的起始地址 
    int lastEnd = -1;           // 上一段的尾结点地址 
    int Size = 0;               // 计算有效链表长度(可能有无效结点)
 
    // 遍历链表计算实际长度 
    int temp = beginAr;
    while(temp != -1){
        Size++;
        temp = Nodes[temp].next;
    }
 
    // 分段处理反转逻辑 
    for(int i = K; i <= Size; i += K){  // 每K个为一组处理 
        int endAr = tempBeginAr;       // 当前段的尾结点地址 
        // 定位当前段的尾结点 
        for(int j = 1; j < K; ++j){
            endAr = Nodes[endAr].next;
        }
        
        int tempBe = Nodes[endAr].next; // 保存下一段的起始地址 
        int t = tempBeginAr;            // 保存当前段原始起始地址(反转后将变为段尾)
        
        // 反转当前段内的指针方向 
        for(int j = 1; j < K; ++j){
            int tempAr = Nodes[tempBeginAr].next;
            Nodes[tempBeginAr].next = Nodes[endAr].next;
            Nodes[endAr].next = tempBeginAr;
            tempBeginAr = tempAr;
        }
 
        // 处理段间连接 
        if(i == K){  // 第一段特殊处理:更新全局起始地址 
            lastEnd = beginAr;    // 原起始地址变为第一段尾 
            beginAr = endAr;      // 全局起始地址更新为当前段头 
        } else {      // 后续段连接前一段 
            Nodes[lastEnd].next = endAr;  // 前一段尾连接当前段头 
            lastEnd = t;         // 更新段尾记录 
        }
        tempBeginAr = tempBe;    // 移动到下一段起始地址 
    }
 
    // 遍历输出结果链表 
    int Address = beginAr;
    while(Address != -1){
        display(Address);
        cout<<" "<<Nodes[Address].data<<" ";
        display(Nodes[Address].next);
        cout<<endl;
        Address = Nodes[Address].next;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值