二分查找——算法专项刷题(十)

十、二分查找

二分查找最关键的地方在于,找到可以进行二分的结果集

10.1 查找插入位置

原题链接

给定一个排序的整数数组 nums 和一个整数目标值 target ,请在数组中找到 target ,并返回其下标。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。

请必须使用时间复杂度为 O(log n) 的算法。

示例 1:

  • 输入: nums = [1,3,5,6], target = 5
  • 输出: 2

提示:

  • 1 <= nums.length <= 10^4
  • -10^4 <= nums[i] <= 10^4
  • nums 为无重复元素的升序排列数组
  • -10^4 <= target <= 10^4

思路: 简单二分查找,查找目标值

注意点: 当数组中不存在target 时,判断目标值和nums[l]对应的值大小,从而判断插入的位置是否+1

class Solution {
    
    
    public int searchInsert(int[] nums, int target) {
    
    

        int l = 0, r = nums.length -1;

  while(l < r){
    
    
      int mid = l + ((r - l) >> 1);

      if(nums[mid] > target){
    
    
          r = mid -1;
      }else if(nums[mid] < target){
    
    
          l = mid + 1;
      }else{
    
    
          return mid;
      }
  }

  return target > nums[l] ? l + 1 : l;
    }
}

10.2山峰数组的顶部

原题链接

符合下列属性的数组 arr 称为 山峰数组(山脉数组)

  • arr.length >= 3

  • 存在 i(0 < i < arr.length - 1)使得:

    • arr[0] < arr[1] < ... arr[i-1] < arr[i]

    • arr[i] > arr[i+1] > ... > arr[arr.length - 1]

    给定由整数组成的山峰数组 arr ,返回任何满足 arr[0] < arr[1] < ... arr[i - 1] < arr[i] > arr[i + 1] > ... > arr[arr.length - 1] 的下标 i ,即山峰顶部。

示例 1:

  • 输入:arr = [0,1,0]
  • 输出:1

提示:

  • 3 <= arr.length <= 104
  • 0 <= arr[i] <= 106
  • 题目数据保证 arr 是一个山脉数组

思路: 二分查找,比较当前下标对应值和下一个数值的大小,寻找山峰

注意点:l < r 可以使得 mid+1 不会越界

class Solution {
    
    
    public int peakIndexInMountainArray(int[] arr) {
    
    

      int len = arr.length;
      
      int l = 0, r = len -1;

      while(l < r){
    
    
          
          int mid = l + ((r - l) >> 1);


         if(arr[mid] < arr[mid + 1]){
    
    
             l = mid + 1;
         }else{
    
    
             r = mid;
         }


      }
      return l;


    }
}

10.3 排序数组中只出现一次的数字

原题链接

给定一个只包含整数的有序数组 nums ,每个元素都会出现两次,唯有一个数只会出现一次,请找出这个唯一的数字。

你设计的解决方案必须满足 O(log n) 时间复杂度和 O(1) 空间复杂度。

示例 1:

  • 输入: nums = [1,1,2,3,3,4,4,8,8]
  • 输出: 2

提示:

  • 1 <= nums.length <= 10^5
  • 0 <= nums[i] <= 10^5

思路: 二分查找,因为两个相同的数字一定是相邻的,通过比较对应相邻元素数值是否相等,判断出现一次的元素的位置

注意点: 通过异或 可以将奇偶数两种情况全部考虑到

class Solution {
    
    
    public int singleNonDuplicate(int[] nums) {
    
    

    
       int l = 0, r = nums.length - 1;
       if( l == r) return nums[l];

       while(l < r){
    
    
           int mid = l + ((r -l) >> 1);
          // 通过异或找到 mid 对应的那对元素
           if(nums[mid] == nums[mid ^ 1]){
    
    
               l = mid + 1;
           }else{
    
    
               r = mid;
           }
       }
         
       return nums[l];
        
 
    }
}

10.4求平方根

给定一个非负整数 x ,计算并返回 x 的平方根,即实现 int sqrt(int x) 函数。

正数的平方根有两个,只输出其中的正数平方根。

如果平方根不是整数,输出只保留整数的部分,小数部分将被舍去。

示例 1:

  • 输入: x = 4
  • 输出: 2

提示:

  • 0 <= x <= 2^31 - 1

思路: 二分查找,从1~ x 之间查找 一个数的平方与 x的大小关系不断缩小查找范围

注意点: 使用 mid x / mid进行比较防止溢出

class Solution {
    
    
    public int mySqrt(int x) {
    
    

   int l = 1, r = x;

   while( l <= r){
    
    

     int mid = l + ((r - l) >> 1);

     if(mid  > x / mid){
    
    
           r = mid -1;
     }else{
    
    
         l = mid + 1;
     }

   }

   return r;

   
    }
}

10.5 按权重生成随机数

原题链接

给定一个正整数数组 w ,其中 w[i] 代表下标 i 的权重(下标从 0 开始),请写一个函数 pickIndex ,它可以随机地获取下标 i,选取下标 i 的概率与w[i]成正比。

例如,对于 w = [1, 3],挑选下标 0 的概率为 1 / (1 + 3) = 0.25 (即,25%),而选取下标 1 的概率为 3 / (1 + 3) = 0.75(即,75%)。

也就是说,选取下标 i 的概率为 w[i] / sum(w)

  • 示例 1:

输入:

  • inputs = [“Solution”,“pickIndex”]
  • inputs = [[[1]],[]]

输出:

  • [null,0]

解释:

  • Solution solution = new Solution([1]);
  • solution.pickIndex(); // 返回 0,因为数组中只有一个元素,所以唯一的选择是返回下标 0。

提示:

  • 1 <= w.length <= 10000
  • 1 <= w[i] <= 10^5
  • pickIndex 将被调用不超过 10000

思路: 对数组求前缀和,元素大于0保证递增,元素总和为sum,对[1,sum]中的随机整数进行二分查找,找到数组中第一个大于等于随机数的值,返回下标

注意点: 随机数范围是[1,sum],如果不加1会出现0的情况

class Solution {
    
    
    int[] w;
    int sum;
     private static final Random random = new Random();

    public Solution(int[] w) {
    
    
        this.w = w;
        int len = w.length;
        
        for(int i =1; i < len; i++){
    
    
            // 前缀和
            w[i] += w[i-1];
        }
        // 总和
       sum =  w[len -1];
    }
    
    public int pickIndex() {
    
    
        int target = random.nextInt(sum) + 1; // [1,sum]范围中的随机整数
        int l =0 ,r = w.length-1;
        while(l <= r){
    
    
            int mid = l + ((r - l) >> 1);
            if(w[mid] < target){
    
    
                l = mid + 1;
            }else{
    
    
                r = mid - 1;
            }
        }
        return l;
    }
}

10.6狒狒吃香蕉

原题链接

狒狒喜欢吃香蕉。这里有 n 堆香蕉,第 i 堆中有 piles[i] 根香蕉。警卫已经离开了,将在 h 小时后回来。

狒狒可以决定她吃香蕉的速度 k (单位:根/小时)。每个小时,她将会选择一堆香蕉,从中吃掉 k 根。如果这堆香蕉少于 k 根,她将吃掉这堆的所有香蕉,然后这一小时内不会再吃更多的香蕉,下一个小时才会开始吃另一堆的香蕉。

狒狒喜欢慢慢吃,但仍然想在警卫回来前吃掉所有的香蕉。

返回她可以在 h 小时内吃掉所有香蕉的最小速度 k(k 为整数)

示例 1:

  • 输入:piles = [3,6,7,11], h = 8
  • 输出:4

提示:

  • 1 <= piles.length <= 104
  • piles.length <= h <= 109
  • 1 <= piles[i] <= 109

思路: 找到数组最大值max,然后对[1,max]进行二分,求出吃完所有香蕉所需时间,然后和h进行比较,判断是否加速或减速,最终找到在h时间内吃完的最小速度

注意点: 求某一速度下吃一堆香蕉花费的时间,需要向上取整

class Solution {
    
    
    public int minEatingSpeed(int[] piles, int h) {
    
    

int max = 0;
        for(int num : piles){
    
    
              max = Math.max(num,max);
        }

  int l = 1, r = max;

  while(l <= r){
    
    
      // 速度
      int mid = l + ((r - l) >> 1);
      // 时间
       int ans = 0;
      for(int num : piles){
    
    
         ans += (num - 1) / mid + 1;  //向上取整
      }
      if(ans > h){
    
    
          l = mid + 1;  //加快速度
      }else{
    
    
          r = mid - 1;   // 降低速度
      }
  }

  return l;

    }
}

猜你喜欢

转载自blog.csdn.net/qq_52595134/article/details/128336568