标了🤦♂️🤦♂️的是完全按照大佬们的题解写的
标了🌟的是理解了大神们的题解后,自己又写的
没标的是自己磕磕绊绊写出来的低水平代码
cppbool check(int x) { // 检查x是否满足某种性质
/* ... */
}
// 区间[low, high]被划分成[low, mid]和[mid + 1, high]时使用
int bsearch_1(int low, int high)
{
while (low < high)
{
int mid = low + high >> 1;
if (check(mid)) high = mid; // check()判断mid是否满足性质
else low = mid + 1;
}
return low;
}
// 区间[low, high]被划分成[low, mid - 1]和[mid, high]时使用
int bsearch_2(int low, int high)
{
while (low < high)
{
int mid = low + high + 1 >> 1;
if (check(mid)) low = mid;
else high = mid - 1;
}
return low;
}
cpp核心思想
二分要对有序序列做,那我们就要找到题目中存在的有序序列,看看能不能用二分来解决题目
另一个关键是对二分后得到数据的逻辑操作
得到二分后数据只是一个操作手段,核心是对所得数据的操作
如果有连续的一组数据都符合二分最终条件
通过判断条件=的位置可以来选定上界或者下界
例如:
// 左极限
while (low < high)
{
int mid = (low + high) >> 1;
if (nums[mid] <= x) high = mid; // check()判断mid是否满足性质
else low = mid + 1;
}
对于数组[1,2,3,4,5,5,5,5,5,6,7,8,9],x=5
最终的low=4,也就是最左边的5,左极限
// 右极限
while (low < high)
{
int mid = (low + high + 1) >> 1;
if (nums[mid] < x) high = mid - 1; // check()判断mid是否满足性质
else low = mid;
}
对于数组[1,2,3,4,5,5,5,5,5,6,7,8,9],x=5
最终的low=8,也就是最右边的5,右极限
难度:简单
题目:
给你一个非负整数 x
,计算并返回 x
的 算术平方根 。
由于返回类型是整数,结果只保留 整数部分 ,小数部分将被 舍去 。
**注意:**不允许使用任何内置指数函数和算符,例如 pow(x, 0.5)
或者 x ** 0.5
。
示例:
输入:x = 4 输出:2
输入:x = 8 输出:2 解释:8 的算术平方根是 2.82842..., 由于返回类型是整数,小数部分将被舍去。
提示:
0 <= x <= 231 - 1
解题思路:
1ll
是个好东西high=mid, low=mid+1
high=mid-1, low=mid
代码:
cppclass Solution {
public:
int mySqrt(int x) {
int low = 0, high = x;
while(low < high){ // 二分寻找mid,1LL是为了在计算时,把int类型的变量转化为long long
int mid = low + high + 1ll >> 1;
if(x / mid < mid){ // 用x/mid来防止溢出,因为返回整数,所以相当于我们要找到下界,那么就让high=mid-1
high = mid - 1;
}
else{
low = mid;
}
}
return low;
}
};
难度:中等
题目:
给你一个下标从 1 开始的整数数组 numbers
,该数组已按 非递减顺序排列 ,请你从数组中找出满足相加之和等于目标数 target
的两个数。如果设这两个数分别是 numbers[index1]
和 numbers[index2]
,则 1 <= index1 < index2 <= numbers.length
。
以长度为 2 的整数数组 [index1, index2]
的形式返回这两个整数的下标 index1
和 index2
。
你可以假设每个输入 只对应唯一的答案 ,而且你 不可以 重复使用相同的元素。
你所设计的解决方案必须只使用常量级的额外空间。
示例:
输入:numbers = [2,7,11,15], target = 9 输出:[1,2] 解释:2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。返回 [1, 2] 。
输入:numbers = [2,3,4], target = 6 输出:[1,3] 解释:2 与 4 之和等于目标数 6 。因此 index1 = 1, index2 = 3 。返回 [1, 3] 。
输入:numbers = [-1,0], target = -1 输出:[1,2] 解释:-1 与 0 之和等于目标数 -1 。因此 index1 = 1, index2 = 2 。返回 [1, 2] 。
提示:
2 <= numbers.length <= 3 * 104
-1000 <= numbers[i] <= 1000
numbers
按 非递减顺序 排列-1000 <= target <= 1000
解题思路:
for
循环来确定一个数的位置,之后使用二分的方式来寻找第二个数low=mid+1
代码:
cppclass Solution {
public:
vector<int> twoSum(vector<int>& numbers, int target) {
vector<int> res;
int length = numbers.size();
for(int i = 0; i < length; i ++){ // 先确定一个数,再通过二分确定第二个数
res.push_back(i + 1);
int low = i + 1, high = length - 1;
while(low < high){
int mid = low + high >> 1; // 计算mid
if(numbers[mid] + numbers[i] >= target){ // 判断条件,正常情况下我们都让low=mid+1
high = mid;
}
else{
low = mid + 1;
}
}
if(numbers[low] + numbers[i] == target){ // 如果满足条件,压入res并返回
res.push_back(low + 1);
return res;
}
res.pop_back();
}
return res;
}
};
难度:中等
题目:
给定一个含有 n
个正整数的数组和一个正整数 target
。
找出该数组中满足其和 ≥ target
的长度最小的 连续子数组 [numsl, numsl+1, ..., numsr-1, numsr]
,并返回其长度**。**如果不存在符合条件的子数组,返回 0
。
示例:
输入:target = 7, nums = [2,3,1,2,4,3] 输出:2 解释:子数组 [4,3] 是该条件下的长度最小的子数组。
输入:target = 4, nums = [1,4,4] 输出:1
输入:target = 11, nums = [1,1,1,1,1,1,1,1] 输出:0
提示:
1 <= target <= 109
1 <= nums.length <= 105
1 <= nums[i] <= 105
解题思路:
167题
高,但是当我们先求出前缀和数组后,和167题
是完全一样的for
循环来确定第一个数的位置,之后通过二分寻找第二个数low=mid+1
代码:
cppclass Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
int length = nums.size();
int sum[length];
int min = length + 1;
sum[0] = nums[0];
for(int i = 1; i < length; i ++){ // 先求出前缀和数组
sum[i] = sum[i-1] + nums[i];
}
for(int i = 0; i < length; i ++){ // 之后就和167题的思路相同,确定第一个数,二分寻找第二个数的位置
int low = i, high = length - 1;
bool flag = false;
while(low < high){
int mid = low + high >> 1;
if(sum[mid] - sum[i] + nums[i] >= target){ // 因为是求上界,所以让low=mid+1
flag = true;
high = mid;
}
else{
low = mid + 1;
}
}
if(sum[low] - sum[i] + nums[i] >= target && low - i + 1 < min){ // 判断是否满足条件
min = low - i + 1;
}
}
if(min == length + 1){ // 判断是否有解
return 0;
}
return min;
}
};
难度:中等
题目:
给你一棵 完全二叉树 的根节点 root
,求出该树的节点个数。
完全二叉树 的定义如下:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h
层,则该层包含 1~ 2h
个节点。
示例:
输入:root = [1,2,3,4,5,6] 输出:6
输入:root = [] 输出:0
输入:root = [1] 输出:1
提示:
[0, 5 * 104]
0 <= Node.val <= 5 * 104
**进阶:**遍历树来统计节点是一种时间复杂度为 O(n)
的简单解决方案。你可以设计一个更快的算法吗?
解题思路:
[0, n]
的完全二叉树,数据范围可以做二分[2^level, 2^(level+1)-1]
代码:
cpp/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
int level = 0;
bool exist(int mid, TreeNode* root){ // 通过位运算判断在完全二叉树中存在mid
int length = 1 << (level - 1);
TreeNode* temp = root;
while(temp && length > 0){
if(!(length & mid)){ // &运算得出mid每一位是0还是1,判断是左子树还是右子树
temp = temp->left;
}
else{
temp = temp->right;
}
length >>= 1;
}
return temp != nullptr;
}
int countNodes(TreeNode* root) {
if(!root){
return 0;
}
TreeNode *temp = root;
while(temp->left){
level ++;
temp = temp->left;
}
int low = 1 << level, high = (1 << (level + 1)) - 1; // 在可能的上限区间内进行二分
while(low < high){
int mid = low + high + 1 >> 1; // 因为是求下界。采用low=mid
if(exist(mid, root)){ // 如果存在,low=mid
low = mid;
}
else{ // 反之,high=mid-1
high = mid - 1;
}
}
return low;
}
};
难度:中等
题目:
给定一个包含 n + 1
个整数的数组 nums
,其数字都在 [1, n]
范围内(包括 1
和 n
),可知至少存在一个重复的整数。
假设 nums
只有 一个重复的整数 ,返回 这个重复的数 。
你设计的解决方案必须 不修改 数组 nums
且只用常量级 O(1)
的额外空间。
示例:
输入:nums = [1,3,4,2,2] 输出:2
输入:nums = [3,1,3,4,2] 输出:3
提示:
1 <= n <= 105
nums.length == n + 1
1 <= nums[i] <= n
nums
中 只有一个整数 出现 两次或多次 ,其余整数均只出现 一次进阶:
nums
中至少存在一个重复的数字?O(n)
的解决方案吗?解题思路:
在 双指针出现过这道题
那么我们现在用二分来解决
整体的思想是抽屉原理
[1, n]
内,如果小于等于x
的数有y
个,且y>x
,那么[1, x]
区间内一定有重复数据那么我们只需要对[1, n]
区间做二分,用每次的mid
在输入数组中搜索,看小于等于mid
的数有多少个,对low
和high
做调整
最终得到的low
即为答案
代码:
cppclass Solution {
public:
int findDuplicate(vector<int>& nums) {
int length = nums.size();
int low = 1, high = length;
int cnt;
while(low < high){
int mid = low + high >> 1;
cnt = 0;
for(int i = 0; i < length; i ++){ // 在输入数组中寻找小于等于mid的个数
if(nums[i] <= mid){
cnt ++;
}
}
if(cnt > mid){ // 利用抽屉原理,如果小于等于mid的数字个数大于mid,那么一定有重复数
high = mid;
}
else{
low = mid + 1;
}
}
return low;
}
};
难度:中等
题目:
给你一个整数数组 nums
,找到其中最长严格递增子序列的长度。
子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7]
是数组[0,3,1,6,2,2,7]
的子序列。
示例:
输入:nums = [10,9,2,5,3,7,101,18] 输出:4 解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。
输入:nums = [0,1,0,3,2,3] 输出:4
输入:nums = [7,7,7,7,7,7,7] 输出:1
提示:
1 <= nums.length <= 2500
-104 <= nums[i] <= 104
进阶:
O(n log(n))
吗?解题思路:
代码:
cppclass Solution {
public:
int lengthOfLIS(vector<int>& nums) {
int length = nums.size();
int len = 0;
int res[length];
for(int i = 0; i < length; i ++){ // 遍历nums
int low = 0, high = len - 1;
while(low < high){ // 对res做二分,找到nums[i]如果要放入res中,应该放在哪个位置
int mid = low + high >> 1;
if(res[mid] >= nums[i]){
high = mid;
}
else{
low = mid + 1;
}
}
if(len == 0){ // 特判一下,如果len为0, 那直接压入
res[len++] = nums[i];
}
else{ // 否则
if(res[low] < nums[i]){ // 如果res对应位置的数更小,说明res中不存在比nums[i]大的,应该加在res最后
res[len++] = nums[i];
}
else{ // 否则,替换当前位置
res[low] = nums[i];
}
}
}
return len; // 返回整体长度
}
};
难度:简单
题目:
给定两个数组 nums1
和 nums2
,返回 它们的交集 。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序 。
示例:
输入:nums1 = [1,2,2,1], nums2 = [2,2] 输出:[2]
输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4] 输出:[9,4] 解释:[4,9] 也是可通过的
提示:
1 <= nums1.length, nums2.length <= 1000
0 <= nums1[i], nums2[i] <= 1000
解题思路:
nums2
nums1
,将取出的数字在nums2
中二分,查找有没有相同的数字nums1
中相同的字母只用一次代码:
cppclass Solution {
public:
bool exist(vector<int>& nums, int temp){ // 在nums中查找有无temp
int low = 0, high = nums.size() - 1;
while(low < high){
int mid = (low + high) >> 1;
if(nums[mid] < temp){
low = mid + 1;
}
else{
high = mid;
}
}
return nums[low] == temp;
}
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
sort(nums1.begin(), nums1.end()); // 排序
sort(nums2.begin(), nums2.end());
vector<int> res;
int length = nums1.size();
for(int i = 0; i < length; i ++){ // 对nums1的每一个数在nums2中通过二分查找是否存在
if(i > 0 && nums1[i] == nums1[i - 1]){
continue;
}
if(exist(nums2, nums1[i])){ // 如果存在就压入
res.push_back(nums1[i]);
}
}
return res;
}
};
难度:简单
题目:
给你两个整数数组 nums1
和 nums2
,请你以数组形式返回两数组的交集。返回结果中每个元素出现的次数,应与元素在两个数组中都出现的次数一致(如果出现次数不一致,则考虑取较小值)。可以不考虑输出结果的顺序。
示例:
输入:nums1 = [1,2,2,1], nums2 = [2,2] 输出:[2,2]
输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4] 输出:[4,9]
提示:
1 <= nums1.length, nums2.length <= 1000
0 <= nums1[i], nums2[i] <= 1000
进阶:
nums1
的大小比 nums2
小,哪种方法更优?nums2
的元素存储在磁盘上,内存是有限的,并且你不能一次加载所有的元素到内存中,你该怎么办?解题思路:
同349题,唯一不同的地方是返回数组可重复
那么就有了两种策略
nums2
中的每个数据是否使用过,定义状态数组的空间复杂度为O(n)
erase()
函数来删除nums2
中使用过的数据,erase()
的时间复杂度为O(n)
我选择空间换时间
只需要判断nums
的low
位置的数值是否是temp
并且有没有被用过即可
其余均与349题相同
代码:
cppclass Solution {
public:
bool status[1000]; // 建立一个状态数组,在349题的基础上做一步判断就好了
bool exist(vector<int>& nums, int temp){ // 在nums中查找有无temp
int len = nums.size();
int low = 0, high = nums.size() - 1;
while(low < high){
int mid = (low + high) >> 1;
if(nums[mid] < temp){
low = mid + 1;
}
else{
high = mid;
}
}
while(low < len && nums[low] == temp){ // 判断nums的low位置的数值是否是temp并且有没有被用过
if(!status[low]){
status[low] = true;
return true;
}
else{
low ++;
}
}
return false;
}
vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
sort(nums1.begin(), nums1.end()); // 排序
sort(nums2.begin(), nums2.end());
vector<int> res;
int length = nums1.size();
for(int i = 0; i < length; i ++){ // 对nums1的每一个数在nums2中通过二分查找是否存在
if(exist(nums2, nums1[i])){ // 如果存在就压入
res.push_back(nums1[i]);
}
}
return res;
}
};
难度:中等
题目:
冬季已经来临。 你的任务是设计一个有固定加热半径的供暖器向所有房屋供暖。
在加热器的加热半径范围内的每个房屋都可以获得供暖。
现在,给出位于一条水平线上的房屋 houses
和供暖器 heaters
的位置,请你找出并返回可以覆盖所有房屋的最小加热半径。
说明:所有供暖器都遵循你的半径标准,加热的半径也一样。
示例:
输入: houses = [1,2,3], heaters = [2] 输出: 1 解释: 仅在位置2上有一个供暖器。如果我们将加热半径设为1,那么所有房屋就都能得到供暖。
输入: houses = [1,2,3,4], heaters = [1,4] 输出: 1 解释: 在位置1, 4上有两个供暖器。我们需要将加热半径设为1,这样所有房屋就都能得到供暖。
输入:houses = [1,5], heaters = [2] 输出:3
提示:
1 <= houses.length, heaters.length <= 3 * 104
1 <= houses[i], heaters[i] <= 109
解题思路:
代码:
cppclass Solution {
public:
int getPosition(int num, vector<int>& heaters){
int low = 0, high = heaters.size() - 1;
while(low < high){ // 二分查找最近的房子
int mid = (low + high) >> 1;
if(heaters[mid] < num){
low = mid + 1;
}
else{
high = mid;
}
}
int temp = abs(heaters[low] - num);
if(low > 0){ // 要注意的是,找到的是大于等于供暖器位置的房子的最小位置,还要和左边的房子进行对比,找到最近距离
int temp1 = abs(heaters[low - 1] - num);
return temp < temp1 ? temp : temp1;
}
return temp;
}
int findRadius(vector<int>& houses, vector<int>& heaters) {
sort(heaters.begin(), heaters.end()); // 对供暖器位置进行排序
int length = houses.size();
int minMax = 0;
for(int i = 0; i < length; i ++){ // 遍历房子的位置
int temp = getPosition(houses[i], heaters); // 找到离房子最近的供暖器和房子的距离
if(temp > minMax){ // 找到最大的最小距离
minMax = temp;
}
}
return minMax; // 返回
}
};
难度:困难
题目:
给定一个整数 n
和一个 无重复 黑名单整数数组 blacklist
。设计一种算法,从 [0, n - 1]
范围内的任意整数中选取一个 未加入 黑名单 blacklist
的整数。任何在上述范围内且不在黑名单 blacklist
中的整数都应该有 同等的可能性 被返回。
优化你的算法,使它最小化调用语言 内置 随机函数的次数。
实现 Solution
类:
Solution(int n, int[] blacklist)
初始化整数 n
和被加入黑名单 blacklist
的整数int pick()
返回一个范围为 [0, n - 1]
且不在黑名单 blacklist
中的随机整数示例:
输入 ["Solution", "pick", "pick", "pick", "pick", "pick", "pick", "pick"] [[7, [2, 3, 5]], [], [], [], [], [], [], []] 输出 [null, 0, 4, 1, 6, 1, 0, 4] 解释 Solution solution = new Solution(7, [2, 3, 5]); solution.pick(); // 返回0,任何[0,1,4,6]的整数都可以。注意,对于每一个pick的调用, // 0、1、4和6的返回概率必须相等(即概率为1/4)。 solution.pick(); // 返回 4 solution.pick(); // 返回 1 solution.pick(); // 返回 6 solution.pick(); // 返回 1 solution.pick(); // 返回 0 solution.pick(); // 返回 4
提示:
1 <= n <= 109
0 <= blacklist.length <- min(105, n - 1)
0 <= blacklist[i] < n
blacklist
中所有值都 不同pick
最多被调用 2 * 104
次解题思路:
官方题解还是牛啊,我只能想到在黑名单二分找有没有随机数,没想到是找合适的白名单对应位置
首先来找我们可以对什么数据做二分
那么先生成一个随机数x,代表要返回白名单的第x个数,注意,从第0个开始
采用thisBlacklist[mid] - mid
来表示在黑名单的第mid个数之前有多少数在白名单中
通过二分找到黑名单中最右侧满足thisBlacklist[low] - low <= x
的数据
low
的基础上加x
,刚好是>=thisBlacklist[low]
,因为从第0
个开始,所以再加一,也就是low+x+1
如果黑名单中最小的数据也大于x,那么直接返回x就好了
代码:
cppclass Solution {
public:
int thisN;
vector<int> thisBlacklist;
Solution(int n, vector<int>& blacklist) {
thisN = n;
thisBlacklist = blacklist;
sort(thisBlacklist.begin(), thisBlacklist.end()); // 对黑名单进行排序,方便后续二分
}
int pick() {
int x = rand() % (thisN - thisBlacklist.size()); // 生成随机数,要返回白名单中的第x个元素
int low = 0, high = thisBlacklist.size() - 1;
while(low < high){
int mid = (low + high + 1) >> 1;
// 类似于,一个萝卜一个坑,如果5号萝卜占据了1号萝卜的坑,那么从1号到4号萝卜都没有坑放了,此时5号萝卜前就有4个萝卜free了,那么换成参数,在thisBlacklist[mid]号萝卜前,free的萝卜就有thisBlacklist[mid] - mid个
if(thisBlacklist[mid] - mid > x){ // 如果mid位置之前有超过x个萝卜free,就将high移动到mid-1
high = mid - 1;
}
else{ // 否则将low移动到mid,目的是找到最右边的符合条件的值
low = mid;
}
}
if(thisBlacklist.size() == 0){ // 特判黑名单是否为空
return x;
}
if(thisBlacklist[low] - low > x){ // 如果最终找到的位置,之前还有超过x个萝卜free,那么直接返回x就好了,从0到x的萝卜都free了;即黑名单里最小的数都大于x,那直接返回x,在白名单里前x的数据就是0到x
return x;
}
else{ // 或者有小于等于x个萝卜free,而且当前位置是最右边的符合条件的
// 有thisBlacklist[low] - low <= x,即 x + low + 1 > thisBlacklist[low]
// 那说明再往右一个的数一定满足thisBlacklist[low + 1] - (low + 1) > x,即x + low + 1 < thisBlacklist[low + 1],也就是x + low + 1在黑名单内未出现
// 可得thisBlacklist[low] < x < thisBlacklist[low + 1]
// 在low的基础上加x + 1,就是得到了白名单的第x个数据,多加的1是因为从0开始计算
return x + low + 1;
}
}
};
/**
* Your Solution object will be instantiated and called as such:
* Solution* obj = new Solution(n, blacklist);
* int param_1 = obj->pick();
*/
本文作者:southyang
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!