查找空间有序
利用l,r两个指针分别指向查找空间首尾,比较中间值与目标值,移动l,r两个指针逐渐逼近目标值
模板1(常用)
用于查找目标值下标
int binarySearch(vector& nums, int target){ if(nums.size() == 0) return -1; int left = 0, right = nums.size() - 1; while(left <= right){ // 防止溢出 int mid = left + (right - left) / 2; if(nums[mid] == target){ return mid; } else if(nums[mid] < target) { left = mid + 1; } else { right = mid - 1; } } // 结束条件:left==right return -1; }
模板2
用于寻找目标值左侧边界
由于right=空间长度
,所以循环条件为left
[left,right)
,最终跳出循环条件为left==right
,每次循环时有right
对应值大于等于mid
对应值,可认为right
作用为取得目标值左边界,left
每次执行加1操作,可认为缩小搜索空间范围,调整mid
值,跳出循环时right==left
返回哪一个都一样,而未找到时,返回-1。
int left_bound(int[] nums, int target) { if (nums.length == 0) return -1; int left = 0; int right = nums.length; // 注意 while (left < right) { // 注意 int mid = (left + right) / 2; if (nums[mid] == target) { right = mid; } else if (nums[mid] < target) { left = mid + 1; } else if (nums[mid] > target) { right = mid; // 注意 } } // target 比所有数都大 if (left == nums.length) return -1; // 类似之前算法的处理方式 return nums[left] == target ? left : -1; }
模板3
用于寻找目标值右侧边界
同上跳出循环时left==right
,此时right
对应值始终大于目标值,用于标定目标值右侧一位,left
用于缩小查找空间,直至left==right
跳出循环,当未找到时返回-1。
int right_bound(int[] nums, int target) { if (nums.length == 0) return -1; int left = 0, right = nums.length; // 注意 while (left < right) { // 注意 int mid = (left + right) / 2; if (nums[mid] == target) { left = mid + 1; // 注意 } else if (nums[mid] < target) { left = mid + 1; } else if (nums[mid] > target) { right = mid; } } if (left == 0) return -1; return nums[left-1] == target ? (left-1) : -1; // 注意 返回 left-1 }
练习题
LeetCode 34
LeetCode 35
通过一个快指针和慢指针在一个for循环下完成两个for循环的工作。
分清快慢指针作用
边界条件,防止越界访问
移除nums
数组中等于val
的元素,剩余元素相对顺序不变。
代码
// 时间复杂度:O(n) // 空间复杂度:O(1) class Solution { public: int removeElement(vector& nums, int val) { int slowIndex = 0; for (int fastIndex = 0; fastIndex < nums.size(); fastIndex++) { if (val != nums[fastIndex]) { nums[slowIndex++] = nums[fastIndex]; } } return slowIndex; } };
练习题
LeetCode 26.删除排序数组中的重复项
LeetCode 283.移动零
滑动窗口,就是不断的调节子序列的起始位置和终止位置,从而得出满足要求的子序列。
主要思想:计算序列右边界扩展新元素对目标值产生的影响,序列左边界丢弃元素对目标值产生影响。
而实际上对于每一个数组元素,仅有入子序列与出子序列两种操作,所以为O(n)
算法。
当右边界扩展生成的子序列满足要求时,记录或比较其与历史最优值并进行更新,左边界丢弃一个元素,右边界继续扩展直至满足条件再次判断。
在右边界扩展生成一个满足条件子序列后,此时右边界继续右移对于目标求解没有贡献,而需移动左边界继续下一次寻解。
给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的 连续 子数组,并返回其长度。如果不存在符合条件的子数组,返回 0。
代码
class Solution { public: int minSubArrayLen(int s, vector& nums) { int result = INT32_MAX; int sum = 0; // 滑动窗口数值之和 int i = 0; // 滑动窗口起始位置 int subLength = 0; // 滑动窗口的长度 for (int j = 0; j < nums.size(); j++) { sum += nums[j]; // 注意这里使用while,每次更新 i(起始位置),并不断比较子序列是否符合条件 while (sum >= s) { subLength = (j - i + 1); // 取子序列的长度 result = result < subLength ? result : subLength; sum -= nums[i++]; // 这里体现出滑动窗口的精髓之处,不断变更i(子序列的起始位置) } } // 如果result没有被赋值的话,就返回0,说明没有符合条件的子序列 return result == INT32_MAX ? 0 : result; } };
Leetcode 904 水果成篮
LeetCode 76 最小覆盖子串
题目
给你一个字符串 s
、一个字符串 t
。返回 s
中涵盖 t
所有字符的最小子串。如果 s
中不存在涵盖 t
所有字符的子串,则返回空字符串 ""
。
注意
输入: s = "a", t = "aa" 输出: "" 解释: t 中两个字符 'a' 均应包含在 s 的子串中, 因此没有符合条件的子字符串,返回空字符串。
代码
class Solution { public: string minWindow(string s, string t) { vectorarray(128, 0); // 用于记录小字符串中每个字母出现次数 int start=-1, l=0, r=0, count=t.size(), size=INT_MAX; // count用于判断每次生成新的子串是否符合要求 for(int i=0; i 0) count--; array[s[r]]--; // 对于未在小字符串中出现的字符,为记录其在l至r子字符串中出现次数的相反数 if(count == 0){ while(l 变量意义
array
数组用于记录l
至r
子串为满足条件其中元素需要出现个数。
r
用于每次向右扩展元素,直至满足条件。
l
移动用于除去不需要的字符,或打破满足条件进行下一次寻解。
count
用于判断l
至r
子串何时满足条件。
start
与size
用于记录最优解。逻辑过程
1、用
array
数组记录子串需出现各元素个数。2、利用
r
扩展元素至满足count
(子串所需元素)为0。3、左边界
l
丢弃多余元素。4、更新最优解
start
与size
。5、移动
l
打破count==0
条件,进行下一次寻解。