当数据具有“二段性”的时候,
也就是根据某个规律,能将数据分为两个部分,又根据某个规律可以将其中一部分给舍弃
1、循环条件?
left <= right
只有left > right时才能跳出循环,因为每次分割出来的区间的数是未知的,当left == right的时候仍然要检查
while (l <= r)
{
// 有溢出风险,如果l和r的和 > int,会溢出
// mid = (l + r) / 2;
// 以下两个计算mid的方法是防止溢出的
// 如果数据量为偶数时,更偏向于找左边的数的下标
mid = l + (r - l) / 2;
// 让l走[l, r]的一半
// 如果数据量为偶数时,更偏向于找左边的数的下标
mid = l + (r - l + 1) / 2;
if (……)
{
r = mid - 1;
}
else if (……)
{
l = mid + 1;
}
else
return ……;
}
省略号部分是根据不同情况下的二段性来填写的
class Solution
{
public:
int search(vector<int>& nums, int target)
{
int l = 0, r = nums.size() - 1, mid;
while (l <= r)
{
// 有溢出风险,如果l和r的和 > int,会溢出
// mid = (l + r) / 2;
// 以下两个计算mid的方法是防止溢出的
// 如果数据量为偶数时,更偏向于找左边的数的下标
mid = l + (r - l) / 2;
// 让l走[l, r]的一半
// 如果数据量为偶数时,更偏向于找左边的数的下标
mid = l + (r - l + 1) / 2;
if (nums[mid] > target)
{
r = mid - 1;
}
else if (nums[mid] < target)
{
l = mid + 1;
}
else
return mid;
}
return -1;
}
};
题目链接:34. 在排序数组中查找元素的第一个和最后一个位置 - 力扣(LeetCode)
class Solution
{
public:
vector<int> searchRange(vector<int>& nums, int target)
{
if (nums.size() == 0)
return {-1, -1};
int ansl = 0, ansr = 0;
// 求左端点
int l = 0, r = nums.size() - 1, mid;
while (l </**/ r)
{/*
1、=的情况可以直接判断,无需继续循环
2、容易死循环,
因为求左端点时当target >= mid,则right = mid
因为求右端点时当target <= mid,则left = mid
*/
mid = l + (r - l/*求偏左边的点*/) / 2;
if (nums[mid] < target)
{
l = mid + 1;
}
else if (nums[mid] >=/**/ target)
{
r = mid;// 因为可能mid这个点就是左端点
}
}
if (nums[l] != target)
return {-1, -1};
else
ansl = l;
// 求右端点
l = 0, r = nums.size() - 1;
while (l </**/ r)
{/*
1、=的情况可以直接判断,无需继续循环
2、容易死循环,
因为求左端点时当target >= mid,则right = mid
因为求右端点时当target <= mid,则left = mid
*/
mid = l + (r - l + 1/*求偏右边的那个点*/) / 2;
if (nums[mid] <=/**/ target)
{
l = mid;// 因为可能mid这个点就是右端点
}
else if (nums[mid] > target)
{
r = mid - 1;
}
}
if (nums[l] != target)
return {-1, -1};
else
ansr = l;
return {ansl, ansr};
}
};
while (l </**/ r)
{/*
1、=的情况可以直接判断,无需继续循环
2、容易死循环,
因为求左端点时当target >= mid,则right = mid
因为求右端点时当target <= mid,则left = mid
*/
mid = l + (r - l/*求偏左边的点,否则可能死循环,因为l一直更新为 = mid*/) / 2;
if (nums[mid] < target)// (……)括号里面填写“二段性”的相关判断条件
{
l = mid + 1;
}
else if (nums[mid] >=/**/ target)// (……)括号里面填写“二段性”的相关判断条件
{
r = mid;// 因为可能mid这个点就是左端点
}
}
if (nums[l] != target)
return {-1, -1};
else
ansl = l;
while (l </**/ r)
{/*
1、=的情况可以直接判断,无需继续循环
2、容易死循环,
因为求左端点时当target >= mid,则right = mid
因为求右端点时当target <= mid,则left = mid
*/
mid = l + (r - l + 1/*求偏右边的那个点,否则可能死循环,因为l一直更新为 = mid*/) / 2;
if (nums[mid] <=/**/ target)// (……)括号里面填写“二段性”的相关判断条件
{
l = mid;// 因为可能mid这个点就是右端点
}
else if (nums[mid] > target)// (……)括号里面填写“二段性”的相关判断条件
{
r = mid - 1;
}
}
二段性: 1、平方 > x 2、平方 <= x
// 求左端点的情况
class Solution
{
public:
int mySqrt(int x)
{
if (x < 1)
return 0;// 边界情况
int l = 1, r = x;
long long mid;// 因为x <= 2^31 - 1, mid * mid可能溢出
while (l < r)
{
mid = l + (r - l + 1) / 2;
if (mid * mid <= x)
l = mid;
else// if (mid * mid > x)
r = mid - 1;// 有 - 1 -> mid计算时要+ 1
}
return l;
}
};
题目链接:. - 力扣(LeetCode)
该题关键点:找第一个大于target的位置 -------> 二段性:(1) < target (2) >= target 左端点的解法
class Solution
{
public:
int searchInsert(vector<int>& nums, int target)
{
int l = 0, r = nums.size() - 1, mid;
while (l < r)
{
mid = l + (r - l) / 2;
if (nums[mid] < target)
l = mid + 1;
else if (nums[mid] >= target)
r = mid;
}
if (nums[l] < target)
return l + 1;
return l;
}
};
题目链接:. - 力扣(LeetCode)
总体先递增再递减 ----> 二段性:左半部分(arr[mid - 1] <= arr[mid]),右半部分(arr[mid - 1] > arr[mid])
class Solution {
public:
int peakIndexInMountainArray(vector<int>& arr)
{
int l = 0, r = arr.size() - 1, mid;
while (l < r)
{
mid = l + (r - l + 1) / 2;
if (mid > 0)
{
if (arr[mid - 1] <= arr[mid])
l = mid;// 因为mid可能就是答案
else
r = mid - 1;// 有 - 1 -----> 上面要 + 1
}
else
break;
}
return r;
// return l;// 都可以
}
};
题目链接:153. 寻找旋转排序数组中的最小值 - 力扣(LeetCode)
特点:递增(设为AB段)、递增(设为CD段) 二段性: AB段:nums[i] > nums[n - 1] CD段:nums[i] <= nums[n - 1] 该解法用的是D来作为参照
class Solution {
public:
int findMin(vector<int>& nums)
{
int size = nums.size();
int l = 0, r = size - 1, mid;
while (l < r)
{
mid = l + (r - l) / 2;
if (nums[mid] > nums[size - 1])// AB段
l = mid + 1;
else
r = mid;
}
return nums[r];// 注意要返回的是 最小 元素 ,不是元素 下标
}
};
题目链接:LCR 173. 点名 - 力扣(LeetCode)
O(n):1、哈希表 2、直接遍历 3、位运算(和正确数组位运算) 4、求和(用等差数列求正确的和,再减去题目所给元素和) O(logN):二分查找 二段性: 在答案位置的前面,正确的学号 - 数组中的学号 = 0 在答案位置的后面,正确的学号 - 数组中的学号 = 1
class Solution {
public:
int takeAttendance(vector<int>& records)
{
int l = 0, r = records.size() - 1, mid;
while (l < r)
{
mid = l + (r - l) / 2;
if (records[mid] - mid == 0)
l = mid + 1;
else// if (records[mid] - mid == 1)
r = mid;
}
return records[l] == l ? l + 1 : l;
}
};