Teaser 2431: [Football league]
From The Sunday Times, 26th April 2009 [link]
Teams A, B, C… (up to some letter) play in a football league. Last season, they finished in alphabetical order. This season, the league consisted of the same teams, but last season’s losers were the champions, and B finished below C, but not in the bottom three. One third of the teams finished higher this season than last; no two moved the same number of places. So, if one team went up four places, no other team will have gone up or down by four places. In particular, just one team had the same position in both seasons.
Starting with the champions, list the order of the teams this season.
This puzzle was originally published with no title.
[teaser2431]









Jim Randell 8:21 am on 3 June 2026 Permalink |
If exactly one third of the teams moved up, then the number of teams must be divisible by 3, and if they are each allocated a letter of the alphabet there can be no more than 26.
B finished below C, but not in the bottom three teams, so there must be at least 5 teams.
So the possible number of teams is: 6, 9, 12, 15, 18, 21, 24.
This Python program looks for the smallest possible number of teams.
It labels the teams from 1 .. n, so last year’s position is the same as the label. It then tries all possible allocations for this year’s positions, and checks to see if all the conditions are met.
It runs in 126ms. (Internal runtime is 55ms).
from enigma import ( Enumerator, irange, subsets, multiset, compare, update, diff, str_upper, item, join, map2str, printf ) # check that pos: map team -> position satisfies the requirements def check(n, pos): # for each team collect the up/down and the gap (updown, gap) = (multiset(), multiset()) for (k, v) in pos.items(): d = k - v updown.add(compare(d, 0)) gap.add(abs(d)) # ups = n/3, non-movers = 1, no duplicate gaps return (updown.count(1) * 3 == n and updown.count(0) == 1 and not gap.is_duplicate()) # solve for teams 1 .. n (= A .. ?) def solve(n): # choose values for teams B and C (1 < C < B < n - 2) for (C, B) in subsets(irange(2, n - 3), size=2): pos1 = { n: 1, 2: B, 3: C } # allocate the remaining positions ks = diff(irange(1, n), pos1.keys()) for vs in subsets(diff(irange(1, n), pos1.values()), size=len, select='P'): pos = update(pos1, ks, vs) if check(n, pos): yield pos # consider multiples of 3 for n in irange(6, 24, step=3): sols = Enumerator(solve(n)) for pos in sols: # construct the order of the teams order = tuple(str_upper[k - 1] for (k, v) in sorted(pos.items(), key=item(1))) # output solution printf("{n} teams -> {order} [pos = {pos}]", order=join(order), pos=map2str(pos)) # stop at first n with solutions if sols.count > 0: breakSolution: The order of the teams (first to last) is: I, H, G, C, B, F, E, A, D.
Fortunately the solution occurs with 9 teams, because this approach gets much slower as the number of teams increases. (I used a more sophisticated version of this approach to check teams up to 18, but it would take too long to check 21 and 24).
LikeLike
John Crabtree 1:49 pm on 4 June 2026 Permalink |
Let there be 3n teams.
The maximum number of places gained is (3n – 1) + (3n – 3) + …(n + 1) with n terms.
The minimum number of places lost is (3n – 2) + (3n – 4) + …(n) + sum[0 to (n -1)].
And so n >= n(n-1)/2, ie n<=3
If n = 1 or 2, the sum of the changes in places is odd. And so n = 3, with places gained = places lost = 18.
The changes in the places are then forced: champions I up 8 to 1; H up 6 to 2; G up 4 to 3; A (not B) down 7 to 8; D (not B or C) down 5 to 9; B down 3 (max) to 5 and C down 1 to 4; E down 2 to 7; and F stays at 6.
LikeLike