Java 从小于O(n)的排序数组中查找唯一数

Java 从小于O(n)的排序数组中查找唯一数,java,algorithm,time-complexity,Java,Algorithm,Time Complexity,我接受了一次采访,有以下问题: 在不到O(n)的时间内从排序数组中查找唯一的数字 我给出了答案,但那是O(n)的 编辑:排序数组大小约为200亿,唯一数字约为1000。我认为不可能在少于O(n)的时间内完成。以数组包含123445为例:为了获得正确的输出,必须查看数组的每个元素,因此O(n)。由于数据由整数组成,因此在任意两个值之间可以出现有限数量的唯一值。因此,首先查看数组中的第一个和最后一个值。如果a[length-1]-a[0]

我接受了一次采访,有以下问题:

在不到O(n)的时间内从排序数组中查找唯一的数字

我给出了答案,但那是O(n)的


编辑:排序数组大小约为200亿,唯一数字约为1000。

我认为不可能在少于O(n)的时间内完成。以数组包含
123445
为例:为了获得正确的输出,必须查看数组的每个元素,因此O(n)。

由于数据由整数组成,因此在任意两个值之间可以出现有限数量的唯一值。因此,首先查看数组中的第一个和最后一个值。如果
a[length-1]-a[0]
,则会有一些重复值。将
a[0]
a[length-1]
放入某个固定访问时间容器中,如散列集。如果这两个值相等,您就知道数组中只有一个唯一的值,您就完成了。您知道数组已排序。因此,如果两个值不同,现在可以查看中间的元素。如果中间元素已经在值集中,那么您知道可以跳过数组的整个左侧部分,而只递归地分析右侧部分。否则,递归地分析左侧和右侧部分

根据阵列中的数据,您将能够在不同数量的操作中获得所有唯一值的集合。如果所有值都是相同的,那么您可以在恒定时间
O(1)
中获得它们,因为您只需检查第一个和最后一个元素就可以知道它们。如果存在“相对较少”的唯一值,那么您的复杂性将接近
O(logn)
,因为在每个分区之后,您将“经常”能够丢弃至少一半的分析子数组。如果这些值都是唯一的并且
a[length-1]-a[0]=length-1
,您也可以在固定时间内“定义”集合,因为它们必须是从
a[0]
a[length-1]
的连续数字。但是,为了实际列出它们,您必须输出每个数字,其中有N个


也许有人可以提供更正式的分析,但我的估计是,该算法在唯一值的数量上,而不是数组的大小上,大致是线性的。这意味着,如果只有很少的唯一值,即使对于一个巨大的数组,您也可以通过很少的操作获得它们(例如,如果只有一个唯一值,则无论数组大小如何,都可以在固定时间内获得)。由于唯一值的数量并不大于数组的大小,因此我认为这使得该算法“优于O(N)”(或者严格地说:“不低于O(N)并且在许多情况下更好”)。

分而治之:

  • 查看排序序列的第一个和最后一个元素(初始序列是
    data[0]…data[data.length-1]
  • 如果两者相等,则序列中唯一的元素是第一个(无论序列有多长)
  • 如果序列不同,则将序列分割,并对每个子序列重复
在平均情况下求解O(log(n))
,在最坏情况下求解O(n)(当每个元素不同时)

Java代码:

public static List<Integer> findUniqueNumbers(int[] data) {
    List<Integer> result = new LinkedList<Integer>();
    findUniqueNumbers(data, 0, data.length - 1, result, false);
    return result;
}

private static void findUniqueNumbers(int[] data, int i1, int i2, List<Integer> result, boolean skipFirst) {

    int a = data[i1];
    int b = data[i2];

    // homogenous sequence a...a
    if (a == b) {
        if (!skipFirst) {
            result.add(a);
        }
    }
    else {
        //divide & conquer
        int i3 = (i1 + i2) / 2;
        findUniqueNumbers(data, i1, i3, result, skipFirst);
        findUniqueNumbers(data, i3 + 1, i2, result, data[i3] == data[i3 + 1]);
    }
}
公共静态列表findUniqueNumbers(int[]数据){
列表结果=新建LinkedList();
findUniqueNumbers(data,0,data.length-1,result,false);
返回结果;
}
私有静态void findUniqueNumbers(int[]数据、int i1、int i2、列表结果、布尔skipFirst){
int a=数据[i1];
int b=数据[i2];
//同质序列a…a
如果(a==b){
如果(!skipFirst){
结果.添加(a);
}
}
否则{
//分而治之
inti3=(i1+i2)/2;
findUniqueNumbers(数据、i1、i3、结果、skipFirst);
findUniqueNumbers(数据,i3+1,i2,结果,数据[i3]==数据[i3+1]);
}
}

如果大小为
n
的排序数组具有
m
不同的元素,则可以执行
O(mlogn)

请注意,当
m
import java.util.*时,这将非常有效;
/**
*删除排序数组中平均O(对数(n))和最差O(n)中的重复项
*@author XXX
*/
公共类唯一值{
公共静态void main(字符串[]args){
int[]test={-1,-1,-1,0,0,0,2,3,4,5,5,6,7,8};
UniqueValue u=新的UniqueValue();
System.out.println(u.getUniqueValues(test,0,test.length-1));
}
//i必须是开始索引,j必须是结束索引
公共列表getUniqueValues(int[]数组,int i,int j){
if(array==null | | array.length==0){
返回新的ArrayList();
}
列表结果=新建ArrayList();
if(数组[i]==数组[j]){
添加(数组[i]);
}否则{
int mid=(i+j)/2;
addAll(getUniqueValues(array,i,mid));
//避免重复划分
而(mid
您必须至少知道最后一个元素,因此您不需要至少遍历所有元素一次吗。因此,如果新的“唯一”编号与上一个索引上的编号相同,则最小界限为O(N)中断循环。因此,如果你到达第一个
3
,你可以停止循环。@Tom它仍然是O(N)@Tom它仍然与元素的数量成线性关系,所以O(N)。我希望你知道O(N)是什么意思?你可以做一些基于抽样/二进制搜索的事情,尽管很难猜测它会如何工作。我同意你的观点,我也给出了同样的答案,但他告诉我这是可能的。这就是为什么我在这里寻找答案,因为我还不知道这是怎么可能的
public static List<Integer> findUniqueNumbers(int[] data) {
    List<Integer> result = new LinkedList<Integer>();
    findUniqueNumbers(data, 0, data.length - 1, result, false);
    return result;
}

private static void findUniqueNumbers(int[] data, int i1, int i2, List<Integer> result, boolean skipFirst) {

    int a = data[i1];
    int b = data[i2];

    // homogenous sequence a...a
    if (a == b) {
        if (!skipFirst) {
            result.add(a);
        }
    }
    else {
        //divide & conquer
        int i3 = (i1 + i2) / 2;
        findUniqueNumbers(data, i1, i3, result, skipFirst);
        findUniqueNumbers(data, i3 + 1, i2, result, data[i3] == data[i3 + 1]);
    }
}
import java.util.*;

/**
 * remove duplicate in a sorted array in average O(log(n)), worst O(n)
 * @author XXX
 */
public class UniqueValue {
    public static void main(String[] args) {
        int[] test = {-1, -1, -1, -1, 0, 0, 0, 0,2,3,4,5,5,6,7,8};
        UniqueValue u = new UniqueValue();
        System.out.println(u.getUniqueValues(test, 0, test.length - 1));
    }

    // i must be start index, j must be end index
    public List<Integer> getUniqueValues(int[] array, int i, int j) {
        if (array == null || array.length == 0) {
            return new ArrayList<Integer>();
        }
        List<Integer> result = new ArrayList<>();
        if (array[i] == array[j]) {
            result.add(array[i]);
        } else {
            int mid = (i + j) / 2;
            result.addAll(getUniqueValues(array, i, mid));

            // avoid duplicate divide
            while (mid < j && array[mid] == array[++mid]);
            if (array[(i + j) / 2] != array[mid]) {
                result.addAll(getUniqueValues(array, mid, j));
            }
        }
        return result;
    }
}