在准备技术面试时,我偶然发现了这个有趣的问题:
您已获得一个已排序然后旋转的数组。
例如
让arr = [1,2,3,4,5]
进行排序,然后旋转两次向右说两次
[4,5,1,2,3]
现在如何最好地搜索这个已排序+旋转的数组?
可以取消旋转数组,然后进行二分查找。但这并不比在输入数组中进行线性搜索更好,因为两者都是最坏的情况O(N)。
请提供一些指示。我已经搜索了很多关于特殊算法的内容,但找不到任何算法。
我理解c和c ++
答案 0 :(得分:148)
这可以使用略微修改的二进制搜索在O(logN)
中完成。
有序和旋转数组的有趣属性是,当你将它分成两半时,至少有两半中的一半将被排序。
Let input array arr = [4,5,6,7,8,9,1,2,3]
number of elements = 9
mid index = (0+8)/2 = 4
[4,5,6,7,8,9,1,2,3]
^
left mid right
因为看起来正确的子数组在左子数组排序时没有排序。
如果mid恰好是旋转点,则左右子阵列将被排序。
[6,7,8,9,1,2,3,4,5]
^
但在 任何情况下,必须对一半(子阵列)进行排序 。
通过比较每一半的开始和结束元素,我们可以很容易地知道哪一半被分类。
一旦我们发现哪一半被分类,我们就可以看到该键是否存在于那一半 - 与极端的简单比较。
如果键存在于那一半,我们递归调用该半的函数 否则我们递归调用另一半的搜索。
我们在每个调用中丢弃一半数组,这使得该算法成为O(logN)
。
伪代码:
function search( arr[], key, low, high)
mid = (low + high) / 2
// key not present
if(low > high)
return -1
// key found
if(arr[mid] == key)
return mid
// if left half is sorted.
if(arr[low] <= arr[mid])
// if key is present in left half.
if (arr[low] <= key && arr[mid] >= key)
return search(arr,key,low,mid-1)
// if key is not present in left half..search right half.
else
return search(arr,key,mid+1,high)
end-if
// if right half is sorted.
else
// if key is present in right half.
if(arr[mid] <= key && arr[high] >= key)
return search(arr,key,mid+1,high)
// if key is not present in right half..search in left half.
else
return search(arr,key,low,mid-1)
end-if
end-if
end-function
这里的关键是总是对一个子数组进行排序,使用它可以丢弃数组的一半。
答案 1 :(得分:15)
您可以进行2次二进制搜索:首先找到i
索引arr[i] > arr[i+1]
。
显然,(arr\[1], arr[2], ..., arr[i])
和(arr[i+1], arr[i+2], ..., arr[n])
都是排序数组。
然后,如果arr[1] <= x <= arr[i]
,则在第一个数组进行二进制搜索,否则在第二个数组进行二进制搜索。
复杂性O(logN)
编辑: the code
答案 2 :(得分:12)
当数组中存在重复元素时,接受的答案有一个错误。例如,我们正在寻找arr = {2,3,2,2,2}
和3。然后接受的答案中的程序将返回-1而不是1.
这篇采访问题在“破解编码面试”一书中有详细讨论。该书中特别讨论了重复元素的条件。由于op在评论中说数组元素可以是任何东西,我在下面给出我的解决方案作为伪代码:
function search( arr[], key, low, high)
if(low > high)
return -1
mid = (low + high) / 2
if(arr[mid] == key)
return mid
// if the left half is sorted.
if(arr[low] < arr[mid]) {
// if key is in the left half
if (arr[low] <= key && key <= arr[mid])
// search the left half
return search(arr,key,low,mid-1)
else
// search the right half
return search(arr,key,mid+1,high)
end-if
// if the right half is sorted.
else if(arr[mid] < arr[low])
// if the key is in the right half.
if(arr[mid] <= key && arr[high] >= key)
return search(arr,key,mid+1,high)
else
return search(arr,key,low,mid-1)
end-if
else if(arr[mid] == arr[low])
if(arr[mid] != arr[high])
// Then elements in left half must be identical.
// Because if not, then it's impossible to have either arr[mid] < arr[high] or arr[mid] > arr[high]
// Then we only need to search the right half.
return search(arr, mid+1, high, key)
else
// arr[low] = arr[mid] = arr[high], we have to search both halves.
result = search(arr, low, mid-1, key)
if(result == -1)
return search(arr, mid+1, high, key)
else
return result
end-if
end-function
答案 3 :(得分:8)
我的第一次尝试是找到使用二元搜索应用的旋转次数 - 这可以通过找到索引n来完成,其中a [n]&gt; a [n + 1]使用通常的二进制搜索机制。 然后进行常规二进制搜索,同时旋转每个班次的所有索引。
答案 4 :(得分:5)
int rotated_binary_search(int A[], int N, int key) {
int L = 0;
int R = N - 1;
while (L <= R) {
// Avoid overflow, same as M=(L+R)/2
int M = L + ((R - L) / 2);
if (A[M] == key) return M;
// the bottom half is sorted
if (A[L] <= A[M]) {
if (A[L] <= key && key < A[M])
R = M - 1;
else
L = M + 1;
}
// the upper half is sorted
else {
if (A[M] < key && key <= A[R])
L = M + 1;
else
R = M - 1;
}
}
return -1;
}
答案 5 :(得分:3)
如果你知道数组已经向右旋转了s,你可以简单地向右移动二进制搜索。这是O(lg N)
通过这个,我的意思是,将左边界限初始化为s,将右边界限初始化为(s-1)mod N,然后在这些之间进行二元搜索,稍加注意在正确的区域内工作。
如果您不知道阵列旋转了多少,您可以使用二进制搜索确定旋转的大小,即O(lg N),然后执行移位二进制搜索O( lg N),总计还是O(lg N)。
答案 6 :(得分:2)
回复上述帖子&#34;这个面试问题将在“破解编码面试”一书中详细讨论。该书中特别讨论了重复元素的条件。由于op在评论中说数组元素可以是任何东西,我在下面给出我的解决方案作为伪代码:&#34;
你的解决方案是O(n)!! (对于单个条件,检查数组的两半的最后一个if条件使得它成为线性时间复杂度的解决方案)
我最好做一次线性搜索,而不是在编码回合中陷入迷宫和分段错误的迷宫中。
我认为在旋转的排序数组(带有重复数据)中搜索有比O(n)更好的解决方案
答案 7 :(得分:2)
您不需要先旋转数组,您可以在旋转的数组上使用二进制搜索(进行一些修改)
假设N是您搜索的数字:
读取第一个数字(arr [start])和数组中间的数字(arr [end]):
如果是arr [开始]&gt; arr [end] - &gt;上半部分未排序,但下半部分已排序:
如果arr [end]&gt; N - >;数字在索引中:(中间+ N - arr [结束])
如果N在数组的第一部分重复搜索(请参见结束为数组前半部分的中间等)。
(如果第一部分被排序,但第二部分不是,则相同)
答案 8 :(得分:2)
如果您知道旋转的方式(远),您仍然可以进行二分查找。
诀窍是你得到两个级别的指数:你做b.s.在虚拟0..n-1范围内,然后在实际查找值时取消旋转它们。
答案 9 :(得分:1)
short mod_binary_search( int m, int *arr, short start, short end)
{
if(start <= end)
{
short mid = (start+end)/2;
if( m == arr[mid])
return mid;
else
{
//First half is sorted
if(arr[start] <= arr[mid])
{
if(m < arr[mid] && m >= arr[start])
return mod_binary_search( m, arr, start, mid-1);
return mod_binary_search( m, arr, mid+1, end);
}
//Second half is sorted
else
{
if(m > arr[mid] && m < arr[start])
return mod_binary_search( m, arr, mid+1, end);
return mod_binary_search( m, arr, start, mid-1);
}
}
}
return -1;
}
答案 10 :(得分:1)
首先,您需要找到移位常数k。 这可以在O(lgN)时间内完成。 从常移k,您可以轻松找到您正在寻找的元素 具有常数k的二分搜索。增强二进制搜索也需要O(lgN)时间 总运行时间为O(lgN + lgN)= O(lgN)
要找到常数移位,k。您只需要在数组中查找最小值。数组最小值的索引告诉您恒定的移位。 考虑排序的数组 [1,2,3,4,5]。
The possible shifts are: [1,2,3,4,5] // k = 0 [5,1,2,3,4] // k = 1 [4,5,1,2,3] // k = 2 [3,4,5,1,2] // k = 3 [2,3,4,5,1] // k = 4 [1,2,3,4,5] // k = 5%5 = 0
要在O(lgN)时间内执行任何算法,关键是始终找到将问题分成一半的方法。 一旦这样做,其余的实现细节很容易
以下是C ++中用于算法的代码
// This implementation takes O(logN) time
// This function returns the amount of shift of the sorted array, which is
// equivalent to the index of the minimum element of the shifted sorted array.
#include <vector>
#include <iostream>
using namespace std;
int binarySearchFindK(vector<int>& nums, int begin, int end)
{
int mid = ((end + begin)/2);
// Base cases
if((mid > begin && nums[mid] < nums[mid-1]) || (mid == begin && nums[mid] <= nums[end]))
return mid;
// General case
if (nums[mid] > nums[end])
{
begin = mid+1;
return binarySearchFindK(nums, begin, end);
}
else
{
end = mid -1;
return binarySearchFindK(nums, begin, end);
}
}
int getPivot(vector<int>& nums)
{
if( nums.size() == 0) return -1;
int result = binarySearchFindK(nums, 0, nums.size()-1);
return result;
}
// Once you execute the above, you will know the shift k,
// you can easily search for the element you need implementing the bottom
int binarySearchSearch(vector<int>& nums, int begin, int end, int target, int pivot)
{
if (begin > end) return -1;
int mid = (begin+end)/2;
int n = nums.size();
if (n <= 0) return -1;
while(begin <= end)
{
mid = (begin+end)/2;
int midFix = (mid+pivot) % n;
if(nums[midFix] == target)
{
return midFix;
}
else if (nums[midFix] < target)
{
begin = mid+1;
}
else
{
end = mid - 1;
}
}
return -1;
}
int search(vector<int>& nums, int target) {
int pivot = getPivot(nums);
int begin = 0;
int end = nums.size() - 1;
int result = binarySearchSearch(nums, begin, end, target, pivot);
return result;
}
Hope this helps!=) Soon Chee Loong, University of Toronto
答案 11 :(得分:1)
public class PivotedArray {
//56784321 first increasing than decreasing
public static void main(String[] args) {
// TODO Auto-generated method stub
int [] data ={5,6,7,8,4,3,2,1,0,-1,-2};
System.out.println(findNumber(data, 0, data.length-1,-2));
}
static int findNumber(int data[], int start, int end,int numberToFind){
if(data[start] == numberToFind){
return start;
}
if(data[end] == numberToFind){
return end;
}
int mid = (start+end)/2;
if(data[mid] == numberToFind){
return mid;
}
int idx = -1;
int midData = data[mid];
if(numberToFind < midData){
if(midData > data[mid+1]){
idx=findNumber(data, mid+1, end, numberToFind);
}else{
idx = findNumber(data, start, mid-1, numberToFind);
}
}
if(numberToFind > midData){
if(midData > data[mid+1]){
idx = findNumber(data, start, mid-1, numberToFind);
}else{
idx=findNumber(data, mid+1, end, numberToFind);
}
}
return idx;
}
}
答案 12 :(得分:0)
Swift 解决方案 100% 工作测试
func searchInArray(A:[Int],key:Int)->Int{
for i in 0..<A.count{
if key == A[i] {
print(i)
return i
}
}
print(-1)
return -1
}
答案 13 :(得分:0)
使用C#实现
public class Solution {
public int Search(int[] nums, int target) {
if (nums.Length == 0) return -1;
int low = 0;
int high = nums.Length - 1;
while (low <= high)
{
int mid = (low + high) / 2;
if (nums[mid] == target) return mid;
if (nums[low] <= nums[mid]) // 3 4 5 6 0 1 2
{
if (target >= nums[low] && target <= nums[mid])
high = mid;
else
low = mid + 1;
}
else // 5 6 0 1 2 3 4
{
if (target >= nums[mid] && target <= nums[high])
low= mid;
else
high = mid - 1;
}
}
return -1;
}
}
答案 14 :(得分:0)
由于中,中1等原因,我不喜欢二进制搜索,这就是为什么我总是使用binary stride/jump search
如何在旋转数组上使用它? 使用两次(一旦找到移位,然后使用.at()查找移位索引->原始索引)
或者比较第一个元素,如果它小于第一个元素,则必须在结尾附近
从末尾进行向后跳搜索,如果发现任何枢轴tyoe沉淀则停止
如果是>开始元素,则执行常规的跳转搜索:)
答案 15 :(得分:0)
这是我的两分钱
如果数组不不包含重复项,则可以在O(log(n))中找到解决方案。正如许多人所展示的那样,可以使用经过调整的二进制搜索版本来查找目标元素。
但是,如果数组包含重复项,我认为无法在O(log(n))中找到目标元素。这是一个示例,说明为什么我认为O(log(n))是不可能的。考虑下面的两个数组:
a = [2,.....................2...........3,6,2......2]
b = [2.........3,6,2........2......................2]
所有点都用数字2填充。您可以看到两个数组都已排序和旋转。如果要考虑二进制搜索,那么他们必须在每次迭代中将搜索域减少一半-这就是我们得到O(log(n))的方式。让我们假设我们正在搜索数字3。在第一种情况下,我们可以看到它隐藏在数组的右侧,而在第二种情况下,它隐藏在数组的第二面。这是我们现阶段对数组的了解:
这是我们所拥有的所有信息。我们可以清楚地看到仅作出决定来排除阵列的一半是不够的。结果,唯一的方法是进行线性搜索。我并不是说我们不能优化O(n)时间,我只是说我们不能做到O(log(n))。
答案 16 :(得分:0)
import java.util.*;
class Main{
public static void main(String args[]){
Scanner sc = new Scanner(System.in);
int n=sc.nextInt();
int arr[]=new int[n];
int max=Integer.MIN_VALUE;
int min=Integer.MAX_VALUE;
int min_index=0,max_index=n;
for(int i=0;i<n;i++){
arr[i]=sc.nextInt();
if(arr[i]>max){
max=arr[i];
max_index=i;
}
if(arr[i]<min){
min=arr[i];
min_index=i;
}
}
int element=sc.nextInt();
int index;
if(element>arr[n-1]){
index=Arrays.binarySearch(arr,0,max_index+1,element);
}
else {
index=Arrays.binarySearch(arr,min_index,n,element);
}
if(index>=0){
System.out.println(index);
}
else{
System.out.println(-1);
}
}
}
答案 17 :(得分:0)
使用JavaScript
var search = function(nums, target,low,high) {
low= (low || low === 0) ? low : 0;
high= (high || high == 0) ? high : nums.length -1;
if(low > high)
return -1;
let mid = Math.ceil((low + high) / 2);
if(nums[mid] == target)
return mid;
if(nums[low] < nums[mid]) {
// if key is in the left half
if (nums[low] <= target && target <= nums[mid])
// search the left half
return search(nums,target,low,mid-1);
else
// search the right half
return search(nums,target,mid+1,high);
} else {
// if the key is in the right half.
if(nums[mid] <= target && nums[high] >= target)
return search(nums,target,mid+1,high)
else
return search(nums,target,low,mid-1)
}
};
输入:nums = [4,5,6,7,0,1,2],目标= 0 输出:4
答案 18 :(得分:0)
问题:在旋转排序数组中搜索
public class SearchingInARotatedSortedARRAY {
public static void main(String[] args) {
int[] a = { 4, 5, 6, 0, 1, 2, 3 };
System.out.println(search1(a, 6));
}
private static int search1(int[] a, int target) {
int start = 0;
int last = a.length - 1;
while (start + 1 < last) {
int mid = start + (last - start) / 2;
if (a[mid] == target)
return mid;
// if(a[start] < a[mid]) => Then this part of the array is not rotated
if (a[start] < a[mid]) {
if (a[start] <= target && target <= a[mid]) {
last = mid;
} else {
start = mid;
}
}
// this part of the array is rotated
else {
if (a[mid] <= target && target <= a[last]) {
start = mid;
} else {
last = mid;
}
}
} // while
if (a[start] == target) {
return start;
}
if (a[last] == target) {
return last;
}
return -1;
}
}
答案 19 :(得分:0)
C ++中的这段代码适用于所有情况,虽然它适用于重复项,但请告诉我此代码中是否存在错误。
#include "bits/stdc++.h"
using namespace std;
int searchOnRotated(vector<int> &arr, int low, int high, int k) {
if(low > high)
return -1;
if(arr[low] <= arr[high]) {
int p = lower_bound(arr.begin()+low, arr.begin()+high, k) - arr.begin();
if(p == (low-high)+1)
return -1;
else
return p;
}
int mid = (low+high)/2;
if(arr[low] <= arr[mid]) {
if(k <= arr[mid] && k >= arr[low])
return searchOnRotated(arr, low, mid, k);
else
return searchOnRotated(arr, mid+1, high, k);
}
else {
if(k <= arr[high] && k >= arr[mid+1])
return searchOnRotated(arr, mid+1, high, k);
else
return searchOnRotated(arr, low, mid, k);
}
}
int main() {
int n, k; cin >> n >> k;
vector<int> arr(n);
for(int i=0; i<n; i++) cin >> arr[i];
int p = searchOnRotated(arr, 0, n-1, k);
cout<<p<<"\n";
return 0;
}
答案 20 :(得分:0)
我的简单代码: -
let count = 0;
let arrA = ["6", "2", "21", "8", "4", "12"];
let arrB = ["8", "2", "12", "2", "5", "11"]; // !< A
let arrC = ["5", "1", "20", "7", "3", "11"]; // < A
let res = arrA.every((e,i) => e > arrB[i]); //False
let res2 = arrA.every((e,i) => e > arrC[i]); //True
if(res) count++; //Doesn't trigger
if(res2) count++; //Triggers
console.log(count);
时间复杂度O(log(N))。
答案 21 :(得分:0)
对于带有重复项的旋转数组,如果需要找到第一次出现的元素,可以使用下面的过程(Java代码):
public int mBinarySearch(int[] array, int low, int high, int key)
{
if (low > high)
return -1; //key not present
int mid = (low + high)/2;
if (array[mid] == key)
if (mid > 0 && array[mid-1] != key)
return mid;
if (array[low] <= array[mid]) //left half is sorted
{
if (array[low] <= key && array[mid] >= key)
return mBinarySearch(array, low, mid-1, key);
else //search right half
return mBinarySearch(array, mid+1, high, key);
}
else //right half is sorted
{
if (array[mid] <= key && array[high] >= key)
return mBinarySearch(array, mid+1, high, key);
else
return mBinarySearch(array, low, mid-1, key);
}
}
这是对上述codaddict程序的改进。请注意以下额外的if条件:
if (mid > 0 && array[mid-1] != key)
答案 22 :(得分:0)
试试这个解决方案
bool search(int *a, int length, int key)
{
int pivot( length / 2 ), lewy(0), prawy(length);
if (key > a[length - 1] || key < a[0]) return false;
while (lewy <= prawy){
if (key == a[pivot]) return true;
if (key > a[pivot]){
lewy = pivot;
pivot += (prawy - lewy) / 2 ? (prawy - lewy) / 2:1;}
else{
prawy = pivot;
pivot -= (prawy - lewy) / 2 ? (prawy - lewy) / 2:1;}}
return false;
}
答案 23 :(得分:0)
使用重复值的另一种方法是找到旋转,然后在我们访问数组时应用旋转进行常规二进制搜索。
test = [3, 4, 5, 1, 2]
test1 = [2, 3, 2, 2, 2]
def find_rotated(col, num):
pivot = find_pivot(col)
return bin_search(col, 0, len(col), pivot, num)
def find_pivot(col):
prev = col[-1]
for n, curr in enumerate(col):
if prev > curr:
return n
prev = curr
raise Exception("Col does not seem like rotated array")
def rotate_index(col, pivot, position):
return (pivot + position) % len(col)
def bin_search(col, low, high, pivot, num):
if low > high:
return None
mid = (low + high) / 2
rotated_mid = rotate_index(col, pivot, mid)
val = col[rotated_mid]
if (val == num):
return rotated_mid
elif (num > val):
return bin_search(col, mid + 1, high, pivot, num)
else:
return bin_search(col, low, mid - 1, pivot, num)
print(find_rotated(test, 2))
print(find_rotated(test, 4))
print(find_rotated(test1, 3))
答案 24 :(得分:0)
这是一个简单的(时间,空间)高效的非递归O(log n)python解决方案,它不会修改原始数组。将旋转的数组减半,直到我只有两个索引来检查并在一个索引匹配时返回正确的答案。
def findInRotatedArray(array, num):
lo,hi = 0, len(array)-1
ix = None
while True:
if hi - lo <= 1:#Im down to two indices to check by now
if (array[hi] == num): ix = hi
elif (array[lo] == num): ix = lo
else: ix = None
break
mid = lo + (hi - lo)/2
print lo, mid, hi
#If top half is sorted and number is in between
if array[hi] >= array[mid] and num >= array[mid] and num <= array[hi]:
lo = mid
#If bottom half is sorted and number is in between
elif array[mid] >= array[lo] and num >= array[lo] and num <= array[mid]:
hi = mid
#If top half is rotated I know I need to keep cutting the array down
elif array[hi] <= array[mid]:
lo = mid
#If bottom half is rotated I know I need to keep cutting down
elif array[mid] <= array[lo]:
hi = mid
print "Index", ix