LeetCode小白算法成长记之双指针
Posted ydongy
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了LeetCode小白算法成长记之双指针相关的知识,希望对你有一定的参考价值。
不积跬步,无以至千里;不积小流,无以成江海。
前言
内容主要是个人学习使用,题目分类以及部分参考资料来自于CyC的博客,非常感谢大佬,题目来源于LeetCode,非常感谢本站支持。
167. 两数之和 II - 输入有序数组(Easy) ??
给定一个已按照升序排列的有序数组,找到两个数使得它们相加之和等于目标数。函数应该返回这两个下标值 index1 和 index2,其中 index1?必须小于?index2。
说明:
返回的下标值(index1 和 index2)不是从零开始的。你可以假设每个输入只对应唯一的答案,而且你不可以重复使用相同的元素。
示例:
输入: numbers = [2, 7, 11, 15], target = 9
输出: [1,2]
解释: 2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。
解题思路:
本题是1. 两数之和的升级版本,题意给定的是一个升序排列的有序数组,利用双指针
,指针1指向值较小的元素,指针2指向值较大的元素。指针1从头向尾遍历,指针2从尾向头遍历。
- 存在有序性,即左边元素始终小于右边元素,所以指针1的元素始终小于指针2的元素
- 当元素和
sum>target
,即需要减小某个元素,所以将指针2向左移动,指针1不动(之所以不把指针1向左移动是促使两个指针相遇,终止条件) - 当元素和
sum<target
,即需要增大某个元素,所以将指针1向右移动,指针2不动 - 当
sum==target
,且指针1和指针2不指向同一个元素
代码实现:
class Solution:
def twoSum(self, numbers: List[int], target: int) -> List[int]:
left = 0
right = len(numbers)-1
while left<right: # 判断指针是否相遇
num = numbers[left] + numbers[right]
if num>target:
right-=1 # 指针2左移
elif num<target:
left+=1 # 指针1右移
else:
# 下标加一返回位置
left+=1
right+=1
return [left,right]
633. 平方数之和(Easy) ??
给定一个非负整数?c?,你要判断是否存在两个整数 a 和 b,使得?a2 + b2 = c。
示例1:
输入: 5
输出: True
解释: 1 * 1 + 2 * 2 = 5
示例2:
输入: 3
输出: False
解题思路:
本题其实是上一题的变形,target等于两个数的平方和,题目核心条件:
- 可取范围是数是
0-√target
,因为a2+b2=target,极端情况是a=0,b=√target,所以当a不断增大,b一定需要小于√target - 由此
0-√target
可以看作是一组有序的数组[0,1,2,3,4,5,6...] - 利用双指针,指针1指向开头,向右移动,指针2指向结尾,向左移动
代码实现:
class Solution:
def judgeSquareSum(self, c: int) -> bool:
left = 0
right = int(c**0.5) # 相当于√target
while left<=right: # 指针可以相遇,例如:1*1+1*1=2,即left指向1,right指向1
num = left*left+right*right
if num>c:
right-=1 # 指针2左移
elif num<c:
left+=1 # 指针1右移
else:
return True
return False
345. 反转字符串中的元音字母(Easy) ??
编写一个函数,以字符串作为输入,反转该字符串中的元音字母。
示例 1:
输入: "hello"
输出: "holle"
示例 2:
输入: "leetcode"
输出: "leotcede"
说明:
元音字母不包含字母"y"。
解题思路:
- 元音字母:
{"a","e","i","o","u",‘A‘, ‘E‘, ‘I‘, ‘O‘, ‘U‘}
,采用集合存储时间复杂度优于列表,Python底层采用的是hash。 - 主要就是反转元音字母,即当元音字母为奇数个,存在一个不动,元音字母为偶数个,则一一对调。
- 一一对调即采用双指针,指针1从左边找,往有运动。指针2从右边找,往左运动。
- 当指针1,2指向的都是元音字母则对调
- 指针1是元音字母,指针2不是,则指针1不动,等待指针2找到元音字母,双方对调
- 反之亦是
- 终止条件是指针1与指针2相遇
代码实现:
class Solution:
def reverseVowels(self, s: str) -> str:
words = {"a", "e", "i", "o", "u", ‘A‘, ‘E‘, ‘I‘, ‘O‘, ‘U‘}
l = list(s) # 拆分列表用于交换元素
left = 0
right = len(l) - 1
while left < right:
if l[left] in words and l[right] in words: # 指针1,2都指向元音
l[left], l[right] = l[right], l[left]
left += 1
right -= 1
elif l[left] not in words: # 指针1没有指向元音,指针2指向元音
left += 1
elif l[right] not in words: # 指针2没有指向元音,指针1指向元音
right -= 1
return "".join(l)
680. 验证回文字符串 Ⅱ(Easy)??
给定一个非空字符串?s,最多删除一个字符。判断是否能成为回文字符串。
示例 1:
输入: "aba"
输出: True
示例 2:
输入: "abca"
输出: True
解释: 你可以删除c字符。
注意:
字符串只包含从 a-z 的小写字母。字符串的最大长度是50000。
解题思路:
本题是9. 回文数的升级版,即给回文字符串一次机会,注意这个机会存在两种情况,具体情况如下:
- 回文字符串采用双指针的思想,指针1从左向右扫描移动,指针2从右向左扫描移动,判断指针1和指针2的值是否相等,相等则,指针1右移一格,指针2左移一格,否则给一次机会,即删除一个字符
- 指针2不动,指针1右移一格,继续上面的步骤,直到指针相遇
- 指针1不动,指针2左移一个,继续上面的步骤,直到指针相遇
官方说明:贪心算法,即当左右指针相等,此时验证就是去头去尾后的字符串,保证它为回文,则整个字符串就是回文,依次递减。
代码实现:
class Solution:
def validPalindrome(self, s: str) -> bool:
left = 0
right = len(s) - 1
while left < right:
if s[left] == s[right]:
left += 1
right -= 1
else:
return self.compare(s, left + 1, right) or self.compare(s, left, right - 1) # 删除一个元素,左边或右边,剩余的检查是否为回文字符串
return True
def compare(self, s, left, right):
while left < right:
if s[left] != s[right]:
return False
left += 1
right -= 1
return True
88. 合并两个有序数组(Easy)??
给你两个有序整数数组?nums1 和 nums2,请你将 nums2 合并到?nums1?中,使 nums1 成为一个有序数组。
说明:
- 初始化?nums1 和 nums2 的元素数量分别为?m 和 n 。
- 你可以假设?nums1?有足够的空间(空间大小大于或等于?m + n)来保存 nums2 中的元素。
示例:
输入:
nums1 = [1,2,3,0,0,0], m = 3
nums2 = [2,5,6], n = 3
输出:?[1,2,2,3,5,6]
解题思路:
当时看到这个题还是有点懵逼,主要是利用num1,由题意可知nums1有足够空间,采用双指针如下:
- 指针1指向num1结尾元素(此时的结尾就是nums1中真正存在元素的最后一个位置),指针2指向num2结尾元素
- 比较指针1元素和指针2元素,谁大,就把当前元素放在num1结尾(此时结尾就是m+n-1)例如:
- nums1 = [1,2,3,0,0,0], nums2 = [2,5,6],指针1=3,指针2=6,6>3,则:
- nums1 = [1,2,3,0,0,6], nums2 = [2,5,6]
- 当某个指针移动到数组头部,存在某种情况:
- 指针1移动到头部,指针2还没,说明剩余nums2的元素小于nums1最小的元素,则把剩余nums2的元素添加到nums1开头
- 指针2移动头部,不管指针1到没到头部都不管,已经将nums2完整合并到nums1
代码实现:
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.
"""
p1 = m - 1
p2 = n - 1
k = m + n - 1
while p1 >= 0 and p2 >= 0:
if nums1[p1] > nums2[p2]:
nums1[k] = nums1[p1] # nums1>nums2,将nums1当前元素添加在末尾
p1 -= 1
else:
nums1[k] = nums2[p2] # nums2>nums1,将nums2当前元素添加在末尾
p2 -= 1
k -= 1 # 每添加一个末尾游标减一
nums1[:p2+1] = nums2[:p2+1] # 将剩余的nums2元素添加在nums对应位置(一般为头部)
141. 环形链表(Easy)??
给定一个链表,判断链表中是否有环。
为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。
示例 1:
输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。
解题思路:
这个题目用了一个很巧妙的方法,就像是脑筋急转弯,主要利用双指针+快慢指针
:
- 双指针即指针1指向第一个节点,指针2指向第二个节点,并且让指针1每次移动一格,指针2每次移动2格,就是所谓的快慢指针。
- 结合本题判断链表是否有环,因为指针2在前并且步长大,如果没有环指针2肯定优先到达链表末尾,结束判断,即结果是无环。如果链表有环,由于指针2比指针1跑的快,所以在某一节点指针2必定追上指针1与其相遇,即结果有环。
代码实现:
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def hasCycle(self, head: ListNode) -> bool:
if(head == None or head.next == None):
return False
slow = head # 慢指针
quick = head.next # 快指针
while quick != None and quick.next != None: # 判断当前快指针和快指针下一个(防止溢出)是否走到链表尾部
if quick == slow: # 快慢相遇
return True
slow = slow.next # 慢指针指向下一个
quick = quick.next.next # 快指针指向下下个
return False
524. 通过删除字母匹配到字典里最长单词(Medium)??
给定一个字符串和一个字符串字典,找到字典里面最长的字符串,该字符串可以通过删除给定字符串的某些字符来得到。如果答案不止一个,返回长度最长且字典顺序最小的字符串。如果答案不存在,则返回空字符串。
示例 1:
输入:
s = "abpcplea", d = ["ale","apple","monkey","plea"]
输出:
"apple"
题意:
- 把s字符串中某些字符删除等到的字符串存在于d数组中,即找出d数组中s的子串
- 存在多个子串,找出最长的那一个
- 存在多个子串,长度最长且相等,选出按字典顺序靠前的那一个
解题思路:
- 把列表字符按照长度和字典顺序排序
- 遍历排序后的数组,判断是不是s的子串
- 利用双指针,指针1指向s,指针2指向当前遍历的字符串,当指针1元素与指针2元素相等,指针同时往后移,当元素不等时,指针1往后移,指针2不动,直到指针2移动到结尾且指针1没有走完,表示是子串,若指针1走到了结尾,指针2还没有走到结尾,表示未在s中找到当前遍历的字符串,即不是子串。
代码实现:
class Solution:
def findLongestWord(self, s: str, d: List[str]) -> str:
d.sort(key=lambda x: [-len(x), x]) # 排序,规则:长的靠前,相同长度按照字典顺序
for item in d:
i = 0
j = 0
while i < len(item) and j < len(s): # 判断某一个串是否到达结尾
if item[i] == s[j]: # 相同,子串指针加一
i += 1
j += 1 # 主串指针始终加一
if len(item) == i: # 找到子串,由于已经是有序的即直接返回
return item
return ""
以上是关于LeetCode小白算法成长记之双指针的主要内容,如果未能解决你的问题,请参考以下文章