Problem Summary

A positive integer is called \(n\)-pandigital if its decimal digits are exactly \(1,2,\dots,n\), each used once. The task is to determine the largest pandigital number that is also prime.

At first glance the search space seems broad, because one might imagine checking pandigital numbers of every length from 1 through 9. The key simplification is that most lengths are impossible for purely arithmetic reasons, so the brute-force search collapses to a very small candidate set.

Mathematical Approach

For each \(n\), let \(\mathcal{P}_n\) denote the set of all numbers formed by permuting the digits \(1,2,\dots,n\). Then \(|\mathcal{P}_n| = n!\), and the problem is to compute

$$\max\{x \in \mathcal{P}_1 \cup \mathcal{P}_2 \cup \cdots \cup \mathcal{P}_9 : \operatorname{Prime}(x)\}.$$

The implementations do not examine this union blindly. They first eliminate impossible digit lengths, then enumerate the remaining candidates in descending order so that the first prime found is automatically the answer.

Eliminating Most Digit Lengths with a Digit-Sum Invariant

Every \(n\)-pandigital number uses the same digits, so every member of \(\mathcal{P}_n\) has the same digit sum

$$s_n = 1 + 2 + \cdots + n = \frac{n(n+1)}{2}.$$

A decimal integer is divisible by 3 exactly when its digit sum is divisible by 3. Therefore, if \(3 \mid s_n\), then every element of \(\mathcal{P}_n\) is divisible by 3 and cannot be prime. The only exception to that logic would be the number 3 itself, but 3 never appears as an \(n\)-pandigital number under this definition.

Modulo 3, the quantity \(\frac{n(n+1)}{2}\) vanishes precisely when \(n \equiv 0\) or \(2 \pmod{3}\). Among \(1 \le n \le 9\), the only surviving lengths are therefore

$$n \in \{1,4,7\}, \qquad s_1 = 1,\quad s_4 = 10,\quad s_7 = 28.$$

So the original optimization problem reduces immediately to

$$\max\{x \in \mathcal{P}_7 \cup \mathcal{P}_4 \cup \mathcal{P}_1 : \operatorname{Prime}(x)\}.$$

The one-digit case contributes only \(1\), which is not prime, so in practice the real competition is between 7-digit and 4-digit pandigital numbers.

Why Descending Search Is Sufficient

Every 7-digit number is larger than every 4-digit number, and every 4-digit number is larger than the 1-digit case. Consequently, if a 7-digit pandigital prime exists, it is automatically larger than every pandigital prime of smaller length.

This is why the implementations scan \(n\) from 9 down to 1. After the divisibility-by-3 filter, that outer loop effectively becomes

$$7 \to 4 \to 1.$$

Within each surviving length, the search starts from the largest possible arrangement

$$7654321,\qquad 4321,\qquad 1,$$

and then moves strictly downward through all remaining permutations. Because the digits have fixed length and are compared left to right, lexicographic order and numerical order coincide here. Therefore the first prime encountered in the descending enumeration is the largest pandigital prime of that length, and the first prime encountered by the outer loop is the global maximum.

The Permutation Invariant Used by the Implementations

The candidate list is traversed by the standard previous-permutation rule. If the current digit string is \(d_1d_2\cdots d_n\), find the rightmost index \(i\) with \(d_i > d_{i+1}\). Then find the rightmost index \(j > i\) with \(d_j < d_i\), swap \(d_i\) and \(d_j\), and reverse the suffix \(d_{i+1}\cdots d_n\).

This construction gives the largest permutation that is still smaller than the current one. That invariant guarantees a strictly descending walk through all pandigital arrangements, with no duplicates and no omissions.

Worked Example

The digit-sum argument already removes the most tempting large cases. For \(n=8\),

$$s_8 = \frac{8 \cdot 9}{2} = 36 \equiv 0 \pmod{3},$$

so every 8-pandigital number is composite. For \(n=7\),

$$s_7 = \frac{7 \cdot 8}{2} = 28 \not\equiv 0 \pmod{3},$$

so 7-digit pandigital primes remain possible.

The descending traversal can be seen in a smaller family. Starting from \(4321\), the previous-permutation rule produces \(4312\). The first candidate is composite because

$$4321 = 29 \cdot 149,$$

and the second is composite because it is even. The algorithm continues in the same descending way until it reaches a prime. The full solution uses exactly this mechanism, but it begins with the 7-digit family because that is where the maximum must live if any 7-digit pandigital prime exists.

How the Code Works

Filtering Lengths and Building the Starting Candidate

The C++, Python, and Java implementations iterate \(n\) from 9 down to 1, compute \(\frac{n(n+1)}{2}\), and immediately skip any length whose digit sum is divisible by 3. For a surviving length, they build the maximal pandigital arrangement \(n,n-1,\dots,1\), which is the correct start state for a descending search.

Generating Candidates in Descending Order

Each implementation repeatedly converts the current digit arrangement into an integer and checks whether it is prime. If not, it advances to the next smaller permutation. The C++ version relies on a standard library previous-permutation routine; the Python and Java versions implement the same lexicographic step directly. In all three cases, the invariant is the same: the current state is always the largest untested pandigital number below the previous state.

Primality Testing and Early Exit

The primality test is plain trial division. Values below 2 are rejected, even numbers are handled separately, and then only odd divisors are tried. The loop guard is written in the mathematically equivalent form \(p \le m/p\), where \(m\) is the candidate being tested, instead of \(p^2 \le m\).

As soon as one candidate passes the test, the program returns immediately. The implementations also include small checkpoint tests using one known pandigital prime and one known composite example, so the primality routine is validated before the full search begins.

Complexity Analysis

After the digit-sum filter, only the lengths \(7,4,1\) remain. In the worst case, the total number of pandigital candidates examined is therefore

$$7! + 4! + 1! = 5040 + 24 + 1 = 5065.$$

For each candidate \(m\), trial division costs \(O(\sqrt{m})\), and here \(m < 10^7\). A clean overall bound is

$$O\!\left(\sum_{n \in \{1,4,7\}} n!\sqrt{10^n}\right),$$

which is dominated by the 7-digit case and is tiny in practice.

Memory usage is \(O(n)\) with \(n \le 7\), because the implementation stores only the current digit arrangement and a few scalar values. No sieve, recursion tree, or large candidate table is required.

Footnotes and References

  1. Problem page: https://projecteuler.net/problem=41
  2. Pandigital number: Wikipedia - Pandigital number
  3. Divisibility rule: Wikipedia - Divisibility rule
  4. Lexicographic order: Wikipedia - Lexicographic order
  5. Prime number: Wikipedia - Prime number

Problemzusammenfassung

Eine positive ganze Zahl heißt \(n\)-pandigital, wenn ihre Dezimalziffern genau \(1,2,\dots,n\) sind und jede davon genau einmal vorkommt. Gesucht ist die größte pandigitale Zahl, die zugleich eine Primzahl ist.

Auf den ersten Blick wirkt der Suchraum breit, weil man scheinbar pandigitale Zahlen aller Längen von 1 bis 9 untersuchen müsste. Die entscheidende Vereinfachung ist jedoch, dass die meisten Längen aus rein arithmetischen Gründen unmöglich sind. Dadurch schrumpft die Brute-Force-Suche auf eine sehr kleine Kandidatenmenge.

Mathematischer Ansatz

Für jedes \(n\) sei \(\mathcal{P}_n\) die Menge aller Zahlen, die durch Permutieren der Ziffern \(1,2,\dots,n\) entstehen. Dann gilt \(|\mathcal{P}_n| = n!\), und das Problem lautet

$$\max\{x \in \mathcal{P}_1 \cup \mathcal{P}_2 \cup \cdots \cup \mathcal{P}_9 : \operatorname{Prime}(x)\}.$$

Die Implementierungen gehen diese Vereinigung nicht blind an. Zuerst werden unmögliche Stellenzahlen ausgeschlossen, danach werden die verbleibenden Kandidaten in absteigender Reihenfolge erzeugt, sodass die erste gefundene Primzahl automatisch die Lösung ist.

Die Meisten Stellenzahlen per Ziffernsummen-Invariante Ausschließen

Jede \(n\)-pandigitale Zahl benutzt dieselben Ziffern. Deshalb besitzt jedes Element von \(\mathcal{P}_n\) dieselbe Ziffernsumme

$$s_n = 1 + 2 + \cdots + n = \frac{n(n+1)}{2}.$$

Eine Dezimalzahl ist genau dann durch 3 teilbar, wenn ihre Ziffernsumme durch 3 teilbar ist. Gilt also \(3 \mid s_n\), dann ist jedes Element von \(\mathcal{P}_n\) durch 3 teilbar und damit nicht prim. Die einzige Ausnahme wäre die Zahl 3 selbst, aber sie tritt unter dieser Definition nie als \(n\)-pandigitale Zahl auf.

Modulo 3 verschwindet \(\frac{n(n+1)}{2}\) genau dann, wenn \(n \equiv 0\) oder \(2 \pmod{3}\) gilt. Unter \(1 \le n \le 9\) bleiben daher nur

$$n \in \{1,4,7\}, \qquad s_1 = 1,\quad s_4 = 10,\quad s_7 = 28.$$

Damit reduziert sich das ursprüngliche Optimierungsproblem sofort auf

$$\max\{x \in \mathcal{P}_7 \cup \mathcal{P}_4 \cup \mathcal{P}_1 : \operatorname{Prime}(x)\}.$$

Der einstellige Fall liefert nur \(1\), und das ist keine Primzahl. Praktisch konkurrieren also nur die 7-stelligen und 4-stelligen pandigitalen Zahlen.

Warum Eine Absteigende Suche Genügt

Jede 7-stellige Zahl ist größer als jede 4-stellige Zahl, und jede 4-stellige Zahl ist größer als der 1-stellige Fall. Existiert also eine 7-stellige pandigitale Primzahl, dann schlägt sie automatisch jede pandigitale Primzahl kleinerer Länge.

Genau deshalb laufen die Implementierungen mit \(n\) von 9 abwärts bis 1. Nach dem Teilbarkeitsfilter durch 3 wird diese äußere Schleife effektiv zu

$$7 \to 4 \to 1.$$

Innerhalb jeder zulässigen Länge startet die Suche bei der größten möglichen Anordnung

$$7654321,\qquad 4321,\qquad 1,$$

und geht dann strikt abwärts durch alle restlichen Permutationen. Da die Ziffern alle dieselbe Länge haben und von links nach rechts verglichen werden, stimmen lexikographische und numerische Ordnung hier überein. Deshalb ist die erste in dieser Reihenfolge gefundene Primzahl die größte pandigitale Primzahl dieser Länge, und die erste Primzahl der äußeren Schleife ist das globale Maximum.

Die Von Den Implementierungen Benutzte Permutationsinvariante

Die Kandidaten werden mit der Standardregel für die vorherige Permutation durchlaufen. Ist die aktuelle Ziffernfolge \(d_1d_2\cdots d_n\), so sucht man den rechtesten Index \(i\) mit \(d_i > d_{i+1}\). Danach sucht man den rechtesten Index \(j > i\) mit \(d_j < d_i\), vertauscht \(d_i\) und \(d_j\) und kehrt das Suffix \(d_{i+1}\cdots d_n\) um.

So entsteht die größte Permutation, die noch kleiner als die aktuelle ist. Diese Invariante garantiert einen strikt fallenden Durchlauf durch alle pandigitalen Anordnungen, ohne Wiederholungen und ohne Lücken.

Durchgerechnetes Beispiel

Schon das Ziffernsummenargument entfernt die attraktivsten großen Fälle. Für \(n=8\) gilt

$$s_8 = \frac{8 \cdot 9}{2} = 36 \equiv 0 \pmod{3},$$

also ist jede 8-pandigitale Zahl zusammengesetzt. Für \(n=7\) gilt dagegen

$$s_7 = \frac{7 \cdot 8}{2} = 28 \not\equiv 0 \pmod{3},$$

weshalb 7-stellige pandigitale Primzahlen möglich bleiben.

Der absteigende Durchlauf ist in einer kleineren Familie leicht zu sehen. Ausgehend von \(4321\) liefert die Regel für die vorherige Permutation als Nächstes \(4312\). Der erste Kandidat ist zusammengesetzt, denn

$$4321 = 29 \cdot 149,$$

und der zweite ist zusammengesetzt, weil er gerade ist. Der Algorithmus setzt diese absteigende Suche fort, bis eine Primzahl erreicht wird. Die vollständige Lösung benutzt exakt denselben Mechanismus, beginnt aber mit der 7-stelligen Familie, weil dort das Maximum liegen muss, falls überhaupt eine 7-stellige pandigitale Primzahl existiert.

Wie Der Code Arbeitet

Stellenzahlen Filtern und Den Startkandidaten Aufbauen

Die Implementierungen in C++, Python und Java iterieren \(n\) von 9 abwärts bis 1, berechnen \(\frac{n(n+1)}{2}\) und überspringen sofort jede Länge, deren Ziffernsumme durch 3 teilbar ist. Für eine überlebende Länge bauen sie die größte pandigitale Anordnung \(n,n-1,\dots,1\) auf. Das ist der richtige Startzustand für eine absteigende Suche.

Kandidaten In Absteigender Reihenfolge Erzeugen

Jede Implementierung wandelt die aktuelle Ziffernanordnung wiederholt in eine ganze Zahl um und prüft, ob sie prim ist. Falls nicht, wird zur nächstkleineren Permutation übergegangen. Die C++-Version benutzt dafür eine Standardbibliotheksroutine, die Python- und Java-Versionen implementieren denselben lexikographischen Schritt explizit. Trotz der syntaktischen Unterschiede gilt überall dieselbe Invariante: Der aktuelle Zustand ist stets die größte noch ungetestete pandigitale Zahl unterhalb des vorherigen Zustands.

Primzahltest und Früher Abbruch

Der Primzahltest ist einfache Probedivision. Werte kleiner als 2 werden verworfen, gerade Zahlen separat behandelt, und danach werden nur ungerade Teiler ausprobiert. Die Schleifenbedingung steht in der mathematisch äquivalenten Form \(p \le m/p\), wobei \(m\) der gerade geprüfte Kandidat ist, statt als \(p^2 \le m\).

Sobald ein Kandidat den Test besteht, liefert das Programm sofort dieses Ergebnis zurück. Zusätzlich enthalten die Implementierungen kleine Prüffälle mit einer bekannten pandigitalen Primzahl und einem bekannten zusammengesetzten Beispiel, sodass die Primzahlroutine vor der eigentlichen Suche validiert wird.

Komplexitätsanalyse

Nach dem Ziffernsummenfilter bleiben nur die Längen \(7,4,1\) übrig. Im schlimmsten Fall werden also insgesamt

$$7! + 4! + 1! = 5040 + 24 + 1 = 5065$$

pandigitale Kandidaten betrachtet.

Für jeden Kandidaten \(m\) kostet die Probedivision \(O(\sqrt{m})\), und hier gilt stets \(m < 10^7\). Eine saubere obere Schranke für das Gesamtverfahren ist daher

$$O\!\left(\sum_{n \in \{1,4,7\}} n!\sqrt{10^n}\right),$$

wobei der 7-stellige Fall klar dominiert und die Laufzeit in der Praxis sehr klein ist.

Der Speicherverbrauch beträgt \(O(n)\) mit \(n \le 7\), denn gespeichert werden nur die aktuelle Ziffernfolge und einige skalare Hilfswerte. Weder ein Sieb noch ein Rekursionsbaum noch eine große Kandidatentabelle sind erforderlich.

Fußnoten und Quellen

  1. Problemseite: https://projecteuler.net/problem=41
  2. Pandigitale Zahl: Wikipedia - Pandigital number
  3. Teilbarkeitsregel: Wikipedia - Divisibility rule
  4. Lexikographische Ordnung: Wikipedia - Lexicographic order
  5. Primzahl: Wikipedia - Prime number

Problem Özeti

Pozitif bir tam sayıya, ondalık basamakları tam olarak \(1,2,\dots,n\) olup bu basamakların her biri yalnızca bir kez kullanılıyorsa \(n\)-pandijital denir. Amaç, aynı zamanda asal olan en büyük pandijital sayıyı bulmaktır.

İlk bakışta arama uzayı geniş görünür; çünkü 1 basamaktan 9 basamağa kadar bütün pandijital uzunlukların incelenmesi gerektiği sanılabilir. Asıl sadeleştirici fikir, bu uzunlukların çoğunun basit aritmetik gerekçelerle daha baştan imkansız olmasıdır. Böylece kaba kuvvet araması çok küçük bir aday kümesine iner.

Matematiksel Yaklaşım

Her \(n\) için, \(1,2,\dots,n\) rakamlarının bütün permütasyonlarından elde edilen sayıların kümesini \(\mathcal{P}_n\) ile gösterelim. O zaman \(|\mathcal{P}_n| = n!\) olur ve problem şu maksimumu bulmaya dönüşür:

$$\max\{x \in \mathcal{P}_1 \cup \mathcal{P}_2 \cup \cdots \cup \mathcal{P}_9 : \operatorname{Prime}(x)\}.$$

Uygulamalar bu birleşimi körlemesine taramaz. Önce imkansız basamak uzunluklarını eler, sonra geriye kalan adayları büyükten küçüğe sıralı biçimde gezer. Böylece bulunan ilk asal doğrudan cevaptır.

Rakam Toplamı Değişmeziyle Çoğu Uzunluğu Eleme

Her \(n\)-pandijital sayı aynı rakamları kullandığı için, \(\mathcal{P}_n\) içindeki her elemanın rakam toplamı aynıdır:

$$s_n = 1 + 2 + \cdots + n = \frac{n(n+1)}{2}.$$

Bir ondalık sayı ancak ve ancak rakam toplamı 3'e bölünüyorsa 3'e bölünür. Dolayısıyla \(3 \mid s_n\) ise, \(\mathcal{P}_n\) içindeki her sayı 3'e bölünür ve asal olamaz. Bunun tek istisnası sayı 3 olurdu; fakat bu tanım altında 3 hiçbir zaman uygun bir pandijital sayı olarak ortaya çıkmaz.

\(\frac{n(n+1)}{2}\) ifadesi mod 3 bakımından tam olarak \(n \equiv 0\) veya \(2 \pmod{3}\) iken sıfır olur. \(1 \le n \le 9\) aralığında geriye yalnızca şu uzunluklar kalır:

$$n \in \{1,4,7\}, \qquad s_1 = 1,\quad s_4 = 10,\quad s_7 = 28.$$

Böylece özgün problem hemen şu hale indirgenir:

$$\max\{x \in \mathcal{P}_7 \cup \mathcal{P}_4 \cup \mathcal{P}_1 : \operatorname{Prime}(x)\}.$$

Tek basamaklı durum yalnızca \(1\) sayısını verir ve bu sayı asal değildir. Bu yüzden pratikte gerçek yarış 7 basamaklı ve 4 basamaklı pandijital sayılar arasındadır.

Neden Azalan Arama Yeterlidir

Her 7 basamaklı sayı, her 4 basamaklı sayıdan büyüktür; her 4 basamaklı sayı da 1 basamaklı durumdan büyüktür. Bu nedenle 7 basamaklı bir pandijital asal bulunduğu anda, daha kısa uzunluklarda elde edilebilecek hiçbir aday onu geçemez.

Uygulamaların \(n\) değerini 9'dan 1'e doğru gezmesinin nedeni budur. 3'e bölünebilme filtresinden sonra dış döngü fiilen

$$7 \to 4 \to 1$$

biçimine indirgenir. Her uygun uzunlukta arama, mümkün olan en büyük dizilimden başlar:

$$7654321,\qquad 4321,\qquad 1.$$

Sonra geri kalan bütün permütasyonlar sıkı biçimde azalan sırayla gezilir. Basamak sayısı sabit olduğu ve karşılaştırma soldan sağa yapıldığı için, burada leksikografik sıra ile sayısal sıra aynıdır. Dolayısıyla bu azalan dizide görülen ilk asal, o uzunluktaki en büyük pandijital asaldır; dış döngüde görülen ilk asal da genel maksimumdur.

Uygulamaların Kullandığı Permütasyon Değişmezi

Adaylar standart previous-permutation kuralıyla dolaşılır. Geçerli basamak dizisi \(d_1d_2\cdots d_n\) olsun. Önce \(d_i > d_{i+1}\) koşulunu sağlayan en sağdaki \(i\) bulunur. Sonra \(j > i\) ve \(d_j < d_i\) olacak en sağdaki \(j\) seçilir, \(d_i\) ile \(d_j\) yer değiştirilir ve son ek \(d_{i+1}\cdots d_n\) ters çevrilir.

Bu işlem, mevcut diziden küçük olan en büyük sonraki permütasyonu üretir. Böylece aramanın her adımında sayılar kesin olarak küçülür; hiçbir permütasyon iki kez görülmez ve hiçbir aday atlanmaz.

Çalışılmış Örnek

Rakam toplamı argümanı en cazip büyük durumları daha baştan yok eder. \(n=8\) için

$$s_8 = \frac{8 \cdot 9}{2} = 36 \equiv 0 \pmod{3},$$

dolayısıyla bütün 8-pandijital sayılar bileşiktir. Buna karşılık \(n=7\) için

$$s_7 = \frac{7 \cdot 8}{2} = 28 \not\equiv 0 \pmod{3},$$

olduğundan 7 basamaklı pandijital asallar mümkün kalır.

Azalan taramayı daha küçük bir ailede görmek kolaydır. \(4321\) sayısından başlanırsa previous-permutation adımı \(4312\)'yi verir. İlk aday bileşiktir, çünkü

$$4321 = 29 \cdot 149,$$

ikinci aday da çifttir, dolayısıyla bileşiktir. Algoritma aynı şekilde aşağı doğru ilerleyip bir asal sayıya ulaşır. Tam çözüm tam olarak bu mekanizmayı kullanır; yalnızca, maksimumun orada yatması gerektiği için 7 basamaklı aileden başlar.

Kod Nasıl Çalışır

Uzunlukları Süzmek ve Başlangıç Adayını Kurmak

C++, Python ve Java uygulamaları \(n\) değerini 9'dan 1'e doğru iterasyonla gezer, \(\frac{n(n+1)}{2}\) değerini hesaplar ve rakam toplamı 3'e bölünen her uzunluğu hemen atlar. Uygun bir uzunluk bulunduğunda başlangıç durumu olarak \(n,n-1,\dots,1\) biçimindeki en büyük pandijital dizilim kurulmuş olur.

Adayları Büyükten Küçüğe Üretmek

Her uygulama geçerli basamak dizisini bir tam sayıya çevirir ve asallığını sınar. Sonuç negatifse bir sonraki küçük permütasyona geçilir. C++ sürümü standart kütüphane desteğini kullanır; Python ve Java sürümleri aynı leksikografik adımı açıkça uygular. Sözdizimi farklı olsa da temel değişmez aynıdır: geçerli durum, bir önceki durumdan küçük olan en büyük henüz denenmemiş pandijital sayıdır.

Asallık Testi ve Erken Dönüş

Asallık testi düz deneme bölmesidir. 2'den küçük değerler reddedilir, çift sayılar ayrı ele alınır, ardından yalnızca tek bölenler denenir. Döngü koşulu \(m\) adayı için \(p \le m/p\) biçiminde yazılır; bu, matematiksel olarak \(p^2 \le m\) ile eşdeğerdir.

Bir aday testi geçer geçmez program anında sonucu döndürür. Uygulamalarda ayrıca, biri bilinen bir pandijital asal, diğeri bilinen bileşik bir örnek olan küçük doğrulama kontrolleri de bulunur. Böylece asallık yordamı tam aramadan önce sınanmış olur.

Karmaşıklık Analizi

Rakam toplamı filtresinden sonra yalnızca \(7,4,1\) uzunlukları kalır. En kötü durumda incelenecek toplam pandijital aday sayısı

$$7! + 4! + 1! = 5040 + 24 + 1 = 5065$$

olur.

Her aday \(m\) için deneme bölmesi \(O(\sqrt{m})\) zaman alır ve burada her zaman \(m < 10^7\) geçerlidir. Bu nedenle toplam çalışma için temiz bir üst sınır

$$O\!\left(\sum_{n \in \{1,4,7\}} n!\sqrt{10^n}\right)$$

şeklindedir; baskın terim açık biçimde 7 basamaklı durumdur ve pratikte süre çok küçüktür.

Bellek kullanımı \(n \le 7\) için \(O(n)\) düzeyindedir. Çünkü yalnızca geçerli basamak dizisi ve birkaç skaler değişken tutulur. Ne bir elek ne bir özyineleme ağacı ne de büyük bir aday tablosu gerekir.

Dipnotlar ve Kaynaklar

  1. Problem sayfası: https://projecteuler.net/problem=41
  2. Pandijital sayı: Wikipedia - Pandigital number
  3. Bölünebilme kuralı: Wikipedia - Divisibility rule
  4. Leksikografik sıra: Wikipedia - Lexicographic order
  5. Asal sayı: Wikipedia - Prime number

Resumen Del Problema

Un entero positivo se llama \(n\)-pandigital si sus cifras decimales son exactamente \(1,2,\dots,n\), cada una usada una sola vez. La tarea consiste en determinar el mayor número pandigital que además sea primo.

A primera vista el espacio de búsqueda parece amplio, porque uno podría pensar que hay que revisar números pandigitales de todas las longitudes entre 1 y 9. La simplificación decisiva es que la mayoría de esas longitudes quedan descartadas por razones aritméticas elementales, de modo que la búsqueda por fuerza bruta se reduce a un conjunto muy pequeño de candidatos.

Enfoque Matemático

Para cada \(n\), sea \(\mathcal{P}_n\) el conjunto de todos los números que se obtienen al permutar las cifras \(1,2,\dots,n\). Entonces \(|\mathcal{P}_n| = n!\), y el problema pasa a ser

$$\max\{x \in \mathcal{P}_1 \cup \mathcal{P}_2 \cup \cdots \cup \mathcal{P}_9 : \operatorname{Prime}(x)\}.$$

Las implementaciones no recorren esta unión a ciegas. Primero eliminan las longitudes imposibles y luego enumeran los candidatos restantes en orden descendente, de modo que el primer primo encontrado ya es la respuesta correcta.

Eliminar La Mayoría De Las Longitudes Con Un Invariante De Suma De Cifras

Todo número \(n\)-pandigital usa las mismas cifras, así que todos los elementos de \(\mathcal{P}_n\) tienen la misma suma de cifras:

$$s_n = 1 + 2 + \cdots + n = \frac{n(n+1)}{2}.$$

Un entero decimal es divisible por 3 si y solo si su suma de cifras es divisible por 3. Por tanto, si \(3 \mid s_n\), entonces todos los elementos de \(\mathcal{P}_n\) son divisibles por 3 y no pueden ser primos. La única excepción posible sería el propio número 3, pero ese valor no aparece como pandigital bajo esta definición.

Módulo 3, la cantidad \(\frac{n(n+1)}{2}\) se anula exactamente cuando \(n \equiv 0\) o \(2 \pmod{3}\). Entre \(1 \le n \le 9\), las únicas longitudes que sobreviven son

$$n \in \{1,4,7\}, \qquad s_1 = 1,\quad s_4 = 10,\quad s_7 = 28.$$

Así, el problema original se reduce de inmediato a

$$\max\{x \in \mathcal{P}_7 \cup \mathcal{P}_4 \cup \mathcal{P}_1 : \operatorname{Prime}(x)\}.$$

El caso de una sola cifra aporta únicamente \(1\), que no es primo, así que en la práctica la competencia real queda entre los pandigitales de 7 cifras y los de 4 cifras.

Por Qué Basta Una Búsqueda Descendente

Cualquier número de 7 cifras es mayor que cualquier número de 4 cifras, y cualquier número de 4 cifras es mayor que el caso de una sola cifra. En consecuencia, si existe un primo pandigital de 7 cifras, automáticamente supera a todos los primos pandigitales de menor longitud.

Por eso las implementaciones recorren \(n\) desde 9 hasta 1. Después del filtro de divisibilidad por 3, ese bucle exterior se convierte en la práctica en

$$7 \to 4 \to 1.$$

Dentro de cada longitud viable, la búsqueda comienza en la disposición más grande posible:

$$7654321,\qquad 4321,\qquad 1.$$

A partir de ahí se desciende estrictamente por todas las permutaciones restantes. Como la longitud es fija y la comparación se hace de izquierda a derecha, el orden lexicográfico coincide con el orden numérico. Por tanto, el primer primo que aparece en esa enumeración descendente es el mayor primo pandigital de esa longitud, y el primer primo encontrado por el bucle exterior es el máximo global.

El Invariante De Permutación Que Usan Las Implementaciones

La lista de candidatos se recorre mediante la regla estándar de la permutación anterior. Si la cadena actual de cifras es \(d_1d_2\cdots d_n\), se busca el índice más a la derecha \(i\) tal que \(d_i > d_{i+1}\). Luego se busca el índice más a la derecha \(j > i\) con \(d_j < d_i\), se intercambian \(d_i\) y \(d_j\), y se invierte el sufijo \(d_{i+1}\cdots d_n\).

Ese procedimiento produce la permutación más grande que sigue siendo menor que la actual. El invariante garantiza así un recorrido estrictamente decreciente por todas las disposiciones pandigitales, sin repeticiones ni omisiones.

Ejemplo Trabajado

El argumento de la suma de cifras ya descarta los casos grandes más tentadores. Para \(n=8\),

$$s_8 = \frac{8 \cdot 9}{2} = 36 \equiv 0 \pmod{3},$$

de modo que todo número pandigital de 8 cifras es compuesto. En cambio, para \(n=7\),

$$s_7 = \frac{7 \cdot 8}{2} = 28 \not\equiv 0 \pmod{3},$$

así que todavía es posible tener primos pandigitales de 7 cifras.

La exploración descendente se ve bien en una familia más pequeña. Empezando por \(4321\), la regla de permutación anterior produce \(4312\). El primer candidato es compuesto porque

$$4321 = 29 \cdot 149,$$

y el segundo es compuesto porque es par. El algoritmo sigue descendiendo del mismo modo hasta encontrar un primo. La solución completa usa exactamente este mecanismo, pero comienza por la familia de 7 cifras porque allí debe estar el máximo si existe algún primo pandigital de 7 cifras.

Cómo Funciona El Código

Filtrar Longitudes y Construir El Candidato Inicial

Las implementaciones en C++, Python y Java iteran \(n\) desde 9 hasta 1, calculan \(\frac{n(n+1)}{2}\) y descartan inmediatamente cualquier longitud cuya suma de cifras sea divisible por 3. Para una longitud que sobrevive, construyen la disposición pandigital máxima \(n,n-1,\dots,1\), que es el estado inicial correcto para una búsqueda descendente.

Generar Candidatos En Orden Descendente

Cada implementación convierte repetidamente la disposición actual de cifras en un entero y comprueba si es primo. Si no lo es, avanza a la siguiente permutación menor. La versión en C++ usa una rutina estándar de la biblioteca; las versiones en Python y Java implementan de forma explícita el mismo paso lexicográfico. Aunque la sintaxis cambia, el invariante es idéntico en los tres casos: el estado actual siempre es el mayor número pandigital aún no probado que está por debajo del estado anterior.

Prueba De Primalidad y Salida Temprana

La prueba de primalidad es división por ensayo. Los valores menores que 2 se rechazan, los números pares se tratan aparte y luego solo se prueban divisores impares. La condición del bucle se escribe como \(p \le m/p\), donde \(m\) es el candidato examinado, lo cual es matemáticamente equivalente a \(p^2 \le m\).

En cuanto un candidato supera la prueba, el programa devuelve el resultado de inmediato. Además, las implementaciones incluyen pequeñas comprobaciones previas usando un ejemplo pandigital primo conocido y un ejemplo compuesto conocido, con lo que la rutina de primalidad queda validada antes de la búsqueda completa.

Análisis De Complejidad

Después del filtro por suma de cifras, solo permanecen las longitudes \(7,4,1\). En el peor caso, el número total de candidatos pandigitales examinados es entonces

$$7! + 4! + 1! = 5040 + 24 + 1 = 5065.$$

Para cada candidato \(m\), la división por ensayo cuesta \(O(\sqrt{m})\), y aquí siempre se cumple \(m < 10^7\). Una cota global limpia para todo el método es

$$O\!\left(\sum_{n \in \{1,4,7\}} n!\sqrt{10^n}\right),$$

dominada claramente por el caso de 7 cifras y muy pequeña en la práctica.

El uso de memoria es \(O(n)\) con \(n \le 7\), porque la implementación solo guarda la disposición actual de cifras y unos pocos valores escalares. No hace falta una criba, ni un árbol recursivo, ni una tabla grande de candidatos.

Notas y Referencias

  1. Página del problema: https://projecteuler.net/problem=41
  2. Número pandigital: Wikipedia - Pandigital number
  3. Regla de divisibilidad: Wikipedia - Divisibility rule
  4. Orden lexicográfico: Wikipedia - Lexicographic order
  5. Número primo: Wikipedia - Prime number

问题概述

如果一个正整数的十进制数字恰好是 \(1,2,\dots,n\),并且每个数字只出现一次,那么它就称为 \(n\) 位泛数字。题目要求找出其中最大的质数。

乍看之下,搜索空间似乎很大,因为好像必须检查从 1 位到 9 位的所有泛数字。真正的突破在于,大多数位数可以被一个非常简单的算术不变量直接排除,因此暴力搜索最后只剩下极少量候选。

数学思路

对每个 \(n\),记 \(\mathcal{P}_n\) 为把数字 \(1,2,\dots,n\) 全部排列后得到的整数集合。那么 \(|\mathcal{P}_n| = n!\),原问题就是求

$$\max\{x \in \mathcal{P}_1 \cup \mathcal{P}_2 \cup \cdots \cup \mathcal{P}_9 : \operatorname{Prime}(x)\}.$$

实现并不是盲目地遍历这个并集。它先删去不可能产生质数的位数,再把剩余候选按从大到小的顺序依次检查。这样一来,第一次遇到的质数就一定是最终答案。

利用数位和不变量排除大多数位数

所有 \(n\) 位泛数字都使用同一组数字,因此 \(\mathcal{P}_n\) 中每个元素的数位和都相同:

$$s_n = 1 + 2 + \cdots + n = \frac{n(n+1)}{2}.$$

十进制整数能否被 3 整除,只取决于数位和能否被 3 整除。因此如果 \(3 \mid s_n\),那么 \(\mathcal{P}_n\) 中所有数都能被 3 整除,自然不可能是质数。理论上的唯一例外会是数字 3 本身,但按照本题的定义,它并不会作为某个 \(\mathcal{P}_n\) 的元素出现。

从模 3 的角度看,\(\frac{n(n+1)}{2}\) 恰好在 \(n \equiv 0\) 或 \(2 \pmod{3}\) 时被 3 整除。因此在 \(1 \le n \le 9\) 的范围内,能够保留下来的位数只有

$$n \in \{1,4,7\}, \qquad s_1 = 1,\quad s_4 = 10,\quad s_7 = 28.$$

于是原问题立刻缩小为

$$\max\{x \in \mathcal{P}_7 \cup \mathcal{P}_4 \cup \mathcal{P}_1 : \operatorname{Prime}(x)\}.$$

其中 1 位情形只给出数字 \(1\),而 \(1\) 不是质数,所以真正有竞争力的只剩 7 位和 4 位泛数字。

为什么按降序搜索就足够

任何 7 位数都大于任何 4 位数,而任何 4 位数都大于 1 位情形。因此,只要找到了一个 7 位泛数字质数,它就必定超过所有更短长度的泛数字质数。

这就是实现从 \(n=9\) 一直向下扫描到 \(n=1\) 的原因。经过被 3 整除的位数过滤后,这个外层循环实际上只会访问

$$7 \to 4 \to 1.$$

在每个保留下来的位数中,搜索都从最大的排列开始:

$$7654321,\qquad 4321,\qquad 1.$$

之后再严格按从大到小的顺序遍历剩余排列。由于位数固定,而且比较时是从左到右逐位比较,这里的字典序与数值大小完全一致。因此,在某个长度内第一次遇到的质数,就是该长度下最大的泛数字质数;而外层循环第一次遇到的质数,就是全局最大值。

实现所依赖的排列不变量

候选的推进规则是标准的 previous permutation。设当前数字串为 \(d_1d_2\cdots d_n\)。先找到最靠右的下标 \(i\),满足 \(d_i > d_{i+1}\)。然后再找到最靠右的下标 \(j > i\),满足 \(d_j < d_i\)。交换 \(d_i\) 与 \(d_j\) 后,再把后缀 \(d_{i+1}\cdots d_n\) 反转。

这样得到的恰好是严格小于当前排列的最大排列。这个不变量保证了搜索过程会按严格降序遍历所有泛数字排列,不会重复,也不会漏掉任何一种情况。

具体示例

数位和论证已经能直接淘汰掉最诱人的大规模情形。对 \(n=8\),有

$$s_8 = \frac{8 \cdot 9}{2} = 36 \equiv 0 \pmod{3},$$

因此所有 8 位泛数字都是合数。对 \(n=7\),则有

$$s_7 = \frac{7 \cdot 8}{2} = 28 \not\equiv 0 \pmod{3},$$

所以 7 位泛数字质数仍然有可能存在。

在更小的 4 位家族里,降序遍历也很直观。起点是 \(4321\),应用一次 previous-permutation 后得到 \(4312\)。第一个候选是合数,因为

$$4321 = 29 \cdot 149,$$

第二个候选也是合数,因为它是偶数。算法会继续沿着同样的降序路径向下走,直到遇到一个质数。完整求解过程使用的就是同一机制,只不过它先从 7 位家族开始,因为如果 7 位泛数字质数存在,那么最大值一定在那里。

代码如何工作

先过滤位数并构造起始候选

C++、Python 和 Java 实现都会让 \(n\) 从 9 递减到 1,计算 \(\frac{n(n+1)}{2}\),并立刻跳过那些数位和能被 3 整除的位数。对于保留下来的位数,程序会构造最大泛数字排列 \(n,n-1,\dots,1\),这正是降序搜索的正确起点。

按降序生成候选

每个实现都会反复把当前数字排列转成整数并做质数判定。如果不是质数,就推进到下一个更小的排列。C++ 版本使用标准库中的 previous-permutation 机制;Python 和 Java 版本则显式实现了同样的字典序步骤。虽然写法不同,但三者维护的是同一个核心不变量:当前状态始终是“小于上一个状态的最大未测试泛数字”。

质数判定与提前返回

质数检测采用最直接的试除法。小于 2 的值会被排除,偶数单独处理,然后只尝试奇数因子。循环条件写成 \(p \le m/p\),其中 \(m\) 是当前候选,这与 \(p^2 \le m\) 在数学上完全等价。

一旦某个候选通过检测,程序立刻返回结果。实现里还带有两个很小的校验点,一个使用已知的泛数字质数,另一个使用已知的合数,因此在正式搜索开始前,质数判定部分已经先被验证过一次。

复杂度分析

经过数位和过滤后,只剩下 \(7,4,1\) 这三种位数。于是最坏情况下需要检查的泛数字候选总数为

$$7! + 4! + 1! = 5040 + 24 + 1 = 5065.$$

对每个候选 \(m\),试除法需要 \(O(\sqrt{m})\) 时间,而且这里始终有 \(m < 10^7\)。因此整个方法可以写出一个清晰的上界:

$$O\!\left(\sum_{n \in \{1,4,7\}} n!\sqrt{10^n}\right),$$

其中显然由 7 位情形主导,而在实际运行中这个规模非常小。

空间复杂度是 \(O(n)\),并且 \(n \le 7\),因为实现只保存当前的数字排列和少量标量变量。不需要筛法,不需要递归搜索树,也不需要大型候选表。

注释与参考资料

  1. 题目页面: https://projecteuler.net/problem=41
  2. 泛数字: Wikipedia - Pandigital number
  3. 整除规则: Wikipedia - Divisibility rule
  4. 字典序: Wikipedia - Lexicographic order
  5. 质数: Wikipedia - Prime number

Краткое Содержание Задачи

Положительное целое число называется \(n\)-панцифровым, если его десятичные цифры суть ровно \(1,2,\dots,n\), и каждая используется ровно один раз. Требуется найти наибольшее панцифровое число, которое одновременно является простым.

Сначала кажется, что пространство поиска велико, потому что можно подумать, будто нужно проверять панцифровые числа всех длин от 1 до 9. Главная идея состоит в том, что большинство длин отбрасываются элементарным арифметическим рассуждением, поэтому полный перебор в итоге сводится к очень небольшому набору кандидатов.

Математический Подход

Для каждого \(n\) обозначим через \(\mathcal{P}_n\) множество всех чисел, полученных перестановкой цифр \(1,2,\dots,n\). Тогда \(|\mathcal{P}_n| = n!\), и задача принимает вид

$$\max\{x \in \mathcal{P}_1 \cup \mathcal{P}_2 \cup \cdots \cup \mathcal{P}_9 : \operatorname{Prime}(x)\}.$$

Реализации не перебирают это объединение вслепую. Сначала исключаются невозможные длины, а затем оставшиеся кандидаты просматриваются в порядке убывания, так что первое найденное простое число автоматически является ответом.

Отсечение Большинства Длин По Инварианту Суммы Цифр

Любое \(n\)-панцифровое число использует один и тот же набор цифр, поэтому все элементы \(\mathcal{P}_n\) имеют одинаковую сумму цифр:

$$s_n = 1 + 2 + \cdots + n = \frac{n(n+1)}{2}.$$

Десятичное число делится на 3 тогда и только тогда, когда на 3 делится сумма его цифр. Следовательно, если \(3 \mid s_n\), то каждый элемент \(\mathcal{P}_n\) делится на 3 и не может быть простым. Единственным исключением могла бы быть сама тройка, но при таком определении она не возникает как допустимое панцифровое число.

По модулю 3 величина \(\frac{n(n+1)}{2}\) обращается в нуль ровно при \(n \equiv 0\) или \(2 \pmod{3}\). Поэтому среди \(1 \le n \le 9\) выживают только длины

$$n \in \{1,4,7\}, \qquad s_1 = 1,\quad s_4 = 10,\quad s_7 = 28.$$

Значит, исходная оптимизационная задача сразу сокращается до

$$\max\{x \in \mathcal{P}_7 \cup \mathcal{P}_4 \cup \mathcal{P}_1 : \operatorname{Prime}(x)\}.$$

Одноразрядный случай дает только число \(1\), которое не является простым, так что на практике состязаются лишь 7-значные и 4-значные панцифровые числа.

Почему Достаточно Идти По Убыванию

Любое 7-значное число больше любого 4-значного числа, а любое 4-значное число больше одноразрядного случая. Следовательно, если существует 7-значное панцифровое простое число, то оно автоматически превосходит все панцифровые простые числа меньшей длины.

Именно поэтому реализации перебирают \(n\) от 9 вниз до 1. После фильтра по делимости на 3 внешний цикл фактически превращается в

$$7 \to 4 \to 1.$$

Внутри каждой допустимой длины поиск начинается с максимально возможной записи

$$7654321,\qquad 4321,\qquad 1,$$

а затем строго спускается через все остальные перестановки. Поскольку длина фиксирована, а сравнение идет слева направо, лексикографический порядок здесь совпадает с числовым. Поэтому первое простое число, встреченное в таком перечислении, является наибольшим панцифровым простым числом данной длины, а первое простое число внешнего цикла является глобальным максимумом.

Инвариант Перестановок, Используемый В Реализациях

Список кандидатов обходится стандартным шагом previous permutation. Пусть текущая строка цифр равна \(d_1d_2\cdots d_n\). Сначала находится самый правый индекс \(i\), для которого выполняется \(d_i > d_{i+1}\). Затем выбирается самый правый индекс \(j > i\), такой что \(d_j < d_i\), после чего цифры \(d_i\) и \(d_j\) меняются местами, а суффикс \(d_{i+1}\cdots d_n\) разворачивается.

Эта процедура дает наибольшую перестановку, которая все еще меньше текущей. Тем самым обеспечивается строго убывающий проход по всем панцифровым расположениям без повторов и без пропусков.

Разобранный Пример

Аргумент с суммой цифр уже сразу убирает самые соблазнительные большие случаи. Для \(n=8\) имеем

$$s_8 = \frac{8 \cdot 9}{2} = 36 \equiv 0 \pmod{3},$$

поэтому любое 8-значное панцифровое число составное. Для \(n=7\) получаем

$$s_7 = \frac{7 \cdot 8}{2} = 28 \not\equiv 0 \pmod{3},$$

так что 7-значные панцифровые простые числа по-прежнему возможны.

На меньшем семействе убывающий обход виден особенно ясно. Если начать с \(4321\), то шаг previous permutation дает \(4312\). Первый кандидат составной, потому что

$$4321 = 29 \cdot 149,$$

а второй составной, потому что он четный. Алгоритм продолжает двигаться вниз тем же способом, пока не встретит простое число. Полное решение использует именно этот механизм, но стартует с 7-значного семейства, потому что максимум должен находиться там, если вообще существует 7-значное панцифровое простое число.

Как Работает Код

Фильтрация Длин и Построение Начального Кандидата

Реализации на C++, Python и Java перебирают \(n\) от 9 до 1, вычисляют \(\frac{n(n+1)}{2}\) и сразу пропускают любую длину, сумма цифр которой делится на 3. Для оставшейся длины строится максимальная панцифровая запись \(n,n-1,\dots,1\), что и является правильным стартовым состоянием для убывающего поиска.

Порождение Кандидатов В Порядке Убывания

Каждая реализация многократно преобразует текущее расположение цифр в целое число и проверяет, является ли оно простым. Если нет, выполняется переход к следующей меньшей перестановке. Версия на C++ использует стандартную библиотечную процедуру, а версии на Python и Java явно реализуют тот же лексикографический шаг. Несмотря на синтаксические различия, инвариант один и тот же: текущее состояние всегда является наибольшим еще не проверенным панцифровым числом, меньшим предыдущего.

Проверка Простоты и Ранний Выход

Проверка простоты основана на обычном пробном делении. Значения меньше 2 сразу отвергаются, четные числа обрабатываются отдельно, а затем перебираются только нечетные делители. Условие цикла записано как \(p \le m/p\), где \(m\) означает текущий кандидат; математически это эквивалентно неравенству \(p^2 \le m\).

Как только некоторый кандидат проходит проверку, программа немедленно возвращает его. Кроме того, в реализациях есть небольшие контрольные проверки с одним известным панцифровым простым и одним известным составным примером, так что процедура проверки простоты подтверждается еще до полного перебора.

Анализ Сложности

После фильтра по сумме цифр остаются только длины \(7,4,1\). Следовательно, в худшем случае число рассматриваемых панцифровых кандидатов равно

$$7! + 4! + 1! = 5040 + 24 + 1 = 5065.$$

Для каждого кандидата \(m\) пробное деление требует \(O(\sqrt{m})\) времени, причем здесь всегда \(m < 10^7\). Поэтому удобная общая верхняя оценка имеет вид

$$O\!\left(\sum_{n \in \{1,4,7\}} n!\sqrt{10^n}\right),$$

и она явно определяется 7-значным случаем, который на практике остается очень малым.

Потребление памяти равно \(O(n)\) при \(n \le 7\), потому что хранятся только текущее расположение цифр и несколько скалярных величин. Ни решето, ни рекурсивное дерево, ни большая таблица кандидатов здесь не нужны.

Примечания И Ссылки

  1. Страница задачи: https://projecteuler.net/problem=41
  2. Панцифровое число: Wikipedia - Pandigital number
  3. Правило делимости: Wikipedia - Divisibility rule
  4. Лексикографический порядок: Wikipedia - Lexicographic order
  5. Простое число: Wikipedia - Prime number

ملخص المشكلة

يسمى العدد الصحيح الموجب شاملا من الرتبة \(n\) إذا كانت أرقامه العشرية هي بالضبط \(1,2,\dots,n\)، وكل رقم يظهر مرة واحدة فقط. المطلوب هو إيجاد أكبر عدد شامل يكون أوليا في الوقت نفسه.

في البداية قد تبدو مساحة البحث كبيرة، لأن من السهل أن نتخيل أننا بحاجة إلى فحص جميع الأطوال من 1 إلى 9. لكن الفكرة الحاسمة هي أن معظم هذه الأطوال مستبعدة سلفا بسبب سبب حسابي بسيط، ولذلك تنكمش عملية البحث الشامل إلى مجموعة صغيرة جدا من المرشحين.

المنهج الرياضي

لكل \(n\)، لنرمز بـ \(\mathcal{P}_n\) إلى مجموعة جميع الأعداد الناتجة من تبديل الأرقام \(1,2,\dots,n\). عندئذ يكون \(|\mathcal{P}_n| = n!\)، وتصبح المسألة هي حساب

$$\max\{x \in \mathcal{P}_1 \cup \mathcal{P}_2 \cup \cdots \cup \mathcal{P}_9 : \operatorname{Prime}(x)\}.$$

التطبيقات لا تفحص هذا الاتحاد مباشرة وبشكل أعمى. فهي تبدأ بحذف أطوال الأعداد المستحيلة، ثم ترتب المرشحين الباقين ضمنيا ترتيبا تنازليا، بحيث يكون أول عدد أولي يتم العثور عليه هو الجواب النهائي تلقائيا.

استبعاد معظم الأطوال بواسطة ثابت مجموع الأرقام

كل عدد شامل من الرتبة \(n\) يستخدم المجموعة نفسها من الأرقام، ولهذا فإن جميع عناصر \(\mathcal{P}_n\) لها مجموع أرقام واحد هو

$$s_n = 1 + 2 + \cdots + n = \frac{n(n+1)}{2}.$$

العدد العشري يقبل القسمة على 3 إذا وفقط إذا كان مجموع أرقامه يقبل القسمة على 3. لذلك، إذا تحقق \(3 \mid s_n\)، فإن كل عنصر في \(\mathcal{P}_n\) يقبل القسمة على 3 ولا يمكن أن يكون أوليا. الاستثناء النظري الوحيد هو العدد 3 نفسه، لكنه لا يظهر هنا كعدد شامل بالمعنى المستخدم في هذه المسألة.

وبالنظر بترديد 3، نجد أن \(\frac{n(n+1)}{2}\) يكون صفرا بالضبط عندما \(n \equiv 0\) أو \(2 \pmod{3}\). ومن بين القيم \(1 \le n \le 9\)، لا تبقى إلا الأطوال

$$n \in \{1,4,7\}, \qquad s_1 = 1,\quad s_4 = 10,\quad s_7 = 28.$$

وهكذا تختزل المسألة الأصلية مباشرة إلى

$$\max\{x \in \mathcal{P}_7 \cup \mathcal{P}_4 \cup \mathcal{P}_1 : \operatorname{Prime}(x)\}.$$

حالة الرقم الواحد لا تعطي إلا العدد \(1\)، وهو ليس أوليا، ولذلك فالمنافسة الحقيقية عمليا تنحصر بين الأعداد الشاملة ذات 7 أرقام وتلك ذات 4 أرقام.

لماذا يكفي البحث التنازلي

كل عدد مكون من 7 أرقام أكبر من أي عدد مكون من 4 أرقام، وكل عدد مكون من 4 أرقام أكبر من الحالة ذات الرقم الواحد. لذلك، إذا وُجد عدد أولي شامل من 7 أرقام، فهو بالضرورة أكبر من كل عدد أولي شامل أقصر منه.

ولهذا السبب تمسح التطبيقات القيم \(n\) من 9 نزولا إلى 1. وبعد تطبيق مرشح القسمة على 3، يصبح هذا المسار الخارجي فعليا

$$7 \to 4 \to 1.$$

داخل كل طول صالح، تبدأ العملية من أكبر ترتيب ممكن للأرقام:

$$7654321,\qquad 4321,\qquad 1.$$

ثم تنتقل بعد ذلك انتقالا تنازليا صارما عبر جميع التبديلات الباقية. وبما أن عدد الخانات ثابت والمقارنة تتم من اليسار إلى اليمين، فإن الترتيب المعجمي يطابق هنا الترتيب العددي. لذا فإن أول عدد أولي يظهر في هذا الترتيب هو أكبر عدد أولي شامل لذلك الطول، وأول عدد أولي يظهر في الحلقة الخارجية هو القيمة العظمى على مستوى المسألة كلها.

ثابت التبديل الذي تستخدمه التطبيقات

يتم المرور على المرشحين باستعمال قاعدة previous permutation القياسية. إذا كانت السلسلة الحالية هي \(d_1d_2\cdots d_n\)، فنبحث أولا عن أقصى فهرس أيمن \(i\) يحقق \(d_i > d_{i+1}\). ثم نبحث عن أقصى فهرس أيمن \(j > i\) يحقق \(d_j < d_i\)، وبعدها نبدل بين \(d_i\) و\(d_j\)، ثم نعكس الذيل \(d_{i+1}\cdots d_n\).

هذه الخطوة تنتج أكبر تبديل ما زال أصغر من التبديل الحالي. ولذلك فهي تضمن مسارا تنازليا صارما يمر بكل ترتيب شامل ممكن مرة واحدة فقط، من دون تكرار ومن دون إسقاط أي حالة.

مثال عملي

حجة مجموع الأرقام تزيل مباشرة أكثر الحالات الكبيرة إغراء. عندما \(n=8\)، لدينا

$$s_8 = \frac{8 \cdot 9}{2} = 36 \equiv 0 \pmod{3},$$

ومن ثم فكل عدد شامل من 8 أرقام عدد مركب. أما عندما \(n=7\)، فإن

$$s_7 = \frac{7 \cdot 8}{2} = 28 \not\equiv 0 \pmod{3},$$

ولهذا تظل إمكانية وجود عدد أولي شامل من 7 أرقام قائمة.

ويمكن رؤية المسار التنازلي بوضوح داخل عائلة أصغر. إذا بدأنا من \(4321\)، فإن خطوة previous permutation تعطي \(4312\). المرشح الأول مركب لأن

$$4321 = 29 \cdot 149,$$

والمرشح الثاني مركب لأنه زوجي. ثم تواصل الخوارزمية النزول بالطريقة نفسها حتى تصل إلى عدد أولي. الحل الكامل يستخدم الآلية نفسها تماما، لكنه يبدأ بعائلة 7 أرقام لأن القيمة العظمى يجب أن تكون هناك إذا وُجد أصلا عدد أولي شامل من هذا الطول.

كيف تعمل الشفرة

ترشيح الأطوال وبناء المرشح الابتدائي

التطبيقات المكتوبة بلغة C++ وPython وJava تمر على \(n\) من 9 نزولا إلى 1، وتحسب \(\frac{n(n+1)}{2}\)، ثم تتجاوز مباشرة كل طول يكون مجموع أرقامه قابلا للقسمة على 3. وإذا نجا طول ما من هذا الترشيح، فإنها تبني أكبر ترتيب شامل ممكن \(n,n-1,\dots,1\)، وهو نقطة البداية الصحيحة لبحث تنازلي.

توليد المرشحين بترتيب تنازلي

كل تطبيق يحول ترتيب الأرقام الحالي مرارا إلى عدد صحيح ثم يفحص أوليته. وإذا لم يكن أوليا، ينتقل إلى التبديل الأصغر التالي. نسخة C++ تستخدم روتينا قياسيا من المكتبة، بينما تنفذ نسختا Python وJava الخطوة المعجمية نفسها بصورة صريحة. وعلى الرغم من اختلاف الصياغة، فإن الثابت واحد في الحالات الثلاث: الحالة الحالية هي دائما أكبر عدد شامل لم يُختبر بعد ويقع أسفل الحالة السابقة.

اختبار الأولية والتوقف المبكر

اختبار الأولية هنا هو القسمة التجريبية المباشرة. القيم الأصغر من 2 تُرفض، والأعداد الزوجية تُعالج على نحو خاص، ثم تُجرَّب القواسم الفردية فقط. ويكتب شرط الحلقة بالشكل \(p \le m/p\)، حيث \(m\) هو المرشح الجاري اختباره، وهو مكافئ رياضيا لعبارة \(p^2 \le m\).

بمجرد أن ينجح أحد المرشحين في اختبار الأولية، تعيد البرامج النتيجة فورا. كما تحتوي التطبيقات على اختبارات تحقق صغيرة تستخدم مثالا معروفا لعدد شامل أولي ومثالا معروفا لعدد مركب، بحيث يتم التأكد من صحة جزء فحص الأولية قبل بدء البحث الكامل.

تحليل التعقيد

بعد مرشح مجموع الأرقام، لا يبقى إلا الأطوال \(7,4,1\). ولذلك فإن العدد الكلي للمرشحين الشاملين الذين قد تتم دراستهم في أسوأ الأحوال هو

$$7! + 4! + 1! = 5040 + 24 + 1 = 5065.$$

لكل مرشح \(m\)، تكلف القسمة التجريبية زمنا من الرتبة \(O(\sqrt{m})\)، وهنا لدينا دائما \(m < 10^7\). ومن ثم يمكن كتابة حد أعلى نظيف للخوارزمية كلها على الصورة

$$O\!\left(\sum_{n \in \{1,4,7\}} n!\sqrt{10^n}\right),$$

وهو حد تهيمن عليه بوضوح حالة 7 أرقام، كما أن حجمه العملي صغير جدا.

استهلاك الذاكرة هو \(O(n)\) مع \(n \le 7\)، لأن التنفيذ يحتفظ فقط بترتيب الأرقام الحالي وبضعة مقادير عددية بسيطة. لا حاجة إلى منخل، ولا إلى شجرة استدعاء تكراري، ولا إلى جدول ضخم للمرشحين.

الهوامش والمراجع

  1. صفحة المسألة: https://projecteuler.net/problem=41
  2. العدد الشامل: Wikipedia - Pandigital number
  3. قاعدة القسمة: Wikipedia - Divisibility rule
  4. الترتيب المعجمي: Wikipedia - Lexicographic order
  5. العدد الأولي: Wikipedia - Prime number