分享丨【算法题单】二分算法(二分答案/最小化最大值/最大化最小值/第K小)- 讨论 - 力扣(LeetCode)
思想:
1.
c++:
// 返回最小的满足nums[i]>=targert的下标
int lower_bound(vector& 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;
} else
left = mid + 1;
}
// 如果是找等于target的下标再单独判断
if(res==n || nums[res]!=target) return -1;
return res;
}
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
二者中的最大值。(1)nums非递减顺序排列
(2)res初始值为n,表示插入到最后
(3)lower_bound函数找最小的满足nums[i]>=target
的下标位置,所有元素比target小就插到n处
(4)可以再调用lower_bound找target+1的下标,然后-1就是target的结束位置
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& 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;
} else
left = mid + 1;
}
return res;
}
vector searchRange(vector& 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& 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;
} else
left = mid + 1;
}
return res;
}
};
注意:
1.结果返回vector
,可以写成{ }形式,用于列表初始化,元素个数不限,但是确定
35. 搜索插入位置 - 力扣(LeetCode)
1.给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
2.上面的lower_bound函数
c++:
class Solution {
public:
int searchInsert(vector& 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;
} else
left = mid + 1;
}
return res;
}
};
704. 二分查找 - 力扣(LeetCode)
1.给定一个 n
个元素有序的(升序) 整型数组 nums
和一个目标值 target
,写一个函数搜索 nums
中的 target
,如果目标值存在返回下标,否则返回 -1
。
2.跟前面两题不同在于"如果目标值存在返回下标,否则返回 -1
,所以再返回res前要再单独判断不满足条件的两种情况:
nums[res]
,再判断其是否等于targetc++:
class Solution {
public:
int search(vector& 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;
} else
left = mid + 1;
}
if (res == n || nums[res] != target)
return -1;
return res;
}
};
744. 寻找比目标字母大的最小字母 - 力扣(LeetCode)
1.给你一个字符数组 letters
,该数组按非递减顺序排序,以及一个字符 target
。letters
里至少有两个不同的字符。返回 letters
中大于 target
的最小的字符。如果不存在这样的字符,则返回 letters
的第一个字符。
2.区别在于找大于 target
的最小的字符,而不是大于等于,为upper_bound
c++:
class Solution {
public:
char nextGreatestLetter(vector& 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;
} else
left = mid + 1;
}
if (res == n)
return letters[0];
return letters[res];
}
};
2529. 正整数和负整数的最大计数 - 力扣(LeetCode)
1.给你一个按 非递减顺序 排列的数组 nums
,返回正整数数目和负整数数目中的最大值。
nums
中正整数的数目是 pos
,而负整数的数目是 neg
,返回 pos
和 neg
二者中的最大值。[0,start)
为负数,共start个数,但是正整数数要分类讨论(调试改正):[end+1,n)
是正整数,共n-end-1个数[end,n)
是正整数,共n-end个数[end,n)
是正整数,共n-end个数c++:
1.我的代码
class Solution {
public:
int lower_bound(vector& 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;
} else
left = mid + 1;
}
return res;
}
int maximumCount(vector& 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)
else
pos = n - end - 1; //[end+1,n)
return max(neg, pos);
}
};
2.变成upper_bound:
class Solution {
public:
int lower_bound(vector& 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;
} else
left = mid + 1;
}
return res;
}
int upper_bound(vector& 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);
// 修改处1
if (nums[mid] > target) {
res = mid;
right = mid - 1;
} else
left = mid + 1;
}
return res;
}
int maximumCount(vector& nums) {
int n = nums.size();
int start = lower_bound(nums, 0), end = upper_bound(nums, 0); //修改处2
int neg = start; //[0,start)
// 修改处3
int pos = n - end; //[end,n);
return max(neg, pos);
}
};
“花费一个 log 的时间,增加了一个条件。” —— 二分答案(如何理解?)
问:如何把二分答案与数组上的二分查找联系起来?
答:假设答案在区间 [2,5]
中,我们相当于在一个虚拟数组 [check(2),check(3),check(4),check(5)]
中二分找第一个(或者最后一个)值为 true 的 check(i)。
c++:
(1)
题目求什么,就二分什么。
c++:
class Solution {
public:
// check函数
bool check(vector& 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& nums, int threshold) {
int n = nums.size();
// 左边界为1,因为除数m不能为0
int 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;
} else
left = mid + 1;
}
return res;
}
};
1.给你一个整数数组 nums
和一个正整数 threshold
,你需要选择一个正整数(二分答案) 作为除数,然后将数组里每个数都除以它,并对除法结果求和。请你找出能够使上述结果小于等于阈值 threshold
(条件) 的除数中 最小 的那个。
(1)分为两个部分:
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& 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& nums, int threshold) {
int n = nums.size();
// 左边界为1,因为除数m不能为0
int 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;
} else
left = mid + 1;
}
return res;
}
};