双指针
链表子串数组题,用双指针别犹豫。
双指针家三兄弟,各个都是万人迷。
快慢指针最神奇,链表操作无压力。
归并排序找中点,链表成环搞判定。
左右指针最常见,左右两端相向行。
反转数组要靠它,二分搜索是弟弟。
滑动窗口老猛男,子串问题全靠它
左右指针滑窗口,一前一后齐头进
自诩十年老司机,怎料农村道路滑。
一不小心滑到了,鼻青脸肿少颗牙。
算法思想很简单,出了bug想升天
简介
本文主要介绍双指针在数组和字符串中
的应用技巧以及面试中的高频考题
一、双指针在移除元素中的应用
第一题:移除元素
Leetcode27-移除元素:简单题
题目简介: 给你一个数组
nums
和一个值val
,你需要原地移除所有数值等于val
的元素,并返回移除后数组的新长度
不要使用额外的数组空间,你必须仅使用O(1)
额外空间并原地
修改输入数组
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素
注: 数组的元素在内存地址中是连续的,不能单独删除数组中的某个元素,只能覆盖
方法1: 暴力解法:时间复杂度O(n²),空间复杂度:O(1)
python完整题解代码
class Solution:
def removeElement(self, nums: List[int], val: int) -> int:
size = len(nums)
i = 0
while i < size:
if nums[i] == val: # 发现需要移除的元素,就将该元素后面的元素集体向前移动一位
for j in range(i + 1, size):
nums[j - 1] = nums[j]
# i默认自增,但需要i继续从该位置进行判断(如果存在连续的需要移除的元素)
i -= 1
size -= 1 # 数组规模-1
i += 1
return size
方法2: 双指针法-快慢指针法: 通过一个快指针
和慢指针
在一个 for 循环下完成两个 for 循环的工作
最关键的在于理解快慢指针的含义
目的:将旧数组原地置换为不包含目标元素的新数组,保证空间复杂度为O(1)
快指针:寻找新数组的元素(不等于目标元素的元素)
慢指针:指向需要更新的新数组的下标位置
快指针先从头开始扫描旧数组
case1:如果快指针扫过的元素不是要移除的元素,则将该元素加入新数组(用快指针指向的元素覆盖掉慢指针指向的元素),接着快慢指针右移
case2:如果快指针扫描到元素的元素是需要移除的元素,慢指针停留在需移除元素的位置,快指针右移寻找新元素,找到非目标元素后即case1
返回慢指针指向的位置(从头到慢指针指向的位置,即移除掉所有目标元素后的新数组)
为了方便大家理解附上下面的动图,图片素材来自网络
python完整题解代码2
class Solution:
def removeElement(self, nums: List[int], val: int) -> int:
slow_index, fast_index = 0, 0
while fast_index < len(nums):
if nums[fast_index] != val: # 如果快指针扫过的元素不是要移除的元素
nums[slow_index] = nums[fast_index]
slow_index += 1
fast_index += 1
return slow_index # 返回慢指针指向的位置
第二题:移动零
Leetcode283-移动零:简单题 详情请点击链接看原题
给定一个数组
nums
,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序,请注意必须在不复制数组的情况下原地对数组进行操作
方法1:和我们上一题移除元素
类似,假设这里的 0 是我们上一题中需要移除的目标元素,我们先用快慢指针扫描移除元素 0,然后再将慢指针指向的位置到数组末尾的元素全部赋值为0
python完整题解代码
class Solution(object):
def moveZeroes(self, nums):
"""
:type nums: List[int]
:rtype: None Do not return anything, modify nums in-place instead.
"""
slow_index, fast_index = 0, 0
while fast_index < len(nums):
if nums[fast_index] != 0:
nums[slow_index] = nums[fast_index]
slow_index += 1
fast_index += 1
for j in range(slow_index, len(nums)): # 将慢指针指向的位置到数组末尾的元素全部赋值为0
nums[j] = 0
附上方法1的动图有助于大家理解
,图片素材来自网络
方法2:不知道什么名字(网上的说法叫循环不变量
)
方法2的思想和方法1类似,快慢指针数组头开始扫描,当快指针扫描到的元素不是0就交换快慢指针所指向的元素(虽然有交换此时快慢指针指向的是同一个元素),快慢指针同时右移
如果快指针扫描到的元素是0,慢指针在0的位置停下来,快指针先行一步,等待快指针找到非零元素用来和慢指针指向的0元素交换
class Solution(object):
def moveZeroes(self, nums):
"""
:type nums: List[int]
:rtype: None Do not return anything, modify nums in-place instead.
"""
slow_index, fast_index = 0, 0
while fast_index < len(nums):
if nums[fast_index] != 0:
nums[fast_index], nums[slow_index] = nums[slow_index], nums[fast_index]
slow_index += 1
fast_index += 1
第三题:删除有序数组中的重复项
Leetcode26-删除有序数组中的重复项:简单题 详情请点击链接看原题
给你一个升序排列的数组
nums
,请你原地删除重复出现的元素,使每个元素只出现一次,返回删除后数组的新长度,元素的相对顺序应该保持有一致,然后返回nums
中唯一元素的个数
细心一点的同学会发现这道题和前两道题的解题方法很像,依然是快慢指针,快慢指针一开始指向同一个位置
快指针: 用来寻找新数组中需要的元素(即不重复的元素)
慢指针: 用来指向新数组中需要更新的位置
快指针找到不重复的元素后先更新慢指针(慢指针+1指向需要更新的位置),再用快指针指向的元素更新慢指针指向的元素
注:若初始化slow_index = 0
,fast_index = 1
则先用快指针指向的元素进行覆盖,再更新慢指针指向的位置
python完整题解代码
class Solution:
def removeDuplicates(self, nums: List[int]) -> int:
slow_index, fast_index = 0, 0
while fast_index < len(nums):
if nums[fast_index] != nums[slow_index]:
slow_index += 1
nums[slow_index] = nums[fast_index]
fast_index += 1
return slow_index + 1
第四题:删除有序数组中的重复项II
给你一个有序数组 nums,请你原地删除重复出现的元素,使得出现次数超过两次的元素只出现两次,返回删除后数组的新长度
此题的思路还是快慢指针,最关键的还是先搞清楚快慢指针的定义
快指针用来寻找目标元素,慢指针用来指向需要被替换的位置,在快慢指针扫描旧数组的同时原地构造新数组,用快指针指向的满足条件的元素放到慢指针指向的位置,一轮扫描就可以构造满足条件的新数组(保证空间复杂度为
O(1)
)
原问题【保留2位】问题可扩展为【保留k位】
case1: 由于是保留k
个相同的数组,对于前k
个数字我们可以直接保留
case2:对于后面的任意数字,能够保留的前提是: 与当前写入的位置【慢指针指向的位置】前面的第 k 个元素进行比较,不相同则保留
举例: [1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 3], k = 2
step1: 首先我们让前 2 位直接保留,得到1, 1,前两位不用做任何处理,故我们让快慢指针同时指向第三个元素,下标为2的位置slow_index = 2
,fast_index = 2
step2:如果nums[fast_index] == nums[slow_index - 2]
,慢指针停下来指向需要被替换的位置,快指针需要往后移动寻找不同的元素,找到后替换慢指针指向的位置nums[slow_index] = nums[fast_index]
(即将快指针指向的满足条件的元素加入新数组)
step3: 如果nums[fast_index] != nums[slow_index - 2]
快指针指向的该元素是需要被保留的元素,该元素直接加入新数组nums[slow_index] = nums[fast_index]
,快慢指针同时右移指向下一个元素继续进行判断
注: 第三步中我们比较的是快指针指向的元素和基于慢指针指向的元素的前 2 个元素,满足条件后用快指针指向的元素覆盖慢指针指向的元素
python完整题解代码
class Solution:
def removeDuplicates(self, nums: List[int]) -> int:
slow_index, fast_index = 2, 2
if len(nums) <= 2:
return len(nums)
while fast_index < len(nums):
if nums[slow_index - 2] != nums[fast_index]:
nums[slow_index] = nums[fast_index]
slow_index += 1
fast_index += 1
return slow_index
二、双指针在验证序列方面的应用
第一题:验证回文串
Leetcode125-验证回文串: 简单题 详情请点击链接看原题
左右指针相向而行,遇到非字母数组字符则跳过(不进行比较)
注:python
中有个特殊的方法.isalnum()
用来判断遍历过程中的字符是否为非数字字母字符
python完整题解代码
class Solution:
def isPalindrome(self, s: str) -> bool:
left, right = 0, len(s) - 1
while left < right:
if not s[left].isalnum(): # 左指针指向的字符非字母数字
left += 1
continue
elif not s[right].isalnum(): # 右指针指向的字符非字母数字
right -= 1
continue
elif s[left].lower() != s[right].lower():
return False
left += 1
right -= 1
return True
第二题:验证回文串 II
Leetcode680. 验证回文串 II:简单题 详情请点击链接看原题
给你一个字符串
s
,最多 可以从中删除一个字符。
请你判断s
是否能成为回文字符串:如果能,返回true
;否则,返回false
python完整题解代码
class Solution:
def validPalindrome(self, s: str) -> bool:
front, back = 0, len(s) - 1
ispalindrome = lambda x: x == x[::-1]
while front < back:
if s[front] != s[back]:
return ispalindrome(s[front + 1:back + 1]) or ispalindrome(s[front:back])
else:
front += 1
back -= 1
return True
第三题:判断子序列
给定字符串
s
和t
,判断s
是否为t
的子序列
字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串(例如: "ace"是"abcde"的一个子序列,而"aec"不是)
设置双指针 i
和 j
分别指向字符串 s
和 t
的首个字符,遍历字符串 t
,s
- 当
s[i] == s[j]
时,代表匹配成功,此时同时i++, j++
【若i
已走过s
尾部,代表s
是t
的子序列,此时应提前返回true
】 - 当
s[i] != t[j]
时,此时仅j++
- 若遍历完字符串
t
后,字符串s
仍未遍历完,代表s
不是t
的子序列,此时返回false
python完整题解代码
class Solution:
def isSubsequence(self, s: str, t: str) -> bool:
i, j = 0, 0 # i 和 j 分别指向字符串 s 和 t 的首个字符
while i < len(s) and j < len(t):
if s[i] != t[j]: # 当 s[i] != t[j]时,此时仅j++
j += 1
else: # 当s[i] == s[j]时,代表匹配成功,此时同时 i++, j++
i += 1
j += 1
if i < len(s): # 若遍历完字符串t后,字符串s仍未遍历完,代表s不是t的子序列,此时返回false
return False
return True
第三题:下一个排列
Leetcode31. 下一个排列:中等题 (详情点击链接见原题)
整数数组的一个 排列 就是将其所有成员以序列或线性顺序排列
将问题转换为:给定若干个数字,将其组合为一个整数,如何将这些数字重新排列得到下一个更大的整数,如果没有更大的整数则输出最小的整数
解题思路
- 希望下一个数比当前数大,因此只需将后面的大数与前面的小数交换就能得到一个更大的数
- 希望下一个数增加的幅度尽可能的小,这样才满足下一个排列与当前排列紧邻的要求
- 在尽可能靠右的低位进行交换,需要从后往前查找
- 将一个尽可能小的大数与前面的小数进行交换
- 将大数换到前面后,需要将大数后面的所有数重置为升序,升序排列就是最小的排列
具体实现
- 从后向前查找第一个相邻升序的元素对
(i, j)
,满足A[i] < A[j]
,此时[j, end)
必然是降序 - 在
[j, end)
从后向前查找第一个满足A[i] < A[k]
的k
,A[k]
就是第一个大于A[i]
的大数 - 将
A[i]
与A[k]
交换 - 可以断定这时
[j, end)
必然是降序,逆置[j, end)
使其升序 - 如果步骤
1
找不到符合的相邻元素对,说明[begin, end)
为一个降序顺序,直接跳到步骤4
python完整题解代码
class Solution:
def nextPermutation(self, nums: List[int]) -> None:
"""
Do not return anything, modify nums in-place instead.
"""
def reverse(nums, i, j): # 逆转函数
while i < j:
nums[i], nums[j] = nums[j], nums[i]
i += 1
j -= 1
first_index = -1
n = len(nums)
for i in range(n - 2, -1, -1): # 特殊:遍历整个nums找不到符合要求元素的相邻元素对则说明整个nums是一个降序顺序如654321
if nums[i] < nums[i + 1]:
first_index = i
break
if first_index == -1: # 如果不存在下一个更大的排列,那么这个数组必须重排为字典序最小的排列
reverse(nums, 0, n - 1)
return
second_index = -1
for i in range(n - 1, first_index, -1):
if nums[i] > nums[first_index]: # 1.从后向前查找第一个满足 nums[i] > nums[first_index]
second_index = i
break
nums[first_index], nums[second_index] = nums[second_index], nums[first_index] # 2.进行交换
reverse(nums, first_index + 1, n - 1) # 3.将[first_index + 1, n - 1]逆置,使其升序
三、双指针在合并序列中的应用
第一题:合并两个有序数组
Leetcode88-合并两个有序数组: 简单题 详情请点击链接看原题
给你两个按 非递减顺序 排列的整数数组
nums1
和nums2
,另有两个整数m
和n
,分别表示nums1
和nums2
中的元素数目
方法1: 不借助于额外的空间(因为nums1
中预留了位置),使用双指针对两个数组进行从后往前进行扫描 时间复杂度:O(m+n) 空间复杂度为O(n)
注意1: 从头开始比较大小无法确定元素在
nums1
数组中的最终位置,而nums1
数组后面又给我们预留了空间,所以我们可以选择从后往前进行比较,将较大的元素放到后面预留的位置
注意2: 如果数组nums2
中的元素多于数组nums1
中的元素,则在进行比较完后需要将nums2
中的剩余元素直接插入到nums1
中
注意3: 如果nums1
中的元素多于nums2
则不用进行任何操作,因为我们本身就是将合并两个数组到nums1
中
python完整题解代码
class Solution:
def merge(self, nums1: List[int], m: int, nums2: List[int], n: int) -> None:
"""
Do not return anything, modify nums1 in-place instead.
"""
tail = m + n - 1 # tail 指针指向 nums1 的末尾
i = m - 1 # i 指针指向 nums1 的最后一个元素
j = n - 1 # j 指针指向 nums2 的最后一个元素
while i >= 0 and j >= 0:
if nums1[i] > nums2[j]: # 将 nums1 或者 nums2 中较大的那个元素放到末尾
nums1[tail] = nums1[i]
i -= 1
else:
nums1[tail] = nums2[j]
j -= 1
tail -= 1
while j >= 0 and tail >= 0: # 如果 nums2 中的元素多于 nums1 中的元素将 nums2 中的剩余元素直接插入到 nums1 中
nums1[tail] = nums2[j]
j -= 1
tail -= 1
方法2:借助于额外的空间,使用双指针对两个数组从前往后进行扫描 空间复杂度为O(n)
python完整题解代码
class Solution:
def merge(self, nums1: List[int], m: int, nums2: List[int], n: int) -> None:
"""
Do not return anything, modify nums1 in-place instead.
"""
i, j = 0, 0 # i指针用来遍历 nums1,j指针用来遍历 nums2
result = [] # 申请额外的空间用来保存结果集
while i < m or j < n:
if i == m: # 如果 nums1 先遍历完,将 nums2 中的剩余元素加入结果集
result.append(nums2[j])
j += 1
elif j == n: # # 如果 nums2 先遍历完,将 nums1 中的剩余元素加入结果集
result.append(nums1[i])
i += 1
elif nums1[i] <= nums2[j]:
result.append(nums1[i])
i += 1
else:
result.append(nums2[j])
j += 1
nums1[:] = result # 题目要求用nums1保存结果集
四、双指针在反转序列中的应用
第一题:翻转字符串中的单词
给你一个字符串
s
,请你反转字符串中 单词 的顺序。单词 是由非空格字符组成的字符串。s
中使用至少一个空格将字符串中的单词分隔开。返回单词顺序颠倒且单词之间用单个空格连接的结果字符串
解题思路如下:
如 " the sky is blue "
- 移除多余空格
the sky is blue
- 将整个字符串反转
eulb si yks eht
- 将每个单词反转
blue is the sky the
python代码解法:
class Solution:
def reverseWords(self, s: str) -> str:
def remove_extra_space(strs): # 1.去除首尾以及中间多余的空格
start, end = 0, len(strs) - 1
while s[start] == ' ':
start += 1
while s[end] == ' ':
end -= 1
array = []
while start <= end:
temp = s[start]
if temp != ' ' or array[-1] != ' ':
array.append(temp)
start += 1
return "".join(array)
st = remove_extra_space(s)
st = st[::-1] # 2.反转整个字符串
def reverse_str(str1):
str1 = list(str1)
i, j = 0, len(str1) - 1
while i < j:
str1[i], str1[j] = str1[j], str1[i]
i += 1
j -= 1
return "".join(str1)
i = 0
start = 0
strs = ""
while i < len(st):
if st[i] == ' ':
strs += reverse_str(st[start:i]) # 反转单词
strs += ' ' # 在单词后面补充空格
start = i + 1
elif i == len(st) - 1:
strs += reverse_str(st[start:i + 1]) # 反转最后一个单词
i += 1
return strs
第二题:轮转数组
Leetcode189-轮转数组:中等题详情请点击链接看原题
给定一个整数数组
nums
,将数组中的元素向右轮转k
个位置,其中k
是非负数
方法1:翻转数组(对比左旋字符串)
1.反转整个字符串
2.反转区间为前k
的子串
3.反转区间为k
到末尾的子串
注: 如果 k
大于数组长度的话,则 k
取 k = k % len(nums)
class Solution:
def str_reverse(self, nums, i, j):
while i < j:
nums[i], nums[j] = nums[j], nums[i]
i += 1
j -= 1
def rotate(self, nums: List[int], k: int) -> None:
nums_len = len(nums)
if k > nums_len:
k %= nums_len
self.str_reverse(nums, 0, nums_len - 1)
self.str_reverse(nums, 0, k - 1)
self.str_reverse(nums, k, nums_len - 1)
五、双指针在其他方面的应用
第一题: 三数之和
Leetcode15. 三数之和:中等题 (详情点击链接见原题)
给你一个整数数组
nums
,判断是否存在三元组[nums[i], nums[j], nums[k]]
满足i != j
、i != k
且j != k
,同时还满足nums[i] + nums[j] + nums[k] == 0
python代码解法:
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
result = []
nums.sort()
for i in range(0, len(nums)):
if nums[i] > 0: # 排序后如果第一个元素已经大于0,直接返回结果
return result
if i > 0 and nums[i] == nums[i - 1]: # 对i指向的元素去重
continue
left = i + 1
right = len(nums) - 1
while left < right:
if nums[i] + nums[left] + nums[right] > 0:
right -= 1
elif nums[i] + nums[left] + nums[right] < 0:
left += 1
else:
result.append([nums[i], nums[left], nums[right]])
# 去重逻辑应放在找到第一个三元组之后
while right > left and nums[right] == nums[right - 1]:
right -= 1
while right > left and nums[left] == nums[left + 1]:
left += 1
left += 1
right -= 1
return result
第二题:最接近的三数之和
Leetcode16. 最接近的三数之和:中等题 (详情点击链接见原题)
给你一个长度为
n
的整数数组nums
和 一个目标值target
。请你从nums
中选出三个整数,使它们的和与target
最接近
- 首先进行数组排序,时间复杂度为
O(nlogn)
- 在数组
nums
中进行遍历,每遍历一个值利用其下标i
,形成一个固定值nums[i]
- 再使用前指针指向
i + 1
处,后指针指向len(nums) - 1
处 - 根据
sum = nums[i] + nums[left] + nums[right]
,判断sum
与目标target
的距离,如果更近则更新结果ans
- 同时判断
sum
与target
的大小关系,因为数组有序,如果sum > target
则right = right - 1
,如果sum < target
则left = left + 1
,如果sum==target
则说明距离为0
直接返回结果 - 总的时间复杂度:
O(nlogn) + O(n²)=O(n²)
python代码解法:
class Solution:
def threeSumClosest(self, nums: List[int], target: int) -> int:
nums.sort()
i = 0
closet = float('inf')
while i < len(nums) - 2:
if i > 0 and nums[i] == nums[i - 1]:
i += 1
continue
left, right = i + 1, len(nums) - 1
while left < right:
total = nums[i] + nums[left] + nums[right]
if abs(closet - target) > abs(total - target):
closet = total
if total < target:
left += 1
elif total > target:
right -= 1
else:
return closet
i += 1
return closet
第三题:盛最多水的容器
Leetcode11. 盛最多水的容器:中等题 (详情点击链接见原题)
给定一个长度为
n
的整数数组height
。有n
条垂线,第i
条线的两个端点是 (i
,0
) 和 (i
,height[i]
)
找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。
返回容器可以储存的最大水量
python代码解法
class Solution:
def maxArea(self, height: List[int]) -> int:
max_area = 0
left, right = 0, len(height) - 1
while left < right:
min_h = min(height[left], height[right])
max_area = max(max_area, min_h * (right - left))
if height[left] < height[right]:
left += 1
else:
right -= 1
return max_area
第四题:压缩字符串
Leetcode443-压缩字符串:中等题 详情请点击链接看原题
给你一个字符数组
chars
,请使用下述算法压缩:
从一个空字符串s
开始。对于chars
中的每组连续重复字符 :
如果这一组长度为 1,则将字符追加到s
中,否则需要向s
追加字符,后跟这一组的长度
方法1: 三指针法
该题使用三指针的本质还是双指针,而且是双指针中的快慢指针
理清楚每个指针的含义
slow
: 指针标记当前字符
fast
: 指针找这个字符有连续多少个
write
: 指针标记当前在数组中的读写位置
class Solution:
def compress(self, chars: List[str]) -> int:
slow, fast = 0, 0
write = 0
while fast < len(chars):
chars[write] = chars[slow]
write += 1
while fast < len(chars) and chars[fast] == chars[slow]:
fast += 1
if fast - slow > 1: # 如果某个连续字符的数量大于 1 个
for i in list(str(fast - slow)):
chars[write] = i
write += 1
slow = fast
return write
注: 本题的难点之一在于如果数组的长度为 10 或者 10 以上,则在 chars 数组中会被拆分为多个字符
python
对字符串的处理有着天然的优势, 对于某个字符的数量为13个
,我们可以使用list(str(13))
将数量13
变为['1', '3']
方法2: 扩展
如果你是纯
python
使用人员,那下面这种方法其实没必要看,不过作为了解也是可以的,因为此方法的思路可以在多种语言中通用
class Solution:
def compress(self, chars: List[str]) -> int:
def reverse(arr, s, e): # 对指定区间范围的字符串做翻转操作
while s < e:
arr[s], arr[e] = arr[e], arr[s]
s += 1
e -= 1
slow, fast = 0, 0 # slow指向答案待插入的位置(在原数组上插入),fast指针指向当前处理到的位置
while fast < len(chars):
index = fast # index指针用来扫描连续出现的字符,初始化fast指针指向的位置
while index < len(chars) and chars[index] == chars[fast]:
index += 1
count = index - fast # count用来记录连续字符的长度
chars[slow] = chars[fast] # 将fast指针指向的当前字符插入到slow指针指向的位置
slow += 1 # slow指针指向下一位位置记录上一个字符出现的次数
if count > 1:
start, end = slow, slow
while count != 0:
chars[end] = chr((count % 10) + ord('0')) # 将数字转成字符串(该方法在所有编程语言中通用)
end += 1
count //= 10 # 对字符出现次数超过1位数的处理方式
reverse(chars, start, end - 1) # 取模运算导致出现次数为12次的字符存储在数组中为['2', '1'],故需对翻转该区间的字符
slow = end # slow指针指向上个字符计数完后的结束位置
fast = index # fast指针指向新字符,开始新的一轮操作
return slow
chars[end] = chr((count % 10) + ord('0'))
count=123
某个字符连续出现了123
次
count % 10
取个位3
,数字转字符串在这里我们不使用python内置的str()
方法,
先将数字3
加上字符'0'
的ASCII码
对应的数值,再使用chr()方法
即可将数字3
变为字符'3'
第五题:有序数组的平方
给你一个按 非递减顺序 排序的整数数组
nums
,返回每个数字的平方
组成的新数组,要求也按非递减顺序
排序
方法1: 暴力解法,每个数平方之后再排个序,这里不做介绍
方法2: 双指针
这道题最大的特点就是题目已经告诉你数组其实是有序的,只不过负数平方之后的大小就可能成为最大数了
那么数组平方的最大值不是在左边就是在右边,不可能是中间
题目要求结果集按非递减顺序排序,而我们一开始是无法确定平方后的最小元素在A数组中所在的位置,不过我们可以确定最大元素的位置,所以我们从新结果集的末尾开始收集按A数组中平方后的元素从大到小的顺序
定义一个新数组result
和A数组一样大小,让k指针指向result数组的末尾,从尾开始按大到小收集结果集
python代码解法:
class Solution:
def sortedSquares(self, nums):
i, j, k = 0, len(nums) - 1, len(nums) - 1
result = [0] * len(nums) # 定义一个新数组result和A数组一样大小
while i <= j:
if nums[i] * nums[i] < nums[j] * nums[j]:
result[k] = nums[j] * nums[j]
j -= 1
else:
result[k] = nums[i] * nums[i]
i += 1
k -= 1
return result
第六题:比较版本号
Leetcode165. 比较版本号:中等题 详情请点击链接看原题
给你两个版本号
version1
和version2
,请你比较它们\
python代码解法:
class Solution:
def compareVersion(self, version1: str, version2: str) -> int:
version1, version2 = version1.split('.'), version2.split('.')
length = max(len(version1), len(version2))
if len(version1) < length:
version1 += ['0'] * (length - len(version1))
if len(version2) < length:
version2 += ['0'] * (length - len(version2))
for i in range(length):
if int(version1[i]) > int(version2[i]):
return 1
elif int(version1[i]) < int(version2[i]):
return -1
else:
continue
return 0
第七题:最短无序连续子数组
Leetcode581. 最短无序连续子数组:中等题 详情请点击链接看原题
给你一个整数数组
nums
,你需要找出一个 连续子数组 ,如果对这个子数组进行升序排序,那么整个数组都会变为升序排序
第八题:接雨水
给定
n
个非负整数表示每个宽度为1
的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水
解题思路(双指针)
如果按照列计算,则宽度一定是 1
,我们再求每一列雨水的高度即可,每一列雨水的高度取决于左侧最高的柱子和右侧最高的柱子之间的最矮柱子的高度,例如区域列 5
的雨水高度
列 4
所能接的雨水面积(宽度为1
,高度即面积)为 min(列3的高度, 列7的高度) - height
python代码解法(暴力解法超时):
class Solution:
def trap(self, height: List[int]) -> int:
ans = 0
for i in range(0, len(height)):
if i == 0 or i == len(height) - 1:
continue
right_height, left_height = height[i], height[i]
for r in range(i + 1, len(height)):
right_height = max(right_height, height[r])
for l in range(i - 1, -1, -1):
left_height = max(left_height, height[l])
h = (min(right_height, left_height) - height[i])
ans += h
return ans
只要记录左边柱子的最高高度和右边柱子的最高高度就可以计算当前位置的雨水面积,这就是通过列来计算,我们用一个数组 max_left
来记录每一个位置的左边最高高度,max_right
来记录每一个位置的右边最高高度,避免重复计算
当前位置左边的最高高度是前一个位置的左边最高高度和本高度的最大值
从左向右遍历,记录每个柱子左边柱子的最大高度:max_left[i] = max(height[i], max_left[i - 1])
从右向左遍历,记录每个柱子右边柱子最大高度:max_right[i] = max(height[i], max_right[i + 1])
python代码解法(优化后):
class Solution:
def trap(self, height: List[int]) -> int:
ans = 0
max_left = [0] * len(height)
max_right = [0] * len(height)
n = len(height)
# 记录每个柱子左边柱子的最大高度
max_left[0] = height[0]
for i in range(1, n):
max_left[i] = max(height[i], max_left[i - 1])
# 记录每个柱子右边柱子的最大高度
max_right[n - 1] = height[n - 1]
for i in range(n - 2, -1, -1):
max_right[i] = max(height[i], max_right[i + 1])
# print(max_left)
# print(max_right)
for i in range(0, n):
ans += (min(max_left[i], max_right[i]) - height[i])
return ans
第九题:接雨水
给定
n
个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为1
。
求在该柱状图中,能够勾勒出来的矩形的最大面积
python代码解法(双指针解法):
class Solution:
def largestRectangleArea(self, heights: List[int]) -> int:
min_left_index = [0] * len(heights)
min_right_index = [0] * len(heights)
n = len(heights)
# 1.记录每个柱子,左边第一个小于该柱子的下标
min_left_index[0] = -1
for i in range(1, n):
t = i - 1
while t >= 0 and heights[t] >= heights[i]:
t -= 1
min_left_index[i] = t
# 2.记录每个柱子,右边第一个小于该柱子的下标
min_right_index[n - 1] = n
for i in range(n - 2, -1, -1):
t = i + 1
while t < n and heights[t] >= heights[i]:
t += 1
min_right_index[i] = t
# print(min_left_index)
# print(min_right_index)
ans = 0
for i in range(n):
ans = max(ans, (min_right_index[i] - min_left_index[i] - 1) * heights[i])
return ans
第九题: 最小差
面试题 16.06. 最小差:中等题 详情请点击链接看原题
给定两个整数数组
a
和b
,计算具有最小差绝对值的一对数值(每个数组中取一个值),并返回该对数值的差
模板题:给你两个有序的非空数组 nums1
和 nums2
, 让你从每个数组中分别挑一个使得二者差的绝对值最小
- 初始化
ans
为无限大 - 使用两个指针,一个指向数组1,一个指向数组2
- 比较两个指针指向的数字的大小,并更新较小的那个指针使其向后移动一位,更新的时候顺便计算
ans
- 返回
ans
python代码解法:
class Solution:
def smallestDifference(self, a: List[int], b: List[int]) -> int:
a.sort()
b.sort()
i, j = 0, 0
ans = sys.maxsize
while i < len(a) and j < len(b):
ans = min(ans, abs(a[i] - b[j]))
if a[i] < b[j]:
i += 1
else:
j += 1
return ans
第十题: 两数之和 II - 输入有序数组
Leetcode167. 两数之和 II - 输入有序数组:中等题 详情请点击链接看原题
给你一个下标从
1
开始的整数数组numbers
,该数组已按 非递减顺序排列 ,请你从数组中找出满足相加之和等于目标数target
的两个数
解题思路:
们要寻找的是符合条件的一对下标 (i,j)(i, j)(i,j)
,它们需要满足的约束条件是:
i
,j
都是合法的下标,0 ≤ i < n, 0 ≤ j < n,
i < j
以 n = 8
为例
case1: A[0] + A[7] < target
,这时候我们应该去找和更大的两个数,由于 A[7]
已经是最大的数了,其他的数和 A[0]
相加和只会更小
case2: A[0] + A[7] > target
,这时候我们应该去找和更小的两个数,由于 A[1]
已经是最小的数了,其他的数和 A[7]
相加和只会更大
python代码解法:
class Solution:
def twoSum(self, numbers: List[int], target: int) -> List[int]:
i, j = 0, len(numbers) - 1
while i < j:
two_sum = numbers[i] + numbers[j]
if two_sum < target:
i += 1
elif two_sum > target:
j -= 1
else:
return [i + 1, j + 1]
return [-1, -1]
第十一题: 按奇偶排序数组
给你一个整数数组
nums
,将nums
中的的所有偶数元素移动到数组的前面,后跟所有奇数元素
解题思路:
使用 left, right
分别代表未处理区间的左右端点
case1:当 nums[left]
为奇数时,将 left
和 right
两个位置互换,则 right
必然是奇数,但是原有位置 left
交换后不确保是偶数,需要再次检查
case2:否则 nums[left]
为偶数则无需处理此时 left
指向的元素, left
指针左移
python代码解法:
class Solution:
def sortArrayByParity(self, nums: List[int]) -> List[int]:
n = len(nums)
left, right = 0, len(nums) - 1
while left < right:
if nums[left] % 2 == 1:
temp = nums[right]
nums[right] = nums[left]
nums[left] = temp
right -= 1
else:
left += 1
return nums
第十二题: 按奇偶排序数组II
给定一个非负整数数组
nums
,nums
中一半整数是 奇数 ,一半整数是 偶数
python代码解法:
class Solution:
def sortArrayByParityII(self, nums: List[int]) -> List[int]:
n = len(nums)
ans = [0] * n
even_index = 0 # 偶数下标
odd_index = 1
i = 0
while i < n:
if nums[i] % 2 == 0:
ans[even_index] = nums[i]
even_index += 2
else:
ans[odd_index] = nums[i]
odd_index += 2
i += 1
return ans
总结
我们全文对面试中关于双指针在数组和字符串操作中的常见高频考题,本文的总结只是方便大家刷题练习总结知识点的时候提升一点效率,对于算法题没有捷径,还是只能靠动脑动手才能真正得到提升,希望大家都能找到心仪的offer,最后,如果你觉得本文对你有帮助的话,请点赞,收藏哦~