Java 在O(log(N))时间内查找排序数组中某个范围内的整数数的有效算法?

Java 在O(log(N))时间内查找排序数组中某个范围内的整数数的有效算法?,java,c,algorithm,sorting,Java,C,Algorithm,Sorting,我遇到了一个面试问题,必须用O(logn)完成 给定一个排序整数数组和一个数字,查找数组中该数字的开始索引和结束索引 Ex1: Array = {0,0,2,3,3,3,3,4,7,7,9} and Number = 3 --> Output = {3,6} Ex2: Array = {0,0,2,3,3,3,3,4,7,7,9} and Number = 5 --> Output = {-1,-1} 我正试图找到一种有效的算法,但fat并没有成功。听起来像是一种二进制搜索—

我遇到了一个面试问题,必须用O(logn)完成

给定一个排序整数数组和一个数字,查找数组中该数字的开始索引和结束索引

Ex1: Array = {0,0,2,3,3,3,3,4,7,7,9} and Number = 3 --> Output = {3,6} 

Ex2: Array = {0,0,2,3,3,3,3,4,7,7,9} and Number = 5 --> Output = {-1,-1} 

我正试图找到一种有效的算法,但fat并没有成功。

听起来像是一种二进制搜索——日志图iirc表示每次增量“减半”的效果,基本上是二进制搜索

伪代码:

Set number to search for
Get length of array, check if number is at the half point
if the half is > the #, check the half of the bottom half. is <, do the inverse
repeat
    if the half point is the #, mark the first time this happens as a variable storing its index
    then repeat binary searches above , and then binary searches below (separately), such that you check for how far to the left/right it can repeat.
    note*: and you sort binary left/right instead of just incrementally, in case your code is tested in a dataset with like 1,000,000 3's in a row or something
设置要搜索的编号
获取数组的长度,检查数字是否在半点处

如果一半>下半部分,请检查下半部分的一半。是双二进制搜索。从下索引=0开始,上索引=长度-1。然后在中途检查该点,并相应地调整索引


诀窍是,一旦找到目标,枢轴将分成两个枢轴。

解决方案是在开始时同时对数组进行二进制搜索(实际上不必是并发的:p)。关键是左右搜索略有不同。对于右侧,如果遇到重复,则必须向右搜索;对于左侧,如果遇到重复,则必须向左搜索。你要搜索的是边界,所以在右边你要检查

 yournum, not_yournum  

这是边界,在左侧,您只需按相反方向搜索边界。在末尾返回边界的索引。

您可以使用二进制搜索的概念来查找起始索引和结束索引:

  • 要查找起始索引,请将数组减半,如果值等于或大于输入数,则对数组的下半部分重复,否则对上半部分重复。当到达大小为1的数组时停止
  • 若要查找起始索引,请将数组减半,如果值大于输入数,则对数组的下半部分重复,否则对上半部分重复。当到达大小为1的数组时停止
请注意,当我们到达一个大小为1的数组时,可能是输入编号旁边的一个单元格,因此我们检查它是否等于输入编号,如果不等于输入编号,则通过从找到的索引中添加/减少1来修复索引

findStartIndex(int[] A, int num)
{
    int start = 0; end = A.length-1;

    while (end != start) 
    {
        mid = (end - start)/2;

        if (A[mid] >= num)
            end = mid;
        else
            start = mid;
    }

    if(A[start] == num) 
        return start;
    else 
        return start+1;
}

findEndIndex(int[] A, int num)
{
    int start = 0; end = A.length-1;

    while (end != start) 
    {
        mid = (end - start)/2;

        if (A[mid] > num)
            end = mid;
        else
            start = mid;
    }

    if(A[start] == num) 
        return start;
    else 
        return start-1;
}
以及整个程序:

int start = findStartIndex(A, num);

if (A[start]!=num) 
{ 
     print("-1,-1"); 
}
else
{
     int end = findEndIndex(A, num);
     print(start, end);
}

由于还没有人发布工作代码,我将发布一些(Java):

公共类DuplicateNumberArrangeFinder{
公共静态void main(字符串[]args){
int[]nums={0,0,2,3,3,3,3,4,7,7,9};
范围范围=findDuplicateNumberRange(nums,3);
系统输出打印项次(范围);
}
公共静态范围findDuplicateNumberRange(int[]nums,int-toFind){
未找到范围=新范围(-1,-1);
if(nums==null | | nums.length==0){
返回未找到;
}
int startIndex=notFound.startIndex;
int endIndex=notFound.endIndex;
int n=单位长度;
int低=0;
int高=n-1;
while(低=toFind){
高=中-1;
}
}
低=0;
高=n-1;
while(低toFind)){
endIndex=mid;
打破
}else if(nums[mid]toFind){
高=中-1;
}
}
返回新范围(开始索引、结束索引);
}
私有静态类范围{
国际标准指数;
内部索引;
公共范围(int startIndex、int endIndex){
this.startIndex=startIndex;
this.endIndex=endIndex;
}
公共字符串toString(){
返回“[”+this.startIndex+”,“+this.endIndex+”];
}
}
}

这可能是我的错误,但罗恩·泰勒的答案在我测试时有一个无限循环。下面是Java中的一个工作示例,如果将
searchRange
函数更改为非静态,则可以对其进行测试

import java.io.*;
import java.util.*;
import java.text.*;
import java.math.*;
import java.util.regex.*;

public class RangeInArray {
    // DO NOT MODIFY THE LIST
    public static ArrayList<Integer> searchRange(final List<Integer> a, int b) {
        ArrayList<Integer> range = new ArrayList<>();

        int startIndex = findStartIndex(a, b);

        if(a.get(startIndex) != b) {
            range.add(-1);
            range.add(-1);
            return range;
        }

        range.add(startIndex);
        range.add(findEndIndex(a, b));
        return range;
    }

    public static int findStartIndex(List<Integer> a, int b) {
        int midIndex = 0, lowerBound = 0, upperBound = a.size() - 1;

        while(lowerBound < upperBound) {
            midIndex = (upperBound + lowerBound) / 2;
            if(b <= a.get(midIndex)) upperBound = midIndex - 1;
            else lowerBound = midIndex + 1;
        }

        if(a.get(lowerBound) == b) return lowerBound;
        return lowerBound + 1;
    }

    public static int findEndIndex(List<Integer> a, int b) {
        int midIndex = 0, lowerBound = 0, upperBound = a.size() - 1;

        while(lowerBound < upperBound) {
            midIndex = (upperBound + lowerBound) / 2;
            if(b < a.get(midIndex)) upperBound = midIndex - 1;
            else lowerBound = midIndex + 1;
        }

        if(a.get(lowerBound) == b) return lowerBound;
        return lowerBound - 1;
    }

    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(1);
        list.add(2);
        list.add(2);
        list.add(2);
        list.add(2);
        list.add(2);
        list.add(2);
        list.add(3);
        list.add(4);
        list.add(4);
        list.add(4);
        list.add(4);
        list.add(5);
        list.add(5);
        list.add(5);
        System.out.println("Calling search range");
        for(int n : searchRange(list, 2)) {
            System.out.print(n + " ");
        }
    }
}
import java.io.*;
导入java.util.*;
导入java.text.*;
导入java.math.*;
导入java.util.regex.*;
公共类射程仪{
//不要修改列表
公共静态ArrayList搜索范围(最终列表a,int b){
ArrayList范围=新的ArrayList();
int startIndex=findStartIndex(a,b);
如果(a.get(startIndex)!=b){
范围。添加(-1);
范围。添加(-1);
返回范围;
}
范围。添加(startIndex);
添加(findEndIndex(a,b));
返回范围;
}
公共静态int findStartIndex(列表a、int b){
int midIndex=0,lowerBound=0,上限=a.size()-1;
while(下限<上限){
midIndex=(上限+下限)/2;

如果(b)解决方案是二进制搜索,带有处理重复条目和返回第一个/最后一个索引的警告。您是否建议对重复项进行线性搜索?但是,您的运行时间取决于重复项的数量。我认为二进制搜索自然会处理重复项,这取决于您返回的是下限还是上限,尽管您在这种情况下,可能需要两个版本。@ZongZhengLi没有阅读答案,每一步都涉及数组的一个划分。没有线性搜索OK,我错过了你不仅建议第二次搜索,而且建议第二次二进制搜索。这只是不必要的,从一开始就使用第二次二进制搜索。@ZongZhengLi哦,我想我可以删除第一次搜索 search@ZongZhengLi第一个二进制搜索确实使algo更具可读性,但这里不需要递归。
import java.io.*;
import java.util.*;
import java.text.*;
import java.math.*;
import java.util.regex.*;

public class RangeInArray {
    // DO NOT MODIFY THE LIST
    public static ArrayList<Integer> searchRange(final List<Integer> a, int b) {
        ArrayList<Integer> range = new ArrayList<>();

        int startIndex = findStartIndex(a, b);

        if(a.get(startIndex) != b) {
            range.add(-1);
            range.add(-1);
            return range;
        }

        range.add(startIndex);
        range.add(findEndIndex(a, b));
        return range;
    }

    public static int findStartIndex(List<Integer> a, int b) {
        int midIndex = 0, lowerBound = 0, upperBound = a.size() - 1;

        while(lowerBound < upperBound) {
            midIndex = (upperBound + lowerBound) / 2;
            if(b <= a.get(midIndex)) upperBound = midIndex - 1;
            else lowerBound = midIndex + 1;
        }

        if(a.get(lowerBound) == b) return lowerBound;
        return lowerBound + 1;
    }

    public static int findEndIndex(List<Integer> a, int b) {
        int midIndex = 0, lowerBound = 0, upperBound = a.size() - 1;

        while(lowerBound < upperBound) {
            midIndex = (upperBound + lowerBound) / 2;
            if(b < a.get(midIndex)) upperBound = midIndex - 1;
            else lowerBound = midIndex + 1;
        }

        if(a.get(lowerBound) == b) return lowerBound;
        return lowerBound - 1;
    }

    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(1);
        list.add(2);
        list.add(2);
        list.add(2);
        list.add(2);
        list.add(2);
        list.add(2);
        list.add(3);
        list.add(4);
        list.add(4);
        list.add(4);
        list.add(4);
        list.add(5);
        list.add(5);
        list.add(5);
        System.out.println("Calling search range");
        for(int n : searchRange(list, 2)) {
            System.out.print(n + " ");
        }
    }
}