双指针--快慢指针和对撞指针

1.基础概念

两个指针有 n*n种组合,因此时间复杂度是 O(n^2) 。而双指针算法就是运用单调性使得指针只能单向移动,因此总的时间复杂度只有 O(2n),也就是O(n)。双指针可以分为两种类型,一种是快慢指针,一种是对撞指针。

2.快慢指针

快慢指针是指定义快指针fast,慢指针slow,两个指针以不同的步长向前移动,直到两个指针的值相等或者满足条件为止。使用快慢指针可以解决如:
(1)leetcode141环形链表是否存在环
(2)剑指offer022链表中环的入口节点
(3)剑指offer022删除链表中的倒数第K个元素

3.对撞指针

对撞指针又叫左右指针,定义左指针left,右指针right,从两端遍历直到两指针相遇或满足条件为止。以下这些题目均使用了对撞指针的思想。
(1)leetcode7.整数反转
(2)leetcode9.回文数
(3)leetcode27.移除元素
(4)leetcode125.验证回文串
(5)leetcode167.两数之II-输入有序数组()
(6)leetcode190.颠倒二进制位()
(7)leetcode344.反转字符串()
(8)leetcode345.反转字符串中的元音字母()
(9)leetcode11.盛水最多的容器(medium)
4.快慢指针举例
leetcode141 环形链表:
在这里插入图片描述

具体分析如下图:
在这里插入图片描述代码如下:

# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None

class Solution:
def hasCycle(self, head: Optional[ListNode]) -> bool:
	fast=head
	slow=head
	if not head: #链表为空的特殊情况
		return False
	while fast and fast.next: #在没有环的情况下,快指针先走完
		fast=fast.next.next
		slow=slow.next
		if fast==slow:
			return True
	return False

leetcode142 环形链表2:141题中判断链表是否为环形链表,只需要快慢指针是否相遇。进一步思考,如何判断链表是从哪个节点进入环的?

在这里插入图片描述

(1)跟上一题相同,仍然设置fast和slow指针,slow指针第一次进入环并且在遍历完环之前快慢指针一定会相遇,原因如下图:

在这里插入图片描述

(2)将链表分成如下图三部分,
第一次相遇时slow走过的路程是s=a+b,
fast走过的路程是f=a+n(b+c)+b, n为快指针比慢指针多走的圈数,f=2s,所以a+n(b+c )+b=2(a+b),化简后a+b=n(b+c)第一次相遇时n=1,所以a=c

在这里插入图片描述

(3)代码如下:

# 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]:
	slow=head #快慢指针
	fast=head
	while fast and fast.next:
		slow=slow.next
		fast=fast.next.next
		if slow==fast:#相遇时:新指针从链表头部出发,slow沿原路线走
			a=head
			while a!=slow:
				a=a.next
				slow=slow.next
			return a #返回a或者slowslow均可
	return None

5.对撞指针举例
对撞指针leetcode977
在这里插入图片描述

该题解法最容易想到的是先对数组每个元素求平方值再进行排序,写法如下:
在这里插入图片描述

思路是正确的但是时间复杂度过高,超出题目要求时间。
仔细观察该题目,该数组已经排序,平方后是两边到中间数值逐渐减小,可以使用左右指针比较两侧的数据,能减少时间复杂度。写法如下:

class Solution:
    def sortedSquares(self, nums: List[int]) -> List[int]:
        n=len(nums)
        res=[-1]*n
        #左右指针从两头开始遍历
        left=0
        right=n-1
        k=n-1
        #当两指针相遇时停止循环
        while right>=left:
            if nums[left]**2>nums[right]**2:
                res[k]=nums[left]**2
                left+=1
            else:
                res[k]=nums[right]**2
                right-=1
            k-=1
        return res

滑动窗和双指针的概念类似,双指针更注重两个指针的值,而滑动窗口更关注窗口这个区间的值。如leetcode209 长度最小的字符串,给定一个数组和target值,求出和为target的最小连续字数组。
在这里插入图片描述

我们很容易就能想到该题目思路:
在这里插入图片描述

我们通常使用循环来实现遍历所有元素的目的,那么start和end该怎么参与到循环里?整个程序结束的条件应该是遍历了所有的子数组,end和start都可以循环到数组最后一个元素时实现遍历所有子数组。但是可以看出end能更先到达最后一个值,此时start-end区间的窗口缩小,改变start位置即可实现。具体代码如下:

class Solution:
    def minSubArrayLen(self, target: int, nums: List[int]) -> int:
        n=len(nums)
        start,end=0,0
        s=0
        list_len=n+1
        while end < n:
            s+=nums[end]
            while s >= target:
                list_len=min(list_len,end-start+1)
                s-=nums[start]
                start+=1
            end+=1
        return 0 if list_len==n+1 else list_len

    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值