මෙන්න sdcvvc / Dimitris Andreou ගේ පිළිතුරු මෙන් සංකීර්ණ ගණිතය මත රඳා නොපවතින විසඳුමක්, කැෆේ සහ කර්නල් භීතිකාව මෙන් ආදාන අරාව වෙනස් නොකරයි, සහ ක්රිස් ලර්චර්, ජෙරමිපී සහ අති විශාල ප්රමාණයේ බිට්සෙට් භාවිතා නොකරයි. තවත් බොහෝ අය එසේ කළහ. මූලික වශයෙන්, මම Q2 සඳහා ස්වාලෝර්සන්ගේ / ගිලාඩ් ඩියුච්ගේ අදහස සමඟ ආරම්භ කළෙමි, එය පොදු නඩුව Qk වෙත සාමාන්යකරණය කර ඇල්ගොරිතම ක්රියාත්මක වන බව ඔප්පු කිරීම සඳහා ජාවා හි ක්රියාත්මක කළෙමි.
අදහස
අපි අත්තනෝමතික පරතරය හිතන්න මම අප පමණක් එය අතුරුදන් සංඛ්යා වශයෙන් එක් දී ඇති බව ඔබ දන්නවාද වන. ආදාන මාලාවක් හරහා එක් සමත් පසු, පමණක් සිට අංක දෙස මම , අපි මුදලක් දෙකම ලබා ගත හැක එස් සහ ප්රමාණය Q සිට අතුරුදන් සංඛ්යා මම . අපි සරලව decrementing විසින් මේ මම ගේ අපි එකෙන් අංක මුහුණ එක් එක් කාල දිග මම (ලබා ගැනීම සඳහා Q ) සහ සියලු සංඛ්යා පෙර ගණනය මුදලක් අඩුවීම මම (ලබා ගැනීම සඳහා එක් එක් කාල බව මුහුණ සංඛ්යාව එස් ).
දැන් අපි S සහ Q දෙස බලමු . නම් ප්ර = 1 , එය එසේ නම් බවයි මම අතුරුදහන් අංක එක් අයෙකු පමණක් අඩංගු, සහ මෙම සංඛ්යාව පැහැදිලිව ම එස් . අපි නිමිත්තෙන් මම නිමි (එය මෙම වැඩසටහන "විස්තර කරනා" ලෙස ද හඳුන්වනු ලැබේ) වන අතර එය තවදුරටත් නො සලකා එය අත්හැර ලෙස. අනෙක් අතට, Q> 1 නම් , I හි අඩංගු නැතිවූ සංඛ්යා වල සාමාන්ය A = S / Q ගණනය කළ හැකිය . සියලුම සංඛ්යා එකිනෙකට වෙනස් බැවින් අවම වශයෙන් එවැනි සංඛ්යා වලින් එකක් A ට වඩා තදින් අඩු වන අතර අවම වශයෙන් එකක් A ට වඩා තදින් වැඩි වේ . දැන් අපි බෙදී මම ගැන ඒකුඩා අන්තරයන් දෙකකට, එක් එක් අවම වශයෙන් එක් අතුරුදහන් අංකයක්වත් අඩංගු වේ. එය පූර්ණ සංඛ්යාවක් නම් අපි A ලබා දෙන කාල පරතරයන්ගෙන් කමක් නැත.
අපි ඒ ගැන ඊළඟ මාලාවක් සමත් ගණනය කරන්න එස් සහ Q වන පරාසය එක් එක් සඳහා, ෙවන් ෙවන් වශෙයන් (නමුත් එම සමත් තුළ) සහ සමග බව ලකුණ ප්රාන්තර පසු Q = 1 හා සමග භේදය ප්රාන්තර Q> 1 . නව "නොපැහැදිලි" අන්තරයන් නොමැති තෙක් අපි මෙම ක්රියාවලිය දිගටම කරගෙන යමු, එනම් අපට බෙදීමට කිසිවක් නැත, මන්ද සෑම කාල පරතරයකම හරියටම නැතිවූ අංකයක් අඩංගු වන හෙයිනි (තවද අපි සෑම විටම මෙම අංකය දන්නේ අපි එස් දන්නා නිසා ). හැකි සෑම අංකයක්ම අඩංගු එකම “සම්පූර්ණ පරාසය” පරතරයෙන් අපි ආරම්භ කරමු ( ප්රශ්නයේ [1..N] වැනි ).
කාලය සහ අවකාශ සංකීර්ණතා විශ්ලේෂණය
මුළු සාමාර්ථ ගණන පි අප එම ක්රියාවලිය නතර වන තුරු සිදු කිරීමට අවශ්ය අතුරුදන් අංක ගණන් වඩා වැඩි නො වේ 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) ට වඩා සැලකිය යුතු ලෙස හොඳය .
ජාවා ක්රියාත්මක කිරීම
ඉහත ඇල්ගොරිතම ක්රියාත්මක කරන ජාවා පන්තියක් මෙන්න. එය සැමවිටම a හරිම අතුරුදහන් සංඛ්යා සාධක විය. ඒ හැරුණු විට, නැතිවූ අංක 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
අත්සන, 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);
}
}
අයිඩියොන් මත ඒවා අත්හදා බලන්න