• LeetCode 92 | 大公司常考的面试题,翻转链表当中指定部分


    今天是LeetCode专题的第58篇文章,我们一起来看看LeetCode 92题,翻转链表II(Reverse LInked List II)。

    这题的官方难度是Medium,2451个赞同,145个反对,通过率38.6%。从这份数据上我们也看得出来,这题的质量很高,广受好评。也的确如此,这是一道非常经典的链表问题,不仅考验我们对于链表的理解和掌握,而且对基本功的要求也很高。

    题意

    给定一个链表和两个整数m和n,m和n分别代表链表当中的第m和第n个元素,其中m <= n。要求我们通过一次遍历将链表当中m到n这一段元素进行翻转

    样例

    Input: 1->2->3->4->5->NULL, m = 2, n = 4
    Output: 1->4->3->2->5->NULL

    题解

    这题的题意很直接,就是让我们翻转链表当中指定的部分,并且只能通过一次遍历完成。

    我们很容易想明白,通过m和n我们可以将链表分成三个部分:

    分别是m左侧的,m到n之间的也就是我们需要翻转的部分,以及n右侧的部分。当然这里可能存在一个特殊情况,m左侧和n的右侧都有可能没有元素,我们可以先忽略,先考虑最一般的情况。

    翻转链表我们最常用的方法就是先把[m, n]区间里的元素先存储起来,人工翻转了之后,再重新构建成一段链表,替换原链表对应的部分。但是很明显也可以发现,这样做是不符合题目要求的。因为我们存储元素遍历了链表一次,我们在构建链表的时候又遍历了一次,至少需要两次遍历才可以完成

    那怎么样才能一次遍历完成链表的翻转呢?其实也很简单,我们只需要倒叙插入就好了。

    比如我们有这样一段链表,我们想要翻转其中2、3、4这三个节点:

    首先,我们从1开始,1是翻转之后的起始部分。我们先记录下1的位置,这里1指向2保持不变。对于2号节点我们需要记录下它的后继,这里我们用tmp记录2号节点的后继,也就是3号节点。之后我们将2号节点的后继置为None。

    再下一步,我们用tmp记录3号节点的后继,将3号节点的后继指向2号节点。

    最后,我们如法炮制,将4号节点的后继指向3号节点,将1号节点的后继指向4号节点,并且将2号节点的后继指向4号节点的原后继,也就是None。这样我们就完成了链表部分的翻转,其中的原理很简单,就是利用了链表遍历和插入时候的性质,每次将需要翻转部分元素的后继指向它们原本的前驱。这句话有些拗口,但是多读几遍也就理解了。

    这个思路非常简单,但是用代码实现却不容易,很容易写错。尤其是在赋值的时候,很容易搞错指针到底应该指向哪里,到底需要哪些临时变量。关于这些内容没有太好的办法,只能加强训练,提升自己的基本功。

    最后, 我们考虑一下特殊情况。题目当中的特殊情况有两种, 第一种是m=1,第二种是n=链表末尾。其中第二种不算是特殊情况,因为在链表当中末尾的元素也有后继,就是None。但是m=1的情况需要小心,因为m=1的时候,翻转部分是没有前驱的,稍稍有些不同。解决也很简单,我们单独特判一下这种情况,人为创建一个前驱节点即可。

    贴上代码:

    # Definition for singly-linked list.
    # class ListNode:
    #     def __init__(self, val=0, next=None):
    #         self.val = val
    #         self.next = next
    class Solution:
        def reverseBetween(self, head: ListNode, m: int, n: int) -> ListNode:
            if m == n:
                return head
            
            pnt = head
            # 移动m-2格,到达翻转部分的前驱节点
            for i in range(m-2):
                pnt = pnt.next
                
            
            flag = False
            # 特判m=1的情况,如果m=1那么人为制造一个节点作为前驱
            if m == 1:
                flag = True
                pnt = ListNode(0)
                pnt.next = head
                head = pnt
                
            # cur即当前待翻转的节点
            cur = pnt.next
            # pre表示cur的前驱
            pre = pnt
            # last表示最后一个翻转的元素
            last = pnt.next
                
            for i in range(m, n+1):
                # 先记录下当前节点的后继
                nxt = cur.next
                # 将当前节点的后继指向前驱
                cur.next = pre
                pnt.next = cur
                pre = cur
                cur = nxt
                
            # 将last指向翻转之后的元素,将链表串起来
            last.next = cur
            return head.next if flag else head
                
                

    总结

    链表的相关操作非常考验基本功,虽然算法简单,但是对链表不熟悉,逻辑思考能力稍稍弱一些的同学想要做出这道题还是比较困难的。也因此,很多公司在面试的时候喜欢询问链表相关的操作和算法,以此考察候选人的基本功。如果想要进入大公司的,建议好好练习一下相关的问题,一定会有帮助的。

    今天的文章到这里就结束了,如果喜欢本文的话,请来一波素质三连,给我一点支持吧(关注、转发、点赞)。

    - END -

    扫码关注,获取更多优质文章

  • 相关阅读:
    iOS开发进阶
    iOS开发UI篇—Quartz2D使用(绘图路径)
    iOS开发UI篇—控制器的View的创建
    iOS开发UI篇—Quartz2D简单使用(三)
    iOS开发从入门到精通
    win7访问win10需要用户名密码
    win10启用guest来宾账户的教程
    2021 年终总结
    vue elementui 树形
    js递归生成树形结构
  • 原文地址:https://www.cnblogs.com/techflow/p/13541281.html
Copyright © 2020-2023  润新知