双指针问题最简单的教程(1)

什么样的问题适合用双指针技巧?当问题是从一个有序的数组或链表中,找到一个元素的子集,该子集需要满足某种限制。 这时候就特别适合用双指针。这个子集可能是某两个元素,某三个元素,甚至是一个子数组。

1 举个栗子

给你一个升序排列的数组和一个目标值,从该数组中找出两个元素,使它们的和等于目标值

看到这个问题,你会不会觉得:

这不就是两层for循环嘛。你轻松写下了如下代码:

def twoSum(nums: List[int], target: int) -> List[int]:
    for i in range(len(nums)):
        for j in range(i+1, len(nums)):
            if nums[i] + nums[j] == target:
                return [i, j]

这个解法,问题在于时间复杂度是 。在实际中有点难以接受。这个时候就可以用上我们的双指针技巧。

在滑动窗口技巧中,我曾经反复强调,滑动窗口不过是对暴力搜索算法的优化。 同理,双指针技巧也是这样的。网上很多讲双指针技巧的,我还没有看到谁从这个角度来讲的,如果有麻烦加微信(milter007)告诉我。而我认为只有从这个角度来讲,才能减少认知的跳跃,更容易理解双指针的本质所在。

2 问题分析

分析上面的暴力算法,我们看到外层循环的目的,是一个一个元素看,尽量找到和该元素之和等于目标值的元素。这个过程是不可能省略的,因为省略就意味着没有查找全面彻底。

但是,内层循环是可以进行优化的。并不需要那么傻乎乎地一个一个查找。举例来说,如图所示:

Pointer1就代表上面的外层循环中的 ,Pointer2就代表上面的内层循环中的 。只不过内层循环是从数组尾部开始而已。目标值是6。

我们看到,此时  ,于是,我们的Pointer2代表的内层循环就会往前走,如图所示:

此时, ,此时,Pointer2代表的内层循环没必要继续了,因为数组是升序排列的,越往前,元素值越小,和Pointer1的和自然越小。所以,内层循环可以提前退出,这就是我们的第一个优化点。

根据上面的暴力算法,内层循环退出后,如果没有找到答案,外层循环就要继续,这就是Pointer1要向前走。Pointer1向前走一步如下所示:

我们这个例子中恰好找到答案了。但假如此时没有找到答案呢?比如这种情况:

向前移动Pointer1之后,  ,这个时候,我们该怎么办呢?需要按照暴力搜索的逻辑,重启内层循环,从数组尾部开始,一个一个和此时的Pointer1(也就是2)进行匹配吗?

答案是不需要。

因为此时,我们已经可以断定,整个数组中是不可能有哪个数与2的和等于6的,为什么呢?因为 3.5 往右的每一个数,它们与1的和都是大于6的,自然与2的和更是大于6。

为什么3.5 往右的每一个数,它们与1的和都是大于6的?因为这是上一轮循环的结果,我们的Pointer2是从尾部一个一个往前推,如果与1的和大于6,就继续,直到小于6才停下来的。

而3.5往左的每一个数,都比3.5还小,它们与2的和只能比6更小。

既然这样,我们就压根不用进行任何内层循环查找了,直接将外层循环推进一步,就是将Pointer1向右推一步,这就是我们的第二个优化点。

细心的你,一定想问,如果是这种情况呢:

此时,  ,那我们就需要进行内层循环了,不过,我们只用从现在的Pointer2的位置继续向数组头部方向进行查找就是,而不用从数组尾部重新开始这就是我们的第三个优化点。

上面,其实就是双指针算法了!表面上看起来,两个指针,一会儿P2向数组头部走,一会儿P1向数组尾部走,看不出章法,第一次学习的同学,可能感到一头雾水。通过咱们的分析,原来就是对暴力查找算法的一个优化而已。 是不是顿时觉得清爽很多了?

3 代码实现

def pair_with_targetsum(arr, target_sum):
  left, right = 0, len(arr) - 1
  while(left < right):
    current_sum = arr[left] + arr[right]
    if current_sum == target_sum:
      return [left, right]

    if target_sum > current_sum:
      left += 1  # 移动Pointer1
    else:
      right -= 1  # 移动Pointer2
return [-1, -1]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值