leetcode206单链表的翻转
题目:
给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
示例 1:
输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]
示例 2:
输入:head = [1,2]
输出:[2,1]
示例 3:
输入:head = []
输出:[]
提示:
链表中节点的数目范围是 [0, 5000]
-5000 <= Node.val <= 5000
进阶:链表可以选用迭代或递归方式完成反转。你能否用两种方法解决这道题?
思路
今天听左神的课,他说的一个观点蛮有意思:链表的题一律按笔试和面试练。笔试就是快速通过就行,而面试需要有较低的时间复杂度才能突出。
这题很容易想的就是用辅助栈,然后再把元素串起来,但空间复杂度是O(n)。
在题目最后说到了迭代和递归两种方法。迭代是利用旧值推出新值,是在函数内某段代码实现循环,递归是不断的调用函数自身实现循环。
1.双指针的方法(迭代法)
1->2->3->null 要变成 3->2->1->null
初始一个前指针pre为None,当前指针cur为head。遍历的时候,如要把2指向1,这时候2与3就会断开,所以先用after存放cur.next,然后把cur的指针指向pre。pre和next都往下移一个,pre=cur,cur=after。最后pre指向3,cur指向空。返回pre就是翻转后的单链表。
时间复杂度是O(n),空间复杂度是O(1)。
双指针代码
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
pre = None
cur = head
while cur:
temp = cur.next
cur.next = pre
pre = cur
cur = temp
return pre
2.递归的方法
递归三要素,参数、终止条件和单层递归逻辑。
首先定义递归函数reverse(pre,cur),里面的参数传两个pre和cur。终止条件就是cur为空,返回pre。单层递归逻辑和双指针一样,先用一个变量after保存cur.next,然后cur指向pre。调用递归函数reverse(cur,after)。
在主函数中调用的时候pre为None,cur为head。
时间复杂度是O(n),空间复杂度是O(n)。空间复杂度取决于递归调用的栈空间,最多是n层。
递归代码:
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
def reverse(pre, cur):
if not cur:
return pre
after = cur.next
cur.next = pre
return reverse(cur, after)
return reverse(None, head)
92. 反转链表 II
题目:
给你单链表的头指针 head 和两个整数 left 和 right ,其中 left <= right 。请你反转从位置 left 到位置 right 的链表节点,返回 反转后的链表 。
示例 1:
输入:head = [1,2,3,4,5], left = 2, right = 4
输出:[1,4,3,2,5]
示例 2:
输入:head = [5], left = 1, right = 1
输出:[5]
提示:
链表中节点数目为 n
1 <= n <= 500
-500 <= Node.val <= 500
1 <= left <= right <= n
进阶: 你可以使用一趟扫描完成反转吗?
思路和代码
8月份刷的时候应该是没有刷出来,提交的代码是官网上的,再刷还是不会。思路有,代码细节处理不好(coding能力不行)。
思路一:
借用上一题的翻转,先遍历一次得到翻转前部分的链表+要翻转的链表部分+后半截链表。对要翻转的链表进行翻转之后再把三节串起来。
代码:
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def reverseBetween(self, head: Optional[ListNode], left: int, right: int) -> Optional[ListNode]:
# 翻转单链表
def reverse(head):
pre, cur = None, head
while cur:
temp = cur.next
cur.next = pre
pre = cur
cur = temp
dummy = ListNode(-1)
dummy.next = head
start = dummy
for _ in range(left-1):
start = start.next
cur = start
for _ in range(right - left + 1):
cur = cur.next
# 得到需要翻转的链表 注意cur.next=None 把后半段割掉之后再翻转
verse = start.next
# 得到最后面的链表部分
after = cur.next
# 得到前一部分链表
start.next = None
cur.next = None
# 翻转
reverse(verse)
# 三段链表连起来
start.next = cur
verse.next = after
return dummy.next
思路二:
上面的方法如果left和right各在两边,那就需要两次遍历,下面这种方法一次遍历即可。
核心思想就是每遍历一个需要翻转的结点,就把这个结点查到要反转的第一个结点位置。例如遍历到5,就把5放在7后面。遍历到4的时候,就把4放到7的后面,那怎么做呢?
需要三个指针,如上图需要把5拉倒2前面(感觉图画的已经很清楚了)。
代码:
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def reverseBetween(self, head: Optional[ListNode], left: int, right: int) -> Optional[ListNode]:
dummy = ListNode(-1)
dummy.next = head
pre = dummy
for _ in range(left-1):
pre = pre.next
cur = pre.next
for _ in range(right - left):
after = cur.next
cur.next = after.next
# after.next = cur 错
after.next = pre.next
pre.next = after
return dummy.next