3️⃣ 二分查找
基础题:
-
使用一个二分搜索即可
-
public int searchInsert(int[] nums, int target) { int n = nums.length; int left = 0, right = n - 1; while(left <= right){ int mid = (left + right) / 2; if(nums[mid] == target){ return mid; }else if(nums[mid] < target){ left = mid + 1; }else{ right = mid - 1; } } return left; }
题解:
-
一开始打算用每一行数组首元素判断, 但是在’='上面有点复杂, 换成在数组末尾元素判断比较简单
-
public boolean searchMatrix(int[][] matrix, int target) { int searchRow = -1; for(int i = 0;i<matrix.length;i++){ if(matrix[i][matrix[0].length-1] >= target){ searchRow = i; break; } } if(searchRow == -1){ return false; } for(int i = 0;i<matrix[0].length;i++){ if(matrix[searchRow][i] == target){ return true; } } return false; }
解题:
-
当目前值等于目标值时,为了查找第一个和最后一个目标值, 需要确定是收缩左边界还是右边界, 可以通过一个布尔型参数
-
在常规二分搜索中, 收缩左边界情况是目前值<目标值, 收缩右边界情况是目前值>目标值
-
如果在求第一个目标值, 在相等时就要收缩右边界;
- 即不再理会目前值右侧也等于目标值的元素
-
如果在求最后一个目标值, 在相等时就要收缩左边界;
- 即不再理会目前值左侧也等于目标值的元素
-
public int[] searchRange(int[] nums, int target){ int leftIdx = binarySearch(nums, target, true); int rightIdx = binarySearch(nums, target, false); if(leftIdx <= rightIdx && rightIdx < nums.length && nums[rightIdx] == target && nums[leftIdx] == target){ return new int[]{leftIdx, rightIdx}; } return new int[]{-1,-1}; } public int binarySearch(int[] nums, int target, int flag){ int left = 0, right = nums.length-1; int ans = nums.length; while(left<=right){ int mid = (left + right )/2; if(nums[mid]>target||(flag&&nums[mid]==target)){ right = mid-1; ans = mid; }else{ left = mid+1; } } return ans; }
整数数组 nums 按升序排列,数组中的值 互不相同 。
在传递给函数之前,nums 在预先未知的某个下标 k(0 <= k < nums.length)上进行了 旋转,使数组变为 [nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]](下标 从 0 开始 计数)。例如, [0,1,2,4,5,6,7] 在下标 3 处经旋转后可能变为 [4,5,6,7,0,1,2] 。
给你 旋转后 的数组 nums 和一个整数 target ,如果 nums 中存在这个目标值 target ,则返回它的下标,否则返回 -1 。你必须设计一个时间复杂度为 O(log n) 的算法解决此问题。
解题:
-
最初想法是二分查找到旋转分界值索引reverse后再用二分查找, 但这样好像有点绕所以没做
-
既然mid天然的将数组分为两组, 且必有一组是递增的, 数组中的值互不相同, 可以将nums[mid]与nums[0]相比, 如果较之大, 则说明0-mid是连续的, 较之小则说明mid~n-1是连续的
-
在将目标值与mid判断, 如果在0~mid之间, 缩减右边界即可 ; 在mid~n-1之间, 缩减左边界即可, 再加一些错误情况判断
-
int n = nums.length; if(n==0){ return -1; }else if( n == 1){ return nums[0] == target ? 0 : -1; } int l = 0, r = n - 1; while(l <= r){ int mid = (l + r) / 2; if(nums[mid] == target){ return mid;} if(nums[mid] > nums[0]){ if(nums[0] <= target && nums[mid] > target){ r = mid - 1; }else{ l = mid + 1; } }else{ if(nums[mid] < target && nums[n-1] >= target){ l = mid+1; }else{ r = mid - 1; } } } return -1;
解决:
-
原本是前一题打算这么做的,不过根据前面几题的经验这题挺好实现的
-
也是分部分的想法, 如果nums[mid]>=nums[0] 那说明最小值存在于后一部分, 如果nums[mid]<nums[0] 说明最小值在前一部分
-
if(nums.length == 1) return nums[0]; int n = nums.length; int l = 0, r = n - 1; int min = nums[0]; while(l <= r){ int mid = (l + r) / 2; if(nums[mid] >= nums[0]){ l = mid+1; }else{ r = mid-1; min = Math.min(min, nums[mid]); } } return min;
给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。算法的时间复杂度应该为 O(log (m+n)) 。
题解:
-
想好久没想出来怎么用二分查找, 怎么看都觉得是应该先合并数组再分奇偶找中位数
-
看了别人的解题才知道怎么做
-
先是分奇偶性, 通过递归找到合并后的第total/2(total/2+1)个元素
-
怎么递归:
- 先设置终结条件: k在递归中不断减少,直至k=1----k=1时即代表此时只需要从两个数组中选取一个较小的值返回, 为避免代码复杂, 预先将数组较小的一个调整为num1 , 如果此时num1已全部判断, 则直接返回num2, 否则↑
- 由于两数组是顺序数组, 可以各取一般索引值的元素, 互相比较从而排除一部分数据
-
public double findMedianSortedArrays(int[] nums1, int[] nums2) { int tot = nums1.length + nums2.length; // 关键点 找到合并后第k个元素 if (tot % 2 == 0) { int left = find(nums1, 0, nums2, 0, tot / 2); int right = find(nums1, 0, nums2, 0, tot / 2 + 1); return (left + right) / 2.0; } else { return find(nums1, 0, nums2, 0, tot / 2 + 1); } } private int find(int[] nums1, int i, int[] nums2, int j, int k) { if (nums1.length - i > nums2.length - j) return find(nums2, j, nums1, i, k); if (k == 1) { if (i == nums1.length) return nums2[j]; else return Math.min(nums1[i], nums2[j]); } if (i == nums1.length) return nums2[j + k - 1]; int si = Math.min(nums1.length, i + k / 2), sj = j + (k - k / 2); if (nums1[si - 1] < nums2[sj - 1]) { return find(nums1, si, nums2, j, k - (si - i)); } else { return find(nums1, i, nums2, sj, k - (sj - j)); } }