引言
双指针算法在数组问题中极为常用。双指针主要用于遍历数组,两个指针指向不同的元素,从而协同完成任务。
一、对撞指针
对撞指针法,顾名思义,就是指针在两边往中间撞。见三
二、快慢指针
快慢指针可以理解为:一个跑的快的人和一个跑的慢的人在一个圆形的赛道上赛跑,在某个时刻,跑得快的人一定会从后面赶上跑得慢的人。快指针fast一次走两步,慢指针slow一次走一步,最终会相遇在环中的某个位置(最小公倍数)
有以下两种情况:
1 fast走到链表末,返回null
2 fast = slow,两指针第一次相遇,slow指针同时从相遇位置和最开始位置出发,相遇位置即为环入口(不理解就记住)
证明:
a = 从开始到环入口步数=4
b = 一环步数 = 5
fast = 2 * slow = 2
slow = 1
相遇时,fast走的步数=slow走的步数+n环步数;fast走的步数还等于slow所走步数的2倍。所以,推出slow走的步数与环的步数相同
fast = slow + nb = > slow = nb
三、LeetCode—167
给定一个有序整型数组和一个整数target,在其中寻找两个元素,使其和为target。返回这两个数的索引。
例:nums=[2,7,11,15],target=9
输出:[1,2]
- 如果没有解怎么办?保证有解
- 如果有多个解怎么样?返回任意解
Note:
nums中有3,3+3+3=9算不?
索引问题,从1开始还是从0开始
解法一:
暴力解法,双层遍历,时间度为O(n^2),容易超时!!!
class Solution:
def findTwosum(self, num, target):
"""
在有序数组中,找到两数和为标签
:param num:
:param target:
:return:
"""
return self.violentSolution(num, target)
def violentSolution(self, num, target):
length = len(num)
# 双循环
for i in range(length):
for j in range(length):
if num[i] + num[j] == target:
return [i, j]
def main():
num = [2, 3, 5, 7, 9, 10]
target = 9
s = Solution()
solution = s.findTwosum(num, target)
print('返回索引为', solution)
if __name__ == '__main__':
main()
解法二:
暴力解法并没有充分利用原数组的性质(有序),所以我们应该想到要使用二分搜索,其时间复杂度为O(nlogn).
二分查找又称折半查找,优点是比较次数少,查找速度快,平均性能好;其缺点是要求待查表为有序表,且插入删除困难。因此,折半查找方法适用于不经常变动而查找频繁的有序列表。
二分查找的原理:首先,假设表中元素是按升序排列,将表中间位置记录的关键字与查找关键字比较,如果两者相等,则查找成功;否则利用中间位置记录将表分成前、后两个子表,如果中间位置记录的关键字大于查找关键字,则进一步查找前一子表,否则进一步查找后一子表。重复以上过程,直到找到满足条件的记录,使查找成功,或直到子表不存在为止,此时查找不成功。
class Solution:
def findTwosum(self,numbers,target):
"""
二分查找
:param num: 数组
:param target: 值
:return: 返回索引
"""
return self.binary_search(numbers,target)
def binary_search(self,numbers,target):
n = len(numbers)
for i in range(n):
low, high = i + 1, n - 1
while low <= high:
mid = (low + high) // 2
if numbers[mid] == target - numbers[i]:
return [i + 1, mid + 1]
elif numbers[mid] > target - numbers[i]:
high = mid - 1
else:
low = mid + 1
def main():
num = [2, 7, 11, 15]
target = 9
s = Solution()
solution = s.findTwosum(num, target)
print('索引为', solution)
if __name__ == '__main__':
main()
解法三:
对撞指针法,顾名思义,就是指针在两边往中间撞。空间复杂度O(1)
class Solution:
def findTwosum(self, num, target):
return self.collision_pointer(num, target)
def collision_pointer(self, num, target):
"""
对撞指针
:param num: 有序数组
:param target: 标签
:return: 返回索引
"""
i, j = 0, len(num) - 1
while i < j:
if num[i] + num[j] == target:
break
elif num[i] + num[j] > target:
j -= 1
else:
i += 1
if num[i] + num[j] == target:
return [i + 1, j + 1]
else:
print('List is not solution!!!')
def main():
num = [2, 7, 11, 15]
target = 9
s = Solution()
solution = s.findTwosum(num, target)
print('索引为', solution)
if __name__ == '__main__':
main()
其他用到对撞指针的问题
125.判断是否为回文字符串
给定一个字符串,只看其中的数字与字母,忽略大小写,判断这个字符串是否为回文字符串
class Solution:
def isPalindrome(self, s):
return self.pointer(s)
# 对撞指针解回文字符串
def pointer(self, s):
s = s.lower()
# 去掉除字符串与数字外的其他
s = [i for i in s if i.isalpha() or i.isnumeric()]
s = "".join(s)
i, j = 0, len(s) - 1
while i < j:
if s[i] == s[j]:
i += 1
j -= 1
else:
return False
return True
def main():
s = "A man, a plan, a canal: Panama"
ss = Solution()
solution = ss.isPalindrome(s)
print(solution)
if __name__ == '__main__':
main()
344.倒序字符串
给定一个字符串,返回这个字符串的倒序字符串
class Solution:
def reverseString(self, s) :
return self.pointer(s)
def pointer(self,s):
i,j = 0,len(s)-1
while i < j:
s[i],s[j] = s[j],s[i]
i += 1
j -= 1
return s
def main():
s = ["h","e","l","l","o"]
ss = Solution()
solution = ss.reverseString(s)
print(solution)
if __name__ == '__main__':
main()
345.将字符串中元音字母翻转
给定一个字符串,将该字符串中的元音字母翻转
# 元音字母有'a', 'e', 'i', 'o', 'u','A', 'E', 'I', 'O', 'U'
class Solution:
def reverseVowels(self, s):
return self.pointer(s)
def pointer(self, s):
s = [i for i in s]
# print(s)
i, j = 0, len(s) - 1
while i < j:
if s[i] not in ['a', 'e', 'i', 'o', 'u','A', 'E', 'I', 'O', 'U']:
i += 1
elif s[j] not in ['a', 'e', 'i', 'o', 'u','A', 'E', 'I', 'O', 'U']:
j -= 1
else:
s[i], s[j] = s[j], s[i]
i += 1
j -= 1
s = ''.join(s)
return s
def main():
s = "hello"
ss = Solution()
solution = ss.reverseVowels(s)
print(solution)
if __name__ == '__main__':
main()
四、LeetCode—142
142.环形链表II
给定一个链表,返回链表入环的第一个节点。如果链表无环,则返回null。为了表示给定链表中的环,我们使用整数pos来表示链表尾连接到链表中的位置(索引从0开始)。如果pos是-1,则表示该链表中没有环。pos 仅仅是用于标识环的情况,并不会作为参数传递到函数中。
说明:不允许修改给定的链表
输入:head = [3,2,0,-4], pos = 1
输出:返回索引为 1 的链表节点
解释:链表中有一个环,其尾部连接到第二个节点
如果我们用一个Set保存已经访问过的节点,我们可以遍历整个链表并返回第一个出现重复的节点。空间复杂度为O(n)
class Solution:
def detectCycle(self, head: ListNode) -> ListNode:
record = set()
while head:
if head in record:
return head
record.add(head)
head = head.next
return None
增加条件:空间复杂度为O(1)
class Solution:
def detectCycle(self, head: ListNode) -> ListNode:
fast,slow = head,head
while fast != None:
fast = fast.next
if fast == None:
break
fast = fast.next
slow = slow.next
if slow == fast:
break
# 链表中没有环
if fast == None:
return None
p1,p2 = slow,head
while p1 != p2:
p1 = p1.next
p2 = p2.next
return p1