다음은 sdcvvc / Dimitris Andreou의 답변처럼 복잡한 수학에 의존하지 않고 caf 및 Panic 대령과 마찬가지로 입력 배열을 변경하지 않으며 Chris Lercher, JeremyP 및 많은 사람들이 그랬습니다. 기본적으로 저는 Q2에 대한 Svalorzen / Gilad Deutch의 아이디어로 시작하여 일반적인 사례 Qk로 일반화하고 알고리즘이 작동 함을 증명하기 위해 Java로 구현했습니다.
아이디어
임의의 간격 I 이 있고 그 중 적어도 하나의 누락 된 숫자 만 포함한다는 것을 알고 있다고 가정하십시오 . 입력 배열을 통해 하나 개의 패스 만 행 번호를 찾고 I , 우리는 합산 모두 얻을 수 S 와 양 Q 로부터 숫자 누락 I를 . 우리는 단순히 감소시키는하여이 작업을 수행 I의 길이 우리의 숫자가 발생할 때마다 I (얻기 위해 Q를 ) 및 모든 숫자의 미리 계산 된 금액을 줄임으로써 나는 그 발생 수 (얻기위한 각 시간 S를 ).
이제 S 와 Q를 봅니다. 경우 Q = 1 , 그 다음 것을 의미 I은 단지 포함 누락 번호 중 하나,이 숫자는 명확 S . 우리는 내가 완성 된 것으로 표시 하고 (프로그램에서 "명확한"이라고 함) 추가 고려에서 제외합니다. 반면에 Q> 1 이면 I에 포함 된 누락 된 숫자 의 평균 A = S / Q 를 계산할 수 있습니다 . 모든 숫자가 고유하기 때문에 이러한 숫자 중 적어도 하나는 A 보다 엄격하게 작고 하나 이상은 A 보다 엄격히 큽니다 . 이제 우리는 분할 I을 에각각 하나 이상의 누락 된 숫자를 포함하는 두 개의 작은 간격으로. 정수인 경우 A 를 할당 하는 간격은 중요하지 않습니다 .
다음 배열 패스 는 각 간격에 대해 S 와 Q 를 개별적으로 계산 하지만 (같은 패스로) 그 이후에는 Q = 1의 마크 간격과 Q> 1의 분할 간격을 만듭니다. 새로운 "모호한"간격이 없어 질 때까지이 과정을 계속합니다. 즉, 각 간격에 정확히 하나의 누락 된 숫자가 포함되어 있기 때문에 분할 할 것이 없습니다 (우리는 S 를 알고 있기 때문에 항상이 숫자를 알고 있습니다 ). 가능한 모든 숫자를 포함하는 유일한 "전체 범위"간격 에서 시작합니다 (문제의 [1..N] 등 ).
시간 및 공간 복잡성 분석
프로세스가 멈출 때까지해야하는 총 패스 수 p 는 누락 된 수 count k 보다 크지 않습니다 . 불평등 p <= k 는 엄격하게 증명 될 수 있습니다. 한편, k의 큰 값에 유용한 경험적 상한 p <log 2 N + 3도 있습니다. 입력 배열의 각 숫자를 이진 검색하여 해당 배열이 속하는 간격을 결정해야합니다. 이것은 시간 복잡성에 로그 k 승수를 추가합니다 .
총 시간 복잡도는 O (N ᛫ min (k, log N) ᛫ log k) 입니다. 큰 k의 경우 이는 sdcvvc / Dimitris Andreou의 방법 (O (N ᛫ k)) 보다 훨씬 낫습니다 .
이 작업을 위해서는 알고리즘 에 최대 k 간격 을 저장하기위한 O (k) 추가 공간이 필요 합니다. 이는 "비트 세트"솔루션에서 O (N) 보다 훨씬 낫습니다 .
자바 구현
위의 알고리즘을 구현하는 Java 클래스가 있습니다. 항상 누락 된 숫자 의 정렬 된 배열을 반환 합니다. 게다가 , 첫 번째 패스에서 계산하기 때문에 누락 된 숫자 카운트 k가 필요하지 않습니다 . 숫자의 전체 범위는 minNumber
and maxNumber
매개 변수로 지정됩니다 (예 : 문제의 첫 번째 예의 경우 1과 100).
public class MissingNumbers {
private static class Interval {
boolean ambiguous = true;
final int begin;
int quantity;
long sum;
Interval(int begin, int end) { // begin inclusive, end exclusive
this.begin = begin;
quantity = end - begin;
sum = quantity * ((long)end - 1 + begin) / 2;
}
void exclude(int x) {
quantity--;
sum -= x;
}
}
public static int[] find(int minNumber, int maxNumber, NumberBag inputBag) {
Interval full = new Interval(minNumber, ++maxNumber);
for (inputBag.startOver(); inputBag.hasNext();)
full.exclude(inputBag.next());
int missingCount = full.quantity;
if (missingCount == 0)
return new int[0];
Interval[] intervals = new Interval[missingCount];
intervals[0] = full;
int[] dividers = new int[missingCount];
dividers[0] = minNumber;
int intervalCount = 1;
while (true) {
int oldCount = intervalCount;
for (int i = 0; i < oldCount; i++) {
Interval itv = intervals[i];
if (itv.ambiguous)
if (itv.quantity == 1) // number inside itv uniquely identified
itv.ambiguous = false;
else
intervalCount++; // itv will be split into two intervals
}
if (oldCount == intervalCount)
break;
int newIndex = intervalCount - 1;
int end = maxNumber;
for (int oldIndex = oldCount - 1; oldIndex >= 0; oldIndex--) {
// newIndex always >= oldIndex
Interval itv = intervals[oldIndex];
int begin = itv.begin;
if (itv.ambiguous) {
// split interval itv
// use floorDiv instead of / because input numbers can be negative
int mean = (int)Math.floorDiv(itv.sum, itv.quantity) + 1;
intervals[newIndex--] = new Interval(mean, end);
intervals[newIndex--] = new Interval(begin, mean);
} else
intervals[newIndex--] = itv;
end = begin;
}
for (int i = 0; i < intervalCount; i++)
dividers[i] = intervals[i].begin;
for (inputBag.startOver(); inputBag.hasNext();) {
int x = inputBag.next();
// find the interval to which x belongs
int i = java.util.Arrays.binarySearch(dividers, 0, intervalCount, x);
if (i < 0)
i = -i - 2;
Interval itv = intervals[i];
if (itv.ambiguous)
itv.exclude(x);
}
}
assert intervalCount == missingCount;
for (int i = 0; i < intervalCount; i++)
dividers[i] = (int)intervals[i].sum;
return dividers;
}
}
공정성을 위해이 클래스는 NumberBag
객체 형식으로 입력을받습니다 . NumberBag
배열 수정 및 임의 액세스를 허용하지 않으며 순차적 순회에 대해 배열이 요청 된 횟수도 계산합니다. 또한 Iterable<Integer>
기본 int
값의 복싱을 피하고 int[]
편리한 테스트 준비를 위해 큰 부분을 래핑 할 수 있기 때문에 대규모 배열 테스트에 더 적합합니다 . 원하는 경우, 대체 어렵지 않다 NumberBag
의해 int[]
또는 Iterable<Integer>
을 입력 find
foreach는 것들로 두에 대한 - 루프를 변경하여 서명.
import java.util.*;
public abstract class NumberBag {
private int passCount;
public void startOver() {
passCount++;
}
public final int getPassCount() {
return passCount;
}
public abstract boolean hasNext();
public abstract int next();
// A lightweight version of Iterable<Integer> to avoid boxing of int
public static NumberBag fromArray(int[] base, int fromIndex, int toIndex) {
return new NumberBag() {
int index = toIndex;
public void startOver() {
super.startOver();
index = fromIndex;
}
public boolean hasNext() {
return index < toIndex;
}
public int next() {
if (index >= toIndex)
throw new NoSuchElementException();
return base[index++];
}
};
}
public static NumberBag fromArray(int[] base) {
return fromArray(base, 0, base.length);
}
public static NumberBag fromIterable(Iterable<Integer> base) {
return new NumberBag() {
Iterator<Integer> it;
public void startOver() {
super.startOver();
it = base.iterator();
}
public boolean hasNext() {
return it.hasNext();
}
public int next() {
return it.next();
}
};
}
}
테스트
이러한 클래스의 사용법을 보여주는 간단한 예가 아래에 나와 있습니다.
import java.util.*;
public class SimpleTest {
public static void main(String[] args) {
int[] input = { 7, 1, 4, 9, 6, 2 };
NumberBag bag = NumberBag.fromArray(input);
int[] output = MissingNumbers.find(1, 10, bag);
System.out.format("Input: %s%nMissing numbers: %s%nPass count: %d%n",
Arrays.toString(input), Arrays.toString(output), bag.getPassCount());
List<Integer> inputList = new ArrayList<>();
for (int i = 0; i < 10; i++)
inputList.add(2 * i);
Collections.shuffle(inputList);
bag = NumberBag.fromIterable(inputList);
output = MissingNumbers.find(0, 19, bag);
System.out.format("%nInput: %s%nMissing numbers: %s%nPass count: %d%n",
inputList, Arrays.toString(output), bag.getPassCount());
// Sieve of Eratosthenes
final int MAXN = 1_000;
List<Integer> nonPrimes = new ArrayList<>();
nonPrimes.add(1);
int[] primes;
int lastPrimeIndex = 0;
while (true) {
primes = MissingNumbers.find(1, MAXN, NumberBag.fromIterable(nonPrimes));
int p = primes[lastPrimeIndex]; // guaranteed to be prime
int q = p;
for (int i = lastPrimeIndex++; i < primes.length; i++) {
q = primes[i]; // not necessarily prime
int pq = p * q;
if (pq > MAXN)
break;
nonPrimes.add(pq);
}
if (q == p)
break;
}
System.out.format("%nSieve of Eratosthenes. %d primes up to %d found:%n",
primes.length, MAXN);
for (int i = 0; i < primes.length; i++)
System.out.format(" %4d%s", primes[i], (i % 10) < 9 ? "" : "\n");
}
}
다음과 같이 대규모 어레이 테스트를 수행 할 수 있습니다.
import java.util.*;
public class BatchTest {
private static final Random rand = new Random();
public static int MIN_NUMBER = 1;
private final int minNumber = MIN_NUMBER;
private final int numberCount;
private final int[] numbers;
private int missingCount;
public long finderTime;
public BatchTest(int numberCount) {
this.numberCount = numberCount;
numbers = new int[numberCount];
for (int i = 0; i < numberCount; i++)
numbers[i] = minNumber + i;
}
private int passBound() {
int mBound = missingCount > 0 ? missingCount : 1;
int nBound = 34 - Integer.numberOfLeadingZeros(numberCount - 1); // ceil(log_2(numberCount)) + 2
return Math.min(mBound, nBound);
}
private void error(String cause) {
throw new RuntimeException("Error on '" + missingCount + " from " + numberCount + "' test, " + cause);
}
// returns the number of times the input array was traversed in this test
public int makeTest(int missingCount) {
this.missingCount = missingCount;
// numbers array is reused when numberCount stays the same,
// just Fisher–Yates shuffle it for each test
for (int i = numberCount - 1; i > 0; i--) {
int j = rand.nextInt(i + 1);
if (i != j) {
int t = numbers[i];
numbers[i] = numbers[j];
numbers[j] = t;
}
}
final int bagSize = numberCount - missingCount;
NumberBag inputBag = NumberBag.fromArray(numbers, 0, bagSize);
finderTime -= System.nanoTime();
int[] found = MissingNumbers.find(minNumber, minNumber + numberCount - 1, inputBag);
finderTime += System.nanoTime();
if (inputBag.getPassCount() > passBound())
error("too many passes (" + inputBag.getPassCount() + " while only " + passBound() + " allowed)");
if (found.length != missingCount)
error("wrong result length");
int j = bagSize; // "missing" part beginning in numbers
Arrays.sort(numbers, bagSize, numberCount);
for (int i = 0; i < missingCount; i++)
if (found[i] != numbers[j++])
error("wrong result array, " + i + "-th element differs");
return inputBag.getPassCount();
}
public static void strideCheck(int numberCount, int minMissing, int maxMissing, int step, int repeats) {
BatchTest t = new BatchTest(numberCount);
System.out.println("╠═══════════════════════╬═════════════════╬═════════════════╣");
for (int missingCount = minMissing; missingCount <= maxMissing; missingCount += step) {
int minPass = Integer.MAX_VALUE;
int passSum = 0;
int maxPass = 0;
t.finderTime = 0;
for (int j = 1; j <= repeats; j++) {
int pCount = t.makeTest(missingCount);
if (pCount < minPass)
minPass = pCount;
passSum += pCount;
if (pCount > maxPass)
maxPass = pCount;
}
System.out.format("║ %9d %9d ║ %2d %5.2f %2d ║ %11.3f ║%n", missingCount, numberCount, minPass,
(double)passSum / repeats, maxPass, t.finderTime * 1e-6 / repeats);
}
}
public static void main(String[] args) {
System.out.println("╔═══════════════════════╦═════════════════╦═════════════════╗");
System.out.println("║ Number count ║ Passes ║ Average time ║");
System.out.println("║ missimg total ║ min avg max ║ per search (ms) ║");
long time = System.nanoTime();
strideCheck(100, 0, 100, 1, 20_000);
strideCheck(100_000, 2, 99_998, 1_282, 15);
MIN_NUMBER = -2_000_000_000;
strideCheck(300_000_000, 1, 10, 1, 1);
time = System.nanoTime() - time;
System.out.println("╚═══════════════════════╩═════════════════╩═════════════════╝");
System.out.format("%nSuccess. Total time: %.2f s.%n", time * 1e-9);
}
}
Ideone에서 사용해보십시오