每日算法刷题计划Day17 5.29:leetcode复习滑动窗口+二分搜索5道题+二分答案1道题,用时1h20min
分享丨【算法题单】二分算法(二分答案/最小化最大值/最大化最小值/第K小)- 讨论 - 力扣(LeetCode)
第一轮基础(不含基础题困难题目,不含进阶部分、思维拓展部分和其他部分)
思想:
1.
一.二分查找
模版套路
1.套路
c++:
// 返回最小的满足nums[i]>=targert的下标
int lower_bound(vector<int>& nums, int target) {int n = nums.size();int res = n; // 初始值为n,插到最后面int left = 0, right = n - 1;while (left <= right) {int mid = left + ((right - left) >> 1);// 大的是下标if (nums[mid] >= target) {res = mid;right = mid - 1;} elseleft = mid + 1;}// 如果是找等于target的下标再单独判断if(res==n || nums[res]!=target) return -1;return res;
}
2.题目描述
1(学习).给你一个按照非递减顺序排列的整数数组 nums
,和一个目标值 target
。请你找出给定目标值在数组中的开始位置和结束位置。如果数组中不存在目标值 target
,返回 [-1, -1]
。
2.给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
3.给定一个 n
个元素有序的(升序) 整型数组 nums
和一个目标值 target
,写一个函数搜索 nums
中的 target
,如果目标值存在返回下标,否则返回 -1
。
4.给你一个字符数组 letters
,该数组按非递减顺序排序,以及一个字符 target
。letters
里至少有两个不同的字符。返回 letters
中大于 target
的最小的字符。如果不存在这样的字符,则返回 letters
的第一个字符。
5(学习).给你一个按 非递减顺序 排列的数组 nums
,返回正整数数目和负整数数目中的最大值。
- 换句话讲,如果
nums
中正整数的数目是pos
,而负整数的数目是neg
,返回pos
和neg
二者中的最大值。
3.学习经验
(1)nums非递减顺序排列
(2)res初始值为n,表示插入到最后
(3)lower_bound函数找最小的满足nums[i]>=target
的下标位置,所有元素比target小就插到n处
(4)可以再调用lower_bound找target+1的下标,然后-1就是target的结束位置
1.1 基础
1. 34.在排序数组中查找元素的第一个和最后一个位置(中等,学习)
34. 在排序数组中查找元素的第一个和最后一个位置 - 力扣(LeetCode)
思想
1.给你一个按照非递减顺序排列的整数数组 nums
,和一个目标值 target
。请你找出给定目标值在数组中的开始位置和结束位置。如果数组中不存在目标值 target
,返回 [-1, -1]
。
2.二分查找只能找一个位置,找最小的满足nums[i]>=target
的下标位置,即开始位置
3.因为是非递减顺序,所以target位置连续,所以target结束位置即为target+1下标位置-1(学习思想)
4.注意res初始值为n,表示插入到最后
代码
c++:
1.lower_bound找最小的满足nums[i]>=targert
的下标
class Solution {
public:// 返回最小的满足nums[i]>=targert的下标int lower_bound(vector<int>& nums, int target) {int n = nums.size();int res = n; // 初始值为n,插到最后面int left = 0, right = n - 1;while (left <= right) {int mid = left + ((right - left) >> 1);// 大的是下标if (nums[mid] >= target) {res = mid;right = mid - 1;} elseleft = mid + 1;}return res;}vector<int> searchRange(vector<int>& nums, int target) {int n = nums.size();int start = lower_bound(nums, target);// 不存在if (start == n || nums[start] != target)return {-1, -1};// 存在,因为是有序的,所以target位置连续,所以找target+1位置,-1就是end位置int end = lower_bound(nums, target + 1) - 1;return {start, end};}
};
2.upper_bound找最小的满足nums[i]>targert
的下标
class Solution {
public:// 返回最小的满足nums[i]>targert的下标int upper_bound(vector<int>& nums, int target) {int n = nums.size();int res = n; // 初始值为n,插到最后面int left = 0, right = n - 1;while (left <= right) {int mid = left + ((right - left) >> 1);// 大的是下标if (nums[mid] > target) {res = mid;right = mid - 1;} elseleft = mid + 1;}return res;}
};
注意:
1.结果返回vector<int>
,可以写成{ }形式,用于列表初始化,元素个数不限,但是确定
2. 35.搜索插入位置(简单)
35. 搜索插入位置 - 力扣(LeetCode)
思想
1.给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
2.上面的lower_bound函数
代码
c++:
class Solution {
public:int searchInsert(vector<int>& nums, int target) {int n = nums.size();int res = n;int left = 0, right = n - 1;while (left <= right) {int mid = left + ((right - left) >> 1);if (nums[mid] >= target) {res = mid;right = mid - 1;} elseleft = mid + 1;}return res;}
};
3. 704.二分查找(简单)
704. 二分查找 - 力扣(LeetCode)
思想
1.给定一个 n
个元素有序的(升序) 整型数组 nums
和一个目标值 target
,写一个函数搜索 nums
中的 target
,如果目标值存在返回下标,否则返回 -1
。
2.跟前面两题不同在于"如果目标值存在返回下标,否则返回 -1
,所以再返回res前要再单独判断不满足条件的两种情况:
- 在最后:res=n
- 不再最后,才能访问
nums[res]
,再判断其是否等于target
代码
c++:
class Solution {
public:int search(vector<int>& nums, int target) {int n = nums.size();int res = n;int left = 0, right = n - 1;while (left <= right) {int mid = left + ((right - left) >> 1);if (nums[mid] >= target) {res = mid;right = mid - 1;} elseleft = mid + 1;}if (res == n || nums[res] != target)return -1;return res;}
};
4. 744.寻找比目标字母大的最小字母(简单)
744. 寻找比目标字母大的最小字母 - 力扣(LeetCode)
思想
1.给你一个字符数组 letters
,该数组按非递减顺序排序,以及一个字符 target
。letters
里至少有两个不同的字符。返回 letters
中大于 target
的最小的字符。如果不存在这样的字符,则返回 letters
的第一个字符。
2.区别在于找大于 target
的最小的字符,而不是大于等于,为upper_bound
代码
c++:
class Solution {
public:char nextGreatestLetter(vector<char>& letters, char target) {int n = letters.size();int res = n;int left = 0, right = n - 1;while (left <= right) {int mid = left + ((right - left) >> 1);if (letters[mid] > target) {res = mid;right = mid - 1;} elseleft = mid + 1;}if (res == n)return letters[0];return letters[res];}
};
5. 2529.正整数和负整数的最大计数(简单,学习lower_bound和upper_bound混用)
2529. 正整数和负整数的最大计数 - 力扣(LeetCode)
思想
1.给你一个按 非递减顺序 排列的数组 nums
,返回正整数数目和负整数数目中的最大值。
- 换句话讲,如果
nums
中正整数的数目是pos
,而负整数的数目是neg
,返回pos
和neg
二者中的最大值。
2.我的思想是转换为找0的初始位置start和结束位置end,从而[0,start)
为负数,共start个数,但是正整数数要分类讨论(调试改正): - end位置为0,
[end+1,n)
是正整数,共n-end-1个数 - end位置不为0,
[end,n)
是正整数,共n-end个数
3.学习使用上面的upper_bound函数,找>targert的最小位置end,就不用分类讨论了,保证[end,n)
是正整数,共n-end个数
代码
c++:
1.我的代码
class Solution {
public:int lower_bound(vector<int>& nums, int target) {int n = nums.size();int res = n;int left = 0, right = n - 1;while (left <= right) {int mid = left + ((right - left) >> 1);if (nums[mid] >= target) {res = mid;right = mid - 1;} elseleft = mid + 1;}return res;}int maximumCount(vector<int>& nums) {int n = nums.size();int start = lower_bound(nums, 0), end = lower_bound(nums, 0 + 1);int neg = start; //[0,start)int pos = 0;if (end == n || nums[end] != 0)pos = n - end; //[end,n)elsepos = n - end - 1; //[end+1,n)return max(neg, pos);}
};
2.变成upper_bound:
class Solution {
public:int lower_bound(vector<int>& nums, int target) {int n = nums.size();int res = n;int left = 0, right = n - 1;while (left <= right) {int mid = left + ((right - left) >> 1);if (nums[mid] >= target) {res = mid;right = mid - 1;} elseleft = mid + 1;}return res;}int upper_bound(vector<int>& nums, int target) {int n = nums.size();int res = n;int left = 0, right = n - 1;while (left <= right) {int mid = left + ((right - left) >> 1);// 修改处1if (nums[mid] > target) {res = mid;right = mid - 1;} elseleft = mid + 1;}return res;}int maximumCount(vector<int>& nums) {int n = nums.size();int start = lower_bound(nums, 0), end = upper_bound(nums, 0); //修改处2int neg = start; //[0,start)// 修改处3int pos = n - end; //[end,n);return max(neg, pos);}
};
二.二分答案
“花费一个 log 的时间,增加了一个条件。” —— 二分答案(如何理解?)
问:如何把二分答案与数组上的二分查找联系起来?
答:假设答案在区间 [2,5]
中,我们相当于在一个虚拟数组 [check(2),check(3),check(4),check(5)]
中二分找第一个(或者最后一个)值为 true 的 check(i)。
模版套路
1.套路
c++:
2.题目描述
3.学习经验
(1)
2.1 求最小
题目求什么,就二分什么。
1.套路
c++:
class Solution {
public:// check函数bool check(vector<int>& nums, int threshold, int m) {long long sum = 0;for (const auto x : nums) {// 向上取整(学习+m-1调整/向下取整)sum += (x + m - 1) / m;// 提前结束if (sum > threshold)return false;}return true;}int smallestDivisor(vector<int>& nums, int threshold) {int n = nums.size();// 左边界为1,因为除数m不能为0int left = 1, right = INT_MIN;// 右边界取最大元素for (const auto x : nums)right = max(right, x);int res = right;while (left <= right) {int mid = left + ((right - left) >> 1);// 满足条件再继续找更小的if (check(nums, threshold, mid)) {res = mid;right = mid - 1;} elseleft = mid + 1;}return res;}
};
2.题目描述
1.给你一个整数数组 nums
和一个正整数 threshold
,你需要选择一个正整数(二分答案) 作为除数,然后将数组里每个数都除以它,并对除法结果求和。请你找出能够使上述结果小于等于阈值 threshold
(条件) 的除数中 最小 的那个。
3.学习经验
(1)分为两个部分:
- 二分搜索所求最小的数:难点在于仔细考虑左边界left和右边界的right的取值,如果check()函数为true说明还可能存在更小的,right=mid-1向左侧更小的搜索
- check函数判断这个数是否满足条件:可以提前退出返回false
(2)符合单调性,即二分搜索的数越小,统计量越大,会超过条件限制,反过来就是二分搜索的数越大,统计量越小,越能满足条件(即越大越合法),所以存在一个最小的数恰好满足条件
1. 1283.使结果不超过阈值的最小除数(中等,学习)
1283. 使结果不超过阈值的最小除数 - 力扣(LeetCode)
思想
1.给你一个整数数组 nums
和一个正整数 threshold
,你需要选择一个正整数作为除数,然后将数组里每个数都除以它,并对除法结果求和。请你找出能够使上述结果小于等于阈值 threshold
的除数中 最小 的那个。
2.学习向上取整的写法,通过加上m-1
将它转换为/的向下取整:(x+m-1)/m
3.单调性分析:
假设除数为 m。
根据题意,每个数除以 m 再上取整,元素和为
∑ i = 0 n − 1 ⌈ n u m s [ i ] m ⌉ \sum_{i=0}^{n-1}\lceil \frac{nums[i]}{m} \rceil ∑i=0n−1⌈mnums[i]⌉
由于 m 越大,上式越小,有单调性,可以二分答案。
最小的满足 ∑ i = 0 n − 1 ⌈ n u m s [ i ] m ⌉ ≤ t h r e s h o l d \sum_{i=0}^{n-1}\lceil \frac{nums[i]}{m} \rceil \leq threshold ∑i=0n−1⌈mnums[i]⌉≤threshold的m就是答案
代码
c++:
class Solution {
public:// check函数bool check(vector<int>& nums, int threshold, int m) {long long sum = 0;for (const auto x : nums) {// 向上取整(学习+m-1调整/向下取整)sum += (x + m - 1) / m;// 提前结束if (sum > threshold)return false;}return true;}int smallestDivisor(vector<int>& nums, int threshold) {int n = nums.size();// 左边界为1,因为除数m不能为0int left = 1, right = INT_MIN;// 右边界取最大元素for (const auto x : nums)right = max(right, x);int res = right;while (left <= right) {int mid = left + ((right - left) >> 1);// 满足条件再继续找更小的if (check(nums, threshold, mid)) {res = mid;right = mid - 1;} elseleft = mid + 1;}return res;}
};