链表反转总结

一、题型总结

二、实现思路

        为解决链表反转类型的题,应从简单到复杂,故首先考虑反转全部链表元素。

        注:在本文中代码中,若参数为 Node *head  ,则代表本函数处理的是带有dummyHead的链表;若参数为 Node *node,则代表本函数处理的是不带dummyHead的链表

        例如,在2.1中需要反转链表的全部元素,所以dummyHead的存在会影响整体代码的书写。而在2.2反转指定区间时,增加dummyHead可减少特殊情况的判断。


2.1 反转链表全部元素

        反转链表全部元素,有两种常见的解题思路,即递归迭代。通常来说,递归是更容易理解与实现的方式。

        递归形式的代码块如下图所示。对以node->next头结点的子链表进行反转,显然,反转后的链表尾结点last为node->next。所以反转完成后,使用last->next = node 完成拼接。

Node *reverse1(Node *node) {
    // node == nullptr是为了应对第一次就传入了nullptr的情况
    // 其余情况下,当node->next == nullptr时就返回了,这是反转后链表的第一个结点
    if(node == nullptr || node->next == nullptr) {
        return node;
    }
    Node *last = node->next;
    Node *first = reverse1(node->next);
    last->next = node;
    node->next = nullptr;
    return first;
}

        接下来,考虑迭代形式实现链表反转。定义两个指针has与remain,分别表示已经完成反转的子链表和还没有完成反转的子链表。初始条件,has指向头结点(next为nullptr),remain指向头结点的下一个结点。

        核心思路:将remain视为一个队列,令即将出队的元素是has的先驱结点,并及时更新has。大白话就是,将现在的has部分拼接到remain指向结点的后面。

Node *reverse2(Node *node) {
    if(node == nullptr) {
        return node;
    }
    Node *has = node;           // 已经完成反转的部分
    Node *remain = node->next;  // 还没有完成反转部分
    has->next = nullptr;
    while(remain != nullptr) {
        Node *front = remain;
        remain = remain->next;
        front->next = has;
        has = front;
    }
    return has;
}

2.2 指定区间反转

        依照leetcode92的题面要求,left和right为从1开始的下标,且链表中不包含dummyHead,

但增加dummyHead后,可大大简化left为1时的情况,所以在写leetcode92时,可以自行增加一个dummyHead。

解释:        

        一般情况下,可将整个链表分为三个部分:指定区间之前的,指定区间,指定区间之后的。在反转指定之前需要找到第一部分的最后一个结点并记录。但,当left=1时,第一部分直接不存在了,这与其他情况不同,需要额外的代码进行处理与判断。

        如果增加了dummyHead,当left=1时,在指定区间之前仍存在一个dummyHead,从而统一了操作,简化了代码的书写。

        首先,遍历找到指定区间前的最后一个结点last1。然后,使用迭代的方式,反转指定区间,得到该区间的起点has和终点last2。最后,将各个部分连接起来。

// 不考虑left,right越界的问题
// left是node结点的下标,下标从1开始,dummyHead不在计数范围内
Node *ReverseBetween(Node *head, int left, int right) {
    // 第一部分,最后一个结点last1
    Node *last1 = head;
    for(int i = 0; i < left-1; i++) {
        last1 = last1->next;
    }

    // 第二部分,要反转的部分,起始结点为has
    // 反转后,第二部分的最后一个结点为last2
    Node *has = last1->next;
    Node *last2 = last1->next;
    Node *remain = has->next;
    has->next = nullptr;
    for(int i = 0; i < right-left; i++) {
        Node *front = remain;
        remain = front->next;
        front->next = has;
        has = front;
    }

    // 第三部分,第一个结点remain
    last2->next = remain;
    last1->next = has;
    return head;
}

2.3 每K个元素反转

        递归算法思路:

  1. 移动k-1步,得到这一组元素的最后一个节点cur,断开cur与之后结点的连接。
  2. 对cur之后的结点递归得调用反转k个元素函数。
  3. 调用反转链表函数调用前k个元素。
  4. 将反转后得结果进行拼接。

注:做好不足k个结点时的判断即可。

Node *reversek1(Node *node, int k) {
    Node *cur = node;
    // 移动k-1次后,此时cur是这一组最后一个结点
    for(int i = 0; cur != nullptr && i < k-1; i++) {
        cur = cur->next;
    }
    // 说明不足k个结点,直接返回
    if(cur == nullptr) {
        return node;
    }

    // cur->next是下一组的开头
    Node *nextStart = cur->next;
    cur->next = nullptr;
    Node *newHead = reverse2(node);
    node->next = reversek1(nextStart, k);
    return newHead;
} 

        使用栈实现思路:

  1. 定义head和res指针。head值不变,一直指向dummyHead。res值指向完成反转的最后一个链表结点,初始值为dummyHead。
  2. 使用remain和cur两个指针,循环开始时两个指针值相同。不断移动cur,判断是否有k个元素。
  3. 如果不满足,直接break出循环,在循环外将remain及其之后的结点拼接到res之后。
  4. 如果满足,不断将栈中的元素依次pop出来,串联在res之后,然后更新remain结点。

注:res->next = remain其实有两个作用

① 当len(list) % k == 0时,将链表最后一个结点的next值修改为nullptr。

     res->next = top; res = res->next;这两行代码表示:当前结点是A,A的下一个结点是B,更新当前结点为B,但并没有告知B的下一个结点是谁。所以在循环结束后,链表最后一个结点并不知道自己的next值是谁,需要程序员手动为其赋值为nullptr。

② 当len(list) % k != 0时,将remain及其之后的结点拼接在res之后。

Node *reversek2(Node *node, int k) {
    Node *remain = node;
    stack<Node*> s;
    Node *head = new Node(-1);
    Node *res = head;
    while(1) {
        Node *cur = remain;
        int cnt = k;
        while(cur != nullptr && cnt--) {
            s.push(cur);
            cur = cur->next;
        } 
        if(cnt > 0) {
            break;
        }
        while(s.size() != 0) {
            Node *top = s.top();
            s.pop();
            res->next = top;
            res = res->next;
        }
        remain = cur;
    }
    res->next = remain;
    return head->next;
} 

2.4 每两个元素反转

        和每K个元素反转一样,使用递归,或者直接调用每K个元素的方法。

Node *reversePair(Node *node) {
    if(node == nullptr || node->next == nullptr) {
        return node;
    }
    Node *n1 = node, *n2 = node->next;
    Node *nextStart = n2->next;
    n1->next = reversePair(nextStart);
    n2->next = n1;
    return n2;
}

三、全部代码

#include <iostream>
#include <vector>
#include <stack>

using namespace std;

struct Node {
    int val;
    Node *next;
    Node(int v) {
        val = v;
        next = nullptr;
    }
};

Node *NewList(vector<int> &arr) {
    Node *dummyHead = new Node(-1);
    Node *cur = dummyHead;
    for(auto val : arr) {
        cur->next = new Node(val);
        cur = cur->next;
    }
    return dummyHead;
}

void DeleteList(Node *head) {
    while(head != nullptr) {
        Node *tmp = head;
        head = head->next;
        delete tmp;
        tmp = nullptr;
    }
}

// 递归形式反转链表,并返回反转后的头结点
Node *reverse1(Node *node) {
    if(node == nullptr || node->next == nullptr) {
        return node;
    }
    Node *last = node->next;
    Node *first = reverse1(node->next);
    last->next = node;
    node->next = nullptr;
    return first;
}

// 迭代方式实现反转链表
Node *reverse2(Node *node) {
    if(node == nullptr) {
        return node;
    }
    Node *has = node;           // 已经完成反转的部分
    Node *remain = node->next;  // 还没有完成反转部分
    has->next = nullptr;
    while(remain != nullptr) {
        Node *front = remain;
        remain = remain->next;
        front->next = has;
        has = front;
    }
    return has;
}

// 使用递归方式实现每k个元素反转一次
Node *reversek1(Node *node, int k) {
    Node *cur = node;
    // 移动k-1次后,此时cur是这一组最后一个结点
    for(int i = 0; cur != nullptr && i < k-1; i++) {
        cur = cur->next;
    }
    // 说明不足k个结点,直接返回
    if(cur == nullptr) {
        return node;
    }

    // cur->next是下一组的开头
    Node *nextStart = cur->next;
    cur->next = nullptr;
    Node *newHead = reverse2(node);
    node->next = reversek1(nextStart, k);
    return newHead;
} 

// 使用栈实现每k个元素反转一次
Node *reversek2(Node *node, int k) {
    Node *remain = node;
    stack<Node*> s;
    Node *head = new Node(-1);
    Node *res = head;
    while(1) {
        Node *cur = remain;
        int cnt = k;
        while(cur != nullptr && cnt--) {
            s.push(cur);
            cur = cur->next;
        } 
        if(cnt > 0) {
            break;
        }
        while(s.size() != 0) {
            Node *top = s.top();
            s.pop();
            res->next = top;
            res = res->next;
        }
        remain = cur;
    }
    res->next = remain;
    return head->next;
}   

Node *reversePair(Node *node) {
    if(node == nullptr || node->next == nullptr) {
        return node;
    }
    Node *n1 = node, *n2 = node->next;
    Node *nextStart = n2->next;
    n1->next = reversePair(nextStart);
    n2->next = n1;
    return n2;
}

Node *ReverseList(Node *head) {
    Node *cur = head->next;
    if(cur == nullptr) {
        return head;
    }
    // Node *first = reverse1(cur);
    Node *first = reverse2(cur);
    head->next = first;
    return head;
}

// 不考虑left,right越界的问题
// left是node结点的下标,下标从1开始,dummyHead不在计数范围内
Node *ReverseBetween(Node *head, int left, int right) {
    // 第一部分,最后一个结点last1
    Node *last1 = head;
    for(int i = 0; i < left-1; i++) {
        last1 = last1->next;
    }

    // 第二部分,要反转的部分,起始结点为has
    // 反转后,第二部分的最后一个结点为last2
    Node *has = last1->next;
    Node *last2 = last1->next;
    Node *remain = has->next;
    has->next = nullptr;
    for(int i = 0; i < right-left; i++) {
        Node *front = remain;
        remain = front->next;
        front->next = has;
        has = front;
    }

    // 第三部分,第一个结点remain
    last2->next = remain;
    last1->next = has;
    return head;
}

Node *ReverseEveryK(Node *head, int k) {
    if(k == 1) {
        return ReverseList(head);
    }
    // head->next = reversek1(head->next, k);
    head->next = reversek2(head->next, k);
    return head;
}

Node *ReversePair(Node *head) {
    // return ReverseEveryK(head, 2);
    head->next = reversePair(head->next);
    return head;
}

void PrintList(Node *head) {
    Node *cur = head->next;
    while(cur != nullptr) {
        cout << cur->val << " ";
        cur = cur->next;
    }
    cout << endl;
}

int main() {
    vector<int> arr{1, 2, 3, 4, 5, 6, 7, 8};
    Node *list = NewList(arr);
    PrintList(list);
    list = ReversePair(list);
    PrintList(list);
    DeleteList(list);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值