给定一个双调数组和数组中的元素 x,在 2log(n) 时间内找到 x 的索引
Posted
技术标签:
【中文标题】给定一个双调数组和数组中的元素 x,在 2log(n) 时间内找到 x 的索引【英文标题】:Given a bitonic array and element x in the array, find the index of x in 2log(n) time 【发布时间】:2013-10-22 18:25:51 【问题描述】:首先,这个问题的双调数组被定义为这样一个数组,对于长度为N
的数组中的某个索引K
,其中0 < K < N - 1
和0到K是一个单调递增的整数序列,而K到N - 1 是一个单调递减的整数序列。
例如:[1, 3, 4, 6, 9, 14, 11, 7, 2, -4, -9]
。它从 1 单调增加到 14,然后从 14 减少到 -9。
这个问题的前兆是在3log(n)
中解决它,这要容易得多。一次修改二分查找找到最大值的索引,然后二分查找分别为 0 到 K 和 K + 1 到 N - 1。
我认为2log(n)
中的解决方案要求您在不找到最大值索引的情况下解决问题。我曾考虑过重叠二进制搜索,但除此之外,我不确定如何继续前进。
【问题讨论】:
我认为一个可行的方法是进行两次二进制搜索,假设中间元素是最大值。然后,在两个二分搜索中,我们都包含一个约束来注意一些不一致,即在某些时候两半中的一个不是单调递增/递减。一旦找到,对它进行的二分搜索就会相应地进行调整。这里的问题是要注意什么。 但是,如果中间不是最大值,则两个二分搜索可能会收敛到一侧,这意味着一个将是多余的。在这种情况下,我们必须强制其中一个二进制搜索以另一种方式进行,直到它自行解决并且不会转向另一个二进制搜索。我想这就是如何去做。 如何在没有常数因子的情况下搜索最大值以精确记录 n? @JudgeMental flexaired.blogspot.com/2013/06/… 【参考方案1】:不幸的是,其他答案(this 和 this)中提出的算法不正确,它们不是 O(logN)!
递归公式 f(L) = f(L/2) + log(L/2) + c 不会导致 f(L) = O(log(N)) 但会导致 f(L) = O((log(N))^2) !
确实,假设 k = log(L),那么 log(2^(k-1)) + log(2^(k-2)) + ... + log(2^1) = log(2 )*(k-1 + k-2 + ... + 1) = O(k^2)。因此,log(L/2) + log(L/4) + ... + log(2) = O((log(L)^2))。
及时解决问题的正确方法~2log(N)如下进行(假设数组先升序后降序):
-
取数组中间
比较中间元素和它的一个邻居,看看最大值是在右边还是在左边
将中间元素与所需值进行比较
如果中间元素小于所需值且最大值在左侧,则对左侧子数组进行双调搜索(我们确定该值不在右侧子数组中)
如果中间元素小于期望值且最大值在右侧,则对右侧子数组进行双调搜索
如果中间元素大于期望值,则对右子数组进行降序二分查找,对左子数组执行升序二分查找。
在最后一种情况下,对可能是双调的子数组进行二进制搜索可能会令人惊讶,但它确实有效,因为我们知道顺序不正确的元素都大于所需的值。例如,对数组 [2, 4, 5, 6, 9, 8, 7] 中的值 5 进行升序二分搜索将起作用,因为 7 和 8 大于所需的值 5。
这是一个完整的双音搜索及时实现(C++)~2logN:
#include <iostream>
using namespace std;
const int N = 10;
void descending_binary_search(int (&array) [N], int left, int right, int value)
// cout << "descending_binary_search: " << left << " " << right << endl;
// empty interval
if (left == right)
return;
// look at the middle of the interval
int mid = (right+left)/2;
if (array[mid] == value)
cout << "value found" << endl;
return;
// interval is not splittable
if (left+1 == right)
return;
if (value < array[mid])
descending_binary_search(array, mid+1, right, value);
else
descending_binary_search(array, left, mid, value);
void ascending_binary_search(int (&array) [N], int left, int right, int value)
// cout << "ascending_binary_search: " << left << " " << right << endl;
// empty interval
if (left == right)
return;
// look at the middle of the interval
int mid = (right+left)/2;
if (array[mid] == value)
cout << "value found" << endl;
return;
// interval is not splittable
if (left+1 == right)
return;
if (value > array[mid])
ascending_binary_search(array, mid+1, right, value);
else
ascending_binary_search(array, left, mid, value);
void bitonic_search(int (&array) [N], int left, int right, int value)
// cout << "bitonic_search: " << left << " " << right << endl;
// empty interval
if (left == right)
return;
int mid = (right+left)/2;
if (array[mid] == value)
cout << "value found" << endl;
return;
// not splittable interval
if (left+1 == right)
return;
if(array[mid] > array[mid-1])
if (value > array[mid])
return bitonic_search(array, mid+1, right, value);
else
ascending_binary_search(array, left, mid, value);
descending_binary_search(array, mid+1, right, value);
else
if (value > array[mid])
bitonic_search(array, left, mid, value);
else
ascending_binary_search(array, left, mid, value);
descending_binary_search(array, mid+1, right, value);
int main()
int array[N] = 2, 3, 5, 7, 9, 11, 13, 4, 1, 0;
int value = 4;
int left = 0;
int right = N;
// print "value found" is the desired value is in the bitonic array
bitonic_search(array, left, right, value);
return 0;
【讨论】:
这里的“dichotomic_search”是指“二分搜索”吗? 绝对!我已经编辑了帖子并将“dichotomic_search”替换为“binary_search”以与解释保持一致。 你能解释一下你的重复关系吗? 二分查找的假设不成立?说整数数组[2, 4, 5, 6, 9, 8, 1]
?
@Sohaib:只要值围绕搜索值进行分区,(自定义)二进制搜索就可以工作。我们确实不能使用标准一(std::binary_search
或 std::lower_bound
),因为它将是 UB。
【参考方案2】:
该算法通过结合双调和二分搜索递归地工作:
def bitonic_search (array, value, lo = 0, hi = array.length - 1)
if array[lo] == value then return lo
if array[hi] == value then return hi
mid = (hi + lo) / 2
if array[mid] == value then return mid
if (mid > 0 & array[mid-1] < array[mid])
| (mid < array.length-1 & array[mid+1] > array[mid]) then
# max is to the right of mid
bin = binary_search(array, value, low, mid-1)
if bin != -1 then return bin
return bitonic_search(array, value, mid+1, hi)
else # max is to the left of mid
bin = binary_search(array, value, mid+1, hi)
if bin != -1 then return bin
return bitonic_search(array, value, lo, mid-1)
所以时间的递归公式是f(l) = f(l/2) + log(l/2) + c
,其中log(l/2)
来自二进制搜索,c
是在函数体中进行比较的成本。
【讨论】:
我假设该方法以 lo 为第一个元素,hi 为最后一个元素? 我不熟悉双调搜索,所以我必须问:&
和 |
是二进制操作还是 &&
和 ||
的简写?
是的,这些是标准的逻辑运算and
和or
(这是伪代码)【参考方案3】:
提供的答案的时间复杂度为 (N/2)*logN。因为最坏的情况可能包括太多不必要的子搜索。一个修改是在搜索之前将目标值与子序列的左右元素进行比较。如果目标值不在单调级数的两端或小于双调级数的两端,则后续搜索是多余的。这种修改导致 2lgN 复杂度。
【讨论】:
【参考方案4】:根据数组的最大元素在哪里,以及中间元素是否大于期望值,主要有5种情况
计算中间元素。 比较中间元素所需的值,如果它匹配搜索结束。否则继续下一步。
比较中间元素和邻居,看看最大元素是在左边还是右边。如果两个邻居都小于中间元素,则数组中不存在元素,因此退出。(问题中提到的数组将首先遇到这种情况,因为最大元素 14 位于中间)
如果中间元素小于期望值且最大元素在右侧,则在右侧子数组中进行双调搜索
如果中间元素小于期望值且最大元素在左侧,则在左侧子数组中进行双调搜索
如果中间元素大于期望值且最大元素在左侧,则在右侧子数组中进行降序二分查找
如果中间元素大于期望值且最大元素在右侧,则在左侧子数组中进行升序二分查找
在最坏的情况下,每次将数组分成两半时,我们将进行两次比较,因此复杂度将是 2*logN
【讨论】:
【参考方案5】: public int FindLogarithmicGood(int value)
int lo = 0;
int hi = _bitonic.Length - 1;
int mid;
while (hi - lo > 1)
mid = lo + ((hi - lo) / 2);
if (value < _bitonic[mid])
return DownSearch(lo, hi - lo + 1, mid, value);
else
if (_bitonic[mid] < _bitonic[mid + 1])
lo = mid;
else
hi = mid;
return _bitonic[hi] == value
? hi
: _bitonic[lo] == value
? lo
: -1;
DownSearch 在哪里
public int DownSearch(int index, int count, int mid, int value)
int result = BinarySearch(index, mid - index, value);
if (result < 0)
result = BinarySearch(mid, index + count - mid, value, false);
return result;
BinarySearch 是
/// <summary>
/// Exactly log(n) on average and worst cases.
/// Note: System.Array.BinarySerch uses 2*log(n) in the worst case.
/// </summary>
/// <returns>array index</returns>
public int BinarySearch(int index, int count, int value, bool asc = true)
if (index < 0 || count < 0)
throw new ArgumentOutOfRangeException();
if (_bitonic.Length < index + count)
throw new ArgumentException();
if (count == 0)
return -1;
// "lo minus one" trick
int lo = index - 1;
int hi = index + count - 1;
int mid;
while (hi - lo > 1)
mid = lo + ((hi - lo) / 2);
if ((asc && _bitonic[mid] < value) || (!asc && _bitonic[mid] > value))
lo = mid;
else
hi = mid;
return _bitonic[hi] == value ? hi : -1;
github
【讨论】:
【参考方案6】:通过标准二分法查找一阶差分之间的符号变化,将采用2Lg(n)
数组访问。
使用称为斐波那契搜索的单峰函数最大值的搜索策略可以做得更好。在每个涉及单个查找的 n 步之后,您将间隔大小减小一个因子 Fn
,对应于大约 Log n/Log φ ~ 1.44Lg(n)
访问以找到最大值。
当数组访问是昂贵的函数评估时,这种边际收益更有意义。
【讨论】:
【参考方案7】:当谈到在 O(log N) 时间内搜索算法时,你必须只考虑二分搜索。 这里的概念是首先找到峰值点, 例如: Array = [1 3 5 6 7 12 6 4 2 ] -> 这里,12 是峰值。一旦检测到并标记为 mid,现在只需在 Array[0:mid] 和 Array[mid:len(Array)] 中进行二进制搜索。
注意:从 mid -> len 开始的第二个数组是一个降序数组,需要在二分查找中做一个小的变化。
用于寻找双调点 :-) [用 Python 编写]
start, end = 0, n-1
while start <= end:
mid = start + end-start//2
if (mid == 0 or arr[mid-1] < arr[mid]) and (mid==n-1 or arr[mid+1] < arr[mid]):
return mid
if mid > 0 and arr[mid-1] > arr[mid]:
end = mid-1
else:
start = mid+1
找到索引后,进行相应的二分搜索。呜拉...全部完成:-)
【讨论】:
【参考方案8】:对于二分法,分三种情况:
-
max item 在右边,然后是二分查找左边,bitoinc 查找右边。
max item 在左边,然后是二分查找右边,bitoinc 查找左边。
max item 正好在分割点,然后左右二进制。
注意:左右使用的二分查找因递增/递减顺序不同。
public static int bitonicSearch(int[] a, int lo, int hi, int key)
int mid = (lo + hi) / 2;
int now = a[mid];
if (now == key)
return mid;
// deal with edge cases
int left = (mid == 0)? a[mid] : a[mid - 1];
int right = (mid == a.length-1)? a[mid] : a[mid + 1];
int leftResult, rightResult;
if (left < now && now < right) // max item is at right
leftResult = binarySearchIncreasing(a, lo, mid - 1, key);
if (leftResult != -1)
return leftResult;
return bitonicSearch(a, mid + 1, hi, key);
else if (left > now && now > right) // max item is at left
rightResult = binarySearchDecreasing(a, mid + 1, hi, key);
if (rightResult != -1)
return rightResult;
return bitonicSearch(a, lo, mid - 1, key);
else // max item stands at the split point exactly
leftResult = binarySearchIncreasing(a, lo, mid - 1, key);
if (leftResult != -1)
return leftResult;
return binarySearchDecreasing(a, mid + 1, hi, key);
【讨论】:
max item stands at the split point exactly
- 误导性评论以上是关于给定一个双调数组和数组中的元素 x,在 2log(n) 时间内找到 x 的索引的主要内容,如果未能解决你的问题,请参考以下文章
Leetcode练习(Python):数组类:第54题:给定一个包含 m x n 个元素的矩阵(m 行, n 列),请按照顺时针螺旋顺序,返回矩阵中的所有元素。