İşte sdcvvc / Dimitris Andreou'nun cevaplarının yaptığı gibi karmaşık matematiğe dayanmayan bir çözüm, giriş dizisini caf ve Albay Panic gibi değiştirmedi ve Chris Lercher, JeremyP ve diğerleri yaptı. Temel olarak, Svalorzen / Gilad Deutch'ın Q2 fikri ile başladım, Qk genel durumuna genelleştirdim ve algoritmanın çalıştığını kanıtlamak için Java'da uyguladım.
Fikir
Biz keyfi bir aralık olduğunu varsayalım I biz sadece eksik sayıların en az birini içerdiğini biliyoruz ki. Giriş dizi boyunca bir geçiş sonrası, sadece gelen numaraları bakarak I , biz toplamı her ikisi de elde edebilir S ve miktarı Q numaraları eksik I . Biz, sadece azaltılmasıyla bunun I'in uzunluğu biz bir dizi sorunla her I (elde edilmesi için Q ) ve tüm sayıların önceden hesaplanan toplamı azaltarak I bu karşılaşılan numarası (elde etmek için her defasında S ).
Şimdi S ve Q'ya bakıyoruz . Eğer Q = 1 , o zaman anlamına gelir Ben sadece içerdiği eksik sayıların biri ve bu sayı açıkça S . Biz işaretlemek ben bitmiş gibi (o programda "kesin" olarak adlandırılır) ve daha fazla dikkate dışarıda bırakın. Diğer taraftan, eğer Q> 1 ise , I'de bulunan eksik sayıların ortalama A = S / Q'unu hesaplayabiliriz . Tüm sayılar farklı olduğu için, bu sayılardan en az biri kesinlikle A'dan küçüktür ve en az biri kesinlikle A'dan büyüktür . Şimdi bölünmüş I içinde Aher biri en az bir eksik sayı içeren iki küçük aralığa bölünür. Bir tamsayı olması durumunda A'yı hangi aralıklarla atadığımızın bir önemi olmadığını unutmayın .
Aralıkların her biri için ayrı ayrı (fakat aynı geçişte) S ve Q hesaplayan bir sonraki dizi geçişini yaparız ve bundan sonra Q = 1 ile işaretleme aralıkları ve Q> 1 ile ayırma aralıkları yaparız . Yeni "belirsiz" aralık kalmayıncaya kadar bu sürece devam ediyoruz, yani bölünecek hiçbir şeyimiz yok çünkü her aralık tam olarak bir eksik sayı içeriyor (ve S'yi bildiğimiz için bu sayıyı her zaman biliyoruz ). Mümkün olan tüm sayıları içeren tek "bütün aralık" aralığından başlıyoruz (örneğin [1..N] gibi ).
Zaman ve mekan karmaşıklığı analizi
İşlem duruncaya kadar yapmamız gereken toplam geçiş sayısı p hiçbir zaman eksik sayılar k'dan daha fazla olamaz . P <= k eşitsizliği titizlikle kanıtlanabilir. Öte yandan, büyük k değerleri için yararlı olan ampirik bir üst sınır p <log 2 N + 3 de vardır . Ait olduğu aralığı belirlemek için giriş dizisinin her sayısı için ikili bir arama yapmamız gerekir. Bu , zaman karmaşıklığına günlük k çarpanını ekler .
Toplamda, zaman karmaşıklığı O (N ᛫ dk (k, log N) ᛫ log k) 'dır . Büyük k için , bunun sdcvvc / Dimitris Andreou'nun O (N ᛫ k) yönteminden önemli ölçüde daha iyi olduğuna dikkat edin .
Çalışması için, algoritma "bitset" çözeltilerinde O (k) 'dan fazla k aralıkta depolamak için ek alan gerektirir , bu da O (N)' den çok daha iyidir .
Java uygulaması
Yukarıdaki algoritmayı uygulayan bir Java sınıfı. Her zaman sıralanmış bir dizi eksik sayı döndürür . Bunun yanı sıra, eksik sayıların k sayısını gerektirmez çünkü ilk geçişte hesaplar. Bütün sayı aralığı minNumber
ve maxNumber
parametreleri tarafından verilir (örneğin, sorudaki ilk örnek için 1 ve 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;
}
}
Adalet için, bu sınıf NumberBag
nesne şeklinde girdi alır . NumberBag
dizi değişikliğine ve rastgele erişime izin vermez ve dizinin ardışık geçiş için kaç kez istendiğini sayar. Aynı zamanda büyük dizi testi için Iterable<Integer>
, ilkel int
değerlerin boksundan kaçınması ve int[]
uygun bir test hazırlığı için büyük bir kısmının sarılmasına izin vermesinden daha uygundur. İstenirse, yerine zor değil NumberBag
tarafından int[]
veya Iterable<Integer>
yazdığınız find
foreach olanları içine onun içinde iki-döngüler değiştirerek, imza.
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();
}
};
}
}
Testler
Bu sınıfların kullanımını gösteren basit örnekler aşağıda verilmiştir.
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");
}
}
Büyük dizi testi şu şekilde yapılabilir:
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);
}
}
Onları Ideone'de deneyin