链表题目
- 一、删除链表中的节点,且只能访问该节点
- 二、找到链表当中的中间节点
- 三、判断一个链表是否有环
- 四、给定一个循环链表,找到环的开始节点
- 五、删除一个链表中的倒数第N个节点
- 六、给定一个链表,分成两个链表
一、删除链表中的节点,且只能访问该节点
只能访问该节点的话,那该节点的上一个节点我们是无法访问的。一般我们的思路是这样的,如果我们要删除节点b,那么我们需要用a节点的next指向b节点next指向的c节点,那么就做好了删除节点的操作了,被删除的节点会被python的垃圾回收机制处理
但是,现在的问题是,我们没有办法访问到a节点,只能从b节点向后访问,那么就需要我们换一个角度了
将c节点的值赋值给b节点,然后将b节点的next指向c的next,这样间接的实现了删除b节点的操作
思路有了,代码的实现是比较简单的
def remove_node(node):
print(node.value)
node.value = node.next.value
node.next = node.next.next
print(node.value)
while node.next != None:
node = node.next
print(node.value)
二、找到链表当中的中间节点
中间节点的查找我们可能第一反应就是获取链表的长度,然后折半,这样就能找到中间节点了,这个思路是没问题的,但是有时候条件可能不是那么友好,如果加入一个限制条件,不允许我们使用链表的长度。这应该怎么办呢?
我们可以设置两个pointer,第一个pointer移动的距离是第二个pointer的2倍,那么,当第一个pointer遍历完整个链表之后,第二个pointer正好到达中间节点,此时输出第二个pointer
上代码
def find_middle_node(node):
assert node.head.next is not None and node.head is None
a1 = node.head # 第一个pointer
b1 = node.head # 第二个pointer
while a1.next != None and a1.next.next != None:
a1 = a1.next.next
b1 = b1.next
print(b1.value)
三、判断一个链表是否有环
一个链表有环的情况无非就是两种
思路:可以准备两个pointer,第一个pointer走的快,第二个pointer走的慢,当第一个pointer追上第二个pointer的时候,说明有环,为什么呢?当pointer的next为None的时候,说明链表已经走完,此时一定无环.
上代码:
def has_cycle(node):
if node is None:
print('has no cycle')
fast = node
slow = node
while fast != None and fast.next != None:
fast = fast.next.next
slow = slow.next
if fast == slow :
print('has cycle!')
break
node1 = Node(1)
node2 = Node(10)
node3 = Node(6)
node1.next = node2
node2.next = node3
node3.next = node1
has_cycle(node1)
node1 = Node(10)
node2 = Node(20)
node3 = Node(50)
node1.next = node2
node2.next = node3
node3.next = node2
has_cycle(node1)
node1 = Node(102)
node2 = Node(204)
node3 = Node(504)
node1.next = node2
node2.next = node3
has_cycle(node1)
结果
四、给定一个循环链表,找到环的开始节点
一个循环链表的环的判定,有两种情况:
左侧是情况1,开始节点和结束节点是重合的
右侧是情况2,环的开始节点在链表的中间部分
情况1的思路跟上一道题的思路是一样的,可以准备两个pointer,fast_pointer每次走两个,slow_pointer每次走一个,当fast_pointer追上slow_pointer的时候,就找到了环的开始节点了
下面附上情况1的代码:
def find_the_node_of_beginCycle(node):
fast = node
slow = node
while fast is not None and fast.next is not None:
fast = fast.next.next
slow = slow.next
if fast == slow:
return fast
break
n1 = Node(10)
n2 = Node(20)
n3 = Node(4)
n4 = Node(5)
n1.next , n2.next, n3.next, n4.next = n2, n3, n4, n1
res = find_the_node_of_beginCycle(n1)
print('the begin node value : %s'%res.value)
情况2的思路需要好好分析分析了
假设情况2的链表当中环的开始节点在a点,链表开始节点到环的开始节点距离是k,那么当slow_pointer走到a点的时候,fast_pointer已经走到了距离a点向后的k个距离处,那么什么时候相遇呢?如下图所示:
首先,我们清楚的是fast_pointer的速度是slow_pointer的两倍,当fast_pointer转了一圈的之后,slow_pointer只走了半圈,这个时候是无法相遇的,那么就需要继续向后再走,当slow_pointer走完剩下的半圈的之后,fast_pointer又走了一圈,那么在这期间两个pointer相遇了,那相遇的点在哪呢?在上图所示的b点,距离a点为k.
当slow_pointer走完一圈,fast_pointer走了两圈之后,这两个指针回到原来的位置,此时slow_pointer和fast_pointer相差k,从当前状态开始,让slow_pointer和fast_pointer保持之前的速度向反方向移动,那么当slow_pointer走到b点的时候经过k个距离,fast_pointer也走到了b点,距离为2k,正好相遇,此时,整个链表中会出现三个k值
这个时候,将slow_pointer挪到起始点,fast_pointer在b点不动,两个pointer都以一个速度依次向前遍历,当两个pointer相遇的时候就找到了链表的环的开始节点
下面附上情况2的代码:
def find_the_node_of_beginCycle(node):
fast = node
slow = node
while fast != None and fast.next != None:
fast = fast.next.next
slow = slow.next
if fast == slow:
fast = node
break
while fast != slow:
fast = fast.next
slow = slow.next
return slow
n1 = Node(10)
n2 = Node(20)
n3 = Node(4)
n4 = Node(5)
n5 = Node(44)
n6 = Node(7)
n1.next = n2
n2.next = n3
n3.next = n4
n4.next = n5
n5.next = n6
n6.next = n3
res = find_the_node_of_beginCycle(n1)
print('the begin node value:%s'%res.value)
五、删除一个链表中的倒数第N个节点
第一印象的话我们可能会去想得到链表的size,然后用size-n获取它的位置,通过指向要删除的节点的next来删除倒数第N个节点。但是如果不能获取size呢?
还是那个思路,准备两个pointer,fast_pointer先走N个节点之后slow_pointer开始走,当fast_pointer走完链表之后,slow_pointer的位置就是要删除的节点
上代码:
def delete_the_nth_to_last(list, n):
fast = list.head
slow = list.head
while n > 0:
fast = fast.next
n -= 1
while fast.next is not None:
fast = fast.next
slow = slow.next
res = slow
res.next = slow.next.next
list.size -= 1
L = LinkedList()
L.insert_first(4)
L.insert_first(23)
L.insert_first(66)
L.insert_first(89)
L.insert_first(75)
L.print_linkedList()
delete_the_nth_to_last(L, 2)
L.print_linkedList()
六、给定一个链表,分成两个链表
如果是偶数个节点直接对半即可,如果是奇数个节点,返回靠前的节点位置
分成两个链表的话需要三个pointer,fast_pointer遍历的速度是slow_pointer的2倍,mid_pointer的速度和slow_pointer是一致的,当fast_pointer遍历完链表之后,slow_pointe和mid_pointer也就到达了链表的中间位置,然后创建一个新的链表L1,将L1的next指向左侧的链表,再创建一个新的链表L2,将L2的next指向mid_pointer.next,mid_pointer.next指向None.
下图所示的链表的操作是偶数个节点的,奇数个节点同理
代码如下:
def split_LinkedList(node):
fast = node
slow = node
mid = slow
while fast is not None:
mid = slow
slow = slow.next
fast = fast.next.next if fast.next is not None else None
left = node
right = mid.next
mid.next = None
return left, right
n1 = Node(10)
n2 = Node(20)
n3 = Node(4)
n4 = Node(5)
n5 = Node(44)
n6 = Node(7)
n1.next = n2
n2.next = n3
n3.next = n4
n4.next = n5
n5.next = n6
left_node = Node()
right_node = Node()
left_node, right_node = split_LinkedList(n1)
L1 = LinkedList()
L1.head.next = left_node
L1.print_linkedList()
L2 = LinkedList()
L2.head.next = right_node
L2.print_linkedList()