LeetCode 每周算法 4(链表)
链表算法:
# Definition for singly-linked list.
# class ListNode(object):
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution(object):
def mergeTwoLists(self, list1, list2):
"""
:type list1: Optional[ListNode]
:type list2: Optional[ListNode]
:rtype: Optional[ListNode]
"""
# 创建一个哨兵节点,哨兵节点的作用是简化头节点可能为空的处理
# 新链表的头节点将会是哨兵节点的下一个节点
prehead = ListNode(-1)
# pre 指针用于构建新链表,初始时指向哨兵节点
pre = prehead
# 当list1和list2都不为空时,循环比较它们的值
while list1 and list2:
# 如果list1的值较小或等于list2的值,则将list1的当前节点接入新链表
if list1.val <= list2.val:
pre.next = list1
list1 = list1.next # 移动list1到下一个节点
else:
# 如果list2的值较小,则将list2的当前节点接入新链表
pre.next = list2
list2 = list2.next # 移动list2到下一个节点
# pre指针也移动到新接入的节点,以便接入下一个节点
pre = pre.next
# 如果l1或l2中还有剩余节点,直接将剩余部分接入新链表
# 注意:这里不需要再比较了,因为已经保证了是升序的
pre.next = list1 if list1 is not None else list2
# 返回新链表的头节点(哨兵节点的下一个节点)
return prehead.next
# Definition for singly-linked list.
# class ListNode(object):
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution(object):
def addTwoNumbers(self, l1, l2):
"""
:type l1: ListNode
:type l2: ListNode
:rtype: ListNode
"""
# 初始化结果链表的头节点,初始时不存储任何值(val=0)
dummy = ListNode(0)
# 使用一个指针来遍历结果链表
current = dummy
# 初始化进位为0
carry = 0
# 当l1和l2都不为空时,进行循环
while l1 is not None or l2 is not None:
# 分别获取l1和l2当前节点的值,如果为空则视为0
x = l1.val if l1 is not None else 0
y = l2.val if l2 is not None else 0
# 计算当前位的和(包括进位)
sum = carry + x + y
# 更新进位
carry = sum // 10
# 创建新节点存储当前位的值(对10取余)
current.next = ListNode(sum % 10)
# 移动指针和链表指针
current = current.next
if l1 is not None:
l1 = l1.next
if l2 is not None:
l2 = l2.next
# 如果循环结束后还有进位,则创建一个新节点存储进位
if carry > 0:
current.next = ListNode(carry)
# 返回结果链表的头节点的下一个节点(跳过初始的dummy节点)
return dummy.next
# Definition for singly-linked list.
# class ListNode(object):
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution(object):
def removeNthFromEnd(self, head, n):
"""
:type head: ListNode
:type n: int
:rtype: ListNode
"""
# 创建一个哨兵节点,它的next指向链表的头节点
# 哨兵节点的作用是简化头节点被删除的情况
dummy = ListNode(0)
dummy.next = head
# 初始化两个指针,都指向哨兵节点
# fast指针会先走n+1步,这样fast和slow之间就隔了n个节点
fast = slow = dummy
# fast指针先走n+1步
for _ in range(n + 1):
fast = fast.next
# 当fast指针到达链表末尾时,slow指针指向倒数第n+1个节点
while fast:
fast = fast.next
slow = slow.next
# 删除倒数第n个节点
# 注意,此时slow指向的是倒数第n+1个节点,所以我们需要删除的是slow.next
slow.next = slow.next.next
# 返回链表的头节点(实际上是哨兵节点的next,即原链表的头节点)
return dummy.next
# Definition for singly-linked list.
# class ListNode(object):
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution(object):
def swapPairs(self, head):
"""
:type head: ListNode
:rtype: ListNode
"""
# 创建一个哑节点(dummy node),它的next指向链表的头节点
# 使用哑节点可以简化对链表头部操作的处理
dummy = ListNode(0)
dummy.next = head
# 初始化两个指针,pre指向哑节点,curr指向链表的头节点
# pre用于在交换过程中保持对交换前curr节点的前一个节点的引用
pre, curr = dummy, head
while curr and curr.next: # 当curr和curr.next都不为空时,继续循环
# 缓存curr的下一个节点,因为交换后curr.next会改变
first = curr
second = curr.next
# 开始交换
# 第一步:curr的next指向second的next
curr.next = second.next
# 第二步:second的next指向first,实现second和first的交换
second.next = first
# 第三步:pre的next指向second,这样pre->second->first就形成了新的链接
pre.next = second
# 更新pre和curr,以便进行下一对节点的交换
pre = first
curr = curr.next
# 循环结束后,dummy.next将是新的头节点
return dummy.next
# Definition for singly-linked list.
# class ListNode(object):
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
# Definition for singly-linked list.
# class ListNode(object):
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution(object):
def reverseKGroup(self, head, k):
"""
:type head: ListNode
:type k: int
:rtype: ListNode
"""
# 定义一个哑节点,其next指向链表的头节点,用于简化边界条件的处理
dummy = ListNode(0)
dummy.next = head
# 初始化pre和tail指针,都指向哑节点,pre用于记录翻转前k个节点组的前一个节点,tail用于遍历检查节点数
pre = tail = dummy
# 使用一个无限循环来处理整个链表,直到tail为空(即链表遍历完成)
while True:
count = k # 初始化计数器,用于检查接下来的节点数是否足够k个
# 使用while循环来移动tail指针,直到它指向第k个节点或链表末尾
while count and tail:
count -= 1
tail = tail.next
# 如果tail为空,说明剩余节点不足k个,跳出循环
if not tail:
break
# head指向当前要翻转的k个节点组的第一个节点(即pre.next)
head = pre.next
# 开始翻转这k个节点
# 当pre.next不是tail时,说明还有节点需要翻转
while pre.next != tail:
current = pre.next # current指向当前要处理的节点(即翻转前的第一个节点)
# 将pre的next指向current的next,这样cur就被“断开”了
pre.next = current.next
# 将current的next指向tail的next,即将current连接到翻转后的链表末尾
current.next = tail.next
# 将tail的next指向current,即将current插入到tail之前
tail.next = current
# 更新pre和tail的位置,为下一轮翻转做准备
# pre移动到翻转后的最后一个节点(即翻转前的第一个节点)
pre = head
# tail重新指向pre(因为翻转后,pre现在是这一组k个节点的末尾)
# 但实际上,由于下一轮循环会重新检查并移动tail,这里直接设为head也是可以的
# 不过为了保持代码的一致性,这里仍然设为pre(即翻转后的最后一个节点)
tail = pre
# 返回翻转后的链表的头节点(哑节点的next)
return dummy.next
"""
# Definition for a Node.
class Node:
def __init__(self, x, next=None, random=None):
self.val = int(x)
self.next = next
self.random = random
"""
class Solution(object):
def copyRandomList(self, head):
"""
:type head: Node
:rtype: Node
"""
if not head: # 如果链表为空,则直接返回None
return None
# 步骤1: 复制节点并插入到原节点之后
curr = head
while curr:
newNode = Node(curr.val, curr.next) # 创建一个新节点,其值与原节点相同,next暂时指向原节点的next
curr.next = newNode # 将原节点的next指向新创建的节点,实现交错链表
curr = newNode.next # 移动到原链表的下一个节点
# 步骤2: 设置随机指针
curr = head # 重新从头节点开始遍历交错链表
while curr:
if curr.random: # 如果原节点有random指针
curr.next.random = curr.random.next # 则新节点的random指针指向原节点random指针的下一个节点(即其副本)
curr = curr.next.next if curr.next else None # 如果当前节点(原节点)有next,则移动到下一个原节点;否则,退出循环
# 步骤3: 分离链表并恢复原链表
dummy = Node(0) # 创建一个哑节点,用于作为新链表的头节点的前驱
prev = dummy # prev用于追踪新链表的最后一个节点
curr = head # 从头节点开始遍历交错链表
while curr:
currCopy = curr.next # curr.next是当前原节点的副本
curr.next = currCopy.next # 恢复原链表的next指针
prev.next = currCopy # 将新链表的最后一个节点的next指向当前副本节点,构建新链表
prev = currCopy # 更新prev为当前副本节点,以便连接到下一个副本节点
curr = curr.next # 移动到原链表的下一个节点
return dummy.next # 返回新链表的头节点(哑节点的下一个节点)
# Definition for singly-linked list.
# class ListNode(object):
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution(object):
def sortList(self, head):
"""
:type head: ListNode
:rtype: ListNode
"""
def sortFunc(head: ListNode, tail: ListNode) -> ListNode:
if not head:
return head
slow = fast = head
while fast != tail:
slow = slow.next
fast = fast.next
if fast != tail:
fast = fast.next
mid = slow
return merge(sortFunc(head, mid), sortFunc(mid, tail))
def merge(head1: ListNode, head2: ListNode) -> ListNode:
dummyHead = ListNode(0)
temp, temp1, temp2 = dummyHead, head1, head2
while temp1 and temp2:
if temp1.val <= temp2.val:
temp.next = temp1
temp1 = temp1.next
else:
temp.next = temp2
temp2 = temp2.next
temp = temp.next
if temp1:
temp.next = temp1
else temp2:
temp.next = temp2
return dummyHead.next
return sortFunc(head, None)
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* mergeKLists(vector<ListNode*>& lists) {
// 如果链表数组为空,则直接返回空指针
if (lists.size() == 0) return {};
// 使用lambda表达式定义一个最小堆的比较函数
priority_queue pq(lists.end(), lists.end(), [](ListNode* l, ListNode* r){return l->val > r->val;});
// 将所有非空链表的头节点加入堆中
for (const auto& node: lists) if (node) pq.push(node);
// 创建一个虚拟头节点,用于简化边界条件的处理
ListNode* dummy = new ListNode;
ListNode* curr = dummy;
// 当堆不为空时,持续合并链表
while(!pq.empty()) {
// 取出堆顶元素(当前最小值)
curr->next = pq.top();
pq.pop();
// 移动当前指针
curr = curr->next;
// 如果取出的节点有下一个节点,则将其加入堆中
if (curr->next) pq.push(curr->next);
}
// 返回合并后的链表的头节点(跳过虚拟头节点)
return dummy->next;
}
};
class DLinkedNode:
def __init__(self, key=0, value=0):
self.key = key # 节点存储的键
self.value = value # 节点存储的值
self.prev = None # 指向前一个节点的指针
self.next = None # 指向后一个节点的指针
class LRUCache:
def __init__(self, capacity: int):
self.cache = dict() # 用于存储键和对应节点的映射
self.head = DLinkedNode() # 虚拟头节点
self.tail = DLinkedNode() # 虚拟尾节点
self.head.next = self.tail # 头节点指向尾节点,表示空链表
self.tail.prev = self.head # 尾节点指回头节点
self.capacity = capacity # 缓存的容量
self.size = 0 # 当前缓存中的节点数
def get(self, key: int) -> int:
if key not in self.cache: # 如果缓存中不存在该键
return -1 # 返回-1表示未找到
node = self.cache[key] # 获取对应的节点
self.moveToHead(node) # 将该节点移动到链表头部,表示最近被访问
return node.value # 返回节点的值
def put(self, key: int, value: int) -> None:
if key not in self.cache: # 如果缓存中不存在该键
node = DLinkedNode(key, value) # 创建新节点
self.cache[key] = node # 将新节点加入缓存映射
self.addToHead(node) # 将新节点添加到链表头部
self.size += 1 # 缓存大小加1
if self.size > self.capacity: # 如果超出容量
removed = self.removeTail() # 移除链表尾部的节点
self.cache.pop(removed.key) # 从缓存映射中移除该键
self.size -= 1 # 缓存大小减1
else: # 如果缓存中已存在该键
node = self.cache[key] # 获取对应的结点
node.value = value # 更新节点的值
self.moveToHead(node) # 将节点移动到链表头部,表示最近被访问
def addToHead(self, node):
node.prev = self.head # 新节点的前一个节点指向头节点
node.next = self.head.next # 新节点的后一个节点指向头节点的下一个节点
self.head.next.prev = node # 更新头节点的下一个节点的前一个节点为新节点
self.head.next = node # 更新头节点的下一个节点为新节点
def removeNode(self, node):
node.prev.next = node.next # 更新前一个节点的后一个节点为新节点的后一个节点
node.next.prev = node.prev # 更新后一个节点的前一个节点为新节点的前一个节点
def moveToHead(self, node):
self.removeNode(node) # 先从链表中移除节点
self.addToHead(node) # 再将节点添加到链表头部
def removeTail(self):
node = self.tail.prev # 获取链表尾部的实际节点(跳过虚拟尾节点)
self.removeNode(node) # 从链表中移除该节点
return node # 返回被移除的节点
# Your LRUCache object will be instantiated and called as such:
# obj = LRUCache(capacity)
# param_1 = obj.get(key)
# obj.put(key,value)