All Euler problems
Project Euler

Passcode Derivation

A common security method for online banking is to ask the user for three random characters from a passcode. Given 50 successful login attempts where each attempt reveals a 3-character subsequence o...

Source sync Apr 19, 2026
Problem #0079
Level Level 02
Solved By 44,874
Languages C++, Python
Answer 73162890
Length 802 words
graphsequencebrute_force

Problem Statement

This archive keeps the full statement, math, and original media on the page.

A common security method used for online banking is to ask the user for three random characters from a passcode. For example, if the passcode was \(531278\), they may ask for the \(2\)nd, \(3\)rd, and \(5\)th characters; the expected reply would be: \(317\).

The text file, keylog.txt, contains fifty successful login attempts.

Given that the three characters are always asked for in order, analyse the file so as to determine the shortest possible secret passcode of unknown length.

Problem 79: Passcode Derivation

Mathematical Foundation

Definition 1 (Subsequence Constraint). A login attempt d1d2d3d_1 d_2 d_3 imposes the constraint that characters d1d_1, d2d_2, d3d_3 appear in this relative order (not necessarily consecutively) within the passcode.

Definition 2 (Precedence Graph). The precedence graph is a directed graph G=(V,E)G = (V, E) where VV is the set of distinct digits appearing in the login data and (u,v)E(u, v) \in E if and only if some login attempt contains uu before vv (i.e., u=diu = d_i and v=djv = d_j with i<ji < j in some attempt d1d2d3d_1 d_2 d_3).

Assumption (No Repeated Digits). We assume the passcode contains each digit at most once. This is justified a posteriori: if the resulting DAG admits a valid topological ordering of length V|V| that satisfies all constraints, then no repetition is needed.

Theorem 1 (Acyclicity). Under the no-repetition assumption, GG is a directed acyclic graph (DAG).

Proof. Suppose GG contains a directed cycle v1v2vkv1v_1 \to v_2 \to \cdots \to v_k \to v_1. Then the passcode requires v1v_1 to appear before v2v_2, v2v_2 before v3v_3, …, and vkv_k before v1v_1. By transitivity of the “precedes” relation, v1v_1 must appear strictly before itself. Since each digit appears exactly once, this is impossible. Hence GG is acyclic. \blacksquare

Theorem 2 (Minimality of Topological Ordering). If GG is a DAG on V|V| vertices, then any topological ordering of GG yields a string of length V|V| respecting all precedence constraints, and no shorter string containing all digits of VV exists.

Proof. A topological ordering vσ(1),vσ(2),,vσ(V)v_{\sigma(1)}, v_{\sigma(2)}, \ldots, v_{\sigma(|V|)} has the property that (vσ(i),vσ(j))E(v_{\sigma(i)}, v_{\sigma(j)}) \in E implies i<ji < j. Thus every precedence constraint is satisfied. The string has length V|V|, and any string containing all V|V| distinct digits must have length V\geq |V|. \blacksquare

Theorem 3 (Uniqueness via Hamiltonian Path). A DAG has a unique topological ordering if and only if it contains a Hamiltonian path. Equivalently, at each step of Kahn’s algorithm, there is exactly one vertex with in-degree 00.

Proof. (\Rightarrow) Let v1,v2,,vnv_1, v_2, \ldots, v_n be the unique topological order. Suppose edge (vi,vi+1)E(v_i, v_{i+1}) \notin E for some ii. Then viv_i and vi+1v_{i+1} have no direct precedence constraint between them. Moreover, swapping them yields a sequence v1,,vi1,vi+1,vi,vi+2,,vnv_1, \ldots, v_{i-1}, v_{i+1}, v_i, v_{i+2}, \ldots, v_n. We claim this is also a valid topological ordering: the only pair whose relative order changed is (vi,vi+1)(v_i, v_{i+1}), and since (vi,vi+1)E(v_i, v_{i+1}) \notin E, no constraint is violated. (We also need (vi+1,vi)E(v_{i+1}, v_i) \notin E, which holds since viv_i precedes vi+1v_{i+1} in the original valid ordering.) This contradicts uniqueness. Hence (vi,vi+1)E(v_i, v_{i+1}) \in E for all ii, forming a Hamiltonian path.

(\Leftarrow) Let v1v2vnv_1 \to v_2 \to \cdots \to v_n be a Hamiltonian path. Then v1v_1 is the unique vertex with in-degree 00 in GG (any other vertex vjv_j with j>1j > 1 has the incoming edge from vj1v_{j-1}). Removing v1v_1 and its outgoing edges, v2v_2 becomes the unique vertex with in-degree 00 in the residual graph. By induction, each step of Kahn’s algorithm has a unique choice, producing a unique topological ordering. \blacksquare

Theorem 4 (Correctness of Kahn’s Algorithm). Given a DAG G=(V,E)G = (V, E), Kahn’s algorithm outputs a valid topological ordering.

Proof. The algorithm maintains a queue QQ of vertices with in-degree 00 and repeatedly: (i) dequeues a vertex vv, (ii) appends vv to the output sequence, and (iii) for each outgoing edge (v,w)(v, w), decrements the in-degree of ww and enqueues ww if its in-degree reaches 00.

We prove the output vσ(1),,vσ(V)v_{\sigma(1)}, \ldots, v_{\sigma(|V|)} is a valid topological ordering by contradiction. Suppose (u,w)E(u, w) \in E but ww appears before uu in the output. Then ww was dequeued before uu. At the time ww was dequeued, its in-degree was 00, meaning every predecessor of ww (including uu, since (u,w)E(u, w) \in E) had already been removed from the graph. But uu has not yet been dequeued, so it has not been removed—a contradiction. \blacksquare

Lemma 1 (Extracted Graph for the Given Data). From the 50 login attempts, the distinct digits are V={0,1,2,3,6,7,8,9}V = \{0, 1, 2, 3, 6, 7, 8, 9\} (digits 44 and 55 never appear, V=8|V| = 8). The extracted precedence edges form a DAG with a unique topological ordering: 7,3,1,6,2,8,9,07, 3, 1, 6, 2, 8, 9, 0.

Proof. Extracting all ordered pairs from each 3-digit attempt (3 pairs per attempt) and computing in-degrees in the resulting DAG, Kahn’s algorithm proceeds:

  1. In-degree 00: only vertex 77.
  2. Remove 77: only vertex 33 has in-degree 00.
  3. Remove 33: only vertex 11 has in-degree 00.
  4. Continue: 66, then 22, then 88, then 99, then 00.

At each step, exactly one vertex has in-degree 00, confirming uniqueness by Theorem 3. \blacksquare

Editorial

Each login attempt gives relative-order information, not exact positions. So the natural model is a precedence graph: from a triple abcabc we learn that aa must come before bb, aa before cc, and bb before cc. Repeating this over all attempts produces a directed graph on the digits that actually appear.

Once the graph is built, the shortest passcode is just a topological ordering of that graph. No extra digits are needed, because every distinct digit in the graph must appear at least once and a valid topological order already satisfies every subsequence constraint. Kahn’s algorithm generates the passcode by repeatedly taking a digit whose remaining in-degree is zero, appending it to the answer, and deleting its outgoing constraints. In this dataset the choice is unique at every stage, so the passcode is forced.

Pseudocode

Collect the distinct digits that appear in the login attempts.

For each three-digit attempt:
    add the precedence edges implied by its relative order

Compute the in-degree of every digit.
Place all digits with in-degree zero into the processing queue.

While the queue is not empty:
    remove the next digit from the queue and append it to the passcode

    For each digit that must come after it:
        decrease that digit's in-degree
        if the in-degree becomes zero, place it into the queue

Return the concatenation of the chosen digits.

Complexity Analysis

Time: Graph construction scans 50 attempts, extracting (32)=3\binom{3}{2} = 3 ordered pairs each, with O(1)O(1)-amortized hash set membership checks. Total: O(150)O(150). Kahn’s algorithm runs in O(V+E)O(|V| + |E|). With V=8|V| = 8 and E56|E| \leq 56 (at most (82)\binom{8}{2} directed edges): O(1)O(1) for this fixed input.

Space: O(V+E)O(|V| + |E|) for the adjacency representation and in-degree array.

Answer

73162890\boxed{73162890}

Code

Each problem page includes the exact C++ and Python source files from the local archive.

C++ project_euler/problem_079/solution.cpp
#include <bits/stdc++.h>
using namespace std;

int main() {
    vector<string> attempts = {
        "319", "680", "180", "690", "129", "620", "762", "689", "762", "318",
        "368", "710", "720", "710", "629", "168", "160", "689", "716", "731",
        "736", "729", "316", "729", "729", "710", "769", "290", "719", "680",
        "318", "389", "162", "289", "162", "718", "729", "319", "790", "680",
        "890", "362", "319", "760", "316", "729", "380", "319", "728", "716"
    };

    // Build precedence DAG
    set<int> digits;
    map<int, set<int>> adj;

    for (auto& s : attempts) {
        int a = s[0] - '0', b = s[1] - '0', c = s[2] - '0';
        digits.insert({a, b, c});
        adj[a].insert(b);
        adj[a].insert(c);
        adj[b].insert(c);
    }

    // Compute in-degrees from the adjacency sets
    map<int, int> indeg;
    for (int d : digits) indeg[d] = 0;
    for (int d : digits)
        for (int v : adj[d])
            indeg[v]++;

    // Kahn's algorithm (topological sort)
    queue<int> q;
    for (int d : digits)
        if (indeg[d] == 0)
            q.push(d);

    string result;
    while (!q.empty()) {
        int u = q.front(); q.pop();
        result += ('0' + u);
        for (int v : adj[u]) {
            if (--indeg[v] == 0)
                q.push(v);
        }
    }

    cout << result << endl;
    return 0;
}