对撞指针基本思想
对撞指针的基本思想是在数组或链表等数据结构中使用两个指针(left
或right
、start
或end
等表示),分别从不同的方向(通常是数组的两端)移动,以便有效地进行搜索、遍历或判断。
对撞指针常见的运用场景包括:
-
排序数组的查找:对撞指针可以用于在已排序的数组中搜索目标元素。通过将一个指针指向数组的起始位置,另一个指针指向数组的末尾位置,根据指针所指元素与目标元素的大小关系,逐步向中间移动指针,以达到快速搜索目标元素的目的。
-
有序数组的两数之和:给定一个有序数组和一个目标数,可以使用对撞指针的方法在数组中找到两个数,使它们的和等于目标数。通过将一个指针指向数组的起始位置,另一个指针指向数组的末尾位置,根据指针所指元素的和与目标数的大小关系,逐步移动指针,以找到满足条件的两个数。
-
回文字符串判断:对撞指针可以用于判断一个字符串是否是回文字符串。通过将一个指针指向字符串的起始位置,另一个指针指向字符串的末尾位置,逐步向中间移动指针,并比较两个指针所指的字符是否相等,以判断字符串是否回文。
-
链表操作:使用对撞指针找到链表的中间节点、判断链表是否有环等。
-
……
对撞指针的优势在于它能够在单次遍历中完成任务,时间复杂度通常较低,且不需要额外的空间。
但是,使用对撞指针的前提是目标数据结构需要满足一定的条件,比如有序或有特定的结构性质。
对撞指针相关习题
两数之和 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 。
题目分析与实现
数据结构:数组
基本实现方法:
- 对撞指针
实现一:对撞指针
- 首先使用Python中的字符串API:
isalnum()
,用于检查字符串中的所有字符是否都是字母和数字字符(即字母或数字),返回值为布尔值。 - 然后,通过两个指针:
left
和right
(left < right)从两边向中间移动逐一判断left
和right
是否相等 - 若双方的值不相等,说明不是回文串,直接返回False
- 若相等,
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 对字符串相向地进行遍历。
- 首先指针 i i i 初始时指向字符串 s s s 的首位,指针 j j j 初始时指向字符串 s s s 的末位。
- 在遍历的过程中,我们不停地将 i i i 向右移动,直到 i i i 指向一个元音字母
- 同时,我们不停地将 j j j 向左移动,直到 j j j 指向一个元音字母。
- 此时,如果 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
分别从字符串的两端向中间移动,直到找到满足条件的两个元音字母或者left
和right
相遇。平均情况下,可以近似认为左右指针在字符串的中间位置相遇,因此需要遍历整个字符串一次。 - 最好时间复杂度: 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),使用了常数个额外空间。