斩题术
有序数组中的搜索问题,首先想到 二分法 解决。
下面以剑指offer中的相关题目进行实践,理论结合实际。
剑指offer 53-I. 在排序数组中查找数字
题目描述:
统计一个数字在排序数组中出现的次数。
示例1
输入: nums = [5,7,7,8,8,10], target = 8
输出: 2
利用二分法思想解题
对于有序数组,统计其中一个数字出现的次数,只要找到这个数字的左右边界即可。
而左右边界的查找可以采用二分法,以右边界为例。
给定数组 [5,7,7,8,8,10],target = 8 ,i = 0,j = 5(最后一个元素的下标)。
二分法: m = (i + j) / 2; 因此m = 2,nums[2] = 7,小于8,因此target应在区间[m+1, j]之间。
更新左边界,令i = m+1;(i 变为 3)
继续二分法:m = (i + j) / 2; 因此m = 4,nums[4] = 8,等于8,但这个8不一定是右边界,继续在[m+1, j]间寻找右边界。
更新左边界,令i = m+1;(i 变为 5)
继续二分法:m = (i + j) / 2; 因此m = 5,nums[5] = 10,大于8,说明原来的m就是右边界了,令j = m - 1; 这个 j 就是右边界。
左边界同理,只需将target - 1即可,因为相当于找的是离target最近的、比target小的某个数的右边界。
这里有一个数学小细节,对于本例数组3、4位置上的数字,它们都是8。
但4-3是1,因此若右边界是下标为4,左边界应是下标为2,4-2=2。
数字8出现了2次,这才正确。
helper函数就是进行循环二分的函数,进行左右边界的查找。
class Solution {
public int search(int[] nums, int target) {
return helper(nums, target) - helper(nums, target - 1);
}
int helper(int[] nums, int tar) {
int i = 0, j = nums.length - 1;
while(i <= j) {
int m = (i + j) / 2;
if(nums[m] <= tar) i = m + 1;
else j = m - 1;
}
return i;
}
}
复杂度分析
时间复杂度:O(logN)
空间复杂度:O(1)
剑指offer 53-II. 0~n-1中缺失的数字
题目描述:
一个长度为n-1的递增排序数组中的所有数字都是唯一的,并且每个数字都在范围0~n-1之内。在范围0~n-1内的n个数字中有且只有一个数字不在该数组中,请找出这个数字。
示例1
输入: [0,1,2,3,4,5,6,7,9]
输出: 8
利用二分法思想解题
前面是统计长度,这里是找缺失的值,但归根到底,还是找右边界。
为什么这么说呢?看我分析。
可以将数组看作两部分,一部分是连续有序序列,直到碰到缺省的那个值;另一部分是缺省值后的连续有序序列。
前一部分特点:nums[m] = m;
后一部分特点:nums[m] ≠ m;
因此缺失的数字就是后一部分序列的首位元素的索引,也就是前一部分的右边界。
具体例子略,直接看代码。
class Solution {
public int missingNumber(int[] nums) {
int i = 0, j = nums.length - 1;
while(i <= j) {
int m = (i + j) / 2;
if(nums[m] == m) {
i = m + 1;
}else {
j = m - 1;
}
}
return i;
}
}
复杂度分析
时间复杂度:O(logN)
空间复杂度:O(1)