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