Teaser 3306: Pin-up
From The Sunday Times, 1st February 2026 [link] [link]
Over the years I have used seven different PINs. The first consisted of a four-digit number (the first digit not being zero), and thereafter each new PIN was obtained by swapping over two of the digits of the previous PIN; each time this increased the PIN.
The first PIN (not surprisingly!) was divisible by 1, the second was divisible by 2, the third by 3, and so on, with the seventh divisible by 7 (in fact the seventh PIN was the only one divisible by 7).
What was the third PIN?
[teaser3306]




Jim Randell 7:06 am on 1 February 2026 Permalink |
It is quicker (and neater) to build the list in reverse order. We can start with a final PIN that is a multiple of 7 (and 3), and then work backwards, undoing the swaps, to get back to the initial PIN.
This Python program runs in 83ms. (Internal runtime is 11.0ms).
from enigma import (irange, subsets, nsplit, nconcat, swap, printf) # possible swaps (by index) swaps = list(subsets(irange(4), size=2)) # solve the puzzle for a sequence of (<number>, <digit>) pairs # at each stage finding the _previous_ PIN def _solve(ss): k = len(ss) # are we done? if k == 7: yield ss else: (n, ds) = ss[0] # swap 2 digits of the following PIN for (i, j) in swaps: ds1 = swap(ds, i, j) # no leading zeros if ds1[0] == 0: continue n1 = nconcat(ds1) # previous PINs must not be divisible by 7; divisible by their place if n1 < n and n1 % 7 > 0 and n1 % (7 - k) == 0: yield from _solve([(n1, ds1)] + ss) # solve the puzzle for final PIN <n> def solve(n): # final PIN is divisible by 7 if n % 7 > 0: return # find previous PINs for ss in _solve([(n, nsplit(n))]): # return the list of numbers yield tuple(n for (n, ds) in ss) # consider the final PIN (divisible by 7 (and 3)) for n in irange.round(1000, 9999, step=21, rnd='I'): for ns in solve(n): printf("{ns}")Solution: [To Be Revealed]
LikeLike
Ruud 11:19 am on 1 February 2026 Permalink |
How do you know that the final PIN does not start with 0 ?
LikeLike
Jim Randell 11:47 am on 1 February 2026 Permalink |
@Ruud: The first PIN has no leading zero, so is greater than 999. And each subsequent PIN is larger than the one preceding it, so all the PINs are greater than 999, hence none of them have a leading zero.
LikeLike
ruudvanderham 11:27 am on 1 February 2026 Permalink |
Ah, I see: because it has to be greater than the first PIN, which is >=1000 .
But, how do you know that the final PIN is divisible by 3?
LikeLike
Jim Randell 11:48 am on 1 February 2026 Permalink |
@Ruud: If a number is divisible by 3 than any number formed from a rearrangement of the digits is also divisible by 3. Hence all the PINs must be divisible by 3.
So if the final PIN is divisible by both 7 and 3, then it must be divisible by 21 (= LCM(7, 3)).
LikeLike
Ruud 9:34 am on 1 February 2026 Permalink |
import istr def solve(pins): for i0, i1 in istr.combinations(range(4), r=2): n = len(pins) + 1 pin = istr.join(pins[-1][int(i0)] if i == i1 else pins[-1][int(i1)] if i == i0 else pins[-1][i] for i in range(4)) if pin > pins[-1] and pin.is_divisible_by(n): if n == 7: yield pins + [pin] elif not pin.is_divisible_by(7): yield from solve(pins + [pin]) for pin in istr(range(1000,10000)): for pins in solve([pin]): print(pins)LikeLike
Frits 1:32 pm on 1 February 2026 Permalink |
from itertools import combinations # generate and check the k-th PIN (decreasing k) def check(s, k): if not k: yield int(''.join(s[4])) # third PIN else: # swap 2 digits for i, j in combinations(range(4), 2): # swap should result in a lower number > 999 if s[-1][i] <= s[-1][j] or (i == 0 and s[-1][j] == "0"): continue new = [s[-1][j] if y == i else s[-1][i] if y == j else s[-1][y] for y in range(4)] # the seventh PIN was the only one divisible by 7 if (n := int(''.join(new))) % k == 0 and n % 7: yield from check(s + [new], k - 1) sols = set() # first possible seventh PIN divisible by 21 (div 7, div 3) f = next(i for i in range(2001, 2024, 3) if i % 7 == 0) for p7 in range(f, 10000, 21): # div 5 case if "5" in (s := str(p7)): # we need an even digit for the div 4 case if set("02468").isdisjoint(s): continue elif "0" in s: # we need at least 2 even digits for the div 4 case if sum(int(x) % 2 for x in s) > 2: continue else: continue # we need at least 7 different permutations if len(set(s)) < 3: continue # generate all PINs and store the third PIN for p3 in check([s], 6): sols.add(p3) print(f"answer: {' or '.join(str(s) for s in sols)}")LikeLike
Ruud 5:43 am on 2 February 2026 Permalink |
ChatGPT solved it in 47 seconds:
from itertools import permutations, combinations def swaps(a, b): """Return True if b can be obtained from a by swapping exactly two digits.""" diffs = [i for i in range(4) if a[i] != b[i]] return len(diffs) == 2 and a[diffs[0]] == b[diffs[1]] and a[diffs[1]] == b[diffs[0]] def valid_chain(chain): """Check divisibility, increasing order, and swap condition.""" for i in range(7): if chain[i] % (i+1) != 0: return False if i > 0: if chain[i] <= chain[i-1]: return False if not swaps(str(chain[i-1]), str(chain[i])): return False # only the 7th is divisible by 7 return all(chain[i] % 7 != 0 for i in range(6)) solutions = [] # choose 4 distinct digits, first not zero digits = [str(d) for d in range(10)] for digs in combinations(digits, 4): for perm in permutations(digs): if perm[0] == "0": continue nums = sorted( int("".join(p)) for p in permutations(perm) if p[0] != "0" ) for chain in permutations(nums, 7): if valid_chain(chain): solutions.append(chain) # print solutions and the third PIN for sol in solutions: print(sol, "→ third PIN:", sol[2])Scary …
LikeLike
Ruud 6:10 am on 2 February 2026 Permalink |
Maybe less scary than I thought.
Although, ChatGPT gave the proper answer, it can’t have been done with this pure brute force program. It takes for ever, even for one PIN.
LikeLike
Ruud 7:06 am on 2 February 2026 Permalink |
I asked ChatGPT again (mentioning that it didn’t finish in reosonable time). And now it comes with a solution. Amazing …
from collections import defaultdict SWAP_PAIRS = [(0,1), (0,2), (0,3), (1,2), (1,3), (2,3)] def swap_digits(s, i, j): if i == j: return s lst = list(s) lst[i], lst[j] = lst[j], lst[i] return "".join(lst) def build_neighbors(): """ neighbors[n] = sorted list of all valid 4-digit numbers obtainable by swapping two digits of n (leading digit cannot become 0). """ neighbors = [[] for _ in range(10000)] for n in range(1000, 10000): s = f"{n:04d}" seen = set() for i, j in SWAP_PAIRS: t = swap_digits(s, i, j) if t[0] == "0": continue m = int(t) if m != n and m not in seen: seen.add(m) neighbors[n] = sorted(seen) return neighbors def find_chains(): neighbors = build_neighbors() # Precompute which numbers satisfy divisibility by k (k=1..7) div_ok = {k: [False]*10000 for k in range(1, 8)} for n in range(1000, 10000): for k in range(1, 8): div_ok[k][n] = (n % k == 0) solutions = [] def dfs(chain): step = len(chain) + 1 # next step index (1..7) if step == 8: solutions.append(chain[:]) return prev = chain[-1] for nxt in neighbors[prev]: if nxt <= prev: continue if not div_ok[step][nxt]: continue # only the 7th is divisible by 7 if step < 7 and nxt % 7 == 0: continue dfs(chain + [nxt]) # Step 1: any 4-digit number works for divisibility by 1, # but it must allow an increasing chain of swaps. # Still, searching all 9000 starts is fine with pruning. for start in range(1000, 10000): # (Optional) tiny prune: if start has no larger neighbors, skip if not neighbors[start] or neighbors[start][-1] <= start: continue dfs([start]) return solutions if __name__ == "__main__": sols = find_chains() print(f"Found {len(sols)} solution chain(s).") for ch in sols: print(" -> ".join(map(str, ch)), " | third PIN:", ch[2])LikeLike