二分查找
//二分查找模板
int binarySearch(int[] nums, int target){
if(nums == null || nums.length == 0) {
return -1;
}
int l = 0, r = nums.length - 1;
while(l <= r){
int mid = l + (r - l) / 2; //避免溢出
if(nums[mid] == target){
return mid;
} else if(nums[mid] < target) {
l = mid + 1;
} else {
r = mid - 1;
}
}
return -1;
}
第一天
第一题:在排序数组中查找元素的第一个和最后一个位置
思路
1.因为序列已经排序,采用二分查找无疑就是很方便的方法。
2.一种方式采用二分找到所要求的元素,然后分别向前向后遍历,找第一个出现位置和最后一次出现位置,但这样在特殊情况下比如所有元素均为所求元素时时间复杂度仍为O(n)。
3.另一种采用二分进行两次逼近,一次向左逼近,一次向右逼近,找到第一次和最后一次位置,这样保证无论什么情况均为O(logn)
代码
class Solution {
public int[] searchRange(int[] nums, int target) {
int left = 0, right = nums.length - 1;
int[] count = {-1,-1}; //初始化,因要求未找到返回[-1,-1]故初始化为[-1,-1]
while(left <= right){
int mid = left + (right - left) / 2; //避免溢出
if(nums[mid] == target){ // 找到目标元素
left = mid;
right = mid;
while(left >= 0 && nums[left] == target){ //向左遍历,找第一次出现位置
left--;
}
while(right < nums.length && nums[right] == target){ //向右遍历,找最后一次出现位置
right++;
}
count[0] = left+1;
count[1] = right-1;
break;
}else if(nums[mid] > target){ //大于目标元素,改变right
right = mid - 1;
}else{ //小于目标元素,改变left
left = mid + 1;
}
}
return count;
}
}
class Solution {
public int[] searchRange(int[] nums, int target) {
int left = 0, right = nums.length - 1;
int[] count = {-1,-1};
if(left > right){
return count;
}
while(left < right){
int mid = left + (right - left) / 2;
if(nums[mid] >= target){ //向左逼近,只要数不小于目标值就移动right
right = mid;
}else{ //小于目标值,移动left
left = mid + 1;
}
}
if(nums[left] != target){ //因为推出条件不止找到元素还有left和right指针相遇,则需要判断此时数组中对应元素是否为目标元素
return count;
}else{
count[0] = left;
}
right = nums.length;
while(left < right){
int mid = left + (right - left) / 2;
if(nums[mid] <= target){ //向右逼近,只要数不大于目标值就移动left
left = mid+1;
}else{ //大于目标值移动right
right = mid;
}
}
count[1] = left - 1;
return count;
}
}
第二题:搜索旋转排序数组
思路
1.因为排序数组进行了旋转也就是移位,使得二分中简单的判断已经不符合要求,需要加入新的判断条件来判断目标元素在中间下标之前还是之后。
2.当中间下标元素值小于目标元素时,如果目标元素也小于或等于右指针下标元素,则说明目标若存在,则必在中间下标与右指针下标之间,移动左指针,反之如果目标元素大于右指针下标元素,则说明目标若存在,则必在中间下标与左指针下标之间,移动右指针。
3.当中间下标元素值大于目标元素时分析同理,与左指针下标元素比较,如果目标元素也大于或等于左指针下标元素,移动右指针,反之移动左指针。
代码
class Solution {
public int search(int[] nums, int target) {
int low = 0;
int high = nums.length - 1;
while(low <= high){
int mid = low + (high - low) / 2; //防止溢出
if(nums[mid] == target){
return mid;
}else if(nums[mid] < nums[high]){
if(nums[mid] < target && target <= nums[high]){ //目标中间下标与右指针下标之间
low = mid + 1;
}else{ //目标中间下标与左指针下标之间
high = mid - 1;
}
}else{ //目标中间下标与左指针下标之间
if(nums[low] <= target && nums[mid] > target){
high = mid - 1;
}else{ //目标中间下标与右指针下标之间
low = mid + 1;
}
}
}
return -1;
}
}
第三题:搜索二维矩阵
思路
标准的二分搜索题,数组变成了二维,但按题意按行先的话元素依然是标准的升序的,现在用左右指针表示行先的元素的位置,则中间位置的行数下标为中间位置除以列数的商,列数下标为中间位置除以列数的余数。
代码
class Solution {
public boolean searchMatrix(int[][] matrix, int target) {
if(matrix.length == 0 || matrix[0].length == 0){
return false;
}
int m = matrix.length,n = matrix[0].length; //计算行数和列数
int low = 0,high = m * n - 1; //计算元素总个数
while(low <= high){
int mid = low + (high - low) / 2; //防止溢出
int i = mid / n; //计算中间位置的行数下标
int j = mid % n; //计算中间元素的列数下标
if(matrix[i][j] == target){
return true;
}else if(matrix[i][j] < target){
low = mid + 1;
}else{
high = mid - 1;
}
}
return false;
}
}
第二天
第四题:寻找旋转排序数组中的最小值
思路
1.排序数组旋转之后的数组无非就是两段升序的数组,从开始到中间某一个数为升序,然后接着就是最小数到最后的一个升序,找最小值就是找第二个升序数组的开始小标,因元素互不相同可知第二个升序数组的最大值是小于第一个升序数组最小值的。
2.所以采用二分法时,若中间位置的元素大于或等于0下标元素,则说明中间元素在第一个升序数组中,则移动左指针,否则移动右指针。
3.因为旋转次数可能为数组长度的整数倍,这时数组整体为升序,找第二个升序数组的开始下标会找到数组末尾,这恰恰是最大的元素,因此在完成二分查找后,还应与数组第一个元素进行比较,如果第一个元素更小应返回第一个元素。
代码
class Solution {
public int findMin(int[] nums) {
int l = 0, r = nums.length - 1;
while(l < r){
int mid = l + (r - l) / 2; //防止溢出
if(nums[mid] >= nums[0]){ //大于首元素,在第一个升序数组段中移动l
l = mid + 1;
}else{
r = mid;
}
}
return nums[l] < nums[0]?nums[l]:nums[0]; //与首元素进行比较
}
}
第五题:寻找峰值
思路
1.本题可以理解为找函数的极大值点,由于nums[-1] = nums[n] = -∞的存在,可知必然存在极大值点。
2.当采用二分算法时,如果中间位置大于下一个元素,则可知在中间位置到下一个位置是下降的,因为nums[-1] = -∞,则可知在左指针位置到中间位置必有先上升后下降,即必有大极点,则移动右指针。
3.如果中间位置小于下一个元素,则可知在中间位置到下一个位置是上升的,因为nums[n] = -∞,则可知在中间位置到右指针必是先上升后下降,即必有极大点,则移动左指针。
代码
class Solution {
public int findPeakElement(int[] nums) {
int l = 0, r = nums.length - 1;
while(l < r){
int mid = l + (r - l) / 2; //防止溢出
if(nums[mid] > nums[mid + 1]){ //左边必有极大点
r = mid;
}else{ //左边必有极大点
l = mid + 1;
}
}
return l;
}
}