记录leetcode上刷过的算法题,本文对双指针进行总结。
算法简述
在处理数组和链表有关的问题时,经常会用到双指针的方法,双指针技巧主要分为两类,左右指针 和 快慢指针。
而在针对的对象上,双指针技巧又可以分为 针对单个元素 的,和针对两个指针 锚定的窗口 的,其中第二种有其专有的名称- 滑动窗口 。
由于左右指针和快慢指针包含了所有针对单个元素的问题,所以本文分如下三类进行总结。
1、左右指针
所谓左右指针,就是两个指针相向而行或者相背而行。
由于其特性,需要指针能够 同时满足向前和向后 两种行走方式,所以通常用在数组和双向链表的题目中。(对这俩结构进行前后序遍历不受结构本身影响。)
这样一看,双向链表和数组除了是否拥有指针域之外,结构特性是非常相似的。
1)最长回文子串
题目
给你一个字符串 s,找到 s 中最长的回文子串。
实现思路
遍历字符串,尝试以每一个字符作为中心点向两边进行拓展,并判断是否为回文串,保留最长的回文串即为所求结果。
代码
class Solution(object):
def longestPalindrome(self, s):
"""
:type s: str
:rtype: str
"""
lens = len(s)
length = 0
loc = 0
flag = 1
for i in range(lens):
record = 1
while ((i - record) >= 0) & ((i + record) < lens):
if s[i - record] == s[i + record]:
record += 1
else:
break
if (record - 1) >= length:
length = record - 1
loc = i
flag = 1
record = 1
while ((i - record + 1) >= 0) & ((i + record) < lens):
if s[i- record + 1] == s[i + record]:
record += 1
else:
break
if (record - 1) > length:
length = record -1
loc = i
flag = 0
record = 1
if lens == 1:
return s[loc]
if flag == 1:
return s[(loc - length):(loc + length + 1)]
else:
return s[(loc - length +1):(loc + length + 1)]
580ms和13.4MB,分别超过82%和46%
2)排序数组查找元素第一个和最后一个位置
题目
给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。
如果数组中不存在目标值 target,返回 [-1, -1]。
实现思路
对排序数组进行一次普通的二分查找,若找到target,就跳出,没找到则循环结束再跳出。
出循环后根据lo是否小于等于hi,判断循环是否找到target,若找到则向左右两个方向延申,确定第一个和最后一个元素位置。
若以上都不满足,return [-1,-1]
代码
class Solution:
def searchRange(self, nums: List[int], target: int) -> List[int]:
n = len(nums)
lo, hi =0, (n - 1)
while lo <= hi:
mid = int((lo+hi)/2)
if nums[mid] == target:
break
elif nums[mid] < target:
lo = mid+1
else:
hi = mid-1
if lo <= hi:
l= mid
r = mid
while l>=0 and nums[l] == nums[mid]:
l -= 1
while r<=n-1 and nums[r] == nums[mid]:
r += 1
return [l+1,r-1]
else:
return [-1,-1]
36ms和16MB,分别超过73%和36%
2、快慢指针
顾名思义,快慢指针就是指利用一快一慢两个指针的方法,通常是同向而行。
同上,不难想象快慢指针适合用来处理 单链表 的问题或者 只需要往一个方向运行 的数组问题,因为单链表的结构只允许往一个方向走。
3、滑动窗口
相比于普通的双指针问题(以上两种),滑动窗口要解决的问题通常要难不少(我个人的刷题感受)主要因为其他双指针的问题大都只需要 对指针指到的单个元素进行操作,而滑动窗口问题需要 对两个指针锚定出来的窗口进行维护,直到得到最终结果,前者一次只要管一到两个元素,后者一次要管一串,具体对比如下两图。
1.)力扣03.无重复字符的最长子串
典型的双指针利用滑动窗口的问题。
题目
给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。
实现思路
先定义两个指针lo和hi,用hi不停地向前试探,观察lo和hi所确定的窗口包含的字串是否有重复的字符,如果没有,hi就向前继续试探,扩大窗口(用来确定最大不重复字串),如果有,就移动lo缩小窗口,更新结果。
直到hi遍历到字符串末尾,结束循环。
代码
class Solution:
def lengthOfLongestSubstring(self, s: str) -> int:
occ = set()
rk, ans = 0, 0
n = len(s)
for i in range(n):
if i != 0:
occ.remove(s[i - 1])
while rk < n and s[rk] not in occ:
occ.add(s[rk])
rk += 1
ans = max(ans,rk - i)
return ans
72ms和15.1MB,分别超过53%和35%
2)暴力实现strStr()
题目
给你两个字符串 haystack 和 needle ,请你在 haystack 字符串中找出 needle 字符串出现的第一个位置(下标从 0 开始)。如果不存在,则返回 -1 。
实现思路
若needle为空或者haystack和needle相同,返回0。
慢指针从头开始遍历(n-m)个字符(n和m分别为haystack和needle的长度),快指针从每一个慢指针开始往后遍历m长度,若能遍历到m,则匹配成功·,返回下标。
若慢指针遍历完没有匹配成功,返回-1。
代码
class Solution:
def strStr(self, haystack: str, needle: str) -> int:
n = len(haystack)
m = len(needle)
if needle =="" or haystack == needle:
return 0
for i in range(n-m+1):
flag = False
p = i
j = 0
while j<m:
if needle[j] == haystack[p]:
p += 1
j += 1
if j == (m):
flag = True
return i
else:
break
return -1