นี่คือวิธีแก้ปัญหาที่ไม่ต้องพึ่งพาคณิตศาสตร์ที่ซับซ้อนเหมือนคำตอบของ sdcvvc / Dimitris Andreou ไม่ต้องเปลี่ยนอาร์เรย์อินพุตเหมือนที่ caf และ Colonel Panic ทำและไม่ใช้บิตเซ็ตขนาดใหญ่อย่าง Chris Lercher, JeremyP และ หลายคนทำ โดยพื้นฐานแล้วฉันเริ่มต้นด้วยแนวคิดของ Svalorzen's / Gilad Deutch สำหรับไตรมาสที่ 2 โดยทั่วไปจะเป็นกรณีทั่วไป Qk และนำไปใช้ใน Java เพื่อพิสูจน์ว่าอัลกอริทึมทำงาน
ความคิด
สมมติว่าเรามีช่วงเวลาที่กำหนดเองผมซึ่งเราจะรู้ว่ามันมีอย่างน้อยหนึ่งของตัวเลขที่หายไป หลังจากหนึ่งผ่านเข้าแถวที่กำลังมองหาเฉพาะที่ตัวเลขจากฉันเราสามารถได้รับทั้งผลรวมSและปริมาณQของตัวเลขที่หายไปจากฉัน เราทำสิ่งนี้โดยการลดความยาวของฉันทุกครั้งที่เราพบตัวเลขจากI (สำหรับการรับQ ) และโดยการลดผลรวมที่คำนวณไว้ล่วงหน้าของตัวเลขทั้งหมดในIด้วยหมายเลขที่พบในแต่ละครั้ง (สำหรับS )
ตอนนี้เรามองไปที่SและQ หากQ = 1ก็หมายความว่าแล้วฉันมีเพียงหนึ่งของตัวเลขที่หายไปและจำนวนนี้เป็นอย่างชัดเจนS เราทำเครื่องหมายว่าฉันเสร็จแล้ว (มันถูกเรียกว่า "โปร่งใส" ในโปรแกรม) และปล่อยให้มันออกมาจากการพิจารณาต่อไป ในทางตรงกันข้ามถ้าถาม> 1เราสามารถคำนวณค่าเฉลี่ยของA = S / Qของตัวเลขที่หายไปอยู่ในฉัน เป็นตัวเลขที่ทุกคนมีความแตกต่างกันอย่างน้อยหนึ่งของตัวเลขดังกล่าวเป็นอย่างเคร่งครัดน้อยกว่าและอย่างน้อยหนึ่งอย่างเคร่งครัดมากกว่า ตอนนี้เราแยกฉันในออกเป็นสองช่วงเวลาเล็ก ๆ ซึ่งแต่ละอันจะมีหมายเลขที่ขาดหายไปอย่างน้อยหนึ่งหมายเลข โปรดทราบว่าไม่สำคัญว่าช่วงใดที่เรากำหนดAในกรณีที่เป็นจำนวนเต็ม
เราจะให้ผ่านอาร์เรย์ต่อไปคำนวณSและQสำหรับแต่ละช่วงเวลาที่แยกต่างหาก ( แต่ในการส่งผ่านเดียวกัน) และหลังจากนั้นช่วงเวลาที่มีเครื่องหมายQ = 1และช่วงเวลาที่แยกกับQ> 1 เราทำกระบวนการนี้ต่อไปจนกว่าจะไม่มีช่วง "กำกวม" ใหม่นั่นคือเราไม่มีอะไรจะแยกเพราะแต่ละช่วงเวลามีหมายเลขที่ขาดหายไปหนึ่งหมายเลข (และเรารู้หมายเลขนี้เสมอเพราะเรารู้S ) เราเริ่มจากช่วง "ช่วงทั้งหมด" แต่เพียงผู้เดียวที่มีตัวเลขที่เป็นไปได้ทั้งหมด (เช่น[1..N]ในคำถาม)
การวิเคราะห์ความซับซ้อนของเวลาและพื้นที่
จำนวนรวมของผ่านหน้าเราต้องทำให้จนกว่ากระบวนการหยุดไม่เคยมีตัวเลขที่สูงกว่าที่หายไปนับk ความไม่เท่าเทียมกันp <= kสามารถพิสูจน์ได้อย่างจริงจัง บนมืออื่น ๆ , นอกจากนี้ยังมีการทดลองที่ถูกผูกไว้บนp <ล็อก2 N + 3ที่เป็นประโยชน์สำหรับค่าที่มีขนาดใหญ่ของk เราจำเป็นต้องทำการค้นหาแบบไบนารี่สำหรับแต่ละอินพุทของอาเรย์เพื่อกำหนดช่วงเวลาที่เป็นของมัน นี่เป็นการเพิ่มตัวคูณlog kเพื่อความซับซ้อนของเวลา
รวมเวลาซับซ้อนคือO (n ᛫นาที (k เข้าสู่ระบบ N) ᛫บันทึก k) โปรดทราบว่าสำหรับขนาดใหญ่kนี้เป็นอย่างดีกว่าที่ sdcvvc / วิธี Dimitris Andreou ซึ่งเป็นO (n ᛫ k)
สำหรับการทำงานอัลกอริทึมต้องใช้พื้นที่เพิ่มเติมO (k)สำหรับการจัดเก็บในช่วงเวลาkเกือบทุกครั้งซึ่งดีกว่าO (N) อย่างมากในโซลูชัน "บิตเซ็ต"
การติดตั้ง Java
นี่คือคลาส Java ที่ใช้อัลกอริทึมข้างต้น มันจะส่งกลับอาร์เรย์ที่เรียงลำดับของตัวเลขที่หายไปเสมอ นอกจากนั้นมันไม่จำเป็นต้องมีตัวเลขที่หายไปนับkเพราะมันจะคำนวณในรอบแรก ช่วงทั้งหมดของตัวเลขถูกกำหนดโดยminNumber
และ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
ลายเซ็นโดยการเปลี่ยนสองห่วงสำหรับมันเป็นคนก่อน
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