leetcode 206 反转链表
示例:
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
解题思路:
把当前的结点(cur)的next指针指向它的前驱结点,需要两个指针去记录,一个是当前结点,一个是前驱结点,循环所有的结点去反转指针即可。此为双指针迭代法
代码思路:
- head为链表的头结点,把它设为当前指针,然后设置头结点的前驱结点prev为None,使用while循环链表,重点是关注3个变量的指针变化! 注 意 这 里 的 赋 值 是 同 时 操 作 的 \color{red}{注意这里的赋值是同时操作的} 注意这里的赋值是同时操作的,我是这样理解的,
- cur.next = prev , cur的next指针指向prev
- cur = cur.next , cur指针指向下一个结点,即cur.next
- prev = cur , prev指向下一个结点,即cur
动图演示
由此可见,其实是一个指针指向变换的过程,可以看下面动图理解:(来自leetcode)
代码:
# Definition for singly-linked list.
# class ListNode(object):
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution(object):
def reverseList(self, head):
cur,prev = head,None
#高效的做法:
while cur:
cur.next,cur,prev = prev,cur.next,cur
return prev
#常见做法:
# 记录当前节点的下一个节点
tmp = cur.next
# 然后将当前节点指向pre
cur.next = pre
# pre和cur节点都前进一位
pre = cur
cur = tmp
复 杂 度 分 析 : \color{red}{复杂度分析:} 复杂度分析:
假设 n 是列表的长度,时间复杂度是 O(n);空间复杂度:O(1)
进阶:使用递归进行反转链表
递归解法比较难理解,不过递归始终只要关注两个关键即可:
- 定义一个终止条件,符合条件才继续调用,否则返回
- 找到本级函数和下一级函数的等价条件
这条题目的两个条件:
- 终止条件:当前结点或下一结点是否等于null,是就返回
- 函数内部改变结点指向,head的下一个结点指向head
可看动图理解:(从leetcode可找到,感谢画图者贡献)
head.next.next=head指的是head节点的下一个结点的next指针指向 head
如head=4,head.next就是5,head.next.next就是空,则5->4
class Solution(object):
def reverseList(self, head):
"""
:type head: ListNode
:rtype: ListNode
"""
# 递归终止条件是当前为空,或者下一个节点为空
if head == None or head.next == None :
return head
cur = self.reverseList(head.next) #cur为最后一个节点
head.next.next = head
#防止链表循环,要将head.next设为空
head.next = None #即4->5被删除了
return cur
复
杂
度
分
析
:
\color{red}{复杂度分析:}
复杂度分析:
假设 nn是列表的长度,那么时间复杂度为 O(n)
由于使用递归,将会使用隐式栈空间。递归深度可能会达到 n 层。,空间复杂度为O(n)
leetcode 24 两两交换链表中的节点
示例:
给定 1->2->3->4, 你应该返回 2->1->4->3.
提示(还有一题是按照固定范围交换结点的,可以去找找)
解题思路:
- 迭代法。找到一个空的节点,连接head,循环链表,若节点为空或者下一个结点为空,那么说明当前没有结点或只有一个节点,无法进行交换
代码思路:
这里使用了个trick,直接使用self代替了新的空节点,只要带有next就好,没什么差别,a代表前一个结点,b代表后一个结点,然后替换结点,更新pre结点到交换后的位置,继续判断。返回空节点的next就是交换后的链表。
- 看图说话(自己画的):
代码:
def swapPairs(self,head):
pre,pre.next = self,head
while pre.next and pre.next.next:
a = pre.next
b = a.next
pre.next,b.next,a.next = b,a,b.next
pre = a #更新pre指针
return self.next
复
杂
度
分
析
:
\color{red}{复杂度分析:}
复杂度分析:
时间复杂度为O(n),空间复杂度为O(1)
递归做法
解题思路:还是交换两个点,本节点和它的next结点,用上面的图解释,假如只有三个结点[a,b,c],a就是head,b是next_node
head.next = self.swapPairs(next_node.next) 这一句就是a指向c(上图的第三步),因为c后面是None,函数传入了next_node.next即c,所以函数直接返回的是c,next_node.next = head就是b指向a,这样就是b->a->c啦!
class Solution(object):
def swapPairs(self, head: ListNode) -> ListNode:
"""
:type head: ListNode
:rtype: ListNode
"""
#终止条件
if head == None or head.next==None:
return head
#被交换的两个点是head和next_node
next_node = head.next #第二个结点
#交换过程
head.next = self.swapPairs(next_node.next)
next_node.next = head
#现在的head是second_node
return next_node
复
杂
度
分
析
:
\color{red}{复杂度分析:}
复杂度分析:
时间复杂度为O(n),空间复杂度为O(n)
leetcode141.环形链表(判断是否有环)
示例:使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。如果有环,pos的值为环所在的结点
输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。
输入:head = [1,2], pos = 0
输出:true
解释:链表中有一个环,其尾部连接到第一个节点。
- 解题思路:快慢指针法。快指针走两步,慢指针走一步,如果有环总会遇到的。
class ListNode(object):
def __init__(self, x):
self.val = x
self.next = None
class Solution:
def hasCycle(self, head: ListNode) -> bool:
if not head:
return head
slow=fast=head
while slow and fast and fast.next:
slow = slow.next
fast = fast.next.next
if slow is fast :
return True
return False
a = ListNode(1)
b = ListNode(2)
c = ListNode(31)
d = ListNode(4)
a.next =b
b.next =c
c.next = d
d.next = b
s = Solution()
x = s.hasCycle(a)
print(x)
复
杂
度
分
析
:
\color{red}{复杂度分析:}
复杂度分析:
时间复杂度为O(n),
空间复杂度:我们只使用了慢指针和快指针两个结点,所以空间复杂度为 O(1)
暴力破解法
通常人是用比较暴力的思维,读取整个链表,然后放进一些容器中,例如列表、字典、哈希表,然后判断是否有重复值,就知道有没有环了。
- 解题思路:这里推荐使用Set(集合),因为比较快,为什么?set去重有两个函数:hash和eq;当两个变量的hash值不同则认定为不相同,若哈希值一样,调用eq函数,当返回True则认为相同,应该去除其中一个变量
class Solution:
def hasCycle(self, head: ListNode) -> bool:
id_list = set()
if not head: return False
while head.next:
if head in id_list: return True
id_list.add(head)
head = head.next
return False
复
杂
度
分
析
:
\color{red}{复杂度分析:}
复杂度分析:
时间复杂度:O(n)对于含有 n 个元素的链表,我们访问每个元素最多一次。添加一个结点到哈希表中只需要花费 O(1)的时间。
空间复杂度:O(n),空间取决于添加到哈希表中的元素数目,最多可以添加 n个元素。