4.1 算法解释
二分查找又称折半查找,每次取区间的二分之一继续查找
4.2 求开方
题目代号: 69 x的平方根
题目描述:
实现 int sqrt(int x) 函数。
计算并返回 x 的平方根,其中 x 是非负整数。
由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去。
测试用例:
示例 1:
输入: 4
输出: 2
示例 2:
输入: 8
输出: 2
说明: 8 的平方根是 2.82842…,
由于返回类型是整数,小数部分将被舍去。
我的分析:
折半查找,一旦mid² <=x;来右边找
反之来左边找
代码:
class Solution {
public int mySqrt(int x) {
//其实就是在0到x之间找个数,满足n*n == x
//如果找不到,那就找它左边的值
int left = 0,right = x,ans = -1;
while (left <= right){
int mid = (left + right)/2;
if((long)mid * mid <= x){
ans = mid;//为啥要写在小的这边呢,是因为最后小数点被舍去,也是找左边的数
left = mid+1;
}else {
right = mid -1;
}
}
return ans;
}
}
4.3 查找区间
题目代号: 34 在排序数组中查找元素的第一个和最后一个位置
题目描述:
给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。
如果数组中不存在目标值 target,返回 [-1, -1]。
测试用例:
示例 1:
输入:nums = [5,7,7,8,8,10], target = 8
输出:[3,4]
示例 2:
输入:nums = [5,7,7,8,8,10], target = 6
输出:[-1,-1]
示例 3:
输入:nums = [], target = 0
输出:[-1,-1]
我的分析:
这里面还是要考虑详细一点的,如果是空就返回[-1,-1]
如果长度为1的数组,且值等于目标值,那就返回[0,0]
正常的数组的话,就先找到一个,找不到的时候就向左或者右折半查找
等找到一个的时候,那就好办了,一步一步递归就好
代码:
class Solution {
public int[] searchRange(int[] nums, int target) {
//现在已经排好序了,那肯定就用二分查找了
int[] zhaobudao = {-1,-1};
int[] ling ={0,0};
if(nums.length == 0){
return zhaobudao;
}
if(nums.length == 1){
return nums[0] == target ? ling:zhaobudao;
}
int left = 0,right = nums.length-1;
while (left <= right){
int[] list =new int[2];
int mid = (left + right)/2;
int midValue = nums[mid];
if(midValue > target){
//在左边来递归
right = mid-1;
}else if(midValue < target){
left = mid+1;
}else {
//现在相等了
while ((mid-1) >= 0 && nums[mid-1] == target){//往左递归
mid--;
}
list[0] = mid;
while ((mid+1) <= (nums.length-1) && nums[mid+1] == target){
mid++;
}
list[1] = mid;
return list;
}
}
return zhaobudao;
}
}
4.4 旋转数组查找数字
题目代号: 81 搜索旋转排序数组 II
题目描述:
假设按照升序排序的数组在预先未知的某个点上进行了旋转。
( 例如,数组 [0,0,1,2,2,5,6] 可能变为 [2,5,6,0,0,1,2] )。
编写一个函数来判断给定的目标值是否存在于数组中。若存在返回 true,否则返回 false。
测试用例:
示例 1:
输入: nums = [2,5,6,0,0,1,2], target = 0
输出: true
示例 2:
输入: nums = [2,5,6,0,0,1,2], target = 3
输出: false
我的分析:
里面有种特殊情况,就是中间值和左边界值相等,这个问题必须解决一下,否则会陷入死循环的
这里面肯定有一半是有序的,就要判断是左还是右了
在有序的前提下,就看处于左半部分还是右半部分了
代码:
class Solution {
public boolean search(int[] nums, int target) {
int left = 0,right = nums.length-1;
while (left <= right){
int mid = (left + right)/2;
if(nums[mid] == target) return true;
if(nums[left] == nums[mid]){//最怕的就是这种情况,一直死循环了
left++;
}else if(nums[left] < nums[mid]){//左半部分有序
if(nums[left] <= target && target < nums[mid]) right = mid - 1;//处于左半部分
else left = mid + 1;//处于右半部分
}else {//右半部分有序
if(nums[mid] < target && target <= nums[right]) left = mid + 1;//处于右半部分
else right = mid - 1;//处于左半部分
}
}
return false;
}
}
4.5 练习题
题目代号: 153 寻找旋转排序数组中的最小值
题目描述:
假设按照升序排序的数组在预先未知的某个点上进行了旋转。例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] 。
请找出其中最小的元素。
测试用例:
示例 1:
输入:nums = [3,4,5,1,2]
输出:1
示例 2:
输入:nums = [4,5,6,7,0,1,2]
输出:0
示例 3:
输入:nums = [1]
输出:1
我的分析:
就看哪边有序了,左边有序,去右边找,右边有序,来左边找
代码:
class Solution {
public int findMin(int[] nums) {
int left = 0,right = nums.length-1;
int mid = 0;
while (left <= right){
mid = (left+right)/2;
if(nums[right] > nums[mid]){//mid也可以是最小的,右边有序,来左边找
right = mid;
}else {//左边有序,去右边找
left = mid+1;
}
}
return nums[mid];
}
}
题目代号: 154 寻找旋转排序数组中的最小值 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
我的分析:
只要旋转过,最左边一定 > 最右边,所以一旦出现左 < 右,就代表找到了
之后就看有序数组在哪边了,有序数组在左边,则最小值一定处于右端
有序数组在右边,则最小值一定处于左端
当中间值和左边值相等的时候,让左端值++,在缩小的区间里来寻找
代码:
class Solution {
public int findMin(int[] nums) {
int left = 0,right = nums.length - 1;
int mid = 0;
if(nums[left] < nums[right]) return nums[left];//你记住,只要旋转过,那就永远头大尾小;所以只要左边<右边,代表有序了
while (left <= right){
if(nums[left] < nums[right]) return nums[left];//如果二分后是有序数组,则返回最左端的值
mid = (left + right)/2;
if(nums[left] < nums[mid]){//左边有序,那说明左边严格单调递增,最小元素必定在mid之后
left = mid + 1;
}else if(nums[left] > nums[mid]){//右边有序,那最小元素肯定处于左边
right = mid;
}else{//既然相等,比如0 1 2 3 3 3 3 变成3 3 3 3 0 1 2,那最小值肯定处于右边
left++;
}
}
return nums[mid];
}
}
题目代号: 540有序数组中的单一元素
题目描述:
给定一个只包含整数的有序数组,每个元素都会出现两次,唯有一个数只会出现一次,找出这个数。
测试用例:
示例 1:
输入: [1,1,2,3,3,4,4,8,8]
输出: 2
示例 2:
输入: [3,3,7,7,10,11,11]
输出: 10
输入:[1,1,2,3,3,4,4,8,8]
输出:2
我的分析:
这道题需要注意的是mid是奇数还是偶数,若是奇数的时候,要转化成偶数(因为情况恰好相反)
当nums[mid] == nums[mid + 1],那就说明结果处于右边
反之就去左边找
最后的结果一定是left =right的时候,所以在while的时候,就不要等于号
代码:
class Solution {
public int singleNonDuplicate(int[] nums) {
int left = 0,right = nums.length-1;
while (left < right){
int mid = left + (right - left)/2;
if(mid % 2 == 1) mid--;
if(nums[mid] == nums[mid + 1]){
left = mid + 2;
}else {
right = mid;
}
}
return nums[left];
}
}
题目代号: 4 寻找两个正序数组的中位数
题目描述:
给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。
测试用例:
示例 1:
输入:nums1 = [1,3], nums2 = [2]
输出:2.00000
解释:合并数组 = [1,2,3] ,中位数 2
示例 2:
输入:nums1 = [1,2], nums2 = [3,4]
输出:2.50000
解释:合并数组 = [1,2,3,4] ,中位数 (2 + 3) / 2 = 2.5
示例 3:
输入:nums1 = [0,0], nums2 = [0,0]
输出:0.00000
示例 4:
输入:nums1 = [], nums2 = [1]
输出:1.00000
示例 5:
输入:nums1 = [2], nums2 = []
输出:2.00000
我的分析:
首先考虑特殊情况,如果一个指针为空,那另一个指针就看是齐数个还是偶数个了
然后就两个指针按照顺序来合并成一个大的指针,怎么合并呢?就按
看谁小了,小的就往大数组里放
当放着放着,肯定就有一个数组放完了,那就该另一个数组来放了,另一个数组全都放上去
放完后,就看这个总的数组是奇数个还是偶数个来分了,然后计算结果即可
代码:
class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int m = nums1.length,n = nums2.length;
int[] hebing = new int[m+n];
//如果有一个数组是空数组
if(m == 0){
//那就来另一个数组找
if(n % 2 == 1){
return nums2[n/2];
}else {
return (nums2[n/2-1]+nums2[n/2])/2.0;
}
}
if(n == 0){
//那就来另一个数组找
if(m % 2 == 1){
return nums1[m/2];
}else {
return (nums1[m/2-1]+nums1[m/2])/2.0;
}
}
//现在两个数组里面都有元素了,那就开始合并吧
int count = 0,index1 = 0,index2 = 0;//三个不断移动的指针
while (count != (m+n)){
//合并主要要考虑一个合并完,另一个还没结束
if(nums1[index1] < nums2[index2]){
hebing[count++] = nums1[index1++];
}else {
hebing[count++] = nums2[index2++];
}
if(index1 == m){
while (index2 != n){
hebing[count++] = nums2[index2++];
}
break;
}
if(index2 == n){
while (index1 != m){
hebing[count++] = nums1[index1++];
}
break;
}
}
//合并好之后就好弄了
if(count % 2 == 1){
return hebing[count/2];
}else {
return (hebing[count/2 - 1] + hebing[count/2])/2.0;
}
}
}