作者介绍:10年大厂数据\经营分析经验,现任大厂数据部门负责人。
会一些的技术:数据分析、算法、SQL、大数据相关、python
欢迎加入社区:码上找工作
作者专栏每日更新:
备注说明:方便大家阅读,统一使用python,带必要注释,公众号 数据分析螺丝钉 一起打怪升级
题目描述
给定一个链表,旋转链表,将链表每个节点向右移动 k
个位置,其中 k
是非负数。
输入格式
- head:链表的头节点。
- k:一个整数,表示旋转的位置数。
输出格式
- 返回旋转后的链表的头节点。
示例
示例 1
输入: 1->2->3->4->5->NULL, k = 2 输出: 4->5->1->2->3->NULL 解释: 向右旋转 1 步: 5->1->2->3->4->NULL 向右旋转 2 步: 4->5->1->2->3->NULL
示例 2
输入: 0->1->2->NULL, k = 4 输出: 2->0->1->NULL 解释: 向右旋转 1 步: 2->0->1->NULL 向右旋转 2 步: 1->2->0->NULL 向右旋转 3 步: 0->1->2->NULL 向右旋转 4 步: 2->0->1->NULL
方法一:闭合为环
解题步骤
- 链表长度:首先遍历整个链表得到链表长度
n
和最后一个节点。 - 闭合成环:将链表的尾节点连接到头节点,形成环。
- 计算新头位置:根据
k % n
计算新的头节点的位置。 - 重置头节点:找到新的头节点和尾节点,断开环。
完整的规范代码
class ListNode: def __init__(self, val=0, next=None): self.val = val self.next = next def rotateRight(head, k): """ 使用闭合环的方法进行链表旋转 :param head: ListNode, 链表的头节点 :param k: int, 旋转的位置数 :return: ListNode, 旋转后的链表头节点 """ if not head or not head.next or k == 0: return head # 计算链表长度并形成环 lastElement = head length = 1 while lastElement.next: lastElement = lastElement.next length += 1 lastElement.next = head # 找到新的尾部:(长度 - k % 长度 - 1)的位置 k = k % length steps_to_new_tail = length - k new_tail = head for i in range(steps_to_new_tail - 1): new_tail = new_tail.next new_head = new_tail.next # 断开环 new_tail.next = None return new_head # 示例代码用于创建链表和打印链表 def create_list(lst): head = current = ListNode(lst[0]) for number in lst[1:]: current.next = ListNode(number) current = current.next return head def print_list(node): while node: print(node.val, end=" -> ") node = node.next print("NULL") # 示例调用 lst = create_list([1, 2, 3, 4, 5]) rotated_lst = rotateRight(lst, 2) print_list(rotated_lst) # 输出: 4 -> 5 -> 1 -> 2 -> 3 -> NULL
算法分析
- 时间复杂度:(O(n)),主要时间开销来自于链表遍历和旋转计算。
- 空间复杂度:(O(1)),除了给定的链表结构,只使用常数额外空间。
方法二:数组模拟
解题步骤
- 转换为数组:遍历链表,存储每个节点的值到数组中。
- 数组旋转:对数组进行旋转操作。
- 重建链表:根据旋转后的数组重建链表。
完整的规范代码
def rotateRight(head, k): """ 使用数组模拟链表旋转 :param head: ListNode, 链表的头节点 :param k: int, 旋转的位置数 :return: ListNode, 旋转后的链表头节点 """ if not head or not head.next or k == 0: return head # 转换链表到数组 nodes = [] current = head while current: nodes.append(current.val) current = current.next # 数组旋转 k = k % len(nodes) nodes = nodes[-k:] + nodes[:-k] # 重建链表 new_head = current = ListNode(nodes[0]) for val in nodes[1:]: current.next = ListNode(val) current = current.next return new_head # 示例调用 lst = create_list([1, 2, 3, 4, 5]) rotated_lst = rotateRight(lst, 2) print_list(rotated_lst) # 输出: 4 -> 5 -> 1 -> 2 -> 3 -> NULL
算法分析
- 时间复杂度:(O(n)),包括链表到数组的转换和数组的旋转。
- 空间复杂度:(O(n)),需要额外空间来存储链表元素的数组。
方法三:直接旋转
解题步骤
- 链表遍历:计算链表长度并连接成环。
- 计算新尾部:根据旋转次数确定新的尾部位置。
- 重建链表:在新的尾部断开环,形成新的链表。
完整的规范代码
def rotateRight(head, k): """ 直接通过链表旋转实现 :param head: ListNode, 链表的头节点 :param k: int, 旋转的位置数 :return: ListNode, 旋转后的链表头节点 """ if not head or not head.next or k == 0: return head # 计算长度并形成环 old_tail = head length = 1 while old_tail.next: old_tail = old_tail.next length += 1 old_tail.next = head # 计算新尾部位置 k = k % length new_tail = head for i in range(length - k - 1): new_tail = new_tail.next new_head = new_tail.next new_tail.next = None return new_head # 示例调用 lst = create_list([1, 2, 3, 4, 5]) rotated_lst = rotateRight(lst, 2) print_list(rotated_lst) # 输出: 4 -> 5 -> 1 -> 2 -> 3 -> NULL
算法分析
- 时间复杂度:(O(n)),需要遍历链表两次。
- 空间复杂度:(O(1)),仅使用常数额外空间。
方法四:改进的数组模拟
解题步骤
- 转换为数组:将链表节点转换为数组,方便随机访问。
- 数组旋转:对数组进行旋转操作。
- 重建链表:根据旋转后的数组重建链表。
完整的规范代码
def rotateRight(head, k): """ 使用改进的数组模拟方法旋转链表 :param head: ListNode, 链表的头节点 :param k: int, 旋转的位置数 :return: ListNode, 旋转后的链表头节点 """ if not head: return None # 转换链表到数组 nodes = [] while head: nodes.append(head) head = head.next k = k % len(nodes) nodes = nodes[-k:] + nodes[:-k] # 重建链表 for i in range(len(nodes) - 1): nodes[i].next = nodes[i + 1] nodes[-1].next = None return nodes[0] # 示例调用 lst = create_list([1, 2, 3, 4, 5]) rotated_lst = rotateRight(lst, 2) print_list(rotated_lst) # 输出: 4 -> 5 -> 1 -> 2 -> 3 -> NULL
算法分析
- 时间复杂度:(O(n)),链表到数组的转换和数组旋转各需要 (O(n))。
- 空间复杂度:(O(n)),需要额外空间来存储节点指针的数组。
方法五:反向索引旋转
解题步骤
- 链表遍历:一次遍历计算链表长度并形成尾部连接。
- 反向计算头部:使用长度减去 (k % \text{长度}) 计算新头部和尾部位置。
- 断开连接:在新尾部断开链表,形成新的链表结构。
完整的规范代码
def rotateRight(head, k): """ 使用反向索引旋转链表 :param head: ListNode, 链表的头节点 :param k: int, 旋转的位置数 :return: ListNode, 旋转后的链表头节点 """ if not head or not head.next or k == 0: return head # 完成一次完整遍历以确定长度并形成环 last = head length = 1 while last.next: last = last.next length += 1 last.next = head # 确定新的头部和尾部 k = k % length if k: for _ in range(length - k): last = last.next head = last.next last.next = None return head # 示例调用 lst = create_list([1, 2, 3, 4, 5]) rotated_lst = rotateRight(lst, 2) print_list(rotated_lst) # 输出: 4 -> 5 -> 1 -> 2 -> 3 -> NULL
算法分析
- 时间复杂度:(O(n)),可能需要遍历链表两次(计算长度和调整位置)。
- 空间复杂度:(O(1)),不需要额外的存储空间,除了输入的链表。
不同算法的优劣势对比
特征 | 方法一: 闭环 | 方法二: 数组模拟 | 方法三: 直接旋转 | 方法四: 改进数组模拟 | 方法五: 反向索引旋转 |
时间复杂度 | (O(n)) | (O(n)) | (O(n)) | (O(n)) | (O(n)) |
空间复杂度 | (O(1)) | (O(n)) | (O(1)) | (O(n)) | (O(1)) |
优势 | 直观简单,实现快速 | 方便理解和实现,便于调试 | 减少了中间转换步骤,提高效率 | 易于理解和操作,灵活处理 | 计算头尾连接,避免额外空间,快速 |
劣势 | 需要特别处理新的头尾 | 需要额外空间存储数组,增加空间复杂度 | 需要准确计算长度和剩余部分 | 需要额外空间且稍复杂 | 需要两次遍历,稍增时间开销 |
应用示例
游戏开发中的角色队列管理:
在多人在线游戏中,经常需要管理玩家的队列,例如在战场游戏中旋转玩家的出场顺序。使用旋转链表的算法可以有效地管理这种动态变化的玩家队列,确保每个玩家都能公平地参与游戏,同时也支持快速的动态调整。
欢迎关注微信公众号 数据分析螺丝钉