菜鸡的刷题记录,基础知识不会写太多,有时间会写专题复习基础知识。第一轮刷题,所以解法代码可能都比较冗余/难看,主要是追求先有思路和会写。
更多优雅代码请参考解题区或评论区的大佬~
链表,是线性表的链式存储结构。一个链表中有若干个结点,每个结点都包含数据域和地址域两部分。数据域用于存储元素,地址域用于存储前驱或后继的地址。
做链表题有一个很重要的点,就是在一开始不熟悉的时候要勤动手!多画!把过程画出来!
思路: 这道题目的思路比较简单,就是修改指针的指向。画图就能比较直观的看出来。
具体实现如下:
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def reverseList(self, head: ListNode) -> ListNode:
"""迭代解法"""
newhead = None
while head:
next_node = head.next
head.next = newhead
newhead = head
head = next_node
return newhead
思路: 这一题与上一题不同点在于它是只要求反转链表中间的一段。在这里我们有四个关键的结点的处理需要注意,分别是反转段头结点的前驱、反转段头结点、反转段尾节点以及反转段尾节点的后继。
我们来画图看一下:(鼠标画图好累,自带的画图工具好难用,俺要去瞧瞧有啥好用的画图软件了…先用纸上画的凑合一下…)
通过画图我们可以看到,除正常反转操作外有两个需要特别处理的结点:
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def reverseBetween(self, head: ListNode, m: int, n: int) -> ListNode:
"""用于标记四个特殊节点"""
modify_prev_node = None
modify_head_node = head
modify_tail_node = None
modeify_next_node = None
"""先定位到要反转的位置"""
for _ in range(m-1):
modify_prev_node = modify_head_node
modify_head_node = modify_head_node.next
"""进行反转链表操作,这里设置许多临时变量是为了防止将前面四个结点的定位改变"""
change_len = n-m+1
new_head = modify_head_node
prev_head = None
next_head = None
for _ in range(change_len):
next_head = new_head.next
new_head.next = prev_head
prev_head = new_head
new_head = next_head
"""反转完成后对四个特殊节点做处理"""
modify_tail_node = prev_head
modeify_next_node = new_head
modify_head_node.next = modeify_next_node
"""这里是为了处理m=1时的特例"""
if modify_prev_node:
modify_prev_node.next = modify_tail_node
else: head = prev_head
return head
这一题会用到一个很常见也很巧妙的方法——双指针。双指针在很多需要在常数/O(n)时间内求解都有很好的应用。
我们用图来看看双指针的解法:
根据这个思路写一份非常直接粗暴的代码如下:
class Solution:
def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
l1, l2 =0, 0
a, b = headA, headB
p1, p2 = headA, headB
# 获取链表长度——遍历链表,时间复杂度O(n)
while a:
a = a.next
l1 += 1
while b:
b = b.next
l2 += 1
# 较长链表指针先向后移动
if l1 > l2:
for i in range(l1-l2):
p1 = p1.next
else:
for i in range(l2-l1):
p2 = p2.next
while p1:
if p1 == p2: return p1
else:
p1 = p1.next
p2 = p2.next
return None
首先我们要先判断这个链表有无环(这里与leetcode141一样)。在上一个例题中我们说了双指针的用法是非常常见的,在这里我们同样用双指针来解题。比如我们在环形跑道跑步过程中,有些人跑得快有些人跑得慢,在跑的圈数足够多的情况下,跑得快的一定会追上跑得慢的。我们用双指针来判断有无环也是一样。假设有一个跑得快的指针和一个跑得慢的指针,如果链表有环,这两个指针一定会相遇。
但是快慢指针相遇的点不一定是环的起点, 因此我们判断环的起点还需要一些其他的方法。在这里我们需要用到一些数学计算,具体我们来看看图解:
通过计算我们可以看到,当一个指针从头结点出发,另一个指针从相遇结点相同速度出发,相遇的结点就是环的起点。
根据以上分析代码如下:
class Solution:
def detectCycle(self, head: ListNode) -> ListNode:
fast, slow = head, head
while True:
if not (fast and fast.next): return None
fast, slow = fast.next.next, slow.next
if fast == slow: break
fast = head
while fast != slow:
slow, fast = slow.next, fast.next
return fast
思路: 与前两题不同,这个不需要用到双指针来进行解题。但是这里会用到另一种在解决链表问题中常用到的解法——引入头结点/哨兵节点。比如说在链表中插入元素的时候,引入一个哨兵节点就能巧妙解决头结点插入需要差异处理的问题。关于这一题头节点的应用参考下图:
具体实现如下:
class Solution:
def partition(self, head: ListNode, x: int) -> ListNode:
# 初始化两个头节点
less_head = less_ptr = ListNode(0)
more_head = more_ptr = ListNode(0)
while head:
if head.val < x:
less_ptr.next = head
less_ptr = less_ptr.next
else:
more_ptr.next = head
more_ptr = more_ptr.next
head = head.next
more_ptr.next = None
# 将两个链表连接起来
less_ptr.next = more_head.next
return less_head.next
先来补充说明一下深拷贝和浅拷贝
深拷贝 (deepCopy) : 在计算机中开辟一个新的内存,存放复制对象。在修改原对象时复制对象不会被改变。
浅拷贝 (shallowCopy) : 在计算机中开辟一个新的内存,存放引用。在修改原对象时复制对象会被改变。
思路:
这一题的难点在于:
拿到这个题目一开始并没有思路,在解题区看到一个大佬解题,详见这里。
class Solution:
def copyRandomList(self, head: 'Node') -> 'Node':
# 1.复制原链表的每一个结点
new_head = head
while new_head:
tmp = Node(new_head.val, new_head.next, None)
new_head.next = tmp
new_head = tmp.next
new_head = head
# 2.复制随机指针(指向原本随机指针指向结点的next)
while new_head:
if new_head.random:
new_head.next.random = new_head.random.next
new_head = new_head.next.next
# 3.将原链表结点和复制的链表结点分开
copy_head = Node(-1, None, None)
new_head = head
curr = copy_head
while new_head:
curr.next = new_head.next
curr = curr.next
new_head.next = curr.next
new_head = new_head.next
return copy_head.next
关于这题的更多解法参见解题区。
思路: 这一题是一个简单题,思路也很简单,就是不断比较两个链表结点的大小,将小的依次连接到新链表上即可。
一种最直接的解法就是利用双指针,很熟悉吧!
用一个指向l1的指针p1和一个指向l2的指针p2,遍历链表,不断比较两个指针指向结点的大小,将节点值较小的连接到新链表上。
下面是python实现:
class Solution:
def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode:
new_head = ListNode(0)
# tmp是指向合并后结点的指针
tmp = new_head
while l1 and l2:
if l1.val < l2.val:
tmp.next = l1
l1 = l1.next
tmp = tmp.next
else:
tmp.next = l2
l2 = l2.next
tmp = tmp.next
if l1:
tmp.next = l1
if l2:
tmp.next = l2
return new_head.next
当然这题还有递归解法,目前还不是很理解,所以先不放上来,可以参考解题区。
链表就先到这里啦!后面再去刷更多的链表题去巩固~
本专题(算法刷题)都是看b站这个视频刷的~一起加油呀!