文章目录
二分模板
left = 0;
right = array.length - 1;
while(left <= right) {
int mid = left + (right - left) / 2;//取left和right的中间值
if (array[mid] == target) {
//find the target
break or return result
} else if (array[mid] < target){
left = mid + 1;
}else {
right = mid + 1;
}
}
一、LeetCode69. x 的平方根
题目描述
链接
实现 int sqrt(int x) 函数。
计算并返回 x 的平方根,其中 x 是非负整数。
由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去。
示例 1:
输入: 4
输出: 2
示例 2:
输入: 8
输出: 2
说明: 8 的平方根是 2.82842...,
由于返回类型是整数,小数部分将被舍去。
二分法
class Solution {
public int mySqrt(int x) {
int left = 0;
int right = x;
int ans = -1;
while (left <= right) {
int mid = left + ((right - left) >> 1);
if ((long)mid * mid <= x) {
ans = mid;
left = mid + 1;
}
else
right = mid - 1;
}
return ans;
}
}
二、LeetCode367. 有效的完全平方数
题目描述
链接
给定一个正整数 num,编写一个函数,如果 num 是一个完全平方数,则返回 True,否则返回 False。
说明:不要使用任何内置的库函数,如 sqrt。
示例 1:
输入:16
输出:True
示例 2:
输入:14
输出:False
二分法
tips:测试数据较大,需要定义为long
class Solution {
public boolean isPerfectSquare(int num) {
if (num == 1)
return true;
long left = 0;
long right = num >> 1;//一个数的平方根必然不会大于该数的1/2
while (left <= right) {
long mid = left + ((right - left) >> 1);
long temp = mid * mid;
if (temp == num)
return true;
else if(temp > num)
right = mid - 1;
else
left = mid + 1;
}
return false;
}
}
或者,处理一下中间结果,让其值不超过 i n t int int的最大范围
class Solution {
public boolean isPerfectSquare(int num) {
if (num == 1)
return true;
int left = 0;
int right = num >> 1;
while (left <= right) {
int mid = left + ((right - left) >> 1);
long temp = mid * mid;
if (num / mid == mid && num % mid == 0)//相当于 mid * mid == num
return true;
else if(num / mid < mid) //相当于 mid * mid > num
right = mid - 1;
else
left = mid + 1;
}
return false;
}
}
三、LeetCode33. 搜索旋转排序数组
题目描述
链接
假设按照升序排序的数组在预先未知的某个点上进行了旋转。
( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。
搜索一个给定的目标值,如果数组中存在这个目标值,则返回它的索引,否则返回 -1 。
你可以假设数组中不存在重复的元素。
你的算法时间复杂度必须是 O(log n) 级别。
示例 1:
输入: nums = [4,5,6,7,0,1,2], target = 0
输出: 4
示例 2:
输入: nums = [4,5,6,7,0,1,2], target = 3
输出: -1
二分法
- 将旋转数组从中间分为两部分的时候,一定有一部分的数组是有序的。比如 n u m s = [ 4 , 5 , 6 , 7 , 0 , 1 , 2 ] nums = [4,5,6,7,0,1,2] nums=[4,5,6,7,0,1,2],从中间分开为 [ 4 , 5 , 6 ] [4,5,6] [4,5,6]和 [ 7 , 0 , 1 , 2 ] [7,0,1,2] [7,0,1,2]两部分,其中左边部分 [ 4 , 5 , 6 ] [4,5,6] [4,5,6]是有序的。
- 那么,我们可以在每次查看mid为分割位置分割出来的两个部分
[
l
e
f
t
,
m
i
d
]
[left, mid]
[left,mid]和
[
m
i
d
+
1
,
r
i
g
h
t
]
[mid + 1, right]
[mid+1,right]哪个部分是有序的,并根据有序的那个部分确定下一次二分搜索的上下界:
- 如果 [ l e f t , m i d ] [left, mid ] [left,mid]是有序数组,并且 t a r g e t target target在区间 [ n u m s [ l e f t ] , n u m s [ m i d ] ) [ nums[left], nums[mid] ) [nums[left],nums[mid]),则将搜索范围缩小至 [ l e f t , m i d − 1 ] [left, mid - 1] [left,mid−1],否则缩小至 [ m i d + 1 , r i g h t ] [mid + 1, right] [mid+1,right];
- 如果 ( m i d , r i g h t ] (mid,right] (mid,right]是有序数组,并且 t a r g e t target target在区间 ( n u m s [ m i d ] , n u m s [ r i g h t ] ] ( nums[mid], nums[right] ] (nums[mid],nums[right]],则将搜索范围缩小至 [ m i d + 1 , r i g h t ] [mid + 1, right] [mid+1,right],否则缩小至 [ l e f t , m i d − 1 ] [left,mid - 1] [left,mid−1]。
class Solution {
public int search(int[] nums, int target) {
int n = nums.length;
if (n <= 0)
return -1;
if (n == 1)
return nums[0] == target ? 0 : -1;
int left = 0;
int right = n - 1;
while (left <= right) {
int mid = left + ((right - left) >> 1);
if (nums[mid] == target)//找到了target
return mid;
if (nums[left] <= nums[mid]) {//这一部分(前半部分)是有序的
if (nums[left] <= target && target < nums[mid]) {//target在这一部分
right = mid - 1;
} else {
left = mid + 1;
}
} else {//nums[left] > nums[mid],这一部分(后半部分)是有序的
if (nums[right] >= target && target > nums[mid]) {//target在这一部分
left = mid + 1;
} else {
right = mid - 1;
}
}
}
return -1;//搜索结束还没找到,说明不存在
}
}
四、LeetCode74. 搜索二维矩阵
题目描述
链接
编写一个高效的算法来判断 m x n 矩阵中,是否存在一个目标值。该矩阵具有如下特性:
每行中的整数从左到右按升序排列。
每行的第一个整数大于前一行的最后一个整数。
示例 1:
输入:
matrix = [
[1, 3, 5, 7],
[10, 11, 16, 20],
[23, 30, 34, 50]
]
target = 3
输出: true
示例 2:
输入:
matrix = [
[1, 3, 5, 7],
[10, 11, 16, 20],
[23, 30, 34, 50]
]
target = 13
输出: false
方法一、缩小领域法
- 因为每一行从左往右递增,每一列从上往下递增,那么可以从右上角或者左下角开始找,每次比较可以排除一行或者一列。
- 我这里写的代码是从右上角开始往左下角找。
- 示例1的搜索路径是:7->5>3
- 示例2的搜索路径是: 7->20->16->11->30->23->结束,没找到
class Solution {
public boolean searchMatrix(int[][] matrix, int target) {
if(matrix == null || matrix.length == 0)
return false;
int rows = matrix.length;
int cols = matrix[0].length;
int row = 0;
int col = cols - 1;
while (row < rows && col >= 0) {//从矩阵右上角开始搜索
if (target == matrix[row][col])
return true;
else if (target > matrix[row][col])
row++;
else
col--;
}
return false;
}
}
时间复杂度为O(n)。
方法二、二分查找
因为每一行从左往右递增,每一列从上往下递增,那么将这个二维矩阵拖为一维矩阵,就是一个有序的一维数组了,从而进行二分查找。
class Solution {
public boolean searchMatrix(int[][] matrix, int target) {
if(matrix == null || matrix.length == 0)
return false;
int rows = matrix.length;
int cols = matrix[0].length;
int left = 0;
int right = rows * cols - 1;
while (left <= right) {
int mid = left + ((right - left) >> 1);
int midValue = matrix[mid / cols][mid % cols];
if (midValue == target)
return true;
else if(target < midValue)
right = mid - 1;
else
left = mid + 1;
}
return false;
}
}
时间复杂度为O(log(n))
五、LeetCode153. 寻找旋转排序数组中的最小值
题目描述
链接
假设按照升序排序的数组在预先未知的某个点上进行了旋转。
( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。
请找出其中最小的元素。
你可以假设数组中不存在重复元素。
示例 1:
输入: [3,4,5,1,2]
输出: 1
示例 2:
输入: [4,5,6,7,0,1,2]
输出: 0
二分法
class Solution {
public int findMin(int[] nums) {
if (nums == null || nums.length == 0)
return -1;
int n = nums.length;
if (n == 1 || nums[0] < nums[n - 1])//数组只有一个元素或者未发生旋转
return nums[0];
int left = 0;
int right = n - 1;
while (left < right) {
int mid = left + ((right - left) >> 1);
if (nums[mid] > nums[right]) { // 中值 > 右值,最小值一定在右半边,收缩左边界
left = mid + 1; // 因为中值 > 右值,中值肯定不是最小值,左边界可以跨过mid
} else if (nums[mid] < nums[right]) { // 中值 < 右值,最小值在左半边,收缩右边界
right = mid; // 因为中值 < 右值,中值也可能是最小值,右边界只能取到mid处
}
}
return nums[left]; // 循环结束,left == right
}
}
六、LeetCode154. 寻找旋转排序数组中的最小值 II
题目描述
链接
假设按照升序排序的数组在预先未知的某个点上进行了旋转。
( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。
请找出其中最小的元素。
注意数组中可能存在重复的元素。
示例 1:
输入: [1,3,5]
输出: 1
示例 2:
输入: [2,2,2,0,1]
输出: 0
二分法
同上一题一样的解法,不同点在于需要在 n u m s [ m i d ] = = n u m s [ r i g h t ] nums[mid] == nums[right] nums[mid]==nums[right]时挪动右边界进行去重就行。
class Solution {
public int findMin(int[] nums) {
if (nums == null || nums.length == 0)
return -1;
int n = nums.length;
int left = 0;
int right = n - 1;
while (left < right) {
int mid = left + ((right - left) >> 1);
if (nums[mid] == nums[right]) { //中值 = 右值,更新右边界,进行去重
right--;
}else if (nums[mid] > nums[right]) {//中值 > 右值,最小值在右半边,收缩左边界
left = mid + 1; // 因为中值 > 右值,中值肯定不是最小值,左边界可以跨过mid
}else { // 中值 < 右值,最小值在左半边,收缩右边界
right = mid; // 因为中值 < 右值,中值也可能是最小值,右边界只能取到mid处
}
}
return nums[left];
}
}