Skip to content

高频链表

两两交换链表节点

两两交换链表节点

题意:给你一个链表,把相邻两个节点交换位置。注意是交换节点本身,不是只交换节点里的值。

swap-nodes-in-pairs-csharp

核心用 dummy 虚拟头节点。每次看 prev 后面的两个节点:

first = prev.nextsecond = first.nextnext = second.next

然后重新连接成:

c
prev -> second -> first -> next
public class ListNode // 定义单链表节点类
{ // 节点类开始
    public int val; // 节点保存的值
    public ListNode next; // 指向下一个节点的指针
    public ListNode(int val = 0, ListNode next = null) // 定义节点构造函数
    { // 构造函数开始
        this.val = val; // 初始化当前节点的值
        this.next = next; // 初始化当前节点的 next 指针
    } // 构造函数结束
} // 节点类结束

public class Solution // 定义题解类
{ // 题解类开始
    public ListNode SwapPairs(ListNode head) // 定义两两交换链表节点的方法
    { // 方法开始
        ListNode dummy = new ListNode(0); // 创建虚拟头节点,方便处理头节点被交换的情况
        dummy.next = head; // 让 dummy 指向原链表头节点
        ListNode prev = dummy; // prev 指向每一组两个节点的前一个节点

        while (prev.next != null && prev.next.next != null) // 只要 prev 后面至少还有两个节点,就可以交换
        { // 循环开始
            ListNode first = prev.next; // first 指向这一组的第一个节点
            ListNode second = first.next; // second 指向这一组的第二个节点
            ListNode next = second.next; // next 保存第二个节点后面的节点,防止链表断掉

            first.next = next; // 第一个节点交换后要接到后面的 next
            second.next = first; // 第二个节点交换后要接到第一个节点前面
            prev.next = second; // prev 要接到交换后的新头节点 second

            prev = first; // prev 移动到交换后的这一组尾部,准备处理下一组
        } // 循环结束

        return dummy.next; // 返回新的链表头节点
    } // 方法结束
} // 题解类结束

复杂度

时间复杂度:O(n),每个节点只处理一次。 空间复杂度:O(1),只用了几个指针变量。

面试记忆

NOTE

dummy 接头,prev 找一对;firstnextsecondfirstprevsecond,最后 prev 移到 first

K 个一组翻转链表

K 个一组翻转链表

题意:链表每 k 个节点翻转一次;如果最后剩下的节点不足 k 个,就保持原样。

reverse-nodes-in-k-group-csharp

核心:每一组先确认够不够 k 个。够,就反转这一段;不够,直接结束。

c
public class ListNode // 定义单链表节点类
{ // 节点类开始
    public int val; // 节点保存的值
    public ListNode next; // 指向下一个节点的指针
    public ListNode(int val = 0, ListNode next = null) // 定义节点构造函数
    { // 构造函数开始
        this.val = val; // 初始化当前节点的值
        this.next = next; // 初始化当前节点的 next 指针
    } // 构造函数结束
} // 节点类结束

public class Solution // 定义题解类
{ // 题解类开始
    public ListNode ReverseKGroup(ListNode head, int k) // 定义 K 个一组翻转链表的方法
    { // 方法开始
        if (head == null || k <= 1) // 如果链表为空,或者 k 为 1
        { // 条件开始
            return head; // 不需要翻转,直接返回原链表
        } // 条件结束

        ListNode dummy = new ListNode(0, head); // 创建虚拟头节点,方便处理第一组被翻转的情况
        ListNode groupPrev = dummy; // groupPrev 指向当前组的前一个节点

        while (true) // 不断尝试处理下一组
        { // 循环开始
            ListNode kth = GetKth(groupPrev, k); // 从 groupPrev 往后找第 k 个节点
            if (kth == null) // 如果找不到第 k 个节点
            { // 条件开始
                break; // 说明剩余节点不足 k 个,停止翻转
            } // 条件结束

            ListNode groupNext = kth.next; // 保存当前组后面的第一个节点
            ListNode prev = groupNext; // 反转时 prev 先指向 groupNext,用来接回后半段
            ListNode curr = groupPrev.next; // curr 指向当前组的第一个节点

            while (curr != groupNext) // 只反转当前这一组,直到走到 groupNext 停止
            { // 反转循环开始
                ListNode temp = curr.next; // 暂存 curr 的下一个节点,防止断链
                curr.next = prev; // 当前节点指向前一个节点
                prev = curr; // prev 前进到当前节点
                curr = temp; // curr 前进到原来的下一个节点
            } // 反转循环结束

            ListNode oldGroupHead = groupPrev.next; // 记录翻转前的组头,翻转后它会变成组尾
            groupPrev.next = kth; // 当前组前面的节点接到翻转后的新组头 kth
            groupPrev = oldGroupHead; // groupPrev 移到当前组尾部,准备处理下一组
        } // 循环结束

        return dummy.next; // 返回新的链表头节点
    } // 方法结束

    private ListNode GetKth(ListNode start, int k) // 从 start 往后走 k 步,找到当前组的第 k 个节点
    { // 方法开始
        while (start != null && k > 0) // 只要节点没空,并且还没走够 k 步
        { // 循环开始
            start = start.next; // 指针向后移动一步
            k--; // 剩余步数减 1
        } // 循环结束

        return start; // 返回找到的第 k 个节点,找不到则返回 null
    } // 方法结束
} // 题解类结束

复杂度

时间复杂度:O(n),每个节点只被处理常数次。 空间复杂度:O(1),只用了几个指针变量。

面试记忆

TIP

先找够 k 个;保存 groupNext;把这一段反转到 groupNext 前面;最后 groupPrev 移到旧组头。

删除链表倒数第 N 个节点

删除链表倒数第 N 个节点

题意:删除链表中倒数第 N 个节点,并返回新的头节点。

remove-nth-node-from-end-csharp

核心用快慢指针

先让 fastn 步。 然后 fastslow 一起走。 当 fast 到最后一个节点时,slow.next 正好是要删除的节点。

c
public class ListNode // 定义单链表节点类
{ // 节点类开始
    public int val; // 节点保存的值
    public ListNode next; // 指向下一个节点的指针
    public ListNode(int val = 0, ListNode next = null) // 定义节点构造函数
    { // 构造函数开始
        this.val = val; // 初始化节点的值
        this.next = next; // 初始化节点的 next 指针
    } // 构造函数结束
} // 节点类结束

public class Solution // 定义题解类
{ // 题解类开始
    public ListNode RemoveNthFromEnd(ListNode head, int n) // 删除链表倒数第 n 个节点
    { // 方法开始
        ListNode dummy = new ListNode(0, head); // 创建虚拟头节点,方便删除真正的头节点
        ListNode fast = dummy; // fast 指针从 dummy 开始
        ListNode slow = dummy; // slow 指针也从 dummy 开始

        for (int i = 0; i < n; i++) // 让 fast 先向前走 n 步
        { // 循环开始
            fast = fast.next; // fast 向后移动一个节点
        } // 循环结束

        while (fast.next != null) // 当 fast 还没有到最后一个节点时
        { // 循环开始
            fast = fast.next; // fast 向后移动
            slow = slow.next; // slow 同步向后移动
        } // 循环结束

        slow.next = slow.next.next; // 删除 slow 后面的节点,也就是倒数第 n 个节点

        return dummy.next; // 返回新的头节点
    } // 方法结束
} // 题解类结束

复杂度

时间复杂度:O(n),只遍历一次链表。 空间复杂度:O(1),只用了几个指针。

面试记忆

IMPORTANT

dummy 接头;fast 先走 N 步;fast 到尾时 slow 在目标前面;跳过 slow.next

合并 K 个升序链表

合并 K 个升序链表

题意:给你 K 个已经升序的链表,把它们合并成一个新的升序链表。

merge-k-sorted-lists-csharp

核心用小根堆:堆里始终放每条链表当前的头节点。每次弹出最小节点,接到结果链表后,再把这个节点的 next 放回堆里。

c
using System.Collections.Generic; // 引入 PriorityQueue 和集合类型

public class ListNode // 定义单链表节点类
{ // 节点类开始
    public int val; // 节点保存的值
    public ListNode next; // 指向下一个节点的指针
    public ListNode(int val = 0, ListNode next = null) // 定义节点构造函数
    { // 构造函数开始
        this.val = val; // 初始化节点值
        this.next = next; // 初始化 next 指针
    } // 构造函数结束
} // 节点类结束

public class Solution // 定义题解类
{ // 题解类开始
    public ListNode MergeKLists(ListNode[] lists) // 定义合并 K 个升序链表的方法
    { // 方法开始
        PriorityQueue<ListNode, int> heap = new PriorityQueue<ListNode, int>(); // 创建小根堆,优先级使用节点值
        foreach (ListNode node in lists) // 遍历每一条链表的头节点
        { // 循环开始
            if (node != null) // 如果当前链表不是空链表
            { // 条件开始
                heap.Enqueue(node, node.val); // 把当前链表头节点放入小根堆
            } // 条件结束
        } // 循环结束

        ListNode dummy = new ListNode(0); // 创建虚拟头节点,方便拼接结果链表
        ListNode tail = dummy; // tail 指向结果链表的尾部

        while (heap.Count > 0) // 只要堆里还有候选节点
        { // 循环开始
            ListNode node = heap.Dequeue(); // 弹出当前最小的节点
            tail.next = node; // 把最小节点接到结果链表尾部
            tail = tail.next; // tail 移动到新的尾节点

            if (node.next != null) // 如果被弹出的节点后面还有节点
            { // 条件开始
                heap.Enqueue(node.next, node.next.val); // 把后继节点加入堆,成为新的候选节点
            } // 条件结束
        } // 循环结束

        tail.next = null; // 断开尾节点后面的旧连接,保证结果链表结尾干净
        return dummy.next; // 返回真正的结果链表头节点
    } // 方法结束
} // 题解类结束

复杂度

设所有链表节点总数是 N,链表数量是 K。 时间复杂度:O(N log K)。 空间复杂度:O(K),堆里最多放 K 个节点。

面试记忆

WARNING

先把所有非空头节点入堆;每次弹出最小节点接到 tail;如果它有 next,就把 next 入堆。

链表排序

链表排序

链表排序面试里最推荐讲归并排序。因为链表不适合随机访问,快排那种按下标交换的思路不舒服;但链表很适合“切开 + 合并”。

sort-linked-list-csharp

核心步骤:

快慢指针找到中点。 把链表切成左右两段。 递归排序左右链表。 合并两个有序链表。

c
public class ListNode // 定义单链表节点类
{ // 节点类开始
    public int val; // 节点保存的值
    public ListNode next; // 指向下一个节点的指针
    public ListNode(int val = 0, ListNode next = null) // 定义节点构造函数
    { // 构造函数开始
        this.val = val; // 初始化节点值
        this.next = next; // 初始化 next 指针
    } // 构造函数结束
} // 节点类结束

public class Solution // 定义题解类
{ // 题解类开始
    public ListNode SortList(ListNode head) // 定义链表排序方法
    { // 方法开始
        if (head == null || head.next == null) // 如果链表为空或者只有一个节点
        { // 条件开始
            return head; // 本身已经有序,直接返回
        } // 条件结束

        ListNode slow = head; // slow 每次走一步,用来找中点
        ListNode fast = head.next; // fast 每次走两步,让 slow 停在左半段尾部

        while (fast != null && fast.next != null) // 当 fast 还能继续走两步时
        { // 循环开始
            slow = slow.next; // slow 向后走一步
            fast = fast.next.next; // fast 向后走两步
        } // 循环结束

        ListNode rightHead = slow.next; // 右半段的头节点
        slow.next = null; // 切断左右两段链表

        ListNode left = SortList(head); // 递归排序左半段
        ListNode right = SortList(rightHead); // 递归排序右半段

        return Merge(left, right); // 合并两个已经有序的链表并返回
    } // 方法结束

    private ListNode Merge(ListNode left, ListNode right) // 合并两个升序链表
    { // 方法开始
        ListNode dummy = new ListNode(0); // 创建虚拟头节点,方便拼接结果
        ListNode tail = dummy; // tail 指向结果链表的尾部

        while (left != null && right != null) // 当两个链表都还有节点时
        { // 循环开始
            if (left.val <= right.val) // 如果左链表当前节点更小或相等
            { // 条件开始
                tail.next = left; // 把左链表当前节点接到结果链表后面
                left = left.next; // 左链表指针向后移动
            } // 条件结束
            else // 如果右链表当前节点更小
            { // 分支开始
                tail.next = right; // 把右链表当前节点接到结果链表后面
                right = right.next; // 右链表指针向后移动
            } // 分支结束

            tail = tail.next; // 结果链表尾指针向后移动
        } // 循环结束

        tail.next = left != null ? left : right; // 把剩余没有合并完的链表直接接上

        return dummy.next; // 返回真正的排序后头节点
    } // 方法结束
} // 题解类结束

复杂度

时间复杂度:O(n log n)。 空间复杂度:递归写法是 O(log n),主要是递归栈。 如果写自底向上的归并,可以做到 O(1) 额外空间。

面试记忆

CAUTION

快慢指针找中点,断开左右两段;递归排好两边,再按“合并两个有序链表”的方式合并。

相交链表

相交链表

题意:判断两个单链表是否在某个节点相交。注意,相交不是值相等,而是两个链表后面共享同一个节点对象

intersection-of-two-linked-lists-csharp

核心用双指针换头走

pAheadA 出发,走完 A 后去走 B。 pBheadB 出发,走完 B 后去走 A。 如果两个链表相交,它们最终会在交点相遇。 如果不相交,它们最终会一起变成 null

c
public class ListNode // 定义单链表节点类
{ // 节点类开始
    public int val; // 节点保存的值
    public ListNode next; // 指向下一个节点的指针
    public ListNode(int x) // 定义节点构造函数
    { // 构造函数开始
        val = x; // 初始化节点值
        next = null; // 初始化 next 指针为空
    } // 构造函数结束
} // 节点类结束

public class Solution // 定义题解类
{ // 题解类开始
    public ListNode GetIntersectionNode(ListNode headA, ListNode headB) // 查找两个链表的相交节点
    { // 方法开始
        if (headA == null || headB == null) // 如果任意一个链表为空
        { // 条件开始
            return null; // 一定不可能相交,直接返回 null
        } // 条件结束

        ListNode pA = headA; // pA 从链表 A 的头节点出发
        ListNode pB = headB; // pB 从链表 B 的头节点出发

        while (pA != pB) // 只要两个指针没有指向同一个节点,就继续移动
        { // 循环开始
            pA = pA == null ? headB : pA.next; // pA 走完 A 后切换到 B 的头节点
            pB = pB == null ? headA : pB.next; // pB 走完 B 后切换到 A 的头节点
        } // 循环结束

        return pA; // 返回相交节点;如果不相交,此时 pA 和 pB 都是 null
    } // 方法结束
} // 题解类结束

复杂度

时间复杂度:O(m + n)。 空间复杂度:O(1)

面试记忆

NOTE

pA 到尾去 headBpB 到尾去 headA;相交就在交点相遇,不相交就在 null 相遇。

复制带随机指针的链表

复制带随机指针的链表

题意:链表节点除了 next,还有一个 random 指针,它可能指向任意节点,也可能是 null。要求复制出一条全新的链表,新链表的 nextrandom 都不能指回原链表节点。

copy-list-with-random-pointer-csharp

核心用三步穿插复制法

第一步:每个原节点后面插入它的复制节点。 第二步:复制 random,关键公式是 copy.random = current.random.next。 第三步:把穿插链表拆成原链表和复制链表。

c
public class Node // 定义带 random 指针的链表节点类
{ // 节点类开始
    public int val; // 节点保存的值
    public Node next; // 指向下一个节点的指针
    public Node random; // 指向任意节点的随机指针
    public Node(int val) // 定义节点构造函数
    { // 构造函数开始
        this.val = val; // 初始化节点值
        this.next = null; // 初始化 next 指针为空
        this.random = null; // 初始化 random 指针为空
    } // 构造函数结束
} // 节点类结束

public class Solution // 定义题解类
{ // 题解类开始
    public Node CopyRandomList(Node head) // 定义复制带随机指针链表的方法
    { // 方法开始
        if (head == null) // 如果原链表为空
        { // 条件开始
            return null; // 空链表复制后仍然是空
        } // 条件结束

        Node current = head; // current 用来遍历原链表

        while (current != null) // 第一步:在每个原节点后面插入复制节点
        { // 循环开始
            Node copy = new Node(current.val); // 创建当前节点的复制节点
            copy.next = current.next; // 复制节点先接到原节点的下一个节点
            current.next = copy; // 原节点接到复制节点
            current = copy.next; // current 跳到下一个原节点
        } // 循环结束

        current = head; // current 重新回到原链表头节点

        while (current != null) // 第二步:复制 random 指针
        { // 循环开始
            Node copy = current.next; // 当前原节点后面的节点就是它的复制节点
            if (current.random != null) // 如果当前原节点有 random 指针
            { // 条件开始
                copy.random = current.random.next; // 原 random 指向节点的 next,就是 random 目标的复制节点
            } // 条件结束
            current = copy.next; // current 跳到下一个原节点
        } // 循环结束

        current = head; // current 再次回到原链表头节点
        Node copyHead = head.next; // 复制链表的头节点是原头节点后面的复制节点

        while (current != null) // 第三步:拆分原链表和复制链表
        { // 循环开始
            Node copy = current.next; // 当前原节点对应的复制节点
            Node nextOriginal = copy.next; // 下一个原节点
            current.next = nextOriginal; // 恢复原链表的 next 指针
            copy.next = nextOriginal != null ? nextOriginal.next : null; // 复制节点接到下一个复制节点
            current = nextOriginal; // current 移动到下一个原节点
        } // 循环结束

        return copyHead; // 返回复制链表的头节点
    } // 方法结束
} // 题解类结束

复杂度

时间复杂度:O(n),整条链表扫描三次。 额外空间复杂度:O(1),不算必须创建的新节点。

面试记忆

IMPORTANT

原节点后面插副本;副本的 random 指向原 randomnext;最后把奇偶位置拆成两条链表。

回文链表

回文链表

题意:判断一个链表是不是回文。比如:

1 -> 2 -> 2 -> 1 是回文。 1 -> 2 -> 3 不是回文。

palindrome-linked-list-csharp

核心做法: 快慢指针找到中点,反转后半段,然后前半段和后半段逐个比较。

c
public class ListNode // 定义单链表节点类
{ // 节点类开始
    public int val; // 节点保存的值
    public ListNode next; // 指向下一个节点的指针
    public ListNode(int val = 0, ListNode next = null) // 定义节点构造函数
    { // 构造函数开始
        this.val = val; // 初始化节点值
        this.next = next; // 初始化 next 指针
    } // 构造函数结束
} // 节点类结束

public class Solution // 定义题解类
{ // 题解类开始
    public bool IsPalindrome(ListNode head) // 判断链表是否是回文链表
    { // 方法开始
        if (head == null || head.next == null) // 如果链表为空或者只有一个节点
        { // 条件开始
            return true; // 空链表和单节点链表都可以认为是回文
        } // 条件结束

        ListNode slow = head; // slow 每次走一步,用来找前半段尾部
        ListNode fast = head; // fast 每次走两步,用来帮助 slow 定位中点

        while (fast.next != null && fast.next.next != null) // 当 fast 还能继续走两步时
        { // 循环开始
            slow = slow.next; // slow 向后走一步
            fast = fast.next.next; // fast 向后走两步
        } // 循环结束

        ListNode secondHalfHead = ReverseList(slow.next); // 反转后半段链表
        ListNode left = head; // left 从前半段头节点开始
        ListNode right = secondHalfHead; // right 从反转后的后半段头节点开始
        bool result = true; // 默认认为链表是回文

        while (right != null) // 只需要比较后半段长度
        { // 循环开始
            if (left.val != right.val) // 如果前后对应节点的值不相等
            { // 条件开始
                result = false; // 标记不是回文链表
                break; // 已经确定不是回文,可以停止比较
            } // 条件结束

            left = left.next; // left 向后移动
            right = right.next; // right 向后移动
        } // 循环结束

        slow.next = ReverseList(secondHalfHead); // 把后半段再反转回来,恢复原链表结构

        return result; // 返回最终判断结果
    } // 方法结束

    private ListNode ReverseList(ListNode head) // 反转单链表
    { // 方法开始
        ListNode prev = null; // prev 表示已经反转部分的头节点
        ListNode curr = head; // curr 表示当前正在处理的节点

        while (curr != null) // 只要当前节点不为空,就继续反转
        { // 循环开始
            ListNode next = curr.next; // 保存 curr 的下一个节点,防止断链
            curr.next = prev; // 当前节点指向前一个节点
            prev = curr; // prev 移动到当前节点
            curr = next; // curr 移动到原来的下一个节点
        } // 循环结束

        return prev; // prev 就是反转后的新头节点
    } // 方法结束
} // 题解类结束

复杂度

时间复杂度:O(n)。 空间复杂度:O(1)

面试记忆

NOTE

快慢指针找中点,反转后半段,前后两段同步比较;最后可以把后半段恢复回来,显得更严谨。

奇偶链表

奇偶链表

题意:把链表中奇数位置的节点放前面,偶数位置的节点放后面,并保持各自原来的相对顺序。

注意:这里的奇偶指的是节点位置,不是节点值。 1 -> 2 -> 3 -> 4 -> 5 变成 1 -> 3 -> 5 -> 2 -> 4

odd-even-linked-list-csharp

核心:维护两条链。

odd 负责串起奇数位置节点。 even 负责串起偶数位置节点。 evenHead 保存偶数链表头,最后接到奇数链表尾部。

c
public class ListNode // 定义单链表节点类
{ // 节点类开始
    public int val; // 节点保存的值
    public ListNode next; // 指向下一个节点的指针
    public ListNode(int val = 0, ListNode next = null) // 定义节点构造函数
    { // 构造函数开始
        this.val = val; // 初始化节点值
        this.next = next; // 初始化 next 指针
    } // 构造函数结束
} // 节点类结束

public class Solution // 定义题解类
{ // 题解类开始
    public ListNode OddEvenList(ListNode head) // 定义奇偶链表重排方法
    { // 方法开始
        if (head == null || head.next == null) // 如果链表为空或者只有一个节点
        { // 条件开始
            return head; // 不需要重排,直接返回
        } // 条件结束

        ListNode odd = head; // odd 指向当前奇数位置链表的尾节点
        ListNode even = head.next; // even 指向当前偶数位置链表的尾节点
        ListNode evenHead = even; // 保存偶数位置链表的头节点,最后要接回去

        while (even != null && even.next != null) // 只要后面还有下一个奇数位置节点
        { // 循环开始
            odd.next = even.next; // 奇数链表接上下一个奇数位置节点
            odd = odd.next; // odd 移动到新的奇数链表尾节点

            even.next = odd.next; // 偶数链表接上下一个偶数位置节点
            even = even.next; // even 移动到新的偶数链表尾节点
        } // 循环结束

        odd.next = evenHead; // 奇数链表尾部接上偶数链表头部

        return head; // 返回重排后的链表头节点
    } // 方法结束
} // 题解类结束

复杂度

时间复杂度:O(n),每个节点只处理一次。 空间复杂度:O(1),只用了几个指针。

面试记忆

IMPORTANT

odd 接下一个奇数位,even 接下一个偶数位;最后 odd 尾巴接 evenHead

旋转链表

旋转链表

题意:把链表整体向右旋转 k 个位置。

比如:

1 -> 2 -> 3 -> 4 -> 5k = 2 结果是:

4 -> 5 -> 1 -> 2 -> 3

rotate-linked-list-csharp

核心:先把链表连成环,再在新的尾节点处断开。

c
public class ListNode // 定义单链表节点类
{ // 节点类开始
    public int val; // 节点保存的值
    public ListNode next; // 指向下一个节点的指针
    public ListNode(int val = 0, ListNode next = null) // 定义节点构造函数
    { // 构造函数开始
        this.val = val; // 初始化节点值
        this.next = next; // 初始化 next 指针
    } // 构造函数结束
} // 节点类结束

public class Solution // 定义题解类
{ // 题解类开始
    public ListNode RotateRight(ListNode head, int k) // 定义向右旋转链表的方法
    { // 方法开始
        if (head == null || head.next == null || k == 0) // 如果链表为空、只有一个节点,或者 k 为 0
        { // 条件开始
            return head; // 不需要旋转,直接返回原链表
        } // 条件结束

        int length = 1; // 记录链表长度,初始包含头节点
        ListNode tail = head; // tail 用来找到链表尾节点

        while (tail.next != null) // 只要还没走到尾节点
        { // 循环开始
            tail = tail.next; // tail 向后移动
            length++; // 链表长度加 1
        } // 循环结束

        k = k % length; // k 可能大于链表长度,所以先取模

        if (k == 0) // 如果取模后 k 为 0
        { // 条件开始
            return head; // 说明旋转后和原链表一样
        } // 条件结束

        tail.next = head; // 尾节点接回头节点,把链表临时变成环

        int stepsToNewTail = length - k - 1; // 新尾节点距离原头节点的步数
        ListNode newTail = head; // newTail 从原头节点开始走

        for (int i = 0; i < stepsToNewTail; i++) // 走到新的尾节点位置
        { // 循环开始
            newTail = newTail.next; // newTail 向后移动一步
        } // 循环结束

        ListNode newHead = newTail.next; // 新尾节点的下一个节点就是新的头节点
        newTail.next = null; // 从新尾节点处断开环,形成新的链表

        return newHead; // 返回旋转后的新头节点
    } // 方法结束
} // 题解类结束

复杂度

时间复杂度:O(n)。 空间复杂度:O(1)

面试记忆

CAUTION

先求长度和尾节点;k 取模;尾接头成环;走到新尾节点,断开它的 next

文章评价

读完这篇,留下你的看法

暂无审核通过的评价。

登录账号后才能评价。

本站访客数0总站访问量0本页访问量0