题目描述(一)
统计一个数字在排序数组中出现的次数。{1,2,3,3,3,3,4,5} 3出现了4次。
解题思路
- 当然可以无脑用哈希表或者直接遍历,空间复杂度O(n),时间复杂度O(1)代码略
- 优化:在找第一个k时,首先也是二分找到中间的k,然后和它上一个数字相比,如果还是k,则在前半段数组继续二分,直到找到中间的数为k且前一个不等于k。
- 用同样的方法,找最后一个k
时间复杂度为O(logn)
参考代码:
package offer;
/**
* 在排序数组中查找数字 统计一个数字在排序数组中出现的次数
*/
public class Offer53 {
public static void main(String[] args) {
int nums[] = {2, 2, 2, 2, 2, 2, 3, 5};
int target = 2;
System.out.println(GetNumberOfK(nums, target));
}
private static int GetFirstK(int[] data, int k, int start, int end) {
if (start > end) {
return -1;
}
int middIndex = (start + end) / 2;
int middData = data[middIndex];
if (middData == k) {
if ((middIndex > 0 && data[middIndex - 1] != k) || middIndex == 0) {
return middIndex;
} else {
end = middIndex - 1;
}
} else if (middData > k) {
end = middIndex - 1;
} else {
start = middIndex + 1;
}
return GetFirstK(data, k, start, end);
}
private static int GetLastK(int[] data, int k, int start, int end) {
if (start > end) {
return -1;
}
int middIndex = (start + end) / 2;
int middData = data[middIndex];
if (middData == k) {
if ((middIndex < data.length - 1 && data[middIndex + 1] != k) || middIndex == end) {
return middIndex;
} else {
start = middIndex + 1;
}
} else if (middData > k) {
end = middIndex - 1;
} else {
start = middIndex + 1;
}
return GetLastK(data, k, start, end);
}
public static int GetNumberOfK(int[] data, int k) {
int number = 0;
if (data != null && data.length > 0) {
int first = GetFirstK(data, k, 0, data.length - 1);
int last = GetLastK(data, k, 0, data.length - 1);
if (first > -1 && last > -1) {
number = last - first + 1;
}
}
return number;
}
}
题目描述(二)
一个长度为n-1的递增排序数组中的所有数字都是唯一的,并且每个数字都在范围0~n-1之内。在范围0 ~ n-1内的n个数字中有且只有一个数字不在该数组中,请找出这个数字。
解题思路
-用公式n*(n-1)/2求出0~n-1所有数字之和s1,在求出数组所有数字之和s2,那个不在数组中的数字就是s1-s2的差,这种解法需要O(n)的时间
- 如果中间元素的值和它的下标相等,那么下一轮只需要找它的右半边
- 如果中间元素的值和它的下标不等,并且它前一个元素和它的下标相等,意味着这个中间的数字就是第一个值与下标不等的数字。
- 如果中间元素的值和它的下标不等,并且他前面的元素和它的下标相等不等,那么只需查找左半边。
参考代码:
package offer;
/**
* 0-n-1中缺失的数字
*/
public class Offer53_2 {
public static void main(String[] args) {
int nums[] = {0, 1, 2, 3, 4, 5, 6, 8, 9, 10};
//System.out.println(getMissingNum(nums));
System.out.println(getMissingNumH(nums));
}
/**
* 一般 O(n)
*
* @param nums
* @return
*/
private static int getMissingNum(int[] nums) {
if (nums == null || nums.length <= 0) {
return -1;
}
int sum1 = 0;
int sum2 = (nums[0] + nums[nums.length - 1]) * (nums.length + 1) / 2;
for (int i = 0; i < nums.length; i++) {
sum1 += nums[i];
}
return sum2 - sum1;
}
/**
* 改良
* {0, 1, 2, 3, 4, 5, 7, 8, 9, 10}
*
* @param arr
* @return
*/
private static int getMissingNumH(int[] arr) {
if (arr == null || arr.length <= 0)
return -1;
int low = 0;
int high = arr.length - 1;
while (low <= high) {
int mid = (low + high) >> 1;
if (arr[mid] != mid) {
if (mid == 0 || arr[mid - 1] == mid - 1)
return mid;
high = mid - 1;
} else {
low = mid + 1;
}
}
return -1;
}
}
题目描述(三)
一个长度为n-1的递增排序数组中的所有数字都是唯一的,并且每个数字都在范围0到n-1之内。在范围0到n-1的n个数字中有且只有一个数字不在该数组中,请找出这个数字。
解题思路
- 先获取数组的中间数,若中间数的值和下标相等,则找到一个满足条件的数;
- 若中间数的值大于它的下标,则根据数组递增的特性可知:中间元素至少比他的下标大1,而且中间数以后的元素的值至少每个比前面大1,同时它们的下标每次也是增加1,所以右边的这些元素的值都大于它们的下标(至少大1),因此需要继续在左边寻找。
- 同理,若中间数的值小于它的下标,则中间数左边的那些元素的值也都小于它们的下标,因此需要继续在右边寻找。
参考代码:
package offer;
/**
* 数组中数值和下标相等的元素
*/
public class Offer53_3 {
public static void main(String[] args) {
int nums[] = {-3, -1, 1, 3, 5};
//System.out.println(getMissingNum(nums));
System.out.println(getMissingNumH(nums));
}
private static int getMissingNumH(int[] arr) {
if (arr == null || arr.length <= 0)
return -1;
int left = 0;
int right = arr.length - 1;
while (left <= right) {
int mid = (left + right) >> 1;
if (arr[mid] == mid) {
return mid;
}
if (arr[mid] > mid) {
right = mid - 1;
} else {
left = mid + 1;
}
}
return -1;
}
}
附录
该题源码在我的 ?Github 上面!