详细题解:这道算法题太简单?你忽略了时间复杂度的要求! (qq.com)
代码详解:详细通俗的思路分析,多解法 - 寻找两个正序数组的中位数 - 力扣(LeetCode) (leetcode-cn.com)
class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int n = nums1.length;
int m = nums2.length;
int left = (n + m + 1) / 2;
int right = (n + m + 2) / 2;
//总和技术,就是将同一位置求两边,不影响
return (getKth(nums1, 0, n - 1, nums2, 0, m - 1, left) + getKth(nums1, 0, n - 1, nums2, 0, m - 1, right)) * 0.5;
}
private int getKth(int[] nums1, int start1, int end1, int[] nums2, int start2, int end2, int k) {
//k永远是第几小的数
int len1 = end1 - start1 + 1;
int len2 = end2 - start2 + 1;
//永远保证短的在上,长的在下
if(len1 > len2){
return getKth(nums2, start2, end2, nums1, start1, end1, k);
}
//只有长的,那么就找第k小的数,-1的目的是因为下标。
if(len1 == 0){
return nums2[start2 + k - 1];
}
//当递归到找最小的数的时候,此时就找两个数组起始位最小的,因为其他不符合条件的已经在前面递归进行了逻辑删除
if(k == 1){
return Math.min(nums1[start1], nums2[start2]);
}
// 分别找到第k/2大的数,min的目的是为了防止越界,-1的目的是找下标
int i = start1 + Math.min(len1, k / 2) - 1;
int j = start2 + Math.min(len2, k / 2) - 1;
//当上面数组对应的值大于下面数组的时候,
//那么下面数组到k/2位置的数都要删除,也就是(j - start2 + 1)
//这里+1的目的是因为要删除[start2,j]这个区间的数值
//删除之后,要对k值进行更新,即:k - 删除区间长度,
//同样起始位置变成了j + 1,因为i,j表示数组的第k/2大的数。
if (nums1[i] > nums2[j]) {
return getKth(nums1, start1, end1, nums2, j + 1, end2, k - (j - start2 + 1));
}
else {
return getKth(nums1, i + 1, end1, nums2, start2, end2, k - (i - start1 + 1));
}
}
}
- 题目要求 O ( l o g n ) O(log_n) O(logn),所以进行二分,因为有确切值,所以使用精确查找。
- 本题因为是旋转过,所以要确定目标值在前半段还是在后半段。
- 如果mid对应的正好和target一致,那么就说明找到了。
- 没有和target一致,那么判断在哪个区间,如果在mid对应值大于等于num[0](因为前半段的区间起始永远是0下标),那么就在该区间进行判断,究竟是right还是left移动。
- 既不满足相等,也没有在前半段,那么只能是数组的后半段了,此时同样就是判断如何去移动left和right。
- 当程序走完还有返回,那么返回-1。
class Solution {
public int search(int[] nums, int target) {
int n = nums.length;
int left = 0, right = n - 1;
//因为有目标值,所以是精确查找
while(left <= right){
int mid = left + ((right - left) >> 1);
if(nums[mid] == target){
return mid;
}else if(nums[mid] >= nums[0]){
//前半段,如果目标值大于起始位置,同时目标值小于中间位置,那么说明目标值在前半段的前半段。移动right
if(target >= nums[0] && target < nums[mid]){
right = mid - 1;
}else {
//在前半段的后半段,移动left
left = mid + 1;
}
}else {
if(target > nums[mid] && target <= nums[right]){
left = mid + 1;
}else {
right = mid - 1;
}
}
}
return -1;
}
}
- 本题给出了目标值,但却是一个模糊查找,因为题目也没说具体是否存在该值。
- 设置两个函数,一个用来找第一个位置的,一个用来找最后一个位置的。
- 找第一个位置函数:
- 如果此时mid值正好等于给定目标值,那么应该将区间缩小而不是直接返回,因为要找的是第一个值出现的位置,此时的缩小策略就是将最右端放置在mid位置。
- 如果此时mid值小于目标值,说明[0,mid]这段区间的值都没有满足的,目标值必定在mid的右侧,那么移动left = mid + 1.
3.如果此时mid值大于目标值,说明[mid,right]这个区间的值都大于目标值,目标值可能出现的范围是[0,mid],此时的mid所指的值也有可能是目标值,因为数字是可以重复的,所以将right移动到mid。- 找最后一个位置函数:
- 在该函数中,求mid的时候,mid = left + ((right - left + 1) >> 1);否则按照查询条件,当只有两个值的时候,永远都只有一侧在进行值的比较,所以要+1.
- 如果此时mid所指向的值等于目标值的,因为是求最后一个位置,说明至少在mid处的值是等于target的,至于后面有不有再说,那么left置为mid,之后还可能出现相同的值。
- 如果此时mid所指向的值小于目标值,那么说明[0,mid)处的值是绝对小于target的,并且是找最后一个,指向mid位置的元素可能是最后一个,也可能不是最后一个,所以left要置为mid。
- 此时如果mid所指的值是大于目标值的,那么说明[mid,right]的值都是大于target的,所以将这段区域排除掉,right = mid - 1.
注:只需要在查找的过程中值是不等的,那么就要记得去缩减搜索空间。
class Solution {
public int[] searchRange(int[] nums, int target) {
int n = nums.length;
if(n < 1){
return new int[]{-1,-1};
}
int firstPosition = findFirstPosition(nums, target);
if(firstPosition == -1){
return new int[]{-1,-1};
}
int lastPosition = findLastPositon(nums, target);
return new int[]{firstPosition, lastPosition};
}
private int findFirstPosition(int[] nums, int target){
int n = nums.length;
int left = 0, right = n - 1;
while(left < right){
int mid = left + ((right - left) >> 1);
if(nums[mid] == target){
//找到的可能不是第一个
right = mid;
}else if(nums[mid] < target){
//目标值大于mid,因为找第一个位置,所以就移动left
left = mid + 1;
}else {
//目标值小于mid,因为找第一个位置,所以将区间缩小,移动right
right = mid;
}
}
if(nums[left] == target){
return left;
}else {
return -1;
}
}
private int findLastPositon(int[] nums, int target){
int n = nums.length;
int left = 0, right = n - 1;
while(left < right){
int mid = left + ((right - left + 1) >> 1);
if(nums[mid] == target){
//找到的可能不是第一个
left = mid;
}else if(nums[mid] < target){
//目标值大于mid,因为找最后一个位置,为了防止漏,那么将left先置为mid位置
left = mid;
}else {
//目标值小于mid,因为找最后一个位置,因为此时mid值已经大于目标值了,所以最后一个位置肯定在mid前面
right = mid - 1;
}
}
return left;
}
}
- 使用二分查找。
- 因为每个数必然会有一个平方根,那么使用精确查找。
- 首先判断给定的x是否为0或者1,是的话,就返回自己。
- left为0,right为x,使用变量res记录结果。
- 开始精确查找,如果(mid * mid <= x),此时就说明了left小了,并且此时mid很可能就会是答案,使用res保存起来,但是此时mid * mid可能数会很大,所以条件表达式应该是mid <= x / mid。如果大了,那么就移动right。
- 返回res。
注:为什么要使用res去记录以及为什么mid<=x/mid的时候res就有可能会是答案?
使用的是精确查找,那么当mid*mid>x
的时候,说明此时不合适,right移动为mid-1,此时再看mid*mid
与x的情况,如果小于等于x了,那么只是有可能为结果(并不一定),还需要再测一测mid至right这个区间的值,更合适的再进行更新。
class Solution {
public int mySqrt(int x) {
if(x == 0 || x == 1){
return x;
}
int left = 0, right = x;
int res = 0;
while(left <= right){
int mid = left + ((right - left) >> 1);
if(mid <= x / mid){
res = mid;
left = mid + 1;
} else {
right = mid - 1;
}
}
return res;
}
}
- 本题其实不用二分,只需要在左下角和右上角选取一个就好。这里选择左下角。
- 如果相等就返回true。如果大于目标值,那么只需要上移就好,如果小于目标值,向右移动。(因为题目说了每行元素值向右依次递增,每列元素值向下依次递增)。
class Solution {
public boolean searchMatrix(int[][] matrix, int target) {
int n = matrix.length;
int m = matrix[0].length;
int i = n - 1, j = 0;
while(i >= 0 && j < m){
if(matrix[i][j] == target){
return true;
}else if(matrix[i][j] > target){
i--;
}else {
j++;
}
}
return false;
}
}
- 因为肯定存在一个重复值,所以使用精确查找,但其实也可以使用模糊查找。
先说精确查找:
- 二分查找都是用来查找顺序的,但给定的数组却不是有序的。如何变为有序是关键。
- 使用计数的方法,举例:以[1,6,2,3,4,5,2,7]为例,此时mid所指的数为4,那么整个数组没有重复的数的话,那么统计出≤4的个数应该最多只能有4个,但此时大于了4,那么说明重复数一定出现在1,2,3,4这四个中。
- 关于缩减:刚才举例的分析,如果此时count > mid,说明,当前重复数一定在(0,mid)之间,所以移动right,并且使用一个变量记录下mid,很可能mid就是那个重复的值。否则,说明重复数在(mid,right)之间,移动left。
模糊查找:
1. 与精确查找不一样,模糊查找不需要额外的变量记录值,因为最后返回的就是left。
2. 区间缩减:当count > mid的时候,值在(0,mid)之间,但此时区间缩减却是right = mid,因为mid指向的值很可能就是答案,其实与精确查找分析一样;否则值就在(mid, right)之间,移动left就好。
举例:
nums | 3,1,3,4,2 |
---|---|
count | 0,1,0,0,2 |
1,2,3,0,4 |
此时mid = 2,而count = 2 == mid,说明重复值在(mid,right)之间,移动left = mid + 1 = 3,而此时mid = 3,此时cnt = 4,用一个res记录此时的mid = 3,同时移动right = 2。
视频题解
class Solution {
public int findDuplicate(int[] nums) {
//因为说了只能使用常量级的额外空间,所以不能使用哈希
//因为明确有重复数字,所以使用精确查找
int n = nums.length;
int left = 0, right = n - 1;
int res = 0;
while(left <= right){
int mid = left + ((right - left) >> 1);
int count = 0;//统计,不能定义在外面,
//每次缩小区间的时候,要重新进行计算
for(int i = 0; i < n; i++){
if(nums[i] <= mid){
count++;
}
}
if(count > mid){
res = mid;
right = mid - 1;
}else {
left = mid + 1;
}
}
return res;
}
}
使用模糊查找:
与上面精确查找的区别在于区间的缩减:
class Solution {
public int findDuplicate(int[] nums) {
int n = nums.length;
int left = 0, right = n - 1;
while(left < right){
int mid = left + ((right - left) >> 1);
int count = 0;//统计
for(int i = 0; i < n; i++){
if(nums[i] <= mid){
count++;
}
}
if(count > mid){
right = mid;
}else {
left = mid + 1;
}
}
return left;
}
}