Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Solving LinkedIn Queens by Linear Optimization

Queens Banner

How to Play Queens

Figure 1:Example of a Queens minigame board. Source: LinkedIn Queens

Objective
To place a crown in each row, column, and colored region on the board.
Rules
There can only be one crown in each row, column and colored region;
There cannot be adjacent crowns, not even along adjacent diagonals.

Problem Modeling

Before solving Queens, it is necessary to translate the game elements into the following components of the LOP:

  • Ranges

  • Sets

  • Decision Variables

  • Objective Function

  • Constraints

Ranges

Defining the ranges is a fundamental step for the proper definition of the other components of the LOP. For the case of Queens, three ranges will be considered:

I={1,,n}I = \{1, \cdots, n\}
Range of rows, where nn is the total number of rows on the board (in this case, nn = 7)
J={1,,m}J = \{1, \cdots, m\}
Range of columns, where nn is the total number of columns on the board (also nn = 7 for this case)
K={1,,p}K = \{1, \cdots, p\}
Range of colored regions in game, where pp is the total number of regions on the board (pp = 7)

Sets

By defining the ranges II and JJ, let’s define three sets for solving the Queens game:

H=I×J={(i,j)iI,jJ}H = I \times J = \{(i,j) \mid \forall i \in I, \forall j \in J\}
Set of all squares (i,j)(i, j) on the board, which is nothing more than the Cartesian product of the ranges II and JJ
R={((i,j),k)(i,j)H,kK}R = \{((i,j),k) \mid (i,j) \in H, k \in K\}
Set of colored regions and says to which region kk a square (i,j)(i,j) belongs.
D={((i,j),(i+1,j+1))(i,j),(i+1,j+1)H}{((i,j),(i+1,j1))(i,j),(i+1,j1)H}D = \begin{array}{cr} \{((i,j), (i+1,j+1)) \mid (i,j), (i+1,j+1) \in H\} & \cup \\ \{((i,j), (i+1,j-1)) \mid (i,j), (i+1,j-1) \in H\} & \end{array}
Set of all pairs of diagonally adjacent squares. This set will be useful so to make defining the constraints easier later on.

Decision variables

The decision variables will be binary variables xijx_{ij}, which will assume only two values:

xij{0,1},(i,j)Hx_{ij} \in \B, \forall (i,j) \in H
xij=1x_{ij} = 1, if the crown is located in row ii and column jj;
xij=0x_{ij} = 0, otherwise.

Objective function

The interesting thing about the Queens game is that, since we don"t have an objective function to maximize or minimize, we are not actually dealing with an optimization problem, but rather a feasibility problem, that is, our objective is only to find a feasible solution that meets all the game’s constraints. Because of this, we can define the objective function as minimizing (or maximizing, it doesn"t matter) an arbitrary constant. Thus, the objective function will not depend on the decision variables, so that the model doesn"t seek to optimize it and only worries about meeting the constraints.

Min C\text{Min} \ C

Constraints

With the previous elements well defined, we can finally translate Queens’ rules into constraints for our BLOP model. Let’s go step by step:

Binarity Constraints
The first constraint we need to define right away is that all decision variables in our problem are binary variables, meaning they only accept 0 and 1 as valid values.
xij{0,1},iI,jJx_{ij} \in \B, \forall i \in I, \forall j \in J
Single-Crown-Per-Row Constraints
Since each row on the board must have only one crown, the sum of all xijx_{ij} belonging to a row ii must equal 1. As there are 7 rows in total, there will be 7 such constraints, one for each row.
jJxij=1,iI\sum_{j \in J}{x_{ij}} = 1, \forall i \in I
Single-Crown-Per-Column Constraints
The same logic applies to the columns. Since there are 7 columns on the board, there will be 7 more constraints, one for each column.
iIxij=1,jJ\sum_{i \in I}{x_{ij}} = 1, \forall j \in J
Single-Crown-Per-Region Constraints
The same logic also applies to the colored regions. For each region of the board, the sum of the xijx_{ij} belonging to a region kk must be equal to 1. Since there are 7 regions, there will be 7 more constraints.
(i,j)Rkxij=1,kK\sum_{(i,j) \in R_k}{x_{ij}} = 1, \forall k \in K
Diagonally-Adjacent-squares Constraints
Finally, we must not forget the rule that there cannot be adjacent crowns, not even along the diagonals. The cases of vertical and horizontal proximity do not need to be addressed, since the row and column constraints already cover this. Therefore, we only need to worry about imposing constraints for the two diagonal directions in each pair of shared-vertex squares. With the set DD well defined, this constraint is easily defined as it follows.
xij+xrs1,((i,j),(r,s))Dx_{ij} + x_{rs} \le 1, \forall ((i,j), (r,s)) \in D

Abstract Model

With all components defined, it is possible to model the game of Queens using the following abstract model.

Min C\text{Min} \ C
S.t.:j=1mxij=1,iIi=1nxij=1,jJ(i,j)Rkxij=1,kKxij+xrs1,((i,j),(r,s))Dxij{0,1},(i,j)H\begin{array}{lll} \text{S.t.:} & \\ & \sum_{j=1}^{m}{x_{ij}} = 1, & \forall i \in I \\ & \sum_{i=1}^{n}{x_{ij}} = 1, & \forall j \in J \\ & \sum_{(i,j) \in R_k}{x_{ij}} = 1, & \forall k \in K \\ & x_{ij} + x_{rs} \le 1, & \forall ((i,j),(r,s)) \in D \\ & x_{ij} \in \B, & \forall (i,j) \in H \end{array}

Concrete Model

In this article, let’s apply LO techniques to solve the Queens No. 307, which will be an instance of the abstract model presented earlier, that is, its concrete model.

Queens No. 307, March 3rd, 2025.  Source: LinkedIn Queens

Figure 2:Queens No. 307, March 3rd, 2025. Source: LinkedIn Queens

The concrete model for the game above will be the following:

Min 0\text{Min} \ 0

S.t.:

Single-Crown-Per-Row Constraints
x11+x12+x13+x14+x15+x16+x17=1x_{11} + x_{12} + x_{13} + x_{14} + x_{15} + x_{16} + x_{17} = 1 (Row 1)
x21+x22+x23+x24+x25+x26+x27=1x_{21} + x_{22} + x_{23} + x_{24} + x_{25} + x_{26} + x_{27} = 1 (Row 2)
x31+x32+x33+x34+x35+x36+x37=1x_{31} + x_{32} + x_{33} + x_{34} + x_{35} + x_{36} + x_{37} = 1 (Row 3)
x41+x42+x43+x44+x45+x46+x47=1x_{41} + x_{42} + x_{43} + x_{44} + x_{45} + x_{46} + x_{47} = 1 (Row 4)
x51+x52+x53+x54+x55+x56+x57=1x_{51} + x_{52} + x_{53} + x_{54} + x_{55} + x_{56} + x_{57} = 1 (Row 5)
x61+x62+x63+x64+x65+x66+x67=1x_{61} + x_{62} + x_{63} + x_{64} + x_{65} + x_{66} + x_{67} = 1 (Row 6)
x71+x72+x73+x74+x75+x76+x77=1x_{71} + x_{72} + x_{73} + x_{74} + x_{75} + x_{76} + x_{77} = 1 (Row 7)
Single-Crown-Per-Column Constraints
x11+x21+x31+x41+x51+x61+x71=1x_{11} + x_{21} + x_{31} + x_{41} + x_{51} + x_{61} + x_{71} = 1 (Column 1)
x12+x22+x32+x42+x52+x62+x72=1x_{12} + x_{22} + x_{32} + x_{42} + x_{52} + x_{62} + x_{72} = 1 (Column 2)
x13+x23+x33+x43+x53+x63+x73=1x_{13} + x_{23} + x_{33} + x_{43} + x_{53} + x_{63} + x_{73} = 1 (Column 3)
x14+x24+x34+x44+x54+x64+x74=1x_{14} + x_{24} + x_{34} + x_{44} + x_{54} + x_{64} + x_{74} = 1 (Column 4)
x15+x25+x35+x45+x55+x65+x75=1x_{15} + x_{25} + x_{35} + x_{45} + x_{55} + x_{65} + x_{75} = 1 (Column 5)
x16+x26+x36+x46+x56+x66+x76=1x_{16} + x_{26} + x_{36} + x_{46} + x_{56} + x_{66} + x_{76} = 1 (Column 6)
x17+x27+x37+x47+x57+x67+x77=1x_{17} + x_{27} + x_{37} + x_{47} + x_{57} + x_{67} + x_{77} = 1 (Column 7)
Single-Crown-Per-Region Constraints
x11+x12+x13+x14+x15+x16+x17+x26+x27+x36+x37+x46+x47+x57+x67+x77=1x_{11} + x_{12} + x_{13} + x_{14} + x_{15} + x_{16} + x_{17} + x_{26} + x_{27} + x_{36} + x_{37} + x_{46} + x_{47} + x_{57} + x_{67} + x_{77} = 1 (Purple Region)
x12+x22+x32+x42+x33+x41+x42+x51+x52+x16+x26+x46+x56+x66+x71+x72+x73+x74+x75+x76=1x_{12} + x_{22} + x_{32} + x_{42} + x_{33} + x_{41} + x_{42} + x_{51} + x_{52} + x_{16} + x_{26} + x_{46} + x_{56} + x_{66} + x_{71} + x_{72} + x_{73} + x_{74} + x_{75} + x_{76} = 1 (Orange Region)
x25+x35=1x_{25} + x_{35} = 1 (Blue Region)
x32+x33=1x_{32} + x_{33} = 1 (Green Region)
x34+x43+x44+x45+x54=1x_{34} + x_{43} + x_{44} + x_{45} + x_{54} = 1 (Gray Region)
x53+x63=1x_{53} + x_{63} = 1 (Red Region)
x55+x56=1x_{55} + x_{56} = 1 (Yellow Region)
Principal Diagonals Constraints
x11+x221x_{11} + x_{22} \le 1
x12+x231x_{12} + x_{23} \le 1
x13+x241x_{13} + x_{24} \le 1
x14+x251x_{14} + x_{25} \le 1
x15+x261x_{15} + x_{26} \le 1
x21+x321x_{21} + x_{32} \le 1
x22+x331x_{22} + x_{33} \le 1
x23+x341x_{23} + x_{34} \le 1
x24+x351x_{24} + x_{35} \le 1
x25+x361x_{25} + x_{36} \le 1
x31+x421x_{31} + x_{42} \le 1
x32+x431x_{32} + x_{43} \le 1
x33+x441x_{33} + x_{44} \le 1
x34+x451x_{34} + x_{45} \le 1
x35+x461x_{35} + x_{46} \le 1
x41+x521x_{41} + x_{52} \le 1
x42+x531x_{42} + x_{53} \le 1
x43+x541x_{43} + x_{54} \le 1
x44+x551x_{44} + x_{55} \le 1
x45+x561x_{45} + x_{56} \le 1
x51+x621x_{51} + x_{62} \le 1
x52+x631x_{52} + x_{63} \le 1
x53+x641x_{53} + x_{64} \le 1
x54+x651x_{54} + x_{65} \le 1
x55+x661x_{55} + x_{66} \le 1
Secondary Diagonals Constraints
x12+x211x_{12} + x_{21} \le 1
x13+x221x_{13} + x_{22} \le 1
x14+x231x_{14} + x_{23} \le 1
x15+x241x_{15} + x_{24} \le 1
x16+x251x_{16} + x_{25} \le 1
x22+x311x_{22} + x_{31} \le 1
x23+x321x_{23} + x_{32} \le 1
x24+x331x_{24} + x_{33} \le 1
x25+x341x_{25} + x_{34} \le 1
x26+x351x_{26} + x_{35} \le 1
x32+x411x_{32} + x_{41} \le 1
x33+x421x_{33} + x_{42} \le 1
x34+x431x_{34} + x_{43} \le 1
x35+x441x_{35} + x_{44} \le 1
x36+x451x_{36} + x_{45} \le 1
x42+x511x_{42} + x_{51} \le 1
x43+x521x_{43} + x_{52} \le 1
x44+x531x_{44} + x_{53} \le 1
x45+x541x_{45} + x_{54} \le 1
x46+x551x_{46} + x_{55} \le 1
x52+x611x_{52} + x_{61} \le 1
x53+x621x_{53} + x_{62} \le 1
x54+x631x_{54} + x_{63} \le 1
x55+x641x_{55} + x_{64} \le 1
x56+x651x_{56} + x_{65} \le 1
Binarity Constraints
x12{0,1}x_{12} \in \B
x13{0,1}x_{13} \in \B
x14{0,1}x_{14} \in \B
x15{0,1}x_{15} \in \B
x16{0,1}x_{16} \in \B
x17{0,1}x_{17} \in \B
x21{0,1}x_{21} \in \B
x22{0,1}x_{22} \in \B
x23{0,1}x_{23} \in \B
x24{0,1}x_{24} \in \B
x25{0,1}x_{25} \in \B
x26{0,1}x_{26} \in \B
x27{0,1}x_{27} \in \B
x31{0,1}x_{31} \in \B
x32{0,1}x_{32} \in \B
x33{0,1}x_{33} \in \B
x34{0,1}x_{34} \in \B
x35{0,1}x_{35} \in \B
x36{0,1}x_{36} \in \B
x37{0,1}x_{37} \in \B
x41{0,1}x_{41} \in \B
x42{0,1}x_{42} \in \B
x43{0,1}x_{43} \in \B
x44{0,1}x_{44} \in \B
x45{0,1}x_{45} \in \B
x46{0,1}x_{46} \in \B
x47{0,1}x_{47} \in \B
x51{0,1}x_{51} \in \B
x52{0,1}x_{52} \in \B
x53{0,1}x_{53} \in \B
x54{0,1}x_{54} \in \B
x55{0,1}x_{55} \in \B
x56{0,1}x_{56} \in \B
x57{0,1}x_{57} \in \B
x61{0,1}x_{61} \in \B
x62{0,1}x_{62} \in \B
x63{0,1}x_{63} \in \B
x64{0,1}x_{64} \in \B
x65{0,1}x_{65} \in \B
x66{0,1}x_{66} \in \B
x67{0,1}x_{67} \in \B
x71{0,1}x_{71} \in \B
x72{0,1}x_{72} \in \B
x73{0,1}x_{73} \in \B
x74{0,1}x_{74} \in \B
x75{0,1}x_{75} \in \B
x76{0,1}x_{76} \in \B
x77{0,1}x_{77} \in \B

Solving Queens

In order to solve the presented game, the linkedin-games library counts on Queens class, which implements the Queens game and its constraints, as well as methods to solve it and visualize the game’s solution.

So, first of all, let’s import the Queens class and create an instance of it, passing its board dimensions as a (rows, columns) tuple and its colored regions as a dictionary of color: {squares} items, as shown bellow:

from linkedin_games import Queens


regions = {
    "#BBA3E1": { # Purple
        (1,1), (1,2), (1,3), (1,4), (1,5), (1,6), (1,7), (2,6),
        (2,7), (3,6), (3,7), (4,6), (4,7), (5,7), (6,7), (7,7)
    },
    "#FFC794": { # Orange
        (2,1), (2,2), (2,3), (2,4), (3,1), (4,1), (4,2),
        (5,1), (5,2), (6,1), (6,2), (6,4), (6,5), (6,6),
        (7,1), (7,2), (7,3), (7,4), (7,5), (7,6)
    },
    "#94BEFF": {(2,5), (3,5)}, # Blue
    "#B3DF9E": {(3,2), (3,3)}, # Green
    "#E0E0E0": {(3,4), (4,3), (4,4), (4,5), (5,4)}, # Gray
    "#FF7B61": {(5,3), (6,3)}, # Red
    "#E6F388": {(5,5), (5,6)} # Yellow
}
queens = Queens(7, regions)

The Queens class features the model attribute, which implements a QueensModel class that, in its turn, implements the Linear Optimization logic of the Queens game.

_model.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
import pyomo.environ as pyo


class QueensModel(pyo.ConcreteModel):
    """The Linear Optimization model for the Queens game."""

    def __init__(self, grid_dims: tuple[int, int], regions: dict[str, set[tuple[int, int]]]) -> None:
        """
        Args:
            grid_dims: Grid dimensionas as a `(rows, columns)` tuple.
            regions: All colored regions on grid as a dictionary of `color: {(row, column), ...}` items.
        """
        super().__init__()

        # BOARD DIMENSIONS
        m, n = grid_dims
        self.m = pyo.Param(initialize=m, domain=pyo.PositiveIntegers)
        self.n = pyo.Param(initialize=n, domain=pyo.PositiveIntegers)

        # RANGE SETS
        I = self.I = pyo.RangeSet(n) # Rows
        J = self.J = pyo.RangeSet(m) # Columns
        K = self.K = pyo.Set(initialize=regions.keys()) # Colored Regions

        # COMPOSITE SETS
        S = self.S = pyo.Set(initialize=lambda model: [(i, j) for i in I for j in J]) # Grid Squares
        R = self.R = pyo.Set(K, initialize=regions, dimen=2, domain=S) # Region Squares
        D = self.D = pyo.Set(initialize=lambda model: # Diagonals
            [((i, j), (i + 1, j + 1)) for (i, j) in S if (i + 1, j + 1) in S] +
            [((i, j), (i + 1, j - 1)) for (i, j) in S if (i + 1, j - 1) in S]
        )

        # OBJECTIVE FUNCTION
        self.obj = pyo.Objective(expr=0) # feasibility problem

        # DECISION VARIABLES
        x = self.x = pyo.Var(S, domain=pyo.Binary, initialize=0)

        # CONSTRAINTS
        self.single_crown_per_row_constraints = pyo.Constraint(
            I, rule=lambda model, i: pyo.quicksum(x[i, j] for j in J) == 1
        )
        self.single_crown_per_column_constraints = pyo.Constraint(
            J, rule=lambda model, j: pyo.quicksum(x[i, j] for i in I) == 1
        )
        self.single_crown_per_region_constraints = pyo.Constraint(
            K, rule=lambda model, k: pyo.quicksum(x[i, j] for (i, j) in R[k]) == 1
        )
        self.adjacent_squares_by_vertex_constraints = pyo.Constraint(
            D, rule=lambda model, i, j, r, s: x[i, j] + x[r, s] <= 1
        )

Program 1:Creating the model with Pyomo components.

With model constructed, the public method solve() calls internally the restricted _set_solution() one to save the solution to the private attribute __crowns, which indicates the crowned squares in the game and can be accessed by the public attribute crowns.

queens.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14

        """
        The crowned squares of Queens game.

        Returns:
            Locations of all crowns as a list of squares as `(row, column)`
            or `None` if the game is not solved yet.
        """
        if not self.is_solved:
            return None
        return sorted((i+1, j+1) for (i, j) in self.__crowns.nodes())


    def _set_solution(self, verbose:bool = False) -> None:

Program 2:Saving the game’s solution to __crowns attribute.

queens.py
1
2
3
4
5
6
7
8

                )
                raise ValueError(msg)



    @property
    def crowns(self) -> list[tuple[int, int]] | None:

Program 3:Implementation of crowns attribute.

With the solution obtained, the method show() displays the results as a Matplotlib plot showing the board with the crowned squares marked on it.

queens.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38


        x = self.model.x
        S = self.model.S
        nx.set_node_attributes(
            self.grid,
            name="value",
            values={(i-1, j-1): round(pyo.value(x[i, j])) for (i, j) in S}
        )

        crowns = [square for square, value in nx.get_node_attributes(self.grid, "value").items() if value == 1]
        self.__crowns = self.grid.subgraph(crowns)
        
        if verbose:
            print("These are the squares that contain a crown:")
            pprint(self.crowns)


    def show(self) -> None:
        """Show the Queens' grid."""
        width = height = self.size * 0.5
        plt.figure(figsize=(width, height))
        nx.draw(
            self.grid,
            pos={(i, j): (j, -i) for i, j in self.grid.nodes()},
            with_labels=True,
            arrows=False,
            labels=
                dict.fromkeys(self.__crowns.nodes(), "O") if self.__crowns is not None
                else dict.fromkeys(self.grid.nodes(), ""),
            node_size=1100,
            node_color=list(nx.get_node_attributes(self.grid, "color").values()),
            node_shape="s", # Squared-shape nodes
            width=0,
            edgecolors="black",
            linewidths=.5
        )
        plt.show()

Program 4:Implementation of show() function.

So to solve the game and display its results, just call the public methods solve() and show().

queens.solve()
queens.show()
<Figure size 340x340 with 1 Axes>

The output board matches the solution of the Queens No. 307, as expected.

Solution of Queens No. 307

Figure 3:Solution of Queens No. 307, March 3rd, 2025. Source: LinkedIn Queens