All Euler problems
Project Euler

Number Mind

A game similar to Mastermind: the player tries to guess a secret 16-digit number. After each guess, the player is told how many digits are in the correct position. Given the following clues, find t...

Source sync Apr 19, 2026
Problem #0185
Level Level 08
Solved By 3,657
Languages C++, Python
Answer 4640261571849533
Length 469 words
searchlinear_algebraoptimization

Problem Statement

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

The game Number Mind is a variant of the well known game Master Mind.

Instead of coloured pegs, you have to guess a secret sequence of digits. After each guess you’re only told in how many places you’ve guessed the correct digit. So, if the sequence was 1234 and you guessed 2036, you’d be told that you have one correct digit; however, you would NOT be told that you also have another digit in the wrong place.

For instance, given the following guesses for a 5-digit secret sequence, \begin {align*} 90342 &;2 \text { correct} \\ 70794 &;0 \text { correct} \\ 39458 &;2 \text { correct} \\ 34109 &;1 \text { correct} \\ 51545 &;2 \text { correct} \\ 12531 &;1 \text { correct} \\ \end {align*}

The correct sequence 39542 is unique.

Based on the following guesses,

\begin {align*} 5616185650518293 &;2 \text { correct} \\ 3847439647293047 &;1 \text { correct} \\ 5855462940810587 &;3 \text { correct} \\ 9742855507068353 &;3 \text { correct} \\ 4296849643607543 &;3 \text { correct} \\ 3174248439465858 &;1 \text { correct} \\ 4513559094146117 &;2 \text { correct} \\ 7890971548908067 &;3 \text { correct} \\ 8157356344118483 &;1 \text { correct} \\ 2615250744386899 &;2 \text { correct} \\ 8690095851526254 &;3\text { correct} \\ 6375711915077050 &;1 \text { correct} \\ 6913859173121360 &;1 \text { correct} \\ 6442889055042768 &;2 \text { correct} \\ 2321386104303845 &;0 \text { correct} \\ 2326509471271448 &;2 \text { correct} \\ 5251583379644322 &;2 \text { correct} \\ 1748270476758276 &;3 \text { correct} \\ 4895722652190306 &;1 \text { correct} \\ 3041631117224635 &;3 \text { correct} \\ 1841236454324589 &;3 \text { correct} \\ 2659862637316867 &;2 \text { correct} \\ \end {align*}

Find the unique 16-digit secret sequence.

Problem 185: Number Mind

Mathematical Foundation

Definition 1. A constraint satisfaction problem (CSP) consists of a set of variables X={x0,x1,,x15}X = \{x_0, x_1, \ldots, x_{15}\}, each with domain Di{0,1,,9}D_i \subseteq \{0, 1, \ldots, 9\}, and a set of constraints. Here, each clue (g,c)(g, c) imposes the constraint:

i=0151[xi=gi]=c.\sum_{i=0}^{15} \mathbf{1}[x_i = g_i] = c.

Theorem 1 (Zero-Clue Elimination). If a clue (g,0)(g, 0) has count 0, then for every position ii, giDig_i \notin D_i in any valid solution.

Proof. If the secret number ss matched gg at any position ii, the count would be at least 1, contradicting c=0c = 0. Hence sigis_i \neq g_i for all ii. \square

Theorem 2 (Feasibility Pruning). During backtracking with partial assignment σ\sigma (assigning values to positions 0,,k10, \ldots, k-1), a clue (g,c)(g, c) can be pruned if either:

  • (a) {i<k:σ(xi)=gi}>c|\{i < k : \sigma(x_i) = g_i\}| > c (too many matches already), or
  • (b) {i<k:σ(xi)=gi}+{ik:giDi}<c|\{i < k : \sigma(x_i) = g_i\}| + |\{i \geq k : g_i \in D_i\}| < c (insufficient potential matches remain).

Proof. Condition (a): If the partial assignment already exceeds the required count, no extension can reduce it. Condition (b): Even if every remaining position matches, the total cannot reach cc. In both cases, no valid completion exists. \square

Lemma 1 (MRV Heuristic). Selecting the variable with the smallest remaining domain (Minimum Remaining Values) minimizes the expected branching factor at each node of the search tree, reducing the total number of nodes explored.

Proof. This is a standard result in CSP theory. A variable with dd candidates creates dd branches. Choosing the variable with smallest dd minimizes the width of the search tree at the current level, leading to earlier discovery of dead ends and more aggressive pruning. \square

Editorial

Uses backtracking with constraint propagation. We initialize domains. We then apply zero-clue elimination. Finally, backtracking search. We perform a recursive search over the admissible choices, prune branches that violate the derived constraints, and keep only the candidates that satisfy the final condition.

Pseudocode

Initialize domains
Apply zero-clue elimination
Backtracking search
MRV: pick position with fewest candidates

Complexity Analysis

  • Time: Worst case exponential O(1016)O(10^{16}), but constraint propagation and the MRV heuristic reduce the effective search space to a manageable size. The zero-clue constraints alone reduce domains to roughly 4—6 candidates per position, giving an effective branching factor far below 10.
  • Space: O(16×clues)O(16 \times |\text{clues}|) for the constraint bookkeeping, plus O(16)O(16) for the recursion stack.

Answer

4640261571849533\boxed{4640261571849533}

Code

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

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

struct Clue {
    string guess;
    int correct;
};

vector<Clue> clues;
int secret[16];
set<int> possible[16]; // possible digits at each position

bool check_feasible(int pos) {
    // Check all clue constraints given assignment up to position `pos`
    // (positions 0..pos are assigned)
    for (auto& c : clues) {
        int matched = 0, can_match = 0;
        for (int i = 0; i < 16; i++) {
            if (i <= pos) {
                if (secret[i] == (c.guess[i] - '0'))
                    matched++;
            } else {
                if (possible[i].count(c.guess[i] - '0'))
                    can_match++;
            }
        }
        if (matched > c.correct) return false;
        if (matched + can_match < c.correct) return false;
    }
    return true;
}

bool solve(int depth, vector<int>& order) {
    if (depth == 16) {
        // Verify all constraints exactly
        for (auto& c : clues) {
            int matched = 0;
            for (int i = 0; i < 16; i++)
                if (secret[i] == (c.guess[i] - '0'))
                    matched++;
            if (matched != c.correct) return false;
        }
        return true;
    }

    int pos = order[depth];
    for (int d : possible[pos]) {
        secret[pos] = d;
        if (check_feasible(pos)) {
            // We need to check with all assigned positions, not just up to pos
            // Actually check_feasible checks positions 0..pos but order might not be sequential
            // Let's fix: check all assigned positions
        }
        if (solve(depth + 1, order))
            return true;
    }
    secret[pos] = -1;
    return false;
}

bool check_feasible_v2(int assigned[], int num_assigned) {
    for (auto& c : clues) {
        int matched = 0, can_match = 0;
        bool is_assigned[16] = {};
        for (int i = 0; i < num_assigned; i++)
            is_assigned[assigned[i]] = true;

        for (int i = 0; i < 16; i++) {
            if (is_assigned[i]) {
                if (secret[i] == (c.guess[i] - '0'))
                    matched++;
            } else {
                if (possible[i].count(c.guess[i] - '0'))
                    can_match++;
            }
        }
        if (matched > c.correct) return false;
        if (matched + can_match < c.correct) return false;
    }
    return true;
}

int assigned_positions[16];

bool solve2(int depth, vector<int>& order) {
    if (depth == 16) {
        for (auto& c : clues) {
            int matched = 0;
            for (int i = 0; i < 16; i++)
                if (secret[i] == (c.guess[i] - '0'))
                    matched++;
            if (matched != c.correct) return false;
        }
        return true;
    }

    int pos = order[depth];
    assigned_positions[depth] = pos;
    for (int d : possible[pos]) {
        secret[pos] = d;
        if (check_feasible_v2(assigned_positions, depth + 1)) {
            if (solve2(depth + 1, order))
                return true;
        }
    }
    secret[pos] = -1;
    return false;
}

int main(){
    clues = {
        {"5616185650518293", 2},
        {"3847439647293047", 1},
        {"5855462940810587", 3},
        {"9742855507068353", 3},
        {"4296849643607543", 3},
        {"3174248439465858", 1},
        {"4513559094146117", 2},
        {"7890971548908067", 3},
        {"8157356344118483", 1},
        {"2615250744386899", 2},
        {"8690095851526254", 3},
        {"6375711915077050", 1},
        {"6913859173121360", 1},
        {"6442889055042768", 2},
        {"2321386104303845", 0},
        {"2326509471271448", 2},
        {"5765889547612327", 0},
        {"6880193648553567", 0},
        {"8265420455326143", 2},
    };

    // Initialize possible digits
    for (int i = 0; i < 16; i++) {
        for (int d = 0; d <= 9; d++)
            possible[i].insert(d);
        secret[i] = -1;
    }

    // Apply zero-clue constraints
    for (auto& c : clues) {
        if (c.correct == 0) {
            for (int i = 0; i < 16; i++) {
                possible[i].erase(c.guess[i] - '0');
            }
        }
    }

    // Order positions by fewest candidates (MRV)
    vector<int> order(16);
    iota(order.begin(), order.end(), 0);
    sort(order.begin(), order.end(), [](int a, int b){
        return possible[a].size() < possible[b].size();
    });

    solve2(0, order);

    for (int i = 0; i < 16; i++)
        cout << secret[i];
    cout << endl;

    return 0;
}