https://docs.google.com/document/d/1qxA2wps0IhVRWULulQ55W4SGPMu2AE5MkBB37h8Dr58/
无序数组二分搜索:找不到的数
假设给你一个有序数组,,那么如果二分搜索其中的每个数,必定都能找到。
如果给你同样的数组,但是却是打乱的,然后使用同样的二分搜索来搜索其中的每个数,会有些数找不到。
请你用 O(N),返回哪些数找不到。
随机数组中用二分法找不到的元素
第四题,一个印度小哥,给定一个没有排序的随机数组,使用二分法找数组里的各个元素,返回有多少元素是用二分法永远找不到的。要求算法复杂度为O(n), 楼主不会。印度小哥让楼主用例子试一试,看看能不能找到什么规律。楼主试了试,没找到规律…………最后给出了nlog(n)的解法,估计最后挂在这了。
思路:
可以用类似于判断树是否为BST的思路:维护一个“值域”R=(lo, hi)(即在当前搜索下标范围[i, j]内,只有在R范围内的数才有可能被二分法找到)。求得当前搜索范围[i, j]的中点mid。如果A[mid]在当前的R中,那A[mid]就一定能被找到。然后分别递归mid的左右两侧[i, mid-1]和[mid+1, j]。递归左侧时,允许的值域是(lo, A[mid]);同理右侧的值域是(A[mid], hi)。如果任何时刻发现值域R已经不包含任何整数,那就表示[i, j]这个下标范围内没有任何能找到的数字。
code
provider: null
private int find(int[] nums) {
// do some sanity check operations
return dfsUtil(nums, 0, nums.length - 1, Integer.MIN_VALUE, Integer.MAX_VALUE);
}
private int dfsUtil(int[] nums, int left, int right, int lower, int upper) {
// base case: there should be no elements between (lower, upper) or (left,right)
if(lower >= upper) return right - left + 1;
if(left > right) return 0;
int mid = left + (right - left) / 2;
int l = dfsUtil(nums, left, mid-1, lower, nums[mid]);
int r = dfsUtil(nums, mid+1, right, nums[mid], upper);
if(nums[mid] > lower && nums[mid] < upper) {
return l + r;
} else {
return l + r + 1;
}
}
需要和面试官clarify是否包含重复值,如果包括,情况会更复杂一点。首先需要定义二分法是找左边界还是右边界,其次,不同位置同样的数是否当做同样的值。提供一个python版本代码。
def bs_in_unsorted_arr(unsorted):
from collections import defaultdict
lo, hi = -1 << 32,1 << 32
left, right = 0, len(unsorted)
found = defaultdict(bool)
def bs(left, right, lo, hi):
if left >= right:
return
if lo >= hi:
return
mid = (left + right) >> 1
if lo <= unsorted[mid] < hi:
found[unsorted[mid]] = True
bs(left, mid, lo, min(unsorted[mid], hi))
bs(mid + 1, right, max(unsorted[mid], lo), hi)
bs(left, right, lo, hi)
return found假设给你一个有序数组,,那么如果二分搜索其中的每个数,必定都能找到。
如果给你同样的数组,但是却是打乱的,然后使用同样的二分搜索来搜索其中的每个数,会有些数找不到。
请你用 O(N),返回哪些数找不到。
把这个无序数组直接用binarySearch 来搜索 [ 2, 7, 9, 10, 1 ]
l=0, h=4, m=2, 找9一次就可以找到。
找2,m=2 是9,所以在左边。则l=0, h=1, m=0, 可以找到
找7,m=2 是9,所以在左边。则l=0, h=1, m=0, 然后 l=1, h=1, m=1 可以找到
找10,m=2 是9,所以在右边。则l=3,h=4,m=3,能找到
找1,m=2 是9,所以在左边。则l=0,h=1,m=0 找不到。
所以对于这个题目,找不到的数是【1】。
l=0, h=4, m=2, 找9一次就可以找到。
找2,m=2 是9,所以在左边。则l=0, h=1, m=0, 可以找到
找7,m=2 是9,所以在左边。则l=0, h=1, m=0, 然后 l=1, h=1, m=1 可以找到
找10,m=2 是9,所以在右边。则l=3,h=4,m=3,能找到
找1,m=2 是9,所以在左边。则l=0,h=1,m=0 找不到。
所以对于这个题目,找不到的数是【1】。
如果知道中位数,则左半部>中位数和右半部<中位数的数会找不到。
那么如何在O(n)时间内找到中位数,可以用两个堆来找,或者平衡二叉树也可以。
再加一个例子:
Given nums: [22, 92, 25, 16, 29, 15, 49, 22, 38, 61]
Can't find: [16, 49, 25, 92, 15]
对于任意数组中的元素,如果用标准的二分法搜索搜索不到,就是最后答案的一员。当然下面的是测试代码,达不到楼顶要求的O(N)了。
for (int j : nums)
if (Arrays.binarySearch(nums, j) < 0)
System.out.println("Can't find " + j);
用堆或者Balanced BST的时间复杂度就不是O(N)了,找中位数果断用Quick Select啊
然后考虑一个样例数组,比如
[4, 3, 5, 7, 6, 8, 2, 9, 1]
假设我们需要寻找8,那么输出是这样的:
也就是找不到8 —— 问题出在第二步position = 6的值为2,所以我们将左边的区间[5, 5]排除了。
. From 1point 3acres bbs
那么进入区间[5, 5]的条件是什么?—— 条件是查找值要大于第一步position = 4的值(也就是6)从而向右走,并小于第二步position = 6的值(也就是2)从而向左走,也就是查找值要大于6并小于2 —— 当然这个条件不可能被满足,所以不仅是7,position = 5的位置设成任何值都是不可能被找到的。
所以总体思路就是维护一个“查找值需要大于X并小于Y才能到达这个区间“的记录,并在递归的每一层更新X和Y
[4, 3, 5, 7, 6, 8, 2, 9, 1]
假设我们需要寻找8,那么输出是这样的:
也就是找不到8 —— 问题出在第二步position = 6的值为2,所以我们将左边的区间[5, 5]排除了。
. From 1point 3acres bbs
那么进入区间[5, 5]的条件是什么?—— 条件是查找值要大于第一步position = 4的值(也就是6)从而向右走,并小于第二步position = 6的值(也就是2)从而向左走,也就是查找值要大于6并小于2 —— 当然这个条件不可能被满足,所以不仅是7,position = 5的位置设成任何值都是不可能被找到的。
所以总体思路就是维护一个“查找值需要大于X并小于Y才能到达这个区间“的记录,并在递归的每一层更新X和Y
- void CheckHidden(const std::vector<int> &data,
- std::vector<bool> &hidden,
- const int lo, const int hi,
- const int min_bound, const int max_bound) {
- if (lo > hi) {
- return;. From 1point 3acres bbs
- }
- const int mid = (lo + hi) / 2;
- const int value = data[mid];
- // 打印更新边界的过程
- std::cout << "data[" << mid << "] = " << data[mid] << ", bound = "
- << "[" << min_bound << ", " << max_bound << "]]\n";
- // hidden记录每个位置的值是否能被查找到
- hidden[mid] = (value <= min_bound || value >= max_bound);
- CheckHidden(data, hidden, lo, mid-1, min_bound, std::min(value, max_bound));
- CheckHidden(data, hidden, mid+1, hi, std::max(min_bound, value), max_bound);
- }
- // Wrapper
- void CheckHidden(const std::vector<int> &data, std::vector<bool> &hidden) {
- // 初始边界是负无穷到正无穷. From 1point 3acres bbs
- CheckHidden(data, hidden, 0, data.size() - 1,
- std::numeric_limits<int>::min(),
- std::numeric_limits<int>::max());
- }