leetcode
leetcode 1 ~ 50
K 个一组翻转链表

K 个一组翻转链表

难度:

标签:

题目描述

给你链表的头节点 head ,每 k 个节点一组进行翻转,请你返回修改后的链表。

k 是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。

你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。

 

示例 1:

输入:head = [1,2,3,4,5], k = 2
输出:[2,1,4,3,5]

示例 2:

输入:head = [1,2,3,4,5], k = 3
输出:[3,2,1,4,5]

 

提示:
  • 链表中的节点数目为 n
  • 1 <= k <= n <= 5000
  • 0 <= Node.val <= 1000

 

进阶:你可以设计一个只用 O(1) 额外内存空间的算法解决此问题吗?

代码结果

运行时间: 52 ms, 内存: 16 MB


/*
题目思路:
1. 使用Java Stream API不能直接操作链表节点,因此这里的实现会将链表转换为数组。
2. 每k个元素进行一次翻转,最后将数组转换回链表。
3. 返回新的链表头。
*/
 
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
 
class ListNode {
    int val;
    ListNode next;
    ListNode(int x) { val = x; }
}
 
public class Solution {
    public ListNode reverseKGroup(ListNode head, int k) {
        // 将链表转换为ArrayList
        List<Integer> values = listToArrayList(head);
        // 分段翻转
        for (int i = 0; i < values.size(); i += k) {
            if (i + k <= values.size()) {
                List<Integer> sublist = values.subList(i, i + k);
                java.util.Collections.reverse(sublist);
            }
        }
        // 将ArrayList转换回链表
        return arrayListToList(values);
    }
 
    private List<Integer> listToArrayList(ListNode head) {
        List<Integer> list = new java.util.ArrayList<>();
        while (head != null) {
            list.add(head.val);
            head = head.next;
        }
        return list;
    }
 
    private ListNode arrayListToList(List<Integer> values) {
        ListNode dummy = new ListNode(0);
        ListNode current = dummy;
        for (int val : values) {
            current.next = new ListNode(val);
            current = current.next;
        }
        return dummy.next;
    }
}

解释

方法:

这个题解使用了递归的思路。首先判断链表长度是否小于k,如果是则直接返回原链表。否则,找到第k个节点,作为当前要反转的子链表的尾节点。然后调用reverse函数反转从head到第k个节点这一段的子链表。反转完后,head成为子链表的尾节点,递归处理之后的节点,将其作为head的next。最后返回反转后的链表的头节点,即反转前的第k个节点。

时间复杂度:

O(n)

空间复杂度:

O(n)

代码细节讲解

🦆
在处理非k整数倍的剩余节点时,题解中提到`如果节点总数不是k的整数倍,那么请将最后剩余的节点保持原有顺序`。这一点是如何在代码中实现的,特别是在寻找子链表的尾节点时的逻辑判断中?
在题解的代码中,这一点通过在寻找子链表的尾节点时的循环中实现。循环试图移动b指针k次,如果在达到k次之前b变成了None,说明剩余节点数小于k,这时函数会直接返回head,即不对这部分进行翻转,保持其原有顺序。这是通过`if b is None: return head`这行代码实现的。
🦆
题解中提到使用递归方法,递归深度为n/k。在实际应用中,递归深度过大可能导致栈溢出。对于最大节点数5000,这种递归方法是否安全,或者是否有必要考虑使用迭代法代替递归法来避免栈溢出问题?
对于最大节点数5000,如果每组k非常小,递归深度可以接近5000/k,这可能导致栈溢出,特别是在栈空间有限的环境下。虽然现代编译器和操作系统对递归的优化较好,但在极端情况下替换为迭代方法以避免栈溢出是一个更安全的选择。迭代方法通常使用循环来替代递归逻辑,能够有效减少对栈空间的使用。
🦆
您的解法中定义了一个内部函数`reverse`用于反转链表的一部分。这个函数的参数是头节点h和尾节点的下一个节点t,能否具体解释这种参数选择对整个链表反转逻辑的影响?
在这种参数选择中,头节点h是反转部分的起始节点,而尾节点的下一个节点t是反转部分的结束位置(不包括t)。这使得函数在到达t之前就停止反转,因此可以方便地处理部分链表的反转而不影响其他部分。此外,这种参数设置也便于在主函数中连接已经反转的部分与未反转的部分,因为在反转结束后,我们知道t节点是未反转部分的开始,而函数返回的是反转部分的新头节点。
🦆
对于反转操作中的`cur.next = pre`步骤,这种修改链表指针的操作在并发环境下是否安全?如果有多个线程同时操作链表,是否需要考虑加锁来保证数据一致性?
在并发环境下,链表的指针修改操作不是线程安全的。如果有多个线程可能同时修改链表的同一部分,这可能会导致数据竞争和不一致的状态。在这种情况下,确实需要通过加锁机制来保证链表操作的原子性和一致性。可以使用互斥锁(mutex)来保护整个链表或链表的关键部分,在进行修改之前获取锁,在修改完成后释放锁,从而保证操作的线程安全性。

相关问题

两两交换链表中的节点

给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。

 

示例 1:

输入:head = [1,2,3,4]
输出:[2,1,4,3]

示例 2:

输入:head = []
输出:[]

示例 3:

输入:head = [1]
输出:[1]

 

提示:

  • 链表中节点的数目在范围 [0, 100]
  • 0 <= Node.val <= 100