算法之双指针(共同点:核心逻辑思路:即先找到比较小的区域(例如决定了存水量),然后在比较小的区域中找到一个最大值))~盛最多水的容器~~~接雨水

Posted 一乐乐

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了算法之双指针(共同点:核心逻辑思路:即先找到比较小的区域(例如决定了存水量),然后在比较小的区域中找到一个最大值))~盛最多水的容器~~~接雨水相关的知识,希望对你有一定的参考价值。

算法之双指针(共同点:核心逻辑思路:即先找到比较小的区域(例如决定了存水量),然后在比较小的区域中找到一个最大值

~盛最多水的容器~~~接雨水

 

1,盛最多水的容器

题意:

给你 n 个非负整数 a1,a2,...,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0) 。找出其中的两条线,

使得它们与 x 轴共同构成的容器可以容纳最多的水。
说明:你不能倾斜容器。

 

解法:双指针

思路:面积的公式~宽(下标之差~双指针)* 高(取决于左右两侧两个柱子中比较小的那个柱子)

 

详细:咱从左侧(left = 0),右侧(right = height.size() - 1)循坏:

如果高出现在左侧: if (height[left] <= height[right])   左侧的柱子比右侧低,则 高 的可能性出现在左侧区域,需要在左侧找到一个最大的值(作为高)

同时,通过更新 当前的面积(如果当前的 宽(双指针下标之差)* 高(左侧某个最大值)与前一个面积比较,大于则更新面积

 

右侧同理。。。。

总结:“左侧的柱子比右侧低,则 高 的可能性出现在左侧区域,需要在左侧找到一个最大的值(作为高)”~思路,即先找到比较小的区域,然后在比较小的区域中找到一个最大值
  public int maxArea(int[] height) {
            int left = 0, right = height.length - 1;
            int ans = 0;
            while (left < right) {
                //面积公式 高:最小的 【左柱子,右柱子】
                int area = Math.min(height[left], height[right]) * (right - left);
                ans = Math.max(ans, area);
                // 需要找小的:
              if (height[left] <= height[right]) {   // 左侧柱子比较低,较小的柱子可能性出现在左侧
                ++left; //遍历找那个最大值,同时更新面积,最后得到的面积即最大面积啦
                }
                else {
                    --right;
                }
            }
            return ans;
        }

 

 

2,接雨水

题意:

给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。

 

解法:双指针

思路: 当前水柱A上可以接收的水量取决于左右两侧最低那个柱子与它的高度差

(例如左侧比右侧低,则左侧区域决定了它的高度差)然后再左侧区域中找到最高的柱子~(与当前水柱A)最大高度差,即单位水量

 

详细:咱从左侧(left = 0),右侧(right = height.size() - 1)循坏:

如果比较低的柱子出现在左侧:if (height[left] <= height[right])   则决定水柱A最大接收水量的可能性出现在左侧区域,

需要在左侧找到一个最大的值

同时,通过更新 当前的左侧最大水柱(当前height[left] 与前一个 left_max 比较,大于则更新左侧最大水柱

 

右侧同理。。。。

 

public int trap(int[] height) {
        int left = 0, right = height.length - 1;
        int ans = 0;
        int left_max = 0, right_max = 0;
        while (left < right) {
            if (height[left] < height[right]) { // 左侧柱子比较低,较小的柱子(决定存水量)可能性出现在左侧
                if (height[left] >= left_max) {
                    left_max = height[left];
                } else {
                    ans += (left_max - height[left]);  //计算当前存储的水量
                }
                ++left;  //遍历找那个最大值,同时更新最大值,最后得到的即是最大值啦
            } else {
                if (height[right] >= right_max) {
                    right_max = height[right];
                } else {
                    ans += (right_max - height[right]);
                }
                --right;
            }
        }
        return ans;
    }

 

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 ""









以上是关于算法之双指针(共同点:核心逻辑思路:即先找到比较小的区域(例如决定了存水量),然后在比较小的区域中找到一个最大值))~盛最多水的容器~~~接雨水的主要内容,如果未能解决你的问题,请参考以下文章

快速排序算法

C++ 不知算法系列之聊聊希尔归并排序算法中的分治哲学

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

leecode题型整理之双指针

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

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