轻松搞定链表反转

时间:2022-07-23
本文章向大家介绍轻松搞定链表反转,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

最近迫于生活,又去面试咯。好在魔都的就业环境还可以,面试机会总是不缺。今天闲下来了,来谈一谈我最近面试遇到的一道题,是跟反转链表相关的。

题目很简洁:给定一个链表的head跟数字k,反转从head开始的交替间隔的大小为k的子列表。也就是说,我反转k个节点之后,跳过k个节点,再反转k个节点,以此类推。反转一个链表不复杂,但是通常我们遇到的题目会附加额外条件,比如:不允许开辟额外的内存空间。

我们先来看看反转一个链表我们会怎么做,我们的思路很直接,从头开始依次取节点,让它指向前面一个节点,到最后我们的链表就反转完成了。代码跟口述过程一样简单,我们直接来上代码:

public static ListNode reverse(ListNode head) {
    ListNode current = head; // 记录当前要被处理的节点
    ListNode previous = null; // 记录之前被处理的节点
    ListNode next = null; // 临时存储下一个节点    
    while (current != null) {
        next = current.next;
        current.next = previous; // 当前节点指向前一个结点
        previous = current; // 更新previous的位置
        current = next; // 向后移动当前节点
    }
    // 循环结束后,previous指向原链表的最后一个节点,此时它已经成为反转后链表的head
    return previous; 
}

我们只要保存好下一个要处理的节点的索引,保证可以按顺序迭代,之后按照正常思路反转引用就好。

好了,有了这道题的基础,我们来增加一些难度,现在给定一个数字k,反转链表中每个长度为k的子列表。也就是说,当k=2时,给我们一个链表1->2->3->4,反转后变成2->1->4->3。现在看起来无从下手,其实跟上题区别不大,我们只要关注每过k个节点重新开始一轮反转,其它的流程照旧即可。

public static ListNode reverse(ListNode head, int k) {
        if (k <= 1 || head == null)
            return head;

        ListNode current = head, previous = null;
        while (true) {
            ListNode lastNodeOfPreviousPart = previous;
            // 反转结束之后,current就是子列表的最后一个节点
            ListNode lastNodeOfSubList = current;
            ListNode next = null; // 用来临时存储下一个节点
            //反转k个节点
            for (int i = 0; current != null && i < k; i++) {
                next = current.next;
                current.next = previous;
                previous = current;
                current = next;
            }

            //跟前面的部分链接
            if (lastNodeOfPreviousPart != null)
                lastNodeOfPreviousPart.next = previous; // previous现在是子列表的第一个节点
            else // 这意味着我们在处理第一个子列表
                head = previous;

            //跟下一部分链接
            lastNodeOfSubList.next = current;

            if (current == null) //到达最后,结束循环
                break;
            // 为下一个子列表做准备
            previous = lastNodeOfSubList;
        }

        return head;
    }

其实不管是要求我们反转一个子列表还是多个子列表,都没问题。反转多个子列表的时候,反转过程是一样的,需要注意的点无非是在各个子列表反转后的前后链接,我们只要记住每个子列表开头跟结束的节点,在反转完成的时候进行链接就好了。

现在我们可以回到开头的问题了,这道题跟上面那道区别也很小哇,好巧啊!(手动狗头?,我才不说我是故意的)这边唯一的区别在于我们得跳过K个节点。管它呢,我们还可以遵循上面那个流程,只不过在每次迭代后,跳过k节点,这不就行了嘛!

public static ListNode reverse(ListNode head, int k) {
        if (k <= 1 || head == null)
            return head;

        ListNode current = head, previous = null;
        while (true) {
            ListNode lastNodeOfPreviousPart = previous;
            //反转结束后,它就是子列表的最后一个节点
            ListNode lastNodeOfSubList = current;
            ListNode next = null; // 用来临时存储下一个节点
            // 反转k个节点
            for (int i = 0; current != null && i < k; i++) {
                next = current.next;
                current.next = previous;
                previous = current;
                current = next;
            }

            // 跟前面的部分链接
            if (lastNodeOfPreviousPart != null)
                lastNodeOfPreviousPart.next = previous; 
            // 'previous'现在是子列表的第一个节点
            else // 
                head = previous;

            // 与后面的部分链接
            lastNodeOfSubList.next = current;

            // 跳过k个节点
            for (int i = 0; current != null && i < k; ++i) {
                previous = current;
                current = current.next;
            }

            if (current == null) // 到最后了,结束循环
                break; 
        }

        return head;
    }

只要在最后加上跳过的逻辑,齐活儿!整个过程中,我们只迭代一次链表,时间复杂度为O(n),而因为我们没有开辟额外的内存空间,空间复杂度为O(1)。

到这里可以发现,反转链表的问题一点也不难,很直接很暴力,也没有像其他问题所说的什么奇技淫巧,按照自己常规思路来就好咯。变来变去无非是变着花样反转子列表,只要大家想明白了它就变不出啥幺蛾子了。