反转链表四种解法


在这里插入图片描述

一、迭代法

1、定义

它通常基于一个初始值,然后按照特定的规则(一般通过循环结构来实现,比如 for 循环、 while 循环等)反复更新这个值,直到满足某个终止条件为止。这个终止条件可以是达到一定的精度要求、循环次数达到设定值等。

2、操作

ListNode* reverseList(ListNode* head) {
    ListNode* prev = NULL;
    ListNode* curr = head;
    ListNode* nextTemp;

    while (curr!= NULL) {
        nextTemp = curr->next;
        curr->next = prev;
        prev = curr;
        curr = nextTemp;
    }

    return prev;
}

在这种方法中:

  • 我们使用三个指针 prevcurrnextTemp
  • prev 初始化为 NULL,它将用来指向反转后的链表的头节点。
  • curr 初始化为链表的头节点 head,它是当前正在处理的节点。
  • nextTemp 用于临时存储当前节点 curr 的下一个节点,以便在反转当前节点的指针时不会丢失下一个节点的信息。
  • 通过不断地在循环中更新这三个指针的值,实现链表节点指针方向的反转,最后返回反转后的链表头节点 prev

二、递归法

在这里插入图片描述

1、定义

在C语言中,递归法求反转列表,就是利用函数自己调用自己的递归机制来改变链表(一种常见的可用于表示列表的数据结构,这里以链表为例来说明)中节点的顺序,将链表从头到尾的顺序进行反转,让原本的尾节点变成头节点,依次类推。

2、操作

ListNode* reverseList(ListNode* head) {
    if (head == NULL || head->next == NULL) {
        return head;
    }

    ListNode* newHead = reverseList(head->next);
    head->next->next = head;
    head->next = NULL;

    return newHead;
}

在递归法中:

  • 首先判断链表的头节点 head 是否为空或者只有一个节点(即 head->next == NULL),如果是,则直接返回 head,因为这样的链表无需反转。
  • 然后通过递归调用 reverseList(head->next) 来反转链表的剩余部分,得到反转后的链表头节点 newHead
  • 接着将当前节点 head 的下一个节点 head->next 的下一个节点设置为 head,实现当前节点与反转后链表的连接。
  • 最后将当前节点 head 的下一个节点设置为 NULL,完成当前节点在反转链表中的位置调整。
  • 最终返回反转后的链表头节点 newHead

3、解释

1、递归终止条件判断
if (head == NULL || head->next == NULL) {
    return head;
}
  • 这里首先判断了两种情况作为递归的终止条件:
    • head 指针为 NULL 时,说明链表为空链表,此时无需进行反转操作,直接返回 NULL 即可,因为空链表反转后还是空链表。
    • head 的下一个节点指针 head->nextNULL 时,说明链表只有一个节点。对于只有一个节点的链表,也无需进行反转操作,直接返回该节点指针 head 就行,因为单个节点的链表反转后还是它本身。
2. 递归调用反转剩余链表
struct ListNode* newHead = reverseList(head->next);
  • 当不满足上述终止条件时,即链表至少有两个节点,此时函数会进行递归调用。它调用自身 reverseList 并传入当前头节点 head 的下一个节点指针 head->next
  • 这个递归调用的目的是先反转当前节点 head 之后的链表部分。例如,对于链表 1 -> 2 -> 3 -> 4,当 head 指向节点 1 时,这里的递归调用会先去反转 2 -> 3 -> 4 这部分链表,并返回反转后的头节点指针,将其存储在 newHead 变量中。假设经过递归调用后,2 -> 3 -> 4 这部分链表反转成了 4 -> 3 -> 2,那么 newHead 就指向节点 4
3. 调整当前节点与反转后链表的连接关系
head->next->next = head;
  • 在完成对当前节点 head 之后的链表部分的反转(通过递归调用得到了反转后的头节点 newHead)后,接下来需要将当前节点 head 正确地连接到反转后的链表上。
  • 这里通过 head->next->next = head 这一操作来实现连接。由于 newHead 指向反转后的链表头节点(比如前面例子中的节点 4),那么 head->next 就指向反转后链表中的下一个节点(比如节点 3),将 head->next->next 设置为 head,就相当于把当前节点 head(比如节点 1)连接到了反转后链表的末尾,使得原本的 4 -> 3 -> 2 变成了 4 -> 3 -> 2 -> 1
4. 断开当前节点原有的后续连接
head->next = NULL;
  • 在将当前节点 head 连接到反转后链表的末尾后,为了保证链表结构的正确性,需要断开当前节点 head 与它原来后续节点的连接。
  • 因为在前面的操作中已经将当前节点 head 重新连接到了反转后链表的合适位置,所以这里通过将 head->next 设置为 NULL,就使得节点 1(前面例子中的当前节点 head)的下一个节点不再指向原来的节点 2,而是 NULL,从而完成了当前节点在反转链表中的位置调整,此时链表就完全反转成了 4 -> 3 -> 2 -> 1
5. 返回反转后的链表头节点
  • 最后,经过上述一系列操作,整个链表已经完成反转,此时通过返回 newHead,就将反转后的链表头节点指针返回给调用者。在前面的例子中,newHead 指向节点 4,所以调用者接收到的就是反转后的链表 4 -> 3 -> 2 -> 1 的头节点指针。

三、头插法(借助辅助头节点)

ListNode* reverseList(ListNode* head) {
    ListNode* newHead = (ListNode*)malloc(sizeof(ListNode));
    newHead->next = NULL;

    ListNode* curr = head;

    while (curr!= NULL) {
        ListNode* nextTemp = curr->next;
        curr->next = newHead->next;
        newHead->next = curr;
        curr = nextTemp;
    }

    ListNode* realHead = newHead->next;
    free(newHead);

    return realHead;
}

在头插法中:

  • 首先创建一个辅助头节点 newHead,并将其下一个节点设置为 NULL
  • 然后通过循环遍历原链表,对于原链表中的每个节点:
    • 先临时保存当前节点 curr 的下一个节点 nextTemp
    • 再将当前节点 curr 的下一个节点设置为辅助头节点 newHead 的下一个节点,实现将当前节点插入到辅助头节点之后的操作。
    • 接着将辅助头节点 newHead 的下一个节点设置为当前节点 curr,完成头插操作。
    • 最后将当前节点 curr 更新为刚才临时保存的下一个节点 nextTemp,以便处理下一个节点。
  • 循环结束后,辅助头节点 newHead 的下一个节点就是反转后的链表,将其赋值给 realHead,然后释放辅助头节点 newHead,最后返回 realHead

四、数组反转

struct ListNode* reverseList(struct ListNode* head) {
    if (head == NULL) {
        return NULL;
    }
    struct ListNode* temp1 = head;
    int arr[5001], count = 0;
    while (temp1 != NULL) {
        arr[count++] = temp1->val;
        temp1 = temp1->next;
    }
    count-=1;
    struct ListNode* temp2 = head;
    while (temp2 != NULL) {
        temp2->val = arr[count--];
        temp2 = temp2->next;
    }
    return head;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值