二分查找法(Binary Search)算法,也叫折半查找算法。二分查找针对的是一个有序的数据集合,查找思想有点类似于分治思想。先确定待查找元素所在的区间范围,在逐步缩小范围,直到找到元素或找不到该元素为止。
二分查找算法的过程如下所示:
class Solution:
def search(self, nums: List[int], target: int) -> int:
l,r = 0,len(nums)-1
while l <= r:
mid = (l+r)//2
if nums[mid] > target:
r = mid - 1
elif nums[mid] < target:
l = mid + 1
else:
return mid
return -1
区间开闭、mid取值、出界条件问题
区间的左闭右闭、左闭右开指的是初始待查找区间的范围。
left = 0
,right = len(nums) - 1
,left
为数组第一个元素位置,right
为数组最后一个元素位置,从而区间 [left, right]
左右边界上的点都能取到。left = 0
,right = len(nums)
,left
为数组第一个元素位置,right
为数组最后一个元素的下一个位置,从而区间 [left, right)
左边界点能取到,而右边界上的点不能取到。最常见的 mid
取值就是 mid = (left + right) // 2
或者 mid = left + (right - left) // 2
。前者是最常见写法,后者是为了防止整型溢出
left <= right
,且查找的元素不存在,则 while
判断语句出界条件是 left == right + 1
,写成区间形式就是 [right + 1, right]
,此时待查找区间为空,待查找区间中没有元素存在,所以此时终止循环可以直接返回 -1
是正确的。
[3, 2]
,不可能存在一个元素既大于等于 3
又小于等于 2
,此时直接终止循环,返回 -1
即可。left < right
,且查找的元素不存在,则 while
判断语句出界条件是 left == right
,写成区间形式就是 [right, right]
。此时区间不为空,待查找区间还有一个元素存在,并不能确定查找的元素不在这个区间中,此时终止循环返回 -1
是错误的。指的是两个指针 left
、right
分别指向序列第一个元素和最后一个元素,然后 left
指针不断递增,right
不断递减,直到两个指针的值相撞(即 left == right
),或者满足其他要求的特殊条件为止。
其实和二分查找是类似的
left
,right
。left
指向序列第一个元素,即:left = 0
,right
指向序列最后一个元素,即:right = len(nums) - 1
。left += 1
。当满足另外一定条件时,将右指针左移,right -= 1
。left == right
),或者满足其他要求的特殊条件时,跳出循环体。【LeetCode】11.盛最多水的容器
给定 n
个非负整数 a1,a2,…,an,每个数代表坐标中的一个点 (i,ai)。在坐标内画 n
条垂直线,垂直线 i
的两个端点分别为 (i,ai) 和 (i,0)。
要求:找出其中的两条垂直线,使得它们与 x
轴共同构成的容器可以容纳最多的水。
- 输入:
[1,8,6,2,5,4,8,3,7]
- 输出:
49
- 解释:图中垂直线代表输入数组
[1,8,6,2,5,4,8,3,7]
。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为49
。
解题思路
从示例中可以看出,如果确定好左右两端的直线,容纳的水量是由 左右两端直线中较低直线的高度 * 两端直线之间的距离
所决定的。所以我们应该使得 较低直线的高度尽可能的高,这样才能使盛水面积尽可能的大。
可以使用双指针求解。移动较低直线所在的指针位置,从而得到不同的高度和面积,最终获取其中最大的面积。具体做法如下:
left
,right
。left
指向数组开始位置,right
指向数组结束位置。left
和 right
所构成的面积值,同时维护更新最大面积值。left
和right
的高度值大小。
left
指向的直线高度比较低,则将 left
指针右移。right
指向的直线高度比较低,则将 right
指针左移。left == right
,跳出循环,最后返回最大的面积。题解:
class Solution:
def maxArea(self, height: List[int]) -> int:
left = 0
right = len(height)-1
ans = 0
while left < right:
area = min(height[left],height[right]) * (right-left)
ans = max(ans,area)
if height[left] < height[right]:
left += 1
else:
right -= 1
return ans
快慢指针:指的是两个指针从同一侧开始遍历序列,且移动的步长一个快一个慢。移动快的指针被称为 「快指针(fast)」,移动慢的指针被称为「慢指针(slow)」。两个指针以不同速度、不同策略移动,直到快指针移动到数组尾端,或者两指针相交,或者满足其他特殊条件时为止。
slow
、fast
。slow
一般指向序列第一个元素,即:slow = 0
,fast
一般指向序列第二个元素,即:fast = 1
。slow += 1
。当满足另外一定条件时(也可能不需要满足条件),将快指针右移,即 fast += 1
。fast == len(nums) - 1
),或者两指针相交,或者满足其他特殊条件时跳出循环体。【LeetCode】26.删除有序数组中的重复项
给定一个有序数组 nums
。
要求:删除数组 nums
中的重复元素,使每个元素只出现一次。并输出去除重复元素之后数组的长度。
注意:不能使用额外的数组空间,在原地修改数组,并在使用 O(1) 额外空间的条件下完成。
示例:
输入:nums = [0,0,1,1,1,2,2,3,3,4] 输出:5, nums = [0,1,2,3,4] 解释:函数应该返回新的长度 5 , 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4 。不需要考虑数组中超出新长度后面的元素。
解题思路
因为数组是有序的,那么重复的元素一定会相邻。
删除重复元素,实际上就是将不重复的元素移到数组左侧。考虑使用双指针。具体算法如下:
slow
,fast
。其中 slow
指向去除重复元素后的数组的末尾位置。fast
指向当前元素。slow
在后, fast
在前。令 slow = 0
,fast = 1
。slow
位置上元素值和fast
位置上元素值是否相等。
slow
后移一位,将 fast
指向位置的元素复制到 slow
位置上。fast
右移 1
位。fast
等于数组长度。slow + 1
即为新数组长度。题解:
class Solution:
def removeDuplicates(self, nums: List[int]) -> int:
if len(nums) <= 1:
return len(nums)
slow, fast = 0, 1
while (fast < len(nums)):
if nums[slow] != nums[fast]:
slow += 1
nums[slow] = nums[fast]
fast += 1
return slow + 1
在计算机网络中,滑动窗口协议(Sliding Window Protocol)是传输层进行流控的一种措施,接收方通过通告发送方自己的窗口大小,从而控制发送方的发送速度,从而达到防止发送方发送速度过快而导致自己被淹没的目的。我们所要讲解的滑动窗口算法也是利用了同样的特性。
滑动窗口(Sliding Window):在给定数组 / 字符串上维护一个固定长度或不定长度的窗口。可以对窗口进行滑动操作、缩放操作,以及维护最优解操作。
滑动窗口利用了双指针中的快慢指针技巧,我们可以将滑动窗口看做是快慢指针两个指针中间的区间,也可以可以将滑动窗口看做是快慢指针的一种特殊形式。
滑动窗口算法一般用来解决一些查找满足一定条件的连续区间的性质(长度等)的问题。该算法可以将一部分问题中的嵌套循环转变为一个单循环,因此它可以减少时间复杂度。
根据问题,我们可以将滑动窗口分为以下两种:
假设窗口的固定大小为 window_size
。
left
、right
。初始时,left
、right
都指向序列的第一个元素,即:left = 0
,right = 0
,区间 [left, right]
被称为一个「窗口」。window_size
大小时,不断移动 right
,先将 window_size
个元素填入窗口中。window_size
大小时,判断窗口内的连续元素是否满足题目限定的条件。
left
,从而缩小窗口长度,即 left += 1
,使得窗口大小始终保持为 window_size
。right
,将元素填入窗口中。right
到达序列末尾。【LeetCode】1343. 大小为 K 且平均值大于等于阈值的子数组数目
给你一个整数数组 arr
和两个整数 k
和 threshold
。
请你返回长度为 k
且平均值大于等于 threshold
的子数组数目。
示例:
输入:arr = [2,2,2,2,5,5,5,8], k = 3, threshold = 4 输出:3 解释:子数组 [2,5,5],[5,5,5] 和 [5,5,8] 的平均值分别为 4,5 和 6 。其他长度为 3 的子数组的平均值都小于 4 (threshold 的值)。
题解:
class Solution:
def numOfSubarrays(self, arr: List[int], k: int, threshold: int) -> int:
left = right = 0
ans = 0
window_sum = 0
while right < len(arr):
window_sum += arr[right]
if right - left + 1 >= k: #判断窗口长度
if window_sum >= k * threshold: #直接将窗口内所有数相加去与k和threshold的乘积去比较,便能得到时候大于平均值,同时相等也应该输出
ans += 1
window_sum -= arr[left]
left += 1 #left加1和right加1便是在将窗口向前移动
right += 1
return ans
left
、right
。初始时,left
、right
都指向序列的第一个元素。即:left = 0
,right = 0
,区间 [left, right]
被称为一个「窗口」。window.add(s[right])
。right
,从而增大窗口长度,即 right += 1
。直到窗口中的连续元素满足要求。window.popleft(s[left])
。left
,从而缩小窗口长度,即 left += 1
。直到窗口中的连续元素不再满足要求。right
到达序列末尾。【LeetCode】3. 无重复字符的最长子串
给定一个字符串 s
,请你找出其中不含有重复字符的 **最长子串 **的长度。
示例:
输入: s = "abcabcbb" 输出: 3 解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
题解:
class Solution:
def lengthOfLongestSubstring(self, s: str) -> int:
left=right = 0
ans = 0
window = dict()
while right<len(s):
if s[right] not in window:
window[s[right]] = 1
else:
window[s[right]] += 1
while window[s[right]]>1: #当字符存在2个时,说明右边新加入了一个,那左边就将删去一个,重新保持不重复
window[s[left]] -= 1
left += 1
ans = max(ans,right-left+1) #取最大的数,最开始是len(s),通过窗口滑动后,找到所有长度中最长的
right += 1
return ans