24. 两两交换链表中的节点

这篇博客探讨了如何解决LeetCode上的24题,即如何两两交换链表中的节点。首先介绍了如何反转链表,然后讲解如何按照指定长度(如2个节点一组)进行反转。文中提供了两种方法,一种是迭代方式,另一种是递归方式,并指出递归方法可能存在栈溢出风险。最后,文章提到了使用尾递归优化来避免栈溢出的问题,并展示了修改后的代码。
摘要由CSDN通过智能技术生成

24. 两两交换链表中的节点

这道题乍一看,这有啥难度 就是把反转链表变成两个两个反转

思路没有错的 解决的问题分别是:

1 如何反转链表

2 如何按照指定长度反转链表

我们一个一个来看,首先是如何反转链表,绝大多数人首先想到的一定是使用一个前置指针prev进行反转。例如下面所示:

ListNode * reverseListNode(ListNode * head){
    if(head == nullptr) return nullptr;
    if(head->next == nullptr) return head;
    ListNode * prevNode  = nullptr;
    ListNode * currentNode = head;
    while(currentNode!=nullptr){
        ListNode * tmpNode = currentNode->next;
        currentNode->next = prevNode;
        prevNode = currentNode;
        currentNode = tmpNode;
    }
    return prevNode;
}

之后是何如K个一组反转链表呢?同leetcode25题。

首先也需要有一个反转链表的函数,我们对上面的函数做一个小小改动添加一个lastNode的参数

//反转位置是head到last-1的链表
ListNode * reverseListNode(ListNode * head, ListNode * last){
    if(head == nullptr) return nullptr;
    if(head->next == nullptr) return head;
    ListNode * currentNode = head;
    ListNode * prevNode = nullptr;
    while(currentNode != last){
        auto tmpNode = currentNode->next;
        currentNode->next = prevNode;
        prevNode = currentNode;
        currentNode = tmpNode;
    }
    return prevNode;
}

ListNode * reverseGroup(ListNode * head, int k){
    ListNode * last = head;
    //注意这里实际上last已经移动到了[k]
    for(auto i = 0; i < k; i++){
        if(last != nullptr){
            last = last->next;
        }else{
            return head;
        }
    }
    auto lastNode = reverse(head, last);
    head->next = reverseGroup(last, k);
    return lastNode;
}

其实这里还有一个问题,如果链表中最后一组不满k个数,可以分为两种输出结果,一种是继续反转剩余不满k个的那组,一个是剩余不满的那组不反转。这里题目中是不反转,如果改为需要反转,可以直接修改为:

    for(auto i = 0; i < k; i++){
        if(last != nullptr){
            last = last->next;
        }else{
            return head;
        }
    }
=======================================
    for(auto i = 0; i < k; i++){
        if(last != nullptr){
            last = last->next;
        }else{
        //注意修改这里
            return reverse(head,last);
        }
    }

这是面试官最喜欢问的一个变种。

回到最初的问题,两个两个交换是不是就是直接把k设置为2.

当然这里有一个更简单的解法,利用递归的方式:

ListNode* swapPairs(ListNode* head) {
    if(head == nullptr) return nullptr;
    if(head->next == nullptr) return head;
    ListNode* newhead = head->next;
    //这里递归
    head->next =  swapPairs(newhead->next);
    newhead->next = head; 
    return newhead;
}

这种递归的方式是不是会有栈溢出的风险呢?

2021.01.26晚更新

今天在公司和同事讨论了这个问题,在此感谢zhanglei541120这个小哥哥提出了可以使用尾递归解决这个栈溢出的问题。

首先理解一下尾递归这里我直接放上百度百科:

现代编译器大都都能做到这样的优化,于是我们稍微修改上述代码:

void swapPairsInternal(ListNode * prev, ListNode * start){
    if(start == nullptr || start -> next == nullptr) {prev->next = start; return;};    
    auto tmp = start->next->next;                            
    prev -> next = start->next;                              
    start-> next -> next = start; 
    //这里实现了尾递归                              
    swapPairsInternal(start, tmp);                           
} 

ListNode* swapPairs(ListNode* head) {
    if(head ==  nullptr || head -> next == nullptr){
        return head;
    }
    auto newhead = head -> next;
    //简单的方法就是用传参的方式替换栈中的局部变量
    swapPairsInternal(head, head->next->next); 
    newhead->next = head;
    return newhead;
}

经过验证:g++ test.cc -O2及其以上的优化可以实现尾递归。在实验中我构造一个循环链表,执行程序过程中不停pstack,始终只有一层栈main,甚至连swapPairs这层栈也被inline内联。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值