力扣刷题DAY5(二分/简单+滑动窗口/中等)
一、二分查找
搜索插入位置
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
int len = nums.size();
int l = 0;
int r = len - 1;
while (l <= r) {
int mid = l + r >> 1;
if (nums[mid] < target) {
l = mid + 1;
} else
r = mid - 1;
}
return l; //l是第一个>=k的位置
}
};
复杂度分析
- 时间复杂度:O(logn),其中 n 为数组的长度。二分查找所需的时间复杂度为 O(logn)。
- 空间复杂度:O(1)。我们只需要常数空间存放若干变量。
相似题目:
第一个错误的版本
// The API isBadVersion is defined for you.
// bool isBadVersion(int version);
class Solution {
public:
int firstBadVersion(int n) {
int mid = 0;
int l = 1;
int r = n;
while (l <= r) {
mid = l + (r - l) / 2; //防止计算时溢出的常规操作
if (isBadVersion(mid))
r = mid - 1;
else
l = mid + 1;
}
return l;
}
};
查漏补缺:
为什么需要防止溢出?
在计算中间值时,如果直接使用 (left + right) / 2,当 left 和 right 都很大时,left + right 可能会超过 int 类型的最大值,导致 整数溢出。
二、滑动窗口
一般把窗口大小不固定的叫双指针,固定的叫滑动窗口。
长度最小的子数组
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
int len = nums.size();
int l = 0, r = 0;
int sum = 0;
int ans = len + 1;
for (; r < len; r++) {
sum += nums[r];
while (sum >= target) {
ans = min(ans, r - l + 1);
sum -= nums[l];
l++;
}
}
return ans <= len ? ans : 0;
}
};
思路:这道题可以用滑动窗口的条件是:数组元素都是正整数,这导致了窗口扩大(右指针右移)只会增大sum,窗口缩小(左指针右移)只会缩小sum。 (简单来说,右指针不断右移是在寻找更多的可能性,左指针因为窗口的扩大被动的右移去满足条件。)
复杂度分析
- 时间复杂度:O(n),右指针遍历一遍,左指针也遍历一遍。但要注意,while中左指针每次都是继续右移,不是重置,所以不是O(N2)。
- 空间复杂度:O(1)。我们只需要常数空间存放若干变量。
易错点:
如果最后 ans = n+1,返回0;
类似题目1:
乘积小于 K 的子数组
class Solution {
public:
int numSubarrayProductLessThanK(vector<int>& nums, int k) {
if (k <= 1)
return 0; //题目要求严格小于的子数组,所以k=1就不算子数组了。
int l = 0, r = 0;
int len = nums.size();
int ans = 0;
int res = 1;
for (; r < len; r++) {
res *= nums[r];
while (res >= k) {
res /= nums[l];
l++;
}
ans += r - l + 1;
}
return ans;
}
};
复杂度分析
- 时间复杂度:O(n)。
- 空间复杂度:O(1)。我们只需要常数空间存放若干变量。
类似题目2:
无重复字符的最长子串
unordered_set版:
class Solution {
public:
int lengthOfLongestSubstring(string s) {
if (s.empty()) return 0;
unordered_set<char> record;
int l = 0, ans = 0;
for (int r = 0; r < s.length(); r++) {
while (record.count(s[r])) { // 如果 s[r] 已经在 set 中,删除左指针的字符,直到 s[r] 不再重复
record.erase(s[l]);
l++;
}
record.insert(s[r]); // 这里一定要在 while 结束后插入 s[r]
ans = max(ans, r - l + 1); // 更新最大长度
}
return ans;
}
};
复杂度分析
- 时间复杂度:O(n)。
- 空间复杂度:O(1)。我们只需要常数空间存放若干变量。
unordered_map版:
class Solution {
public:
int lengthOfLongestSubstring(string s) {
int n = s.length(), ans = 0, left = 0;
unordered_map<char, int> cnt; // 维护从下标 left 到下标 right 的字符
for (int right = 0; right < n; right++) {
char c = s[right];
cnt[c]++;
while (cnt[c] > 1) { // 窗口内有重复字母
cnt[s[left]]--; // 移除窗口左端点字母
left++; // 缩小窗口
}
ans = max(ans, right - left + 1); // 更新窗口长度最大值
}
return ans;
}
};