如何在未排序的只读数组中找到第K个最小整数?

时间:2015-06-09 09:34:23

标签: arrays algorithm

这是一个标准问题,已在多个网站中多次回答,但在此版本中还有其他限制:

  1. 数组是只读的(我们无法修改数组)。
  2. 在O(1)空间中进行。
  3. 有人可以在最佳的时间复杂性中向我解释这种方法。

3 个答案:

答案 0 :(得分:23)

实际上有一种方法可以解决 O(n log d)时间复杂度& O(1)空间复杂度,无需修改数组。这里 n 代表数组的长度,而 d 是其中包含的数字范围的长度。

这个想法是为第k个最小元素执行binary search。从lo = minimum元素开始,hi =最大元素。在每个步骤中,检查有多少元素小于mid并相应地更新它。以下是我的解决方案的Java代码:

    public int kthsmallest(final List<Integer> a, int k) {
        if(a == null || a.size() == 0)
             throw new IllegalArgumentException("Empty or null list.");
        int lo = Collections.min(a);
        int hi = Collections.max(a);

        while(lo <= hi) {
            int mid = lo + (hi - lo)/2;
            int countLess = 0, countEqual = 0;

            for(int i = 0; i < a.size(); i++) {
                if(a.get(i) < mid) {
                    countLess++;
                }else if(a.get(i) == mid) {
                    countEqual++;
                }
                if(countLess >= k) break;
            }

            if(countLess < k && countLess + countEqual >= k){
                return mid;
            }else if(countLess >= k) {
                hi = mid - 1;
            }else{
                lo = mid + 1;
            }
        }


        assert false : "k cannot be larger than the size of the list.";
        return -1;
    }

请注意,此解决方案也适用于具有重复和/或负数的数组。

答案 1 :(得分:11)

我将假设只读数组是一个强烈要求,并尝试在此答案中找到不违反它的权衡(因此,使用selection algorithm不是一个选项,因为它修改了数组)

作为旁注,来自wikipedia,如果不修改数组,就无法在O(1)空间中完成:

  

选择所需的空间复杂度很容易看出是k +   O(1)(或n - k,如果k> n / 2),就地算法可以选择   只有O(1)额外的存储空间......空间复杂度可以降低   以获得近似答案或正确答案为代价   一定概率

可以在O(nlogk)时间的 O(k)空间中完成。如果k是常量,则为O(1)解决方案

我们的想法是保持k的最大heap。首先使用第一个k元素填充它,然后继续迭代数组。如果某个元素x小于堆的顶部,则弹出旧头,然后插入x

完成后,堆的头部是k最小的元素。

就大O符号而言,可以使用大小为O(n)的新数组在 O(k) 2k空间中完成,加载元素块到阵列,并在此辅助阵列上使用selection algorithm来查找k元素。丢弃大于第k个元素的所有元素,并重复下一个块(加载更多k个元素)。复杂性为O(k*n/k) = O(n)

然而,这很少使用,并且常常比堆解决方案更糟糕,堆解决方案经常使用。

如果您真的想使用O(1)空间,可以通过查找最少k次来使用强力解决方案。你只需要记住旧的最小值,即恒定的空间。对于O(1)空间,此解决方案 O(nk)时间,就时间而言,效率明显低于替代方案。

答案 2 :(得分:0)

我已经在InterviewBit上看到了这个问题,我想我已经解决了,捕获了我在下面的数组中找到第k个最小元素的逻辑。

  • 找到数组中最小的元素,这是最后一个最小的元素。
  • 在循环中,对于“ k”次,找到在循环的最后一次迭代中找到的最小元素之后的第二大元素(在第一步中找到的最后一个最小元素将在循环的第一次迭代中使用)
  • 返回循环中最后找到的下一个最大元素 复制下面的代码。

int getSmallest(const int* A, int nSize){ int nSmallestElemIndex = -1; int i; for(i=0; i < nSize; i++){ if (-1 == nSmallestElemIndex){ nSmallestElemIndex = i; continue; } if (A[i] < A[nSmallestElemIndex]){ nSmallestElemIndex = i; } } return nSmallestElemIndex; } int nextBig(const int* A, int nSize, int nCurrentSmallest){ int nNextBig = -1; int i; for(i=0; i < nSize; i++){ if (i == nCurrentSmallest || A[i] < A[nCurrentSmallest]){ continue; } if (A[i] == A[nCurrentSmallest] && i <= nCurrentSmallest){ continue; } if (nNextBig == -1){ nNextBig = i; continue; } if (A[i] >= A[nCurrentSmallest] && A[i] < A[nNextBig]){ nNextBig = i; } } return nNextBig; } int kthsmallest(const int* A, int n1, int B) { int nSmallestElem = getSmallest(A, n1); int nCount = 1; int nRes = nSmallestElem; while(nCount < B){ nRes = nextBig(A, n1, nRes); nCount++; } return nRes; }

我在机器上的测试用例中执行并验证了它,但Interviewbit不接受它。

如果解决方案通过您的验证,我将感到高兴。让我知道您的评论。