链表面试专题总结:你真的会反转链表么?

链表类型的题目,一直以来是面试必考的题型。考查的核心有两点:

  • 一、思路的考察,如快慢双指针、递归反转、环的入口等。
  • 二、对指针的处理细节,如头尾节点、反转节点指针等

往往只有两者结合,才能正确解决链表问题。否则容易出现没有思路,或者有了思路但却不知道如何正确处理指针走向,导致解题失败。

在链表面试题中,反转链表更是一个热门+必问考点。全链表反转属于基础,由此延伸出的问题更是五花八门。对于这类问题,如果没有正确解答,往往宣告面试的结束。

本专题总结了链表反转的六大题型,希望大家看完之后,都可以轻松解决面试中遇到的链表问题,拿到心仪的offer。

反转链表相关题目列表:

1. 反转整个链表
2. 反转链表前n个节点
3. 反转链表后n个节点
4. 反转部分链表
5. 反转相邻链表
6. 反转链表k个一组
7. 判断链表是否回文结构


1.反转整个链表

非递归方式利用了pre和next指针,保存前驱节点用于反转,后继节点用于循环。
反转循环过程核心为三步:

  1. 保存后继节点
  2. 反转当前节点
  3. 指针后移继续循环
  /**
     * 1.反转整个链表 非递归
     */
    public ListNode reverseAllList(ListNode head) {
        ListNode pre = null;
        ListNode next;
        while (head != null) {
            next = head.next;
            head.next = pre;
            pre = head;
            head = next;
        }
        return pre;
    }

链表反转递归方式为经典的体现递归题目。要明确递归反转的方法含义,即反转head为头节点的链表,返回反转之后的头节点。回到方法中,此时last节点指向的,是以head.next为头节点的链表,反转之后的头节点。然后进行指针的反转,并且末尾指向NULL。注意递归的终止条件为head.next == null,但是防止入参为空,所以需要加上head == null判断。

    /**
     * 1.反转整个链表 递归
     */
    public ListNode reverseAllListRecur(ListNode head) {
        if (head == null || head.next == null) {
            return head;
        }
        ListNode last = reverseAllListRecur(head.next);
        head.next.next = head;
        head.next = null;
        return last;
    }

2.反转链表前n个节点

非递归方法:理解了全链表反转,该题只需要注意指针的处理。反转前n个节点,需要保存第n+1个节点,与反转后的前N个节点链表进行拼接。

  /**
     * 2.反转链表前n个节点 非递归
     */
    public ListNode reverseFirstN(ListNode head, int n) {
        ListNode nNext = head;
        for (int i = 0; i < n; i++) {
            nNext = nNext.next;
        }
        ListNode pre = null;
        ListNode next;
        ListNode cur = head;
        while (cur != nNext) {
            next = cur.next;
            cur.next = pre;
            pre = cur;
            cur = next;
        }
        head.next = nNext;
        return pre;
    }

递归方法类似全链表反转,注意递归的终止条件

    ListNode nNext = null; // 后驱节点

    /**
     * 2.反转链表前n个节点 递归
     */
    public ListNode reverseFirstNRecur(ListNode head, int n) {
        // n==1则走到了第n个节点,找到第n+1个节点
        if (n == 1) {
            nNext = head.next;
            return head;
        }
        ListNode last = reverseFirstNRecur(head.next, n - 1);
        head.next.next = head;
        head.next = nNext;
        return last;
    }

3.反转链表后n个节点

 public ListNode reverseLastN(ListNode head, int n) {
        ListNode slow = head;
        ListNode fast = head;
        for (int i = 0; i < n; i++) {
            fast = fast.next;
        }
        while (fast.next != null) {
            slow = slow.next;
            fast = fast.next;
        }

        ListNode cur = slow.next;
        ListNode pre = null;
        ListNode next;
        while (cur != null) {
            next = cur.next;
            cur.next = pre;
            pre = cur;
            cur = next;
        }

        slow.next = pre;
        return head;
    }

4.反转部分链表

反转部分链表,从第from个节点到第to个节点,则需要有指向第from-1个节点的指针startPre,指向to节点的指针end。而我们需要的start节点和to的后节点,均可通过两者后移得到。
注意: from是1的情况,此时返回的头节点不再是原来的头节点,而是反转后的头节点。

 /**
     * 4.反转链表其中一部分 从第from个节点到第to个节点的部分
     */
    public ListNode reversePart(ListNode head, int from, int to) {
        int len = 0;
        ListNode cur = head;
        ListNode startPre = null;
        ListNode end = null;
        while (cur != null) {
            len++;
            if (len == from - 1) {
                startPre = cur;
            }
            if (len == to) {
                end = cur;
            }
            cur = cur.next;
        }

        //考虑from是1的情况
        ListNode start = startPre == null ? head : startPre.next;
        ListNode endNext = end.next;

        cur = start;
        ListNode pre = null;
        ListNode next;
        while (cur != endNext) {
            next = cur.next;
            cur.next = pre;
            pre = cur;
            cur = next;
        }

        if (startPre != null) {
            startPre.next = pre;
        } else {
            head = pre;
        }
        start.next = endNext;
        return head;
    }

5.反转相邻链表

在头节点前设置前驱节点dummy,每次循环取得cur节点的后两个节点,设为p、q,反转pq的指针。注意调整的顺序。

    /**
     * 5.反转链表相邻节点 非递归
     */
    public ListNode reverseInPairs(ListNode head) {
        ListNode dummy = new ListNode(0);
        dummy.next = head;
        ListNode cur = dummy;
        while (cur.next != null && cur.next.next != null) {
            ListNode p = cur.next;
            ListNode q = cur.next.next;

            cur.next = q;
            p.next = q.next;
            q.next = p;
            cur = p;
        }
        return dummy.next;
    }


    /**
     * 5.反转链表相邻节点 递归
     */
    public ListNode reverseInPairsRecur(ListNode head) {
        if ((head == null) || (head.next == null)) {
            return head;
        }
        ListNode next = head.next;
        head.next = reverseInPairsRecur(head.next.next);
        next.next = head;
        return next;
    }

6.K个一组反转链表

结合全链表反转,将链表分成K个一组进行全链表反转,反转完成后再拼接回原链表中,继续下一轮循环。

    /**
     * 6.k个一组反转链表
     */
    public ListNode reverseKGroup(ListNode head, int k) {
        ListNode dummy = new ListNode(0);
        dummy.next = head;

        ListNode pre = dummy;
        ListNode end = dummy;

        //k个为一组,分为子链表。pre记录子链表的头节点的前驱节点,end记录子链表尾节点
        while (end.next != null) {
            for (int i = 0; i < k && end != null; i++) {
                end = end.next;
            }
            if (end == null) {
                break;
            }
            ListNode start = pre.next;
            ListNode endNext = end.next;
            end.next = null;
            pre.next = reverseAllList(start);

            //重新续上反转后的子链表
            //start此时反转后,变成尾节点
            start.next = endNext;
            pre = start;
            end = start;
        }
        return dummy.next;
    }

7.判断链表是否回文结构

反转链表后半部分,与前半部分链表逐一比较判断是否回文。判断完成后再将后半部分链表反转回去

    /**
     * 7.判断链表是否回文结构
     */
    public boolean isPalindrome(ListNode head) {
        if (head == null || head.next == null) {
            return true;
        }
        ListNode fast = head;
        ListNode slow = head;
        // 根据快慢指针,找到链表的中点
        while (fast.next != null && fast.next.next != null) {
            fast = fast.next.next;
            slow = slow.next;
        }

        boolean result = true;

        ListNode rHead = reverseAllList(slow.next);
        ListNode cur = rHead;
        while (cur != null) {
            if (head.val != cur.val) {
                result = false;
                break;
            }
            head = head.next;
            cur = cur.next;
        }

        //将后半部分链表反转回来,并拼接
        slow.next = reverseAllList(rHead);
        return result;
    }

Git项目源码地址

12-27 14:09