1.双指针法
文章目录
写在前面
本系列笔记主要作为笔者刷题的题解,所用的语言为Python3
,若于您有助,不胜荣幸。
在这个章节里,总结一下一些典型的利用双指针法来解决的题目,做到查漏补缺。
1.1移除元素
给你一个数组 nums
和一个值 val
,你需要 原地 移除所有数值等于 val
的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1)
额外空间并 原地 修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
思路:面对这道题目,很容易想到的一个方法是暴力法,使用两个循环来进行遍历,第一个循环用于查找目标值,第二个循环用来更新数组,这样的时间复杂度为 O ( n 2 ) \mathcal{O}(n^2) O(n2)。另外一个方法是使用双指针,我们可以使用一个快指针来进行遍历,一个慢指针来对数组进行原地修改,同时通过慢指针我们也可以获取修改后的数组长度,这样我们就可以在一个循环里完成这两件事,时间复杂度为 O ( n ) \mathcal{O}(n) O(n)。
class Solution:
def removeElement(self, nums: List[int], val: int) -> int:
fast: int = 0
slow: int = 0
while fast < len(nums):
if nums[fast] != val:
nums[slow] = nums[fast]
slow += 1
fast += 1
return slow
1.2反转字符串
编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 s
的形式给出。
不要给另外的数组分配额外的空间,你必须**原地修改输入数组**、使用 O(1) 的额外空间解决这一问题。
思路:这道题也是一种典型的双指针题目,我们使用两个指针从头部和从尾部开始遍历,交换相应位置的值,这样我们就能顺利实现反转字符串。
class Solution:
def reverseString(self, s: List[str]) -> None:
"""
Do not return anything, modify s in-place instead.
"""
left: int = 0
right: int = len(s) - 1
while left < right:
s[left], s[right] = s[right], s[left]
left += 1
right -= 1
1.3反转字符串中的单词
给你一个字符串 s
,请你反转字符串中 单词 的顺序。
单词 是由非空格字符组成的字符串。s
中使用至少一个空格将字符串中的 单词 分隔开。
返回 单词 顺序颠倒且 单词 之间用单个空格连接的结果字符串。
思路:这道题的思路在于,我们需要先分隔单词,分割完毕后再进行反转。
class Solution:
def reverseWords(self, s: str) -> str:
res: List[str] = [word.strip() for word in s.split()]
left: int = 0
right: int = len(res) - 1
while left < right:
res[left], res[right] = res[right], res[left]
left += 1
right -= 1
return ' '.join(res)
1.4反转链表
给你单链表的头节点 head
,请你反转链表,并返回反转后的链表。
思路:为了统一链表的操作,我们需要使用虚拟头节点,这样对链表的处理过程就能被统一起来,无需再进行特殊处理。这里实际上我们也使用了两个指针,通过两个指针一起移动,我们判断快指针是否指向None
,如果快指针已经指向了None
,那就说明我们的遍历已经完成了。
# 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: Optional[ListNode] = None
cur: Optional[ListNode] = head
while cur:
temp = cur.next
cur.next = pre
pre = cur
cur = temp
return pre
1.5删除链表的倒数第N个节点
给你一个链表,删除链表的倒数第 n
个结点,并且返回链表的头结点。
思路:这道题常规的思路是进行两次遍历,第一次遍历找到链表的长度len
,第二次遍历找到倒数的第n
的节点,这种方式效率比较低,在使用虚拟头节点的前提下,我们可以使用一个快指针和一个慢指针来进行一次遍历,快指针先于慢指针n+1
步,这样快指针指向None
的时候,慢指针就指向了倒数第n
个节点的前一个节点。
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def removeNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]:
dummyhead: Optional[ListNode] = ListNode(next=head)
fast: Optional[ListNode] = dummyhead
slow: Optional[ListNode] = dummyhead
for _ in range(n+1): # fast领先n+1个节点
fast = fast.next
while fast: # 找到倒数第n个节点的前一个节点
fast = fast.next
slow = slow.next
slow.next = slow.next.next
return dummyhead.next
1.6链表相交
给你两个单链表的头节点 headA
和 headB
,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null
。
思路:这道题判断链表是否相交的条件不是节点的值是否相等,而是节点指针是否相等,如果指针相等那么才能够表明这两个节点确实相交了。我们首先需要做的事情是让这两个链表的末尾进行对齐。
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def getLength(self, head: ListNode) -> int:
count: int = 0
while head:
head = head.next
count += 1
return count
def moveForward(self, head: ListNode, steps: int) -> ListNode:
for _ in range(steps):
head = head.next
return head
def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
lenA: int = self.getLength(headA)
lenB: int = self.getLength(headB)
if lenA > lenB:
headA = self.moveForward(headA, lenA-lenB)
else:
headB = self.moveForward(headB, lenB-lenA)
while headA:
if headA == headB:
return headA
headA = headA.next
headB = headB.next
return None
1.7环形链表II
给定一个链表的头节点 head
,返回链表开始入环的第一个节点。 如果链表无环,则返回 null
。
如果链表中有某个节点,可以通过连续跟踪 next
指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos
来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos
是 -1
,则在该链表中没有环。注意:pos
不作为参数进行传递,仅仅是为了标识链表的实际情况。
思路:我们如何来判断链表里面是否存在环呢?一个暴力的思路就是,我们使用相当多的额外空间来保存我们访问过的每一个节点,如果在遍历的过程中再次访问了这些节点,那么就能够说明链表内存在环,并且查询该节点的位置,我们就能够获取索引,这是一种十分暴力的解法,浪费了很多空间复杂度。另一种思想是使用双指针来进行遍历,快指针每次遍历两个节点,慢指针每次遍历一个节点,这样党快指针追上慢指针的时候就表明链表内存在环。然后我们需要查找到环的入口,我们从相遇的位置和起始位置再开始遍历,当这两个指针再次相遇的时候,我们就获得了环的入口。
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def detectCycle(self, head: Optional[ListNode]) -> Optional[ListNode]:
fast: Optional[ListNode] = head
slow: Optional[ListNode] = head
while fast and fast.next:
fast = fast.next.next
slow = slow.next
if fast == slow:
cur = fast
pre = head
while cur != pre:
cur = cur.next
pre = pre.next
return pre
return None
1.8三数之和
给你一个整数数组 nums
,判断是否存在三元组 [nums[i], nums[j], nums[k]]
满足 i != j
、i != k
且 j != k
,同时还满足 nums[i] + nums[j] + nums[k] == 0
。请
你返回所有和为 0
且不重复的三元组。
思路:遇到这种数字之和的题目,比如两数之和
我们很容易联想到使用哈希表的方法来进行求解,但是这道题目如果使用哈希法来进行求解的话,需要考虑非常多的边界条件,很复杂,所以我们可以在一个循环中使用双指针法来进行处理。具体的思路是我们先将数组进行排序,然后使用左右指针来进行移动。
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
nums.sort()
res: List[List[int]] = []
for i in range(len(nums)):
if i==0 and nums[i] > 0: # 剪枝(可省略)
return res
if i > 0 and nums[i] == nums[i-1]: # 对首元素去重
continue
left: int = i + 1
right: int = len(nums) - 1
while left < right:
s: int = nums[i] + nums[left] + nums[right]
if s < 0:
left += 1
elif s > 0:
right -= 1
else:
res.append([nums[i], nums[left], nums[right]])
while left < right and nums[left] == nums[left+1]: # 对剩余两个元素进行去重
left += 1
while left < right and nums[right] == nums[right-1]:
right -= 1
left += 1
right -= 1
return res
1.9四数之和
给你一个由 n
个整数组成的数组 nums
,和一个目标值 target
。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]]
(若两个四元组元素一一对应,则认为两个四元组重复):
class Solution:
def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
nums.sort()
res: List[List[int]] = []
for i in range(len(nums)):
if nums[i] > target and nums[i] > 0 and target > 0: # 剪枝(可省略)
break
if i > 0 and nums[i] == nums[i-1]: # 对第一个元素去重
continue
for j in range(i+1, len(nums)):
if nums[i] + nums[j] > target and target > 0: # 剪枝(可省略)
break
if j > i+1 and nums[j] == nums[j-1]: # 对第二元素去重
continue
left: int = j + 1
right: int = len(nums) - 1
while left < right:
s: int = nums[i] + nums[j] + nums[left] + nums[right]
if s < target:
left += 1
elif s > target:
right -= 1
else:
res.append([nums[i], nums[j], nums[left], nums[right]])
while left < right and nums[left] == nums[left+1]: # 对第三个元素去重
left += 1
while left < right and nums[right] == nums[right-1]: # 对第四个元素去重
right -= 1
left += 1
right -= 1
return res