class Solution {
public int search(int[] nums, int target) {
if(nums==null || nums.length==0 || target<nums[0] || target>nums[nums.length-1])
return 0;
int n = nums.length;
int left=0, right=n-1, mid;
int leftBorder=-1, rightBorder=n;
思路
- 找到第一个k的索引和最后一个k的索引就知道有序数组中k出现的次数了
笔记
- 二分法中,最好先判断arr[mid]>k, 接着arr[mid]
- 注意下面程序中getFirst和getLast的递归终止条件的不同
- 看到有序数组要联想二分法
int mid = (lo+hi)>>1;
有溢出的风险, 最好写成int mid = lo+(hi-lo)>>1;
- getFirst的递归终止条件是lo>hi
- getLast的递归终止条件是lo==hi
- getFirst会比getLast多进行一次递归,也就是lo==hi之后再多进行一次
- 递归execute中的条件可以写成
if((mid>lo && array[mid-1]!=k) || (mid==lo && array[mid]==k))
也可以写成if((mid>0 && array[mid-1]!=k) || (mid==0 && array[mid]==k))
,感觉写成前面那种更舒服些?
第三次做, 二分法,O(logN); 二分到死会有left>right, 本题中left>right说明数组中没有该元素; 其实更好的做法是找完左边界接着判断leftBorder是否等于-1 或者判断left是否大于right, 是的话说明数组中没有该元素, 直接返回0即可, 就不用再寻找右边界了; 最开始想着一次二分直接找出左右边界, 发现行不通, 还是得一个边界一个边界地找; 二分流程比之前的循环版本写得好一点, 尤其是else的处理
public class Solution {
public int GetNumberOfK(int [] array , int k) {
if(array==null || array.length==0)
return 0;
int left=0,right=array.length-1, mid, leftBorder=-1, rightBorder=-1;
while(left<=right){
mid = left + ((right-left)>>1);
if(array[mid] < k)
left = mid+1;
else if(array[mid] > k)
right = mid-1;
else{
if(mid==0){
leftBorder = mid;
break;
}
if(array[mid-1]!=k){
leftBorder = mid;
break;
}
right = mid - 1;
}
}
left=0;
right=array.length-1;
while(left<=right){
mid = left + ((right - left) >> 1);
if(array[mid] < k)
left = mid + 1;
else if(array[mid] > k)
right = mid - 1;
else{
if(mid==array.length-1){
rightBorder = mid;
break;
}
if(array[mid+1]!=k){
rightBorder = mid;
break;
}
left = mid + 1;
}
}
return left > right ? 0 : rightBorder - leftBorder + 1;
}
}
第三次做, 双向遍历; while循环中更新数组索引后, 需要检查索引是否越界; O(N)
public class Solution {
public int GetNumberOfK(int [] array , int k) {
if(array==null || array.length==0)
return 0;
int left=0, right = array.length-1;
while(left<array.length && array[left] != k)
left++;
while(right>=0 && array[right]!=k)
right--;
return left <= right ? right - left + 1 : 0;
}
}
第二次做,二分法,循环版; 还是要明确:两个元素的mid指向靠左的元素; base case怎么处理? 可以写在while循环的第一个if里, 也就是第一个if是base case,else if, else是普遍情况; 这个二分法写的很不简洁,逻辑倒是还清晰; mid可以在while循环的最后更新,见最后一个写法
public class Solution {
public int GetNumberOfK(int [] array , int k) {
if(array==null || array.length==0)
return 0;
int left = 0, right = array.length-1, mid;
int leftBorder=-1, rightBorder=-1;
while(left<=right){
mid = left + ((right - left)>>1);
if(array[mid] > k)
right = mid - 1;
else if(array[mid] < k)
left = mid + 1;
else{
if(mid!=0 && array[mid-1]==k)
right = mid - 1;
else if(mid!=0 && array[mid-1]!=k){
leftBorder = mid;
break;
}
else{
leftBorder = mid;
break;
}
}
}
if(leftBorder == -1)
return 0;
left=0;
right=array.length-1;
while(left<=right){
mid = left + ((right - left) >> 1);
if(array[mid] > k)
right = mid - 1;
else if(array[mid] < k)
left = mid + 1;
else{
if(mid==array.length-1){
rightBorder = mid;
break;
}
else if(array[mid+1]==k)
left = mid+1;
else{
rightBorder=mid;
break;
}
}
}
return rightBorder - leftBorder + 1;
}
}
第二次做,二分法;主要是要记住:两个元素的mid指向靠左的元素; 递归版
public class Solution {
public int GetNumberOfK(int [] array , int k) {
if(array==null || array.length==0)
return 0;
int left = CoreLeft(k, array, 0, array.length-1);
int right = CoreRight(k, array, 0, array.length-1);
if(left==-1)
return 0;
return right - left + 1;
}
public int CoreLeft(int k, int[] arr, int left, int right){
if(left==right && arr[left]==k)
return left;
if(left==right && arr[left]!=k)
return -1;
int mid = left + ((right-left)>>1);
int index;
if(arr[mid] > k)
index = CoreLeft(k, arr, left, mid-1);
else if(arr[mid] < k)
index = CoreLeft(k, arr, mid+1, right);
else{
if(mid != 0 && arr[mid-1]==k)
index = CoreLeft(k, arr, left, mid-1);
else if(mid!=0 && arr[mid-1]!=k)
index = mid;
else
index = mid;
}
return index;
}
public int CoreRight(int k, int[] arr, int left, int right){
if(left==right && arr[left]==k)
return left;
if(left==right && arr[left]!=k)
return -1;
int mid = left + ((right - left )>>1);
int index;
if(arr[mid] > k)
index = CoreRight(k, arr, left, mid-1);
else if(arr[mid] < k)
index = CoreRight(k, arr, mid+1, right);
else{
if(arr[mid+1]==k)
index = CoreRight(k, arr, mid+1, right);
else
index = mid;
}
return index;
}
}
第二次做,一遍循环非常方便
public class Solution {
public int GetNumberOfK(int [] array , int k) {
int count=0;
for(int i=0; i<array.length; i++)
if(array[i] == k)
count++;
return count;
}
}
第二次做,双向遍历写起来比较麻烦
public class Solution {
public int GetNumberOfK(int [] array , int k) {
if(array==null || array.length == 0)
return 0;
int left = 0, right = array.length-1;
for(; left<array.length; left++)
if(array[left]==k)
break;
for(; right>=0; right--)
if(array[right]==k)
break;
if(left==array.length && array[0]==k && array[array.length-1]==k)
return array.length;
else if(left==array.length)
return 0;
else
return right - left + 1;
}
}
递归写法
public class Solution {
public int GetNumberOfK(int [] array , int k) {
if(array.length<1)
return 0;
int first = getFirst(array,0, array.length-1, k);
int last = getLast(array,0, array.length-1, k);
if(first==-1)
return 0;
return last-first+1;
}
public int getFirst(int[] array, int lo, int hi, int k){
if(lo>hi)
return -1;
int mid = (lo+hi)>>1;
int index = -1;
if(array[mid] == k){
if((mid>0 && array[mid-1]!=k) || (mid==0 && array[mid]==k))
index = mid;
else
index = getFirst(array,lo, mid-1, k);
}
else if(array[mid]>k)
index = getFirst(array, lo, mid-1, k);
else if(array[mid]<k)
index = getFirst(array, mid+1, hi, k);
return index;
}
public int getLast(int[] array, int lo, int hi, int k){
if(lo==hi)
return array[lo]==k?lo:0;
int mid = (lo+hi)>>1;
int index = -1;
if(array[mid] == k){
if((mid<array.length-1 && array[mid+1]!=k) || (mid==hi && array[mid]==k))
index = mid;
else
index = getLast(array,mid+1, hi, k);
}
else if(array[mid]>k)
index = getLast(array, lo, mid-1, k);
else if(array[mid]<k)
index = getLast(array, mid+1, hi, k);
return index;
}
}
二分查找法的循环写法也务必熟悉,getLast是循环写法(自己的太笨重…)
public class Solution {
public int GetNumberOfK(int [] array , int k) {
if(array.length<1)
return 0;
int first = getFirst(array,0, array.length-1, k);
int last = getLast(array,0, array.length-1, k);
if(first==-1)
return 0;
return last-first+1;
}
public int getFirst(int[] array, int lo, int hi, int k){
if(lo>hi)
return -1;
int mid = (lo+hi)>>1;
int index = -1;
if(array[mid] == k){
if((mid>lo && array[mid-1]!=k) || (mid==lo && array[mid]==k))
index = mid;
else
index = getFirst(array,lo, mid-1, k);
}
else if(array[mid]>k)
index = getFirst(array, lo, mid-1, k);
else if(array[mid]<k)
index = getFirst(array, mid+1, hi, k);
return index;
}
public int getLast(int[] array, int lo, int hi, int k){
int mid=-1,index=-1;
while(lo <= hi){
mid = (lo+hi)>>1;
if(array[mid]==k){
if(mid+1>hi){
index = hi;
break;
}
else if(array[mid+1]==k)
lo = mid+1;
else{
index = mid;
break;
}
}
else if(array[mid] > k)
hi = mid-1;
else
lo = mid+1;
}
return index;
}
}
循环版本的二分法
public class Solution {
public int GetNumberOfK(int [] array , int k) {
int length = array.length;
if(length == 0){
return 0;
}
int firstK = getFirstK(array, k, 0, length-1);
int lastK = getLastK(array, k, 0, length-1);
if(firstK != -1 && lastK != -1){
return lastK - firstK + 1;
}
return 0;
}
private int getFirstK(int [] array , int k, int start, int end){
if(start > end){
return -1;
}
int mid = (start + end) >> 1;
if(array[mid] > k){
return getFirstK(array, k, start, mid-1);
}else if (array[mid] < k){
return getFirstK(array, k, mid+1, end);
}else if(mid-1 >=0 && array[mid-1] == k){
return getFirstK(array, k, start, mid-1);
}else{
return mid;
}
}
private int getLastK(int [] array , int k, int start, int end){
int length = array.length;
int mid = (start + end) >> 1;
while(start <= end){
if(array[mid] > k){
end = mid-1;
}else if(array[mid] < k){
start = mid+1;
}else if(mid+1 < length && array[mid+1] == k){
start = mid+1;
}else{
return mid;
}
mid = (start + end) >> 1;
}
return -1;
}
}
题目二 0~n-1中缺失的数字
一个长度为n-1的递增排序数组中的所有数字都是唯一的,并且每个数字都在范围0~n-1之内。
在范围0~n-1内的n个数字中有且只有一个数字不在该数组中,请找出这个数字。
示例 1:
输入: [0,1,3]
输出: 2
示例 2:
输入: [0,1,2,3,4,5,6,7,9]
输出: 8。
第一次做; 有序数组; 二分法, 时间复杂度O(logN); 核心: 1)找到第一个nums[i]!=i的i, 此i就是缺失的数字 2)二分法的while循环条件中如果有等于, 那么必须用mid+1或者mid-1更新left或者right, 不能用mid更新left或者right, 会产生死循环; 如果while循环条件中没有等于, 那么可以用mid更新left或者right
class Solution {
public int missingNumber(int[] nums) {
int n = nums.length;
int left=0, right=n-1, mid;
while(left<right){
mid = left + ((right-left)>>1);
if(nums[mid] != mid)
right = mid;
else
left = mid+1;
}
return nums[left]==left? left+1 : left;
}
}
力扣题解; 这种写法中, low指向nums[mid]!=mid的mid, 而high的指向不确定, 都有可能, 所以最后用low判断
class Solution {
public int missingNumber(int[] nums) {
int low = 0;
int high = nums.length - 1;
while(low < high){
int mid = low + ((high - low) >> 1);
if(nums[mid] != mid){
high = mid - 1;
} else {
low = mid + 1;
}
}
return nums[low] == low ? nums[low]+1 : nums[low]-1;
}
}