Leetcode | 对撞指针算法笔记

对撞指针基本思想

对撞指针的基本思想是在数组或链表等数据结构中使用两个指针(leftrightstartend等表示),分别从不同的方向(通常是数组的两端)移动,以便有效地进行搜索、遍历或判断

对撞指针常见的运用场景包括:

  1. 排序数组的查找:对撞指针可以用于在已排序的数组中搜索目标元素。通过将一个指针指向数组的起始位置,另一个指针指向数组的末尾位置,根据指针所指元素与目标元素的大小关系,逐步向中间移动指针,以达到快速搜索目标元素的目的。

  2. 有序数组的两数之和:给定一个有序数组和一个目标数,可以使用对撞指针的方法在数组中找到两个数,使它们的和等于目标数。通过将一个指针指向数组的起始位置,另一个指针指向数组的末尾位置,根据指针所指元素的和与目标数的大小关系,逐步移动指针,以找到满足条件的两个数。

  3. 回文字符串判断:对撞指针可以用于判断一个字符串是否是回文字符串。通过将一个指针指向字符串的起始位置,另一个指针指向字符串的末尾位置,逐步向中间移动指针,并比较两个指针所指的字符是否相等,以判断字符串是否回文。

  4. 链表操作:使用对撞指针找到链表的中间节点、判断链表是否有环等。

  5. ……

对撞指针的优势在于它能够在单次遍历中完成任务,时间复杂度通常较低,且不需要额外的空间

但是,使用对撞指针的前提是目标数据结构需要满足一定的条件,比如有序或有特定的结构性质。

对撞指针相关习题

两数之和 II - 输入有序数组

题目描述

给你一个下标从 1 开始的整数数组 numbers ,该数组已按 非递减顺序排列 ,请你从数组中找出满足相加之和等于目标数 target 的两个数。如果设这两个数分别是 numbers[index1]numbers[index2] ,则 1 <= index1 < index2 <= numbers.length

以长度为 2 的整数数组 [index1, index2] 的形式返回这两个整数的下标index1 index2

  • 你可以假设每个输入 只对应唯一的答案 ,而且你 不可以 重复使用相同的元素。

  • 你所设计的解决方案必须只使用常量级的额外空间。

题目分析与实现

数据结构:数组

基本实现方法:

  • 对撞指针
实现一:对撞指针
# Use Python to solve
def two_sum(numbers, target):
    '''
    查找满足两数之和的位置
    
    参数:
    	numbers(list[int]): 非递减顺序排列的整数数组
    	target(int): 目标数
    返回值:
    	两个整数的下标    	
    '''
    
    # left指针从左往右运动
    # right指针从右往左运动
    left = 0
    right = len(numbers) - 1
    
    while left < right:
        current_sum = numbers[left] + numbers[right]
        # 如果左右指针之和大于目标数,说明right指针需要往左移动一位
        # 反之,left指针需要往右移动一位,通过这样的方式可以有效地缩小搜索空间
        if current_sum > target:
            right -= 1
        elif current_sum < target:
            left += 1
        else:            
   			 return left + 1, right + 1

时间复杂度分析:

  • 平均时间复杂度: O ( n ) O(n) O(n),其中 n 是数组的长度。在最坏的情况下,左指针和右指针分别从数组的两端向中间移动,直到找到满足条件的两个数或者左指针和右指针相遇。平均情况下,可以近似认为左右指针在数组的中间位置相遇,因此需要遍历整个数组一次。
  • 最好时间复杂度: O ( 1 ) O(1) O(1),当数组中不存在目标值时,只需遍历一次数组,即左右指针分别指向数组的首尾元素,然后直接返回结果。
  • 最坏时间复杂度: O ( n ) O(n) O(n),当数组中的所有元素都等于目标值时,需要遍历整个数组,即左右指针从两端向中间移动直到相遇。

空间复杂度分析:

  • O ( 1 ) O(1) O(1),使用了常数个额外空间。

验证回文串

题目描述

如果在将所有大写字符转换为小写字符、并移除所有非字母数字字符之后,短语正着读和反着读都一样。则可以认为该短语是一个 回文串 。

  • 字母和数字都属于字母数字字符。
  • 给你一个字符串 s,如果它是 回文串 ,返回 true ;否则,返回 false 。

题目分析与实现

数据结构:数组

基本实现方法:

  • 对撞指针
实现一:对撞指针
  1. 首先使用Python中的字符串API:isalnum(),用于检查字符串中的所有字符是否都是字母和数字字符(即字母或数字),返回值为布尔值。
  2. 然后,通过两个指针:leftrightleft < right)从两边向中间移动逐一判断leftright是否相等
  3. 若双方的值不相等,说明不是回文串,直接返回False
  4. 若相等,left前进一步,right后退一步继续进行判断,直到双方达到字符串中间部分。
# Use Python to solve
def is_palindrome(string):
    '''
    检验给定字符串中是否是回文
    参数:
        string (str): 给定字符串
    
    返回值:
        bool
    '''
    
    cleaned_string = "".join(ch.lower() for ch in string if ch.isalnum())
    
    left = 0
    right = len(cleaned_string) - 1
    # 左右指针向字符串中间进发
    while left < right:
        # 如果发现不等,直接返回False
        if cleaned_string[left] != cleaned_string[right]:
            return False
        # 若left和right的值相等,双方继续向中间进发
        left, right = left + 1, right - 1
    # 若遍历完成,说明该字符串是一个回文串
    return True

时间复杂度分析:

  • 平均时间复杂度: O ( n ) O(n) O(n),其中 n 是字符串的长度。在平均情况下,需要遍历字符串一次来构建新的字符串 cleaned_string,其中只包含字母和数字,并且转换为小写字母。然后,需要再次遍历 cleaned_string 的一半来检查是否满足回文的条件。因此,时间复杂度是线性的,即 O ( n ) O(n) O(n)
  • 最好时间复杂度: O ( 1 ) O(1) O(1),当字符串为空字符串时,直接返回结果 True,无需进行任何操作。
  • 最坏时间复杂度: O ( n ) O(n) O(n),当字符串是一个回文字符串时,需要遍历整个字符串的一半,即左右指针从两端向中间移动。

空间复杂度分析:

  • O ( n ) O(n) O(n),创建了一个新的字符串 cleaned_string ,其中存储了输入字符串中的字母和数字,并转换为小写字母。

反转字符串中的元音字母

题目描述

给你一个字符串 s ,仅反转字符串中的所有元音字母,并返回结果字符串。元音字母包括 'a''e''i''o''u',且可能以大小写两种形式出现不止一次。

题目分析与实现

数据结构:数组

基本实现方法:

  • 对撞指针
实现一:对撞指针

我们可以使用两个指针 i i i j j j 对字符串相向地进行遍历。

  1. 首先指针 i i i 初始时指向字符串 s s s 的首位,指针 j j j 初始时指向字符串 s s s 的末位。
  2. 在遍历的过程中,我们不停地将 i i i 向右移动,直到 i i i 指向一个元音字母
  3. 同时,我们不停地将 j j j 向左移动,直到 j j j 指向一个元音字母。
  4. 此时,如果 i < j i<j i<j,那么我们交换 i i i j j j 指向的元音字母,否则说明所有的元音字母均已遍历过,就可以退出遍历的过程。
# Use Python to solve
def reverse_vowels(s):
    '''
    反转给定字符串中的元音字母
    
    参数:
        s (str): 一个给定字符串
    
    返回值:
        str: 完成反转后的字符串    
    '''
    char_list = list(s)
    length = len(s)
    left = 0
    right = length - 1
    vowel_list = ['a', 'e', 'i', 'o', 'u',
                  'A', 'E', 'I', 'O', 'U']
    while left < right:
        # 在每次循环迭代中,判断 char_list[left](或char_list[right) 是否为元音字母且 left 小于 right,
        # 如果不是,则将 left (或right)指针向右(或左)移动,直到找到一个元音字母。
        while char_list[left] not in vowel_list and left < right:
            left += 1
        while char_list[right] not in vowel_list and left < right:
            right -= 1
        # 找到两个元音字母后,将它们进行反转
        # 然后,将 left 指针向右移动一位,将 right 指针向左移动一位,以继续下一轮迭代。
        char_list[left], char_list[right] = char_list[right], char_list[left]
        left += 1
        right -= 1
    # 最后,使用将字符列表转换为字符串,并将其作为函数的返回值
    return "".join(char_list)

时间复杂度分析:

  • 平均时间复杂度: O ( n ) O(n) O(n),其中 n 是字符串的长度。在最坏的情况下,左指针 left 和右指针 right 分别从字符串的两端向中间移动,直到找到满足条件的两个元音字母或者 leftright 相遇。平均情况下,可以近似认为左右指针在字符串的中间位置相遇,因此需要遍历整个字符串一次。
  • 最好时间复杂度: O ( 1 ) O(1) O(1),当字符串中没有元音字母时,只需遍历一次字符串,即左右指针初始时在同一位置,然后直接返回结果。
  • 最坏时间复杂度: O ( n ) O(n) O(n),当字符串中的所有字符都是元音字母时,需要遍历整个字符串,即左右指针从两端向中间移动直到相遇。

空间复杂度分析:

  • O ( n ) O(n) O(n),其中 n 是字符串的长度。

盛最多水的容器

题目描述

给定一个长度为 n n n 的整数数组 h e i g h t height height 。有 n n n 条垂线,第 i i i 条线的两个端点是 ( i , 0 ) (i, 0) (i,0)
( i , h e i g h t [ i ] ) ( i, height[i]) (i,height[i])

找出其中的两条线,使得它们与 x x x 轴共同构成的容器可以容纳最多的水。

  • 返回容器可以储存的最大水量。

题目分析与描述

数据结构:数组

基本实现方法:

  • 对撞指针
实现一:对撞指针
# Use Python to solve
def max_area(height):
    '''
    解决最大容器问题
    
    参数:
    	height(int): 给定整数数组,表示容器的高度
    	
    返回值:
    	int:最大可容纳水量    
    '''
    # 初始化双指针和最大面积
    left = 0
    right = len(height) - 1
    max_area = 0
	
    # 两个指针向中间移动
    while left < right:
        # 比较两个指针,找到最小的那个
        # 谁小,谁移动
        if height[left] < height[right]:
           max_area = max(max_area, (right - left) * height[left])
           left += 1                
        else:
           max_area = max(max_area, (right - left) * height[right])
           right -= 1
            
    return max_area

时间复杂度分析:

  • 平均时间复杂度: O ( n ) O(n) O(n),其中 n 是容器高度数组的长度。在平均情况下,使用双指针的方法遍历数组一次,通过比较左右指针所指的高度来确定木桶的最小高度,然后计算并更新当前的最大面积。因此,时间复杂度是线性的,即 O ( n ) O(n) O(n)
  • 最好时间复杂度: O ( 1 ) O(1) O(1),当容器高度数组为空或只包含一个元素时,直接返回结果 0,无需进行任何操作。
  • 最坏时间复杂度: O ( n ) O(n) O(n),当容器高度数组形成一个大的斜坡(高度递增或递减)时,需要遍历整个数组一次。

空间复杂度分析:

  • O ( 1 ) O(1) O(1),使用了常数个额外空间。

猜你喜欢

转载自blog.csdn.net/YuvalNoah/article/details/131562895