算法面试常用套路之双指针法dual pointers, 2022-08-16

(2022.08.16 Tues)
双指针法采用两个指针/变量,对容器进行遍历。容器类型包括,数组、链表、字符串等。数组(不管有序无序)问题,往往可以用双指针法来解决。典型案例是merge排序对双指针的应用。

遍历方式:

  • 同向遍历
  • 反向/相向遍历

遍历约束条件

  • 两指针相遇
  • 题目约束的其他条件达成

案例

正向移动指针案例

  • 将数组中的所有零元素移到数组结尾
    给定一个数组,比如[1,2,0,4,5,0,10],将所有0元素移到数组结尾,并且其他元素的相对顺序不变。
    可使用两个指针法,正向移动完成遍历。
def move_zeros(arr):
    '''
    Move zeros to the end of array, with dual points
    '''
    i, j = 0, 0
    print(i, j)
    result = [None] * len(arr)
    while i < len(arr):
        if arr[i] != 0:
            result[j] = arr[i]
            j += 1
        i += 1
        print(i, j)
    while j < len(arr):
        result[j] = 0
        j += 1
    return result

(2022.08.27 Sat)

  • leetcode 1. 给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。

算法1:可使用双指针法解决该问题。固定左指针,从左指针的右边开始遍历。算法复杂度为。
算法2:利用哈希。遍历数组,每个遍历的值作为哈希的key其index作为哈希的value存入字典,遍历过程中计算是否在哈希中存在。算法复杂度为。

class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        tmp = {}
        res = []
        for i in range(len(nums)):
            if target-nums[i] in tmp and tmp[target-nums[i]]!=i:
                res = [tmp[target-nums[i]], i]
                break
            tmp.update({nums[i]: i}) # 在判断之后更新字典,避免出现nums中有重复值,且重复值之和为target的情况
        return res

反向移动指针案例

  • 判断一个字符串或数字是否为回文数(palindrome)。可以采用两个指针,从头、尾两个方向遍历容器,每次指针移动步数相同,一旦指针对应的值不同,则遍历结束,返回Flase
def palindrome(al):
    '''
    Tell whether the input is a palindrome
    Output:
        True/False
    '''
    if isinstance(al, str):
        tmp = al
    elif isinstance(al, int):
        tmp = str(al)
    if len(tmp)< 2:
        return True
    p_left, p_right = 0, len(tmp)-1
    while p_left <= (len(tmp) - 1)//2:
        if tmp[p_left] == tmp[p_right]:
            p_left += 1
            p_right -= 1
            continue
        else:
            return False
    return True
  • 给定一个有序递增数组和一个目标值,判断是否有数组中的两个元素之和等于该目标值。进阶:1 数组无序,2 判断三个元素之和是否等于目标值。
    对于有序递增数组,可从头、尾两个方向向数组中间行进,求和判断是否符合要求。也可根据二分法,判断数据中间的值是否大于或小于目标值,在使用双向指针。对于无序数组,可选择先排序,再用相对行进的指针,复杂度,也可以用同向指针计算,复杂度。
def find_target(arr, target):
    '''
    tell whether the sum of any two elements in the sorted arr is equal to target
    Output:
        index pair
    '''
    if len(arr) < 2:
        if sum(arr) == target:
            return (len(arr))
        else:
            return False
    pl = 0
    pr = len(arr)-1
    result = []
    while plpl and pr > 0:
        if arr[pl] + arr[pr] == target:
            result.append((pl, pr))
            pl += 1
            continue
        elif arr[pl] + arr[pr] > target:
            pr -= 1
            continue
        elif arr[pl] + arr[pr] < target:
            pl += 1
            continue
    return False if not result else result

双向移动

(2022.08.27 Sat)

  • letcode 13. 包含有n个数的整数数组nums,问其中是否包含三个元素abc,使得a+b+c=0?如果有,返回元素元组,且要求不重复。如果没有,返回空列表。

本题如果不使用双指针,一种常规的解法如下:
遍历数组,固定第一个元素ii后面的数组为新数组,对其遍历,新数组第一个元素为jj后面的数组为新数组,对其遍历,元素为k。通过三层遍历,判断i+j+k==0。该算法复杂度为。

下面使用双指针解决本题。对数组进行升序排序。遍历排序后的数组,选定元素i,对于元素i后面的元素形成新的序列。在新序列上使用双指针,从头、尾两个方向进行遍历。如果遍历元素求和为0,则左右两个指针向中间行进;如果和大于0,说明右指针值大,右指针向左移动;如果和小于0,说明左指针值大,左指针向右移动。考虑到不能有重复结果,在选定元素i和左右两个指针出都需要进行去重操作。代码如下

class Solution:
    def threeSum(self, nums: List[int]) -> List[List[int]]:
        nums = sorted(nums)  # 排序,nlog(n)
        res = [] 
        if len(nums) < 3 or len(nums) > 3000:
            return res
        for b in range(len(nums)-2):
            # 对选定元素的去重操作
            if b > 0 and nums[b] == nums[b-1]:
                continue
            # 选定左右两个指针
            left, right = b+1, len(nums)-1
            while left < right: # 指针对撞是结束遍历的条件
                s = nums[b] + nums[left] + nums[right]
                if s == 0:
                    # 符合条件,则保存
                    res.append([nums[b], 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
                    continue
                elif s > 0:
                    right -= 1
                    continue
                elif s < 0:
                    left += 1
                    continue
        return res

复杂度分析:使用双指针,首先做排序,复杂度为。选定了基准点,对基准点后面的数组进行遍历,复杂度为。总复杂度为,比不用排序的方法低了一个数量级。

  • leetcode 881. 救生艇:给定数组 people ,people[i]表示第 i 个人的体重 ,船的数量不限,每艘船可以承载的最大重量为 limit。每艘船最多可同时载两人,但条件是这些人的重量之和最多为 limit。返回 承载所有人所需的最小船数 。条件:people[i] <= limit。

该题可首先对数组排序,排序后从头尾两端进行双指针遍历,一旦双指针对应元素之和小于limit,则最小船数加1且双指针同时向中间移动,否则船数加1且只移动右边指针。

class Solution:
    def numRescueBoats(self, people: List[int], limit: int) -> int:
        people = sorted(people)
        res = 0
        if len(people) < 2 or len(people) > 5 * 10**4:
            return res
        left, right = 0, len(people)-1
        while left <= right:
            if people[left] + people[right] <= limit:
                left += 1
            right -= 1
            res += 1
            continue
        return res

Reference

1 双指针法,飞翔的猪,知乎
2 双指针法各类提醒总结,ByeGuo,知乎

你可能感兴趣的:(算法面试常用套路之双指针法dual pointers, 2022-08-16)