前段时间我有一次有趣的面试经历。问题一开始很简单:

Q1:我们有一个袋子,里面有数字1,2,3,…,100。每个数字恰好出现一次,所以有100个数字。现在从袋子里随机抽取一个数字。找到丢失的号码。

当然,我以前听过这个面试问题,所以我很快就回答了这个问题:

A1:嗯,1 + 2 + 3 +…+ N的和是(N+1)(N/2)(参见维基百科:等差级数的和)。当N = 100时,和是5050。 因此,如果所有的数字都在袋子里,总和将恰好是5050。因为少了一个数,总和就会小于这个数,差的就是这个数。所以我们可以在O(N)时间和O(1)空间中找到这个缺失的数。

在这一点上,我认为我做得很好,但突然间,问题发生了意想不到的转变:

这是正确的,但是如果少了两个数字,你会怎么做?

我以前从未见过/听过/考虑过这种变化,所以我很恐慌,无法回答这个问题。面试官坚持要知道我的思考过程,所以我提到,也许我们可以通过与预期产品进行比较来获得更多信息,或者在从第一次传递中收集到一些信息后再进行第二次传递,等等,但我真的只是在黑暗中拍摄,而不是真正有一个明确的解决方案的路径。

面试官试图鼓励我说,有第二个方程确实是解决问题的一种方法。在这一点上,我有点不安(因为事先不知道答案),并问这是一种通用的(阅读:“有用的”)编程技术,还是只是一个技巧/答案。

面试官的回答让我惊讶:你可以把这个技巧概括为3个缺失的数字。事实上,你可以推广它来找到k个缺失的数。

Qk:如果袋子里少了k个数字,你如何有效地找到它?

这是几个月前的事了,我还不明白这个技巧是什么。显然有一个Ω(N)的时间下限,因为我们必须扫描所有的数字至少一次,但面试官坚持认为,解决技术的时间和空间复杂度(减去O(N)次输入扫描)定义为k而不是N。

所以问题很简单:

如何解决Q2? 你会如何解决Q3? 如何求解Qk?


澄清

Generally there are N numbers from 1..N, not just 1..100. I'm not looking for the obvious set-based solution, e.g. using a bit set, encoding the presence/absence each number by the value of a designated bit, therefore using O(N) bits in additional space. We can't afford any additional space proportional to N. I'm also not looking for the obvious sort-first approach. This and the set-based approach are worth mentioning in an interview (they are easy to implement, and depending on N, can be very practical). I'm looking for the Holy Grail solution (which may or may not be practical to implement, but has the desired asymptotic characteristics nevertheless).

当然,你必须以O(N)为单位扫描输入,但你只能捕获少量的信息(用k而不是N定义),然后必须以某种方式找到k个缺失的数字。


当前回答

如果一个数字只出现一次,用下面的方法很容易分辨:

创建一个大小为给定数字的布尔数组boolArray;这里是100。

遍历输入数字,并根据数字值将一个元素设置为true。例如,如果找到45,则设置boolArray[45-1] = true;

这是一个O(N)运算。

然后循环遍历boolArray。如果一个元素保持为false,那么element + 1的下标就是缺失的数字。例如,如果boolArray[44]为false,我们就知道第45号丢失了。

这是O(n)运算。空间复杂度为O(1)。

所以这个解可以从一个给定的连续数集中找到任何缺失的数。

其他回答

我们假设它是一个从1到N的数组,它的元素是a1, a2, ....一个:

1+N=N+1;
2+N-1=N+1;

… 所以这个和是唯一的。我们可以从头到尾扫描数组来添加两个元素。如果和是N+1;好吧,否则它们就不见了。

for (I <= N/2) {
    temp = a[I] + a[n-I];
    if (temp != N+1) then
        Find the missing number or numbers
}

迭代这个循环,很容易就能得到答案。

对于Q2,这是一个比其他解决方案效率更低的解决方案,但仍然有O(N)个运行时和O(k)个空间。

这个想法是运行原始算法两次。在第一个例子中,你得到了缺失的总数,这给了你缺失数字的上界。我们称这个数为N,你知道这两个数的和是N,所以第一个数只能在[1,floor((N-1)/2)]区间内,而第二个数将在[floor(N/2)+1,N-1]区间内。

因此,再次循环所有数字,丢弃第一个间隔中不包括的所有数字。你可以记录它们的和。最后,你将知道丢失的两个数字中的一个,进而知道第二个数字。

我有一种感觉,这种方法可以被推广,也许在一次输入传递期间,多个搜索可以“并行”运行,但我还没有弄清楚如何做到这一点。

谢谢你这个有趣的问题:

因为你让我想起了牛顿的工作,它真的可以解决这个问题

请参考牛顿恒等式

As变量的数量=方程的数量(必须为一致性)

我认为,对于这个问题,我们应该提高袋数的幂,以便创建不同的方程。

我不知道,但是,我相信如果有一个函数,比如f,我们要加上f(xi)

x1+x2+…+ xk = z1

x12 + x22 + ... + xk2 = z2

............

............

............

x1k + x2k + ... + xkk = XP

休息是一个不确定时间和空间复杂性的数学工作,但牛顿恒等式肯定会发挥重要作用。

我们不能用集合理论吗 .difference_update()或在这个问题方法中是否有线性代数的机会?

我认为这不需要任何复杂的数学方程和理论。下面是一个建议的到位和O(2n)时间复杂度的解决方案:

输入表格假设:

袋子里的数字# = n

缺失数字的数量= k

袋子里的数字由长度为n的数组表示

算法的输入数组长度= n

数组中缺失的条目(从袋子中取出的数字)将被数组中第一个元素的值替换。

如。最初袋子看起来像[2,9,3,7,8,6,4,5,1,10]。 如果4被取出,value 4将变成2(数组的第一个元素)。 因此,在取出4后,袋子将看起来像[2,9,3,7,8,6,2,5,1,10]

此解决方案的关键是在遍历数组时,通过对索引处的值求负来标记访问数的INDEX。

    IEnumerable<int> GetMissingNumbers(int[] arrayOfNumbers)
    {
        List<int> missingNumbers = new List<int>();
        int arrayLength = arrayOfNumbers.Length;

        //First Pass
        for (int i = 0; i < arrayLength; i++)
        {
            int index = Math.Abs(arrayOfNumbers[i]) - 1;
            if (index > -1)
            {
                arrayOfNumbers[index] = Math.Abs(arrayOfNumbers[index]) * -1; //Marking the visited indexes
            }
        }

        //Second Pass to get missing numbers
        for (int i = 0; i < arrayLength; i++)
        {                
            //If this index is unvisited, means this is a missing number
            if (arrayOfNumbers[i] > 0)
            {
                missingNumbers.Add(i + 1);
            }
        }

        return missingNumbers;
    }

我使用Java 8和Java 8之前的版本编写代码。 它使用一个公式:(N*(N+1))/2作为所有数字的和。

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

   /**
 * 
 * 
 * @author pradeep
 * 
 *         Answer : SumOfAllNumbers-SumOfPresentNumbers=Missing Number;
 * 
 *         To GET SumOfAllNumbers : Get the highest number (N) by checking the
 *         length. and use the formula (N*(N+1))/2
 * 
 *         To GET SumOfPresentNumbers: iterate and add it
 * 
 * 
 */
public class FindMissingNumber {
    /**
     * Before Java 8
     * 
     * @param numbers
     * @return
     */
    public static int missingNumber(List<Integer> numbers) {
        int sumOfPresentNumbers = 0;
        for (Integer integer : numbers) {
            sumOfPresentNumbers = sumOfPresentNumbers + integer;
        }
        int n = numbers.size();
        int sumOfAllNumbers = (n * (n + 1)) / 2;
        return sumOfAllNumbers - sumOfPresentNumbers;
    }
    /**
     * Using Java 8 . mapToInt & sum using streams.
     * 
     * @param numbers
     * @return
     */
    public static int missingNumberJava8(List<Integer> numbers) {
        int sumOfPresentNumbers = numbers.stream().mapToInt(i -> i).sum();
        int n = numbers.size();
        int sumOfAllNumbers = (n * (n + 1)) / 2;
        return sumOfAllNumbers - sumOfPresentNumbers;
    }
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        list = Arrays.asList(0, 1, 2, 4);
        System.out.println("Missing number is :  " + missingNumber(list));
        System.out.println("Missing number using Java 8 is : " + missingNumberJava8(list));
    }
}*