数组篇刷题模板总结一——二分查找
二分查找(细节是魔鬼)
二分查找模板
二分查找可以分为三种格式,此处将三种方式总结为一种通用模板,方便记忆。
- 查找某个数在有序数组中的位置
- 查找某个数在有序数组中的左侧边界
- 查找某个数在有序数组中的右侧边界
//查找某个数在有序数组中的位置
public int binarySearch(int[] nums; int target) {
if (nums.length <= 0) {
return -1;
}
int left = 0;
int right = nums.length - 1;
while (left <= right) {
int mid = left ((right - left) / 2);
if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] > target) {
right = mid - 1;
} else if(nums[mid] == target) {
// 直接返回
return mid;
}
}
retun -1;
}
//查找某个数在有序数组中的左侧边界
public int leftBound(int[] nums, int target) {
int left = 0, right = nums.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] > target) {
right = mid - 1;
} else if (nums[mid] == target) {
// 别返回,锁定左侧边界
right = mid - 1;
}
}
// 最后要检查 left 越界的情况
if (left >= nums.length || nums[left] != target)
return -1;
return left;
}
//查找某个数在有序数组中的右侧边界
public int rightBound(int[] nums, int target) {
int left = 0, right = nums.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] > target) {
right = mid - 1;
} else if (nums[mid] == target) {
// 别返回,锁定右侧边界
left = mid + 1;
}
}
// 最后要检查 right 越界的情况
if (right < 0 || nums[right] != target)
return -1;
return right;
}
【拓展】搜索插入位置
若题目要求某数在有序数列中插入的位置,只需要在二分查找算法上面稍加改进即可。
代码如下:
public int binarySearch(int[] nums; int target) {
if (nums.length <= 0) {
return -1;
}
int left = 0;
int right = nums.length - 1;
while (left <= right) {
if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] > target) {
right = mid - 1;
} else if(nums[mid] == target) {
// 直接返回
return mid;
}
}
return left;
//return right + 1;
}
【拓展】查找左侧和右侧边界
若有序数组中不存在该数直接返回[-1.-1],否则返回[左边界,右边界]数组。根据模板直接解答即可,注意变量问题和数组边界检测。
public int[] searchRange(int[] nums,int target) {
if (nums.length <= 0) {
return new int[] {-1,-1};
}
int left = 0;
int right = nums.length - 1;
return new int[] {leftBound(nums,target),rightBound(nums,target)};
}
public int leftBound(int[] nums, int target){
while (left <= right) {
int middle = left + (right - left) / 2;
if (target > nums[middle]) {
left = middle + 1;
}
else if (target < nums[middle]) {
right = middle - 1;
}
else if (target == nums[middle]) {
right = middle - 1;
}
}
if (left >= nums.length || nums[left] != target) {
return -1;
}
return left;
}
public int rightBound(int[] nums, int target){
while (left <= right) {
int middle = left + (right - left) / 2;
if (target > nums[middle]) {
left = middle + 1;
}
else if (target < nums[middle]) {
right = middle - 1;
}
else if (target == nums[middle]) {
left = middle + 1;
}
}
if (right < 0 || nums[right] != target) {
return -1;
}
return right;
}
【拓展】脱离数组,利用二分法解决生活中的题目
思路:最小速度 <=>最左边界,把吃香蕉的速度作为一个线性搜索的数组。解决较为轻松,此类题型都是如此,找准关键变量建立关系即可。
代码如下:
public int minEatingSpeed(int[] piles, int H) {
int left = 1;
int right = getmax(piles);
while (left <= right) {
int mid = left + (right - left) / 2;
if (canFinish(piles, mid, H)) {
right = mid - 1;
}
else {
left = mid + 1;
}
}
return left;
}
public int getmax(int[] piles) {
int max = 0;
for (int n : piles) {
max = Math.max(n,max);
}
return max;
}
public boolean canFinish(int[] piles, int K, int H) {
int time = 0;
for (int n : piles) {
time += timeOf(K,n);
}
return time <= H;
}
public int timeOf(int K,int n) {
return (n / K) + ((n % K) > 0 ? 1 : 0);
}
类似的
代码如下
public int splitArray(int[] nums, int m) {
if (nums.length <= 0) {
return -1;
}
int left = getMax(nums);
int right = getSum(nums);
while (left <= right) {
int mid = left + (right - left) / 2;
if (canFinish(mid,m,nums)) {
right = mid - 1;
}
else {
left = mid + 1;
}
}
return left;
}
// 确定nums子数组最小值,即m = nums.length的时候,nums数组中单个元素最大值
public int getMax(int[] nums) {
int max = 0;
for (int i : nums) {
max = Math.max(i,max);
}
return max;
}
// 确定nums子数组最大值,即m = 1的时候,nums整体最大值,即数组所有元素和
public int getSum(int[] nums) {
int sum = 0;
for (int j : nums) {
sum += j;
}
return sum;
}
public boolean canFinish(int maxMin, int m,int[] nums) {
// 未分组时整个数组看作一个子数组,即n代表子数组的个数
int n = 1;
int sum = 0;
for (int i = 0; i < nums.length; i++) {
// 贪心算法,如果当前sum叠加nums[i]超出maxMin即mid,则重新分出一个新的子数组,所以n也随之加1
if (sum + nums[i] > maxMin) {
n++;
// 新的一个子数组第一项
sum = nums[i];
}
// sum的值未超过maxMin,可以继续叠加
else {
sum += nums[i];
}
}
// 判断此时n与m的大小关系
return n <= m;
}
代码如下
public int shipWithinDays(int[] weights, int D) {
int left = getMin(weights);
int right = getMax1(weights);
while (left <= right) {
int mid = left + (right - left) / 2;
if (canFinish1(weights,D,mid)) {
right = mid - 1;
}
else {
left = mid + 1;
}
}
return left;
}
// 重点部分,判断条件,看D天内是否可以完成任务。贪心算法解决
public boolean canFinish1(int[] weights, int D, int cap) {
int i = 0;
for (int day = 0; day < D; day++) {
// 每天更新运载能力
int maxcap = cap;
while ((maxcap -= weights[i]) >= 0) {
i++;
if (i == weights.length){
return true;
}
}
}
return false;
}
//另解
/**
public boolean canFinish1(int[] weights, int D, int cap) {
//由于重点是能否完成任务,也可以以天数来判断
int EverydaySum = 0;
int day = 1;
for (int i = 0; i < weights.length; i++) {
if (EverydaySum + weights[i] > cap) {
day++;
EverydaySum = weights[i];
}
else {
EverydaySum += weights[i];
}
}
return day <= D;
}
*/
public int getMin(int[] weights) {
int max = 0;
for (int i : weights) {
max = Math.max(i,max);
}
return max;
}
public int getMax1(int[] weights) {
int sum = 0;
for (int i : weights) {
sum += i;
}
return sum;
}
思路其实和前面所见到的差不多,只是这里二分判断的条件有一些变化。本题目“翻译”过来其实就是将一个定义好的数组分为m个子数组,并且使得所有子数组的和的最大值在除去每个子数组的最大值之后是最小的,在二分判断里实际上就是保证除去最大值后,每个子数组的和仍然不超过定义好的T,即刚好满足条件的最小T。
代码如下
public int minTime(int[] time, int m) {
int left = 0;
int right = 0;
for (int i : time) {
left = Math.max(left,i);
right += i;
}
while (left <= right) {
int mid = left + (right - left) / 2;
if (canFinish(time, m, mid)) {
right = mid - 1;
}
else {
left = mid + 1;
}
}
return left;
}
public boolean canFinish(int[] time, int m, int T) {
int useday, totaltime, maxtime;
useday = 1; totaltime = maxtime = 0;
for (int i=0; i< time.length; ++i) {
int nexttime = Math.min(maxtime, time[i]);
if (nexttime + totaltime <= T) {
totaltime += nexttime;
maxtime = Math.max(maxtime, time[i]);
} else {
++useday;
totaltime = 0;
maxtime = time[i];
}
}
return (useday <= m);
}
记录复习过程,共勉(~ ̄▽ ̄)~