通俗易懂的经典反转链表OJ(附拓展题)

链表在笔试面试胸还是很重要的,为了加深印象,我们今天整几道反转链表得经典题目,废话不多说,直接开整

 

目录

 

1.反转链表

2.链表内指定区间反转

3.链表的回文结构


 

1.反转链表

牛客链接BM1 反转链表

反转链表可以说是比较经典的题目了,而且代码和思维都比较简单

这里我们采用最朴素的方法,直接在原链表的头节点处进行头插,就可以对链表进行反转了

注意:这里要注意头插完之后要对原头节点的next域置空,否则的话节点将形成环

 

我们用这样的四个节点的链表来举例

db681bbf9dd1466f84353e5be38e426a.png

我们要对这个链表进行反转,也就是得到如下的链表

3bde93987cbd4c9e8c1305a2929d119f.png

 这样我们就可以考虑使用头插法,把2先头插到1的前面,再把3插到2的前面

我们进行如下的步骤

ea8be1045bc347a9aba6129bd95abdbc.png

 

通过这张图,就可以理解到头插是如何进行的了,接下来就是通过代码得到实现了

public ListNode ReverseList(ListNode head) {
        if(head == null || head.next == null){
            return head;
        }
        ListNode cur = head.next;
        head.next = null;
        while(cur != null){
            ListNode curNext = cur.next;
            cur.next = head;
            head = cur;
            cur = curNext;
        }
        return head;
        
    }

如上就是代码的实现,我们可以看到,链表的反转这一块用了四行代码,我们根据图来走,可以很轻松的理解。我举个例子

就由第一次头插来说

77b57b16c1b447ea916c49ce74a2b44c.png

 就这样一步一步的走,从而达到反转链表的效果

时间复杂度:O(n)

空间复杂度:O(1)

这样我们就做掉了一道简单的链表题,那我们趁热打铁,再做一题稍微进阶一点的

2.链表内指定区间反转

牛客链接BM2 链表内指定区间反转

这一题属于是上面一题的进阶玩法,其实并没有难多少,但是牛客却给这道题提了一个难度

我们直接看着题的描述和示例

b0b550d3dfc647d49d07f8afee1eb28b.png

 这里我们可以发现这题不用我们处理m,n异常的情况,所以就省去了一些麻烦

看到这题,你有没有想到思路呢?

是不是依然是通过头插法,但是这次我们截取一段进行头插即可

就好像这个链表

52da0d921cb64dddb0807fc42fb4085e.png

 

让我们对2位置开始4位置结束进行反转 ,我拿到这题,思路直接就是先截取再反转,最后再拼接

也就是我们先把2-4先抽取出来,用两个节点先记住1节点和5节点,然后对234进行反转,最后再把1和5拼接上去即可,因为反转链表上面已经讲过,这里就不在赘述,直接看代码,我们通过代码来讲解

public ListNode reverseBetween (ListNode head, int m, int n) {
        if(head.next == null){
            return head;
        }
        ListNode mPrev = head; //左边链表的最后一个节点
        ListNode nHead = head; //右边链表的第一个节点
        if(m == 1){
            while(n >0){
            nHead = nHead.next; //找到右边节点的第一个节点
            n--;
            }
            ListNode cur = head.next; //反转中间链表的临时变量
            ListNode tmp = head;      //记录右边第一个节点的前一个节点
            while(cur != nHead){      //反转链表
                ListNode curNext = cur.next;
                cur.next = head;
                head = cur;
                cur = curNext;
            }
            tmp.next = nHead;         //因为m == 1 所以不用拼接左边的节点
            
        }else{
                while(m-2 > 0){   
                mPrev = mPrev.next;   //找到左边节点的最后一个节点
                m--;
            }
            while(n >0){
                nHead = nHead.next; //同上
                n--;
            }
            ListNode mHead = mPrev.next;//中间链表的头节点
            ListNode cur = mHead.next;
            ListNode tmp = mHead;
            while(cur != nHead){
                ListNode curNext = cur.next;
                cur.next = mHead;
                mHead = cur;
                cur = curNext;
            }
            tmp.next = nHead;          //拼接右边链表
            mPrev.next = mHead;        //拼接左边链表
            }
        
        return head;
    }

这里我们考虑了m = 1 和 m != 1的两种情况,因为这两种情况操作不同,所以用稍有不同的代码来实现,如果你有更好,更简洁的代码,欢迎指正

27f09f876d4147788d80f3bc9685fd48.png

 节点如图所示,只要通过代码一点一点实现,即可理解本题

 

到这里我们再上升一个难度,体验一下较难题,我们真的无从下手吗?

3.链表的回文结构

牛客链接OR36 链表的回文结构

这题我们可以看到,在牛客网已经达到了惊人的较难级别了,但是真的有那么难吗?

 

回文我们都知道是什么,那我们单链表是无法从后往前遍历的,那我们怎么判断回文呢?

对,就是链表的反转,我们只需要在中间位置,对中间位置后面的元素进行反转,然后再一次比较,就可以得到结果了

如图所示

38649e6a8da54206b4a5c70bb2094ae4.png

 我们只要将上面的变成下面的,然后一个从左边节点开始遍历,一个从右边节点开始遍历就可以达到想要得到结果

 

这里我们如果像让一个节点走到最后,一个节点走到中间,我们只需要利用快慢“指针”即可

快慢“指针”:

设置一个fast,一个slow,都从第一个开始走,fast一次走两步,slow一次走一步,这样我们的fast的速度就是slow的两倍,当fast走完的时候,slow刚好走到中间位置,利用代码实现就是这样的

ListNode fast = head;
        ListNode slow = head;
        //找到中间节点
        while(fast != null && fast.next != null){
            fast = fast.next.next;
            slow = slow.next;
        }

然后我们得到了最后一个和中间的节点之后,再进行反转

//对中间之后的节点头插
        ListNode cur = slow.next;
        while(cur != null){
            ListNode curNext = cur.next;
            cur.next = slow;
            slow = cur;
            cur = curNext;
        }

反转之后,再用slow往后走,一个从头节点开始走,就可以达到判断回文的效果

注意:这里的slow经过上面的反转代码之后,已经走到了最后那个1

 ListNode right = head;
        while(head != slow && head.next != slow){
            if(head.val != slow.val){
                return false;
            }
            slow = slow.next;
            head = head.next;
        }
        return true;

这样我们再处理一下细节上面的问题,就可以通过编译了

全部代码如下:

 public boolean chkPalindrome(ListNode head) {
        if(head == null){
            return true;
        }
        ListNode fast = head;
        ListNode slow = head;
        //找到中间节点
        while(fast != null && fast.next != null){
            fast = fast.next.next;
            slow = slow.next;
        }
        //对中间之后的节点头插
        ListNode cur = slow.next;
        while(cur != null){
            ListNode curNext = cur.next;
            cur.next = slow;
            slow = cur;
            cur = curNext;
        }
        ListNode right = head;
        while(head != slow && head.next != slow){
            if(head.val != slow.val){
                return false;
            }
            slow = slow.next;
            head = head.next;
        }
        return true;

这样我们就六枪打掉了三道和反转链表有关的题目,每天刷点题,让自己更强大

 

 

 

 

 

 

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值