题目
删除链表中的节点
请编写一个函数,使其可以删除某个链表中给定的(非末尾)节点,你将只被给定要求被删除的节点。
现有一个链表 – head = [4,5,1,9],它可以表示为:
示例 1:
输入: head = [4,5,1,9], node = 5
输出: [4,1,9]
解释: 给定你链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9.
示例 2:
输入: head = [4,5,1,9], node = 1
输出: [4,5,9]
解释: 给定你链表中值为 1 的第三个节点,那么在调用了你的函数之后,该链表应变为 4 -> 5 -> 9.
说明:
链表至少包含两个节点。
链表中所有节点的值都是唯一的。
给定的节点为非末尾节点并且一定是链表中的一个有效节点。
不要从你的函数中返回任何结果
方法:与下一个节点交换
从链表里删除一个节点 node 的最常见方法是修改之前节点的 next 指针,使其指向之后的节点。
因为我们无法访问我们想要删除的节点之前的节点,我们始终不能修改该节点的 next
指针相反,我们必须将想要删除的节点的值替换为它后面节点中的值,然后删除它之后的节点。
因为我们知道要删除的节点不是列表的末尾,所以我们可以保证这种方法是可行的
# Definition for singly-linked list.
# class ListNode(object):
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution(object):
def deleteNode(self, node):
node.val = node.next.val
node.next = node.next.next
删除链表的倒数第N个节点
给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。
示例:
给定一个链表: 1->2->3->4->5, 和 n = 2.
当删除了倒数第二个节点后,链表变为 1->2->3->5.
说明:
给定的 n 保证是有效的。
进阶:
你能尝试使用一趟扫描实现吗?
题目有个提示,这个问题可以用一趟扫描就能解决。
其实初级的思想就是扫描两次,第一次是为了知道这个链表有多长,从而确定要删去的节点在链表里面的位置是哪里
然而更好的方法是用两个指针去实现,左边的指针和右边的指针距离为n,当右边的指针到链表的尾部时,左边指针的对象.next的对象,就是我们要删去的节点。而这个方法之需要扫描一次就可以了
这个问题还有一个需要注意的点,就是头节点和普通的节点删除的方法不同的,要处理好
# Definition for singly-linked list.
# class ListNode(object):
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution(object):
def removeNthFromEnd(self, head, n):
tmp1 = head
tmp2 = head
# 左指针和右指针的距离是n
for i in range(n):
tmp2 = tmp2.next
# 删去的是头节点
if tmp2 == None:
head = tmp1.next
# 删去的是普通节点
else:
while tmp2.next != None:
tmp1 = tmp1.next
tmp2 = tmp2.next
# 删除的过程:释放被删除节点所占用的内存
del_node = tmp1.next
tmp1.next = del_node.next
del del_node
# 把链表通过数组的形式录入
a = []
while head != None:
a.append(head.val)
head = head.next
return a
合并两个有序链表
将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例:
输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4
这个就是一个链表实现的归并排序的merge函数
甚至还要更简单,因为当其中一个链表已经遍历完以后,不需再去新增节点了,直接让尾指针指向还没有遍历完的链表的剩下的部分即可~
# Definition for singly-linked list.
# class ListNode(object):
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution(object):
def mergeTwoLists(self, l1, l2):
"""
:type l1: ListNode
:type l2: ListNode
:rtype: ListNode
"""
head = ListNode(-1) # 虚头指针
tmp = head
while l1 and l2 :
if l1.val <= l2.val:
tmp.next = ListNode(l1.val)
tmp = tmp.next
l1 = l1.next
else:
tmp.next = ListNode(l2.val)
tmp = tmp.next
l2 = l2.next
if l2:
tmp.next = l2
else:
tmp.next = l1
return head.next
其实这样做空间复杂度不是最优的哦,因为还要创建对象,更优化的方法是直接改变链表中节点next指针的指向,充分利用了链表灵活性的特点,改动的地方:
while l1 and l2 :
if l1.val <= l2.val:
tmp.next = l1
tmp = tmp.next
l1 = l1.next
else:
tmp.next = l2
tmp = tmp.next
l2 = l2.next
回文链表
请判断一个链表是否为回文链表。
示例 1:
输入: 1->2
输出: false
示例 2:
输入: 1->2->2->1
输出: true
进阶:
你能否用O(n) 时间复杂度
和O(1) 空间复杂度
解决此题?
- 我自己的思路是新建链表,然后双指针判断
- 但是题解上面用的是数组:先遍历链表,然后把value依次填入数组中,最后和数组中判断回文序列相同的方法。这个方法的好处是只需要记录value而不需要记录next,因为数组是有序的,同时创建对象的过程省去之后,实际上也会使得running time减小~
class Solution(object):
def isPalindrome(self, head):
"""
回文链表:
创建一个新的链表,指针是反向的,比如
原来的:1 -> 2 -> 3 -> 4
新建的:1 <- 2 <- 3 <- 4
然后两个链表分别从自己的头节点开始扫描,进行逻辑判断
"""
if head == None or head.next == None: return True
ptr1 = head
ptr2 = None
while ptr1:
tmp = ListNode(ptr1.val)
tmp.next = ptr2
ptr2 = tmp
ptr1 = ptr1.next
ptr1 = head
while True:
if ptr2.val != ptr1.val:
return False
ptr2 = ptr2.next
ptr1 = ptr1.next
if ptr1 == None or ptr2 == None:
return True
然鹅一切并没有结束:我们只实现了O(n) 时间复杂度
,那O(1) 空间复杂度
呢?
- 使用快慢指针找中点:原理是利用fast和slow两个指针,每次快指针走两步,慢指针走一步,等快指针走完时,慢指针的位置就是中点。
- 然后利用原地逆置的方法(空间复杂度0(1)) 将后半段逆置,就可以与前半段相比较了
class Solution(object):
def isPalindrome(self, head):
fast = slow = head
# 快慢指针找中点
while fast:
if fast.next:
fast = fast.next.next
else:
fast = fast.next
slow = slow.next
tail = self.reverse(slow)
"""
测试reverse函数:
while tail:
print (tail.val)
tail = tail.next
"""
# 双指针比较左两边
while tail and head:
if tail.val != head.val:
return False
tail = tail.next
head = head.next
return True
# 原地逆置,空间复杂度o(n)
def reverse(self, s):
p = t = None
while s:
t = s.next
s.next = p
p = s
s = t
return p
环形链表
给定一个链表,判断链表中是否有环。
为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。
示例 1:
输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。
示例 2:
输入:head = [1,2], pos = 0
输出:true
解释:链表中有一个环,其尾部连接到第一个节点。
示例 3:
输入:head = [1], pos = -1
输出:false
解释:链表中没有环。
进阶:
你能用 O(1)(即,常量)内存解决此问题吗?
# 方法一: 用时80ms,内存消耗14.2 MB
class Solution(object):
"""
使用原地逆置的方法做的,遍历链表,然后【把指针的方向取反】
1. 如果有环:我们一定可以回到头指针
2. 如果没有环:我们会碰到空指针
时间复杂度o(n),空间复杂度o(1)
"""
def hasCycle(self, head):
pre = nex = None
cur = head
while cur:
if (cur.next == head): return True
nex = cur.next
cur.next = pre
pre = cur
cur = nex
return False
# 方法二:用时56 ms,内存消耗17.1 MB
class Solution(object):
"""
用快慢指针的方法
1. 如果有环:快指针一定会追上慢指针
2. 如果没有环:快指针的后面为空
时间复杂度o(n),空间复杂度o(1)
"""
def hasCycle(self, head):
if head == None or head.next == None: # 考虑特殊情况,确保fast、slow不是None
return False
fast = head.next
slow = head
while slow != fast:
# 注意这里判断的顺序不能交换
if fast == None or fast.next == None:
return False
fast = fast.next.next
slow = slow.next
return True
总结
- 至此结束。此类题目不算特别难,都是很基础性的问题
- 学到了两个有用的新方法
- 快慢指针
- 原地逆置
- 在处理链表的时候一定要考虑边界问题(头指针、尾指针)
- 注意利用其他数据结构:字典、数组等帮助解决链表相关的问题