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"

题意:

  1. 把s字符串中某些字符删除等到的字符串存在于d数组中,即找出d数组中s的子串
  2. 存在多个子串,找出最长的那一个
  3. 存在多个子串,长度最长且相等,选出按字典顺序靠前的那一个

解题思路:

  • 把列表字符按照长度和字典顺序排序
  • 遍历排序后的数组,判断是不是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小白算法成长记之双指针的主要内容,如果未能解决你的问题,请参考以下文章

java踩坑记之双花括号初始化实例导致内存泄露

java踩坑记之双花括号初始化实例导致内存泄露

leetcode刷题之双指针(C++&Java)

leecode题型整理之双指针

LeetCode刷题总结之双指针法

力扣刷题之双指针(C++)