1.寻找当前第一个>=H的位置
class Solution {
public int find(int[] nums, int H) {
int left = 0, right = nums.length;//注意可以取到len
while(left < right){
int mid = (left + right)>>>1;
if(nums[mid] >= H){//此处为>=
right = mid;
}else{
left = mid + 1;
}
}
return left;
}
}
2.寻找第一个>H的数
class Solution {
public int find(int[] nums, int H) {
int left = 0, right = nums.length;//注意可以取到len
while(left < right){
int mid = (left + right)>>>1;
if(nums[mid] > H){//唯一变化的地方
right = mid;
}else{
left = mid + 1;
}
}
return left;
}
}
L69
- x 的平方根 实现 int sqrt(int x) 函数。
计算并返回 x 的平方根,其中 x 是非负整数。
由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去。
示例 1:
输入: 4 输出: 2 示例 2:
输入: 8 输出: 2 说明: 8 的平方根是 2.82842…,
由于返回类型是整数,小数部分将被舍去
目前注意的是,mid为向上取整,主要在于left = mid,这种赋值语句,容易mid取值时,在只有两个数的时候,mid =left,始终成立,程序无法结束。
同时在书写的时候,注意,判断等号究竟和谁在一起,>=还是<=,依据就是等号和谁在一起是同一种情况,同时对应的left,right一般为一个等于mid ,一个等于mid+1,或者mid -1;
class Solution {
public int mySqrt(int x) {
if(x == 0) return 0;
//注意考虑边界情况,有可能存在0,1的值,但是right不一定能取到1
//为防止溢出,都用long
long left = 0, right = x/2 +1;//其实可以直接取x-1;
while(left < right){
long mid = left + (right - left +1)/2;//注意右边界为mid,要加一,不然会超时
//目标是寻找当前最接近的且小于x的值
long res = mid * mid;
if(res > x){
right = mid - 1;//其实模板与寻找第一个大于x的位置相似,只是right = mid - 1;
}else{
left = mid;//这个也是不同的地方
}
}
return (int)left;
}
}
L875
- 爱吃香蕉的珂珂 珂珂喜欢吃香蕉。这里有 N 堆香蕉,第 i 堆中有 piles[i] 根香蕉。警卫已经离开了,将在 H 小时后回来。
珂珂可以决定她吃香蕉的速度 K (单位:根/小时)。每个小时,她将会选择一堆香蕉,从中吃掉 K 根。如果这堆香蕉少于 K
根,她将吃掉这堆的所有香蕉,然后这一小时内不会再吃更多的香蕉。珂珂喜欢慢慢吃,但仍然想在警卫回来前吃掉所有的香蕉。
返回她可以在 H 小时内吃掉所有香蕉的最小速度 K(K 为整数)。
示例 1:
输入: piles = [3,6,7,11], H = 8 输出: 4 示例 2:
输入: piles = [30,11,23,4,20], H = 5 输出: 30
class Solution {
public int minEatingSpeed(int[] piles, int H) {
//先确定速度的范围,二分查找
if(piles.length == 0) return -1;
int left = 1;
int right = 0;
for(int pile : piles){
right = Math.max(right, pile);
}
//速度与耗时是线性相关,但是是负相关,所以要变化一下
//二分查找,实际是找最接近H,即小于等于的位置,与寻找第一个大于等于的类似
while(left < right){
int mid = (left + right )>>>1;
if(judge(piles, mid) <= H){//唯一变化的地方就是>=变成<=
right = mid;
}else{
left = mid + 1;
}
}
return left;
}
int judge(int[] nums, int val){
int sum = 0;
for(int num:nums){
sum += (num + val - 1)/val;//注意要往上取整
//这就是向上取整的表达式,前提得是num,val均为整数才行,小数则不满足了
}
return sum;
}
}
L287
== 这个数据hot100 ==
- 寻找重复数 给定一个包含 n + 1 个整数的数组 nums,其数字都在 1 到 n 之间(包括 1 和 n),可知至少存在一个重复的整数。假设只有一个重复的整数,找出这个重复的数。
示例 1:
输入: [1,3,4,2,2] 输出: 2 示例 2:
输入: [3,1,3,4,2] 输出: 3 说明:
不能更改原数组(假设数组是只读的)。 只能使用额外的 O(1) 的空间。 时间复杂度小于 O(n2) 。
数组中只有一个重复的数字,但它可能不止重复出现一次。
class Solution {
public int findDuplicate(int[] nums) {
int len = nums.length;
if(len == 0) return -1;//不过题目是说一定存在的
//利用二分法,非常规的二分法,数组虽然不是有序,但是一定是顺序分布,最多有一个重复的
int left = 0, right = len - 1;
while(left < right){//当left = right时,一定能找到,内心要先把数组进行排序
//实质是对排序之后的数组的搜索
int mid = (left + right) >>>1;//这样无符号右移,不会溢出
int cnt = 0;
//因为现在是乱序的,所以要整体遍历
//遍历整个数组<= mid的个数
for(int i = 0; i < len; i++){//注意数组仍然从0开始,不要写成1,只是left,right指的是具体值
if(nums[i] <= mid)cnt++;
}
if(cnt > mid){//当前严格大于mid,说明1-mid之间一定有重复的
right = mid;
}else{
left = mid + 1;
}
}
return left;
}
}
L374
- 猜数字大小 我们正在玩一个猜数字游戏。 游戏规则如下: 我从 1 到 n 选择一个数字。 你需要猜我选择了哪个数字。 每次你猜错了,我会告诉你这个数字是大了还是小了。 你调用一个预先定义好的接口 guess(int num),它会返回 3
个可能的结果(-1,1 或 0):-1 : 我的数字比较小 1 : 我的数字比较大 0 : 恭喜!你猜对了! 示例 :
输入: n = 10, pick = 6 输出: 6
class GuessGame {
private static final int NUM = 6;
int guess(int num) {
if (num == NUM) {
return 0;
} else if (num < NUM) {
return -1;
}
return 1;
}
}
public class Solution extends GuessGame {
public int guessNumber(int n) {
int left = 1, right = n;
while(left <right){
int mid = (left + right)>>>1;//不加上1就是会超时,因为mid分在了右边
//int mid= (left + right)/2;即使不溢出,也是超时,关键要+ 1
int val = guess(mid);
if(val == -1){//说明当前真实的数字比mid较小
right = mid - 1;
}else{
left = mid;//mid也是有可能的
}
}
return left;
}
}
对比一下
public class Solution extends GuessGame {
public int guessNumber(int n) {
int left = 1, right = n;
while(left <right){
int mid = (left + right)>>>1;//对于这种即使不+1也是不会出错
//如果加上1反而会超时!!!
//int mid= (left + right)/2;
int val = guess(mid);
if(val == 1){//说明当前真实的数字比mid较大
left = mid + 1;
}else{
right = mid;//mid也是有可能的
}
}
return left;
}
}
多放一种if分支
public class Solution extends GuessGame {
public int guessNumber(int n) {
int left = 1, right = n;
while(left <right){
int mid = (left + right + 1)>>>1;
//int mid= (left + right)/2;
int val = guess(mid);
if(val == 1){//说明当前真实的数字比mid较大
left = mid + 1;
}else if(val == -1){
right = mid - 1;
}else return mid;//其实碰到概率还是很低的,不见得会时间短
}
return left;
}
}
L1300
(前面总结的规律再一次体现)
- 转变数组后最接近目标值的数组和 给你一个整数数组 arr 和一个目标值 target ,请你返回一个整数 value ,使得将数组中所有大于 value 的值变成 value 后,数组的和最接近 target (最接近表示两者之差的绝对值最小)。
如果有多种使得和最接近 target 的方案,请你返回这些整数中的最小值。
请注意,答案不一定是 arr 中的数字。
示例 1:
输入:arr = [4,9,3], target = 10 输出:3 解释:当选择 value 为 3 时,数组会变成 [3, 3,
3],和为 9 ,这是最接近 target 的方案。 示例 2:输入:arr = [2,3,5], target = 10 输出:5 示例 3:
输入:arr = [60864,25176,27249,21296,20204], target = 56803 输出:11361
提示:
1 <= arr.length <= 10^4 1 <= arr[i], target <= 10^5
此题在于,并不是严格单调的,当value如果过大的话,其sum值有可能是保持不变的,所以当等于target时,并不退出循环
同时有可能出现图中的情况,一般left - 1,所以最终二者再进行比较即可
class Solution {
public int findBestValue(int[] arr, int target) {
if(arr == null || arr.length == 0) return -1;
int left = 0, right = target;
//参考的value值越大,对应的总和就越大,线性相关
//寻找第一个大于等于targe的值,常规做法
while(left < right){
int mid = (left + right)>>>1;
if(calcu(arr, mid) >= target){
right = mid;
}else{
left = mid + 1;
}
}
//有可能是在left与left - 1之间,所以还需要再次判断
//注意left,right表示的是最终的值,不是位置,不需要考虑数组越界
int ans1 = calcu(arr, left - 1);
int ans2 = calcu(arr, left);
if(target - ans1 <= ans2 - target){//判断哪个更接近
return left - 1;
}else return left;
}
int calcu(int[] arr, int val){
int sum = 0;
for(int num:arr){
sum += Math.min(val, num);
}
return sum;
}
}
2.同样可以寻找小于target,同时也是最接近的数
与69题有区别
class Solution {
public int findBestValue(int[] arr, int target) {
if(arr == null || arr.length == 0) return -1;
int left = 0, right = target;
//参考的value值越大,对应的总和就越大,线性相关
//寻找最后一个小于等于targe的值,常规做法
//与69题的区别在于,此题是找最接近的,[2,3,5],10,输出:10,正确结果为5
//所以即使当前满足calcu(arr, mid) = target,仍然还是要缩小,对应的calcul规则是不同的
while(left < right){
int mid = (left + right +1)>>>1;//由于left = mid,所以加1
if(calcu(arr, mid) >= target){//这里为>=,与69题还是不同的
right = mid - 1;//对应这里就要改变一下
}else{
left = mid;
}
}
//有可能是在left与left + 1之间,所以还需要再次判断
//注意left,right表示的是最终的值,不是位置,不需要考虑数组越界
int ans1 = calcu(arr, left);
int ans2 = calcu(arr, left + 1);
if(target - ans1 <= ans2 - target){//判断哪个更接近
return left;
}else return left + 1;
}
int calcu(int[] arr, int val){
int sum = 0;
for(int num:arr){
sum += Math.min(val, num);
}
return sum;
}
}