算法思想
二分查找
public int binarySearch(int key, int[] nums) {
int left = 0;
int right = nums.length - 1;
while (left <= right) {
int mid = left + (right - left)/2; //<1>
if (key == nums[mid]){
return mid; //找到了
}
if (key < nums[mid]){
right = mid - 1;
}else{
left = mid + 1;
}
}
return -1; //没找到
}
算法时间复杂度
O(logN)
计算mid
不要使用mid=(left + right)/2这种方式去计算mid,因为left+right可能会导致加法溢出,应该使用mid=left + (right - left)/2;
计算right
当循环条件为 left <= right时,则 right = mid - 1;因为如果 right = mid,会出现循环无法退出的情况,例如 left = 1,right = 1,此时 mid 也等于 1,如果此时继续执行 right = mid,那么就会无限循环。
当循环条件为 left < right时,则 right = mid。因为如果 right = mid - 1,会错误跳过查找的数,例如对于数组 [1,2,3],要查找 1,最开始 left = 0,right = 2,mid = 1,判断 key < arr[mid] 执行 right = mid - 1 = 0,此时循环退出,直接把查找的数跳过了;
返回值
在循环条件为 left<= right 的情况下,循环退出时 left 总是比 right 大 1,并且 left 是将 key 插入 nums 中的正确位置。例如对于 nums = {0,1,2,3},key = 4,循环退出时 left = 4,将 key 插入到 nums 中的第 4 个位置就能保持 nums 有序的特点;
在循环条件为 left < right 的情况下,循环退出时 l 和 h 相等;
如果只是想知道 key 存不存在,在循环退出之后可以直接返回 -1 表示 key 不存在于 nums 中;
算法例题解析
LeetCode 69:x的平方根
实现 int sqrt(int x) 函数。计算并返回 x 的平方根,其中 x 是非负整数。由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去;
示例 1:
输入: 4
输出: 2
示例 2:
输入: 8
输出: 2
说明: 8 的平方根是 2.82842..., 由于返回类型是整数,小数部分将被舍去;
一个数x的开方根sqrt一定是在 0 ~ x 之间,并且满足 sqrt = x / sqrt;
所以,可以利用二分查找在 0 ~ x 之间查找 sqrt;
对于 x = 8,它的开方是 2.82842...,最后应该返回 2 而不是 3;在循环条件为 left <= right 并且循环退出时,right总是比 left小1,也就是说 right = 2,left= 3,因此最后的返回值应该为 right 而不是 left;
public int mySqrt(int x) {
if (x <= 1){
return x; //x是非负整数
}
int left = 1, right = x;
while (left <= right) {
int mid = left + (right - left) / 2;
int sqrt = x / mid;
if (sqrt == mid){
return mid; //求得结果
}
if (sqrt < mid){
right = mid - 1; //sqrt < mid ,说明mid*mid > x,应该把mid变小
}else{
left = mid + 1;
}
}
return right;
}
LeetCode 441:排列硬币
你总共有n枚硬币,你需要将它们摆成一个阶梯形状,第k行就必须正好有k枚硬币。给定一个数字 n,找出可形成完整阶梯行的总行数;
n 是一个非负整数,并且在32位有符号整型的范围内;
示例 1:
n = 5
硬币可排列成以下几行:
¤
¤ ¤
¤ ¤
因为第三行不完整,所以返回2.
示例 2:
n = 8
硬币可排列成以下几行:
¤
¤ ¤
¤ ¤ ¤
¤ ¤
因为第四行不完整,所以返回3.
第 i 行摆 i 个,统计能够摆的行数;
n 个硬币能够摆的行数 row 在 0 ~ n 之间,并且满足 n = row * (row + 1) / 2,因此可以利用二分查找在 0 ~ n 之间查找 row;
对于 n = 8,它能摆的行数 row = 3,这是因为最后没有摆满的那一行不能算进去,因此在循环退出时应该返回 h;
public int arrangeCoins(int n) {
int l = 0, h = n;
while (l <= h) {
int mid = l + (h - l) / 2;
long x = mid * (mid + 1) / 2;
if (x == n) return mid;
else if (x < n) l = mid + 1;
else h = mid - 1;
}
return h;
}