二分法刷题记录
- 69. x 的平方根
- 167. 两数之和 II - 输入有序数组
- 350. 两个数组的交集 II
- 367. 有效的完全平方数
- 374. 猜数字大小
- 392. 判断子序列
- 441. 排列硬币
- 475. 供暖器
- 704. 二分查找
- 744. 寻找比目标字母大的最小字母
- 34. 在排序数组中查找元素的第一个和最后一个位置
- 153. 寻找旋转排序数组中的最小值
- 162. 寻找峰值
- 287. 寻找重复数
- 378. 有序矩阵中第 K 小的元素
- 875. 爱吃香蕉的珂珂(能力检测二分法)
- 33. 搜索旋转排序数组(有序+无序的二分法)
- 35. 搜索插入位置(二分法)
- 74. 搜索二维矩阵
- 240. 搜索二维矩阵 II(二分法以及Z字法)
- 278. 第一个错误的版本(右边界问题)
- 852. 山脉数组的峰顶索引
69. x 的平方根
class Solution {
public int mySqrt(int x) {
// 已知结果是整数,使用二分法来进行搜索
// 解空间为 【0,x】
if(x == 1)
return 1;
if(x == 0)
return 0;
int left = 0;
int right = x;
while(left <= right){
int mid = left + (right - left) / 2;
// 直接使用mid * mid会溢出
if(mid == x / mid){
return mid;
}else if(mid > x / mid){
// 大于目标值,往左边找
right = mid - 1;
}else{
left = mid + 1;
}
}
// 8的平方根是2.8多 最后结果是2.这是一个最右边界问题
return right;
}
}
167. 两数之和 II - 输入有序数组
class Solution {
public int[] twoSum(int[] numbers, int target) {
// Map<Integer,Integer> map = new HashMap<>();
// for(int i = 0; i < numbers.length; i++){
// if(map.containsKey(target - numbers[i])){
// // 这里注意返回的次序
// return new int[]{map.get(target-numbers[i]) + 1,i+1};
// }
// // 储存
// map.put(numbers[i],i);
// }
// return null;
// 使用双指针法,这个其实不算二分查找,其实核心思想是理解缩小搜索空间,变为o(n)
int len = numbers.length;
int left = 0;
int right = len - 1;
// 题目要求,不可以重复使用元素
while(left < right) {
int sum = numbers[left] + numbers[right];
if(sum < target){
// 说明sum要加大
left++;
}else if(sum > target){
// 说明sum要减小
right--;
}else{
// 相等情况
return new int[]{left+1,right+1};
}
}
return null;
}
}
350. 两个数组的交集 II
class Solution {
public int[] intersect(int[] nums1, int[] nums2) {
// 交集问题不就是重复问题 ==》哈希表
// 使用哈希表处理问题的时候,这里涉及到频次问题
Map<Integer,Integer> map = new HashMap<>();
// 定义结果集
int[] res = new int[nums1.length];
// 重复元素个数
int count = 0;
// 遍历nums1
for(int num:nums1){
// 储存值和频次信息
map.put(num,map.getOrDefault(num,0) + 1);
}
// 遍历nums2,进行比较
for(int num:nums2){
if(map.get(num)!=null && map.get(num) > 0){
// 发现相同值,储存到结果集中,更新频次
res[count++] = num;
map.put(num,map.get(num) - 1);
}else{
// 说明频次用完了,要移除这个值啦(防止后期的匹配)~
map.remove(num);
}
}
// 利用Arrays类方法截取数据
return Arrays.copyOfRange(res,0,count);
}
}
注意:
Arrays类的常用方法总结:
copyOfRange 只需要源数组就就可以了,通过返回值,就能够得到目标数组了。
除此之外,需要注意的是 copyOfRange 的第3个参数,表示源数组的结束位置,是取不到的。
class Solution {
public int[] intersect(int[] nums1, int[] nums2) {
// 先排序,在使用双指针法,本题解答很像167. 两数之和 II - 输入有序数组]的双指针解法
Arrays.sort(nums1);
Arrays.sort(nums2);
// 定义结果解
int[] res = new int[nums1.length];
// 结果个数
int count = 0;
// 双指针,对应两个数组
int p1,p2;
p1 = p2 = 0;
// 但凡有一个指针走到尽头直接over
while(p1 < nums1.length && p2 < nums2.length){
if(nums1[p1] > nums2[p2]){
// p2该努力啦~
p2++;
}else if(nums1[p1] < nums2[p2]){
// p1 该努力啦!
p1++;
}else{
// 匹配成功,两个都一起努力走~
res[count++] = nums1[p1];
p1++;
p2++;
}
}
// 调用Arrays类方法截取数据
return Arrays.copyOfRange(res,0,count);
}
}
367. 有效的完全平方数
class Solution {
public boolean isPerfectSquare(int num) {
// 这题不就是x的平方的变种题嘛~
// 解空间是[1,num]
int left = 1;
int right = num;
while(left <= right){
int mid = left + (right - left) / 2;
// 这里使用了double进行强制类型转换
// 默认的除法是 取出小数点的~,所以使用double1保留整小数点
if(mid == (double)num / mid){
// 找到了
return true;
}else if(mid > (double)num / mid){
// 往左边找
right = mid - 1;
}else{
// 右边找
left = mid + 1;
}
}
// 默认找不到
return false;
}
}
374. 猜数字大小
/**
* Forward declaration of guess API.
* @param num your guess
* @return -1 if num is lower than the guess number
* 1 if num is higher than the guess number
* otherwise return 0
* int guess(int num);
*/
public class Solution extends GuessGame {
public int guessNumber(int n) {
// 解空间是[1,n],典型的二分法
int left = 1;
int right = n;
while(left <= right){
int mid = left + (right - left) / 2;
if(guess(mid) == 0){
// 猜对了
return mid;
}else if(guess(mid) == -1){
// 猜大了
right = mid - 1;
}else{
left = mid + 1;
}
}
return 0;
}
}
392. 判断子序列
class Solution {
public boolean isSubsequence(String s, String t) {
// 这个子序列不是连续的数组,所以不能用滑动窗口解题
// 很明显是一个双指针问题,在特定情况下s的指针才进行移动
int p1 = 0;
int p2 = 0;
if(s.length() == 0){
return true;
}
if(s.length() > t.length()){
return false;
}
while(p1 < s.length()){
if(p2 == t.length()){
// p2到达终点了,p1还没有达到终点
return false;
}
if(s.charAt(p1) == t.charAt(p2)){
// 找到相同字母
p1++;
p2++;
}else {
// 字母不相同
p2++;
}
}
return true;
}
}
441. 排列硬币
class Solution {
public int arrangeCoins(int n) {
// 这题有点考察数学了
// 在数学问题上:第k行有k个硬币,那么意味着到这一行为止的银币有total = k * (k + 1) / 2
// 抽象成业务就是我要找到一个最大的k(最右边的,其中解空间为[1,n])使得total<=n
// 本题最终转换成了二分法的,求解最右边界问题
// 使用int的会超过范围
long left = 1;
long right = n;
while(left <= right){
long mid = left + (right - left) / 2;
long total = mid * (mid + 1) / 2;
if(total == n){
// 正好等于总银币
return (int)mid;
}else if(total < n){
// 到右边查找
left = mid + 1;
}else{
right = mid - 1;
}
}
// 返回的是最右边界的结果
return (int)right;
}
}
475. 供暖器
// class Solution {
// public int findRadius(int[] houses, int[] heaters) {
// // 先进行升序排列
// Arrays.sort(houses);
// Arrays.sort(heaters);
// int radius = 0;
// int i = 0;
// for (int house : houses) {
// while (i < heaters.length && heaters[i] < house) {
// // 一直找到处于房屋右侧的热水器
// i++;
// }
// if (i == 0)
// radius = Math.max(radius, heaters[i] - house);
// else if (i == heaters.length)
// // 最后一个热水器
// return Math.max(radius, houses[houses.length-1] - heaters[heaters.length-1]);
// else
// // 房屋右侧的热水器和房屋左侧的热水器,取小的那个
// radius = Math.max(radius, Math.min(heaters[i] - house, house - heaters[i - 1]));
// }
// return radius;
// }
// }
/**
(1)对供暖器按位置排序。
(2)对每一个house,使用自定义的二分查找函数,寻找它介于哪两个供暖器之间(house位置可能与供暖器重合,或者只有左边的供暖器,或者只有右边的供暖器)。
(3)对于某个house:
如果它位于供暖器i和供暖器i+1之间,那么这两个供暖器只要有一个能加热到这个house就可以,因此取这两个供暖器与house的距离的较小值作为加热半径即可。
对于house与供暖器重合的情况,加热半径为0即可。
对于house只有左边或者右边一个供暖器的情况,取供暖器与house的距离作为加热半径。
(4)想要所有的house都被加热到,就要从使各个house满足条件的加热半径中选出最大值。
*/
class Solution {
public int findRadius(int[] houses, int[] heaters) {
Arrays.sort(heaters);
int res = 0;
// 1、计算每个房子到供暖器的位置。
for (int house : houses) {
// 单个房子是利用二分查找寻找最小值
// 所有房子是单个房子中的最大值
res = Math.max(res, binarySearch(house, heaters));
}
return res;
}
public int binarySearch(int house, int[] heaters) {
int left = 0;
int right = heaters.length - 1;
while (left <= right) {
int mid = (left + right) / 2;
if (house == heaters[mid]) {
// 房子和热水器重叠,距离设置为0
return 0;
} else if (house < heaters[mid]) {
// 中点值大于房子,往左边查找
right = mid - 1;
} else {
// 中点值小于房子,往右边查找
left = mid + 1;
}
}
// 其实无非三种情况:
// 热水器和房子重叠(二分查找成功)、热水器在房子左侧(房子-热水器),热水器在房子右侧(热水器-房子)
if (left > heaters.length - 1) {
// 代表最后一个热水器在房子左侧,返回的距离就是 房子值-热水器
return house - heaters[heaters.length - 1];
} else if (right < 0) {
// 代表第一个热水器在房子右侧,返回的距离就是 热水器 - 房子
return heaters[0] - house;
} else {
// 左边两边距离取最小
return Math.min(heaters[left] - house, house - heaters[right]);
}
}
}
704. 二分查找
class Solution {
public int search(int[] nums, int target) {
int left = 0;
int len = nums.length;
int right = len - 1;
while(left <= right){
int mid = left + (right - left) / 2;
if(nums[mid] == target){
return mid;
}else if(nums[mid] > target){
right = mid -1;
}else{
left = mid + 1;
}
}
return -1;
}
}
744. 寻找比目标字母大的最小字母
class Solution {
public char nextGreatestLetter(char[] letters, char target) {
// 字符数组已经排好序了 ==》使用二分法
// 将问题抽象化,这是一个寻找最左边界的二分法
int left = 0;
int len = letters.length;
int right = len - 1;
while(left <= right){
int mid = left + (right - left) / 2;
if( target >= letters[mid]){
// 这是有可能的解空间
left = mid + 1;
}else{
right = mid - 1;
}
}
// 返回最左边界值,这里使用了模运算,插入位置是最后一个,就返回第一个值
return letters[left % len];
}
}
注意:本题二分查找边界的处理以及模运算的使用!!!
34. 在排序数组中查找元素的第一个和最后一个位置
class Solution {
public int[] searchRange(int[] nums, int target) {
// 数组有序,首先想到用二分法
// 比较特殊的是,这题是返回的是一维数组,也就是说有多个值,那么就要考虑好边界问题
// 问题就变成了我要寻找一个小于target的最右边界值和一个大于target的最左边界值
int left = binarySearchLeft(target,nums);
int right = binarySearchRight(target,nums);
return new int[]{left,right};
}
// 搜索小于target的第一个数
public int binarySearchLeft(int target,int[]nums){
int left = 0;
int right = nums.length - 1;
while(left <= right){
int mid = left + (right - left) / 2;
// 边界条件的处理
// 如果mid大于target,往左走
if(nums[mid] > target ){
right = mid - 1;
}else if(nums[mid] < target){
// 往右走
left = mid + 1;
}else if( (mid == 0 || nums[mid-1] < target) && nums[mid] == target){
// 搜索到target并且是起点
return mid;
}else{
// 搜索到target,但不是起点
right = mid - 1;
}
}
// 没有搜索到,就返回-1
return -1;
}
// 搜索大于target的第一个数
public int binarySearchRight(int target,int[]nums){
int left = 0;
int right = nums.length - 1;
while(left <= right){
int mid = left + (right - left) / 2;
if(nums[mid] > target ){
// 往左走
right = mid-1 ;
}else if(nums[mid] < target){
// 往右走
left = mid + 1 ;
}else if((mid == nums.length-1 || nums[mid+1] > target ) && nums[mid] == target){
// 搜索到target,并且是终点
return mid;
}else{
// 搜索到target,但不是终点
left = mid + 1;
}
}
// 没有找到返回 -1
return -1;
}
}
注意:本题的最大收获就是掌握了边界条件的判定,很多时候等号是最难判定的,比如此题中的等于target判定,你要判定这个找到的target是不是起点(终点),如果是,直接返回,如果不是,继续搜索!!!这是解题的核心。
== 不过还好,掌握了二分法的最左最右判定法,再注意一些细节就ok了!==
153. 寻找旋转排序数组中的最小值
class Solution {
public int findMin(int[] nums) {
// 时间复杂度O(nlogn)还比不上暴力遍历法(O(n))
// Arrays.sort(nums);
// return nums[0];
// 分析:左边有序,右边有序,这是一个部分有序的问题
// 画出折线图可知,这是一个寻找最左边界的问题(target = nums[0])
// 左边有序区域遵循>=nums[0],右边有序区域遵循<nums[0]
int left = 0;
int len = nums.length;
int right = len - 1;
while(left <= right){
int mid = left + (right - left) / 2;
if(nums[mid] >= nums[0]){
// 在左边的有序区域,这时候需要向右边寻找
left = mid + 1;
}else{
// 在右侧区域,需要向左边寻找
right = mid - 1;
}
}
// 如果本身有序,那么left值是len长度
if(left == len){
return nums[0];
}
// 返回的是最左边界值
return nums[left];
}
}
注意:本题核心就在于画图,找到关键的信息,左边排序数组>=nums[0],右边排序数组<nums[0],最后根据二分寻找出小于target(nums[0])的最左边界的值。
162. 寻找峰值
class Solution {
public int findPeakElement(int[] nums) {
// 时间复杂度为o(logn)说明使用的是二分法
// 从峰值的定义可值,左边区间是递增的,右边区间是递减的
//nums[-1] = nums[n] = -∞,这就代表着 只要数组中存在一个元素比相邻元素大,那么沿着它一定可以找到一个峰值
// 根据上述结论,我们就可以使用二分查找找到峰值
// 所以这里的区间划分就是mid 和mid +1了
int left = 0;
int len = nums.length;
int right = len - 1;
while(left <= right){
int mid = left + (right - left) / 2;
// 在使用mid + 1的时候一定要判定是否会越界
if(mid < len - 1 && nums[mid] < nums[mid + 1]){
// 峰值在右侧
left = mid + 1;
}else{
// 峰值在左侧
right = mid - 1;
}
}
// 寻找峰值的过程本质上是一个最左边界的探索过程
return left;
}
}
注意:本题核心在于理解nums[-1] == nums[n]等于负无穷这个条件,这就以为只要有一个点大于其相邻的,就必是峰值,在编程过程中,为何mid + 1的值比mid值大,就一定有峰值?很简单的一个思想,有上坡那么就必有坡顶,我们就是在寻找这个坡顶,向右推移,当跳出循环条件时候,left值就是峰值所在的索引(可以举例,画图)
287. 寻找重复数
class Solution {
public int findDuplicate(int[] nums) {
// 题目要求的空间复杂度是O(1),所以排除了哈希表的解法
// 不修改数组,意味着不能进行排序操作,不能使用二分法
// 考虑使用双指针法之快慢指针
// 方式一:暴力法,时间复杂度o(n^2),最后时间超时
// for(int i = 0; i < nums.length - 1; i++ ){
// int temp = nums[i];
// for(int j = i + 1; j < nums.length; j++ ){
// if(temp == nums[j]){
// return temp;
// }
// }
// }
// return 0;
// 快慢指针法,如何将数组构造成一个循环的链表?
// 众所周知,链表是由值和next指针构成的,链表值就是数组值,链表next指针就是数组的索引。索引和数组之间就构成了映射关系。形成了环形链表。
int fast = 0;
int slow = 0;
// 在环形链表中快指针走两步:
// fast = fast.next.next等价于 nums[nums[fast]]这样的隐射关系
fast = nums[nums[fast]];
slow = nums[slow];
while(fast != slow){
// 快指针走两步
fast = nums[nums[fast]];
// 慢指针走一步
slow = nums[slow];
}
// 相遇后将快指针指向链表头,重新制造相遇机会
fast = 0;
while(fast != slow){
// 各自走一步
fast = nums[fast];
slow = nums[slow];
}
// 这时候相遇的点,就是环的起点,也就是重复的值
return fast;
}
}
注意:本题的难点在于如何将数组转换成链表!!!这里使用了映射的原理,能这么使用最初的原因就是数组个数是N+1,下标是绝对不会溢出的。把数组当成对链表的一种描述, 数组里的每一个元素的值表示链表的下一个节点的索引。
378. 有序矩阵中第 K 小的元素
class Solution {
public int kthSmallest(int[][] matrix, int k) {
/**
分析:
最开始的想法是将二维数组储存到一维数组中,然后调用排序算法。这样子的弊端就是时间复杂度太高了,时间复杂度为O(n^2*logn),对n^2个数进行排序!空间复杂度为O(n^2)
另解:这是一个二维数组,每行每列都按照升序排序==》二维数组的二分法题型
*/
// 直接排序法
// int[] temp = new int[matrix.length * matrix.length];
// int count = 0;
// for(int i = 0; i < matrix.length; i++){
// for(int j = 0; j < matrix[0].length; j++){
// temp[count++] = matrix[i][j];
// }
// }
// Arrays.sort(temp);
// return temp[k-1];
// }
// 二分法,参考了官方题解得图形和精选题解的思路
// 核心在于理解题意,第一个数matrix[0][0]是最小值
// 最后一个数matrix[n-1][n-1]是最大值
// 那么每次二分后的mid就成为关键了,mid值将二维数组划分成两个区域,小于等于mid区域和大于mid区域。
// 小于等于mid区域个数 <= k的话,说明该元素在右区域
// 小于等于mid区域的个数 > k的话,说明该元素在左区域
int row = matrix.length;
int col = matrix[0].length;
int left = matrix[0][0];
int right = matrix[row-1][col-1];
while(left <= right){
int mid = left + (right - left) / 2;
int count = findLowerMid(matrix,mid,row,col);
if(count < k){
// 在右区域
left = mid + 1 ;
}
// 其实就是最左二分法的使用
// 找到最左满足count>=k的mid返回即可
else {
// 在左区域
right = mid - 1 ;
}
}
// 返回最左值
return left;
}
public int findLowerMid(int[][]matrix,int mid,int row,int col){
// 按列查找
int i = row - 1;
int j = 0;
int count = 0;
while( i >= 0 && j < col){
if(matrix[i][j] <= mid){
// 该列的数计入count
count += i + 1;
// 列偏移
j++;
}else{
// 行偏移
i--;
}
}
// 返回计数
return count;
}
}
875. 爱吃香蕉的珂珂(能力检测二分法)
class Solution {
public int minEatingSpeed(int[] piles, int h) {
/**
分析:
对香蕉数组进行排序,明确解空间为[1,max]
在解空间中二分查找一个数,定义一个check函数,判定该数会不会小于等于h
若小于等于h,则向左寻找更小值,否则向右寻找可能值
结论:
这是一个能力检测(最左二分法)
*/
Arrays.sort(piles);
int len = piles.length;
// 速度最小是1
int left = 1;
int right = piles[len - 1];
while(left <= right){
int mid = left + (right - left) / 2;
if(check(mid,h,piles)){
right = mid - 1;
}else{
left = mid + 1;
}
}
return left;
}
public boolean check(int mid ,int h, int[] piles){
int ans = 0;
for(int p:piles){
// 向上取整用Math.ceil(double a)
//向下取整用Math.floor(double a)
ans += Math.ceil((double)p / mid);
}
return ans<=h;
}
}
注意:这里的向上取整的写法为,Math.ceil((double)p / mid),其中还要只用double进行强制转换,因为int的 / 默认是会去除小数点的.
补充两个取整的知识点:
向上取整用Math.ceil(double a)
向下取整用Math.floor(double a)
33. 搜索旋转排序数组(有序+无序的二分法)
注意:思路要清晰,剩下的就是边界条件的处理…
class Solution {
public int search(int[] nums, int target) {
/**
分析:
题目要求时间复杂度是o(logn),并且局部有序,很容易想到使用二分查找。
观察可以知道:
1.旋转数组,从中间划分,一定有一边是有序的
2.一定有一边是有序的,所以根据有序的两个边界值来判断目标值在有序一边还是在无序的一边
3.找到目标值,返回指针下标即可
4.由于有序的一边的边界值可能等于目标值,所以判断目标值是否在有序的那边需要加个等号(边界条件的判断是难点)
*/
int left = 0, right = nums.length - 1;
while(left <= right){
int mid = left + (right - left) / 2;
if(nums[mid] == target){
// 直接找到
return mid;
}
// mid值大于等于left值,说明mid的左边有序,mid右边未知(按照无序处理)
// 这里要使用等号,为了避免只有两个数字旋转时,找不到的场景
if(nums[left] <= nums[mid]){
// 若target落在左边有序的区段,则在左边找即可
if(target >= nums[left] && target < nums[mid]){
// 往左边找
right = mid - 1;
}else{
// target落在右边无序区段,那么往右边找
left = mid + 1;
}
}else{
// 若target落在右边有序的区段,则在右边找即可
if(target > nums[mid] && target <= nums[right]){
// 往右边找
left = mid + 1;
}else{
// target落在左边无序区段,那么往左边找
right = mid - 1;
}
}
}
return -1;
}
}
35. 搜索插入位置(二分法)
class Solution {
public int searchInsert(int[] nums, int target) {
// 一个排序数组:立马想到二分法
// 这里还使用了一个特例判定法
int len = nums.length;
int left = 0;
int right = len - 1;
while(left <= right){
int mid = (right + left) / 2;
if(nums[mid] == target){
// 找到数据
return mid;
}else if(nums[mid] > target){
// 大于目标值往左边找
right = mid - 1;
}else{
left = mid + 1;
}
}
// 返回left,可以使用数据试探,奇数和偶数数据试探
return left;
}
}
74. 搜索二维矩阵
注意:其实本题考查的就是如何将二维数组下标和一维数组下标进行互相转换。
二维转换为一维:实际上就是求数组的个数
一维转换为二维:实际上就是考查二维数组列n和/ 和 % 的用法, / =>行下标, %=>列下标
class Solution {
public boolean searchMatrix(int[][] matrix, int target) {
// 这个二维数组是有序的
// 使用二分法
// 获取获取m值,和n值
int m = matrix.length;
int n = matrix[0].length;
// 防止溢出
int left = 0,right = m*n-1;
int mid = left + (right - left) / 2;
// 如何将二维数组的下标索引转换成一维数组索引
while(left <= right){
//
int value = matrix[mid/n][mid%n];
if(value==target){
return true;
}else if(value>target){
right = mid - 1;
}else{
left = mid + 1;
}
mid = left + (right - left) / 2;
}
return false;
}
}
240. 搜索二维矩阵 II(二分法以及Z字法)
class Solution {
public boolean searchMatrix(int[][] matrix, int target) {
/**
分析:
这道题是剑指offer的题,可以使用常规的每行进行二分查找,也可以特殊使用变种的z字型查找
第一想法就是使用二分法。
*/
int m = matrix.length;
int n = matrix[0].length;
// 每行进行二分查找
for(int i = 0; i < m; i++){
// 特判
if(matrix[i][0] > target){
continue;
}
if(matrix[i][n-1] < target){
continue;
}
// 调用二分查找
int res = binarySearch(matrix[i],target);
if(res != -1){
return true;
}
}
return false;
}
public int binarySearch(int[]matrix,int target){
int left = 0, right = matrix.length - 1;
while(left <= right){
int mid = left + (right - left) / 2;
if(matrix[mid] == target){
return mid;
}else if(matrix[mid] < target){
left = mid + 1;
}else{
right = mid - 1;
}
}
return -1;
}
}
下面是Z字遍历
class Solution {
public boolean searchMatrix(int[][] matrix, int target) {
/**
分析:
这道题是剑指offer的题,可以使用常规的每行进行二分查找,也可以特殊使用变种的z字型查找
第一想法就是使用二分法。
可以尝试优化,从右上角进行遍历,会发现,这是一颗二查搜索树。每判断一次就排除一行或者一列
*/
// 从右上角遍历
int m = 0, n = matrix[0].length - 1;
while( m < matrix.length && n >= 0 ){
// 保证不溢出
if(matrix[m][n] == target){
return true;
}else if(matrix[m][n] < target){
// 往下走
m++;
}else{
// 往左走
n--;
}
}
return false;
}
278. 第一个错误的版本(右边界问题)
/* The isBadVersion API is defined in the parent class VersionControl.
boolean isBadVersion(int version); */
public class Solution extends VersionControl {
public int firstBadVersion(int n) {
// 有序问题使用二分法
int left = 1;
int right = n;
while(left <= right){
int mid = left + (right - left) / 2;
if(isBadVersion(mid)){
// 是错误版本,往左边找
right = mid - 1;
}else{
// 是正确版本,往右边找
left = mid + 1;
}
}
// 这是返回第一个错误版本,并且是右边界问题
return right + 1;
}
}
852. 山脉数组的峰顶索引
class Solution {
public int peakIndexInMountainArray(int[] arr) {
/**
分析:
很明显,左边升序,右边降序。使用二分法解决问题,找到最左边界下标
*/
int left = 0, right = arr.length - 1;
while( left <= right){
int mid = left + (right - left) / 2;
// 为了防止下标溢出,使用了mid == 0
if(mid == 0 || arr[mid - 1] < arr[mid]){
// 落在左边区域,向右边查找
left = mid + 1;
}else{
right = mid - 1;
}
}
return right;
}
}
或者使用加法的代码
class Solution {
public int peakIndexInMountainArray(int[] arr) {
/**
分析:
很明显,左边升序,右边降序。使用二分法解决问题,找到最左边界下标
*/
int left = 0, right = arr.length - 1;
while( left <= right){
int mid = left + (right - left) / 2;
if(arr[mid] < arr[mid + 1]){
// 落在左边区域,向右边查找
left = mid + 1;
}else{
right = mid - 1;
}
}
return left;
}
}