Computing Number of Conflicting Pairs in N-Queen Board in Linear Time and Space Complexity | by Tanvir Sojal | Towards Data Science

N-Queen problem is a classical problem in the field of Artificial Intelligence, where we try to find a chessboard configuration where in a N × N board there will be N queens, not attacking each other.
There are different ways to address this problem, each having own time complexity. After generating states, evaluating number of conflicting pairs in each one of them is also time consuming. Below we will discuss how we define this problem and how linear time complexity can be achieved in calculating number of conflicts in a board.
Few things to note:
For simplicity, we will use array from index 1 (since, we numbered rows and columns from 1 to N). Index 0 is unused.Simple C++ code (idea) is given. If you understand the approach, you can always code more flexible program with dynamic memory allocation and so on.A link to python implementation can be found at the end of the article.
For simplicity, we will use array from index 1 (since, we numbered rows and columns from 1 to N). Index 0 is unused.
Simple C++ code (idea) is given. If you understand the approach, you can always code more flexible program with dynamic memory allocation and so on.
A link to python implementation can be found at the end of the article.
Problem definition: We will describe the board configuration as an 1-dimensional array. The indices will indicate the column numbers and the value in corresponding indices will indicate the row number of the queen in that particular column.
For example, {3, 4, 3, 2} corresponds to this board configuration (image below):
Why define like this? Because In every column, placing more than one queen makes no sense, they’ll simply attack each other thus does not help in solving the problem. And defining as an 1-D array saves a lot of space.
Generating a board configuration will take O(N) time, assuming we generate a random value from 1 to N in O(1).
Conflict checking (naive): We can use nested loop to compute number of conflicts, thus for every queen, check all the next queens and count how many conflicts are there and add it to result. Conflict occurs, when:
The queens are in the same row (row numbers of both queens are same).The queens are in same column (column numbers of both queens are same, in our problem definition, this will not happen).Both queens are on same diagonal (the absolute difference in rows equals the absolute difference in columns).
The queens are in the same row (row numbers of both queens are same).
The queens are in same column (column numbers of both queens are same, in our problem definition, this will not happen).
Both queens are on same diagonal (the absolute difference in rows equals the absolute difference in columns).
This approach takes O(n2).
Achieving Linear Time Complexity: We will use O(N) space to achieve linear time complexity in conflict checking.
Three 1-dimensional tables (or arrays) are introduced. One for storing frequency of queens in each row. One for storing frequency of queens in main diagonals and another one for secondary diagonals.
Check the following board image for row and column indexing for our problem definition.
Now we know there are N rows, so the row-frequency array size will be N (or N+1, since index 0 is unused). Let’s declare it as f_row[] where initially all indices will contain zero(0).
Let’s denote our chessboard configuration array as Queen[]. We can populate the f_row[] in this way:
for (int i = 1; i <= N; i++){    int val = Queen[i];    f_row[val]++;}
For every row value, we increment corresponding index in f_row[] by 1. Fairly easy! Now how do we check conflicts? Let’s assume, queen of one row does not affect the queens of other row (they do if they are in the same diagonal, but for now let’s not consider this). So if the conflicts are mutually exclusive, we can separately compute conflicts for each row and just add them to get total horizontal conflicts, no need to check for overlapping.
Let’s consider this situation, where 4 queens are on same row, the conflicting pairs are connected by red lines.
The lines or edges are undirected, we do not check for backward conflicts, i.e. if Queen A attacks Queen B, Queen B also attacks Queen A, but we consider this as a single conflict.
We can observe that all the conflicting queens in a single row actually form a “Complete Graph”, thus every queen is connected to every other queen. And the number of edges are the number of conflicts. The formula to check number of edges in a complete graph, where there are N nodes:
(N * (N-1)) / 2
The important thing to note is, this observation also applies when we will be checking for diagonals.
For our example with 4 queens, total conflict in that row = (4 * (4–1)) / 2 = 6.
We have row frequency stored in f_row[]. To compute total number of horizontal conflicts we can use this approach:
for (int i = 1; i <= N; i++){    int queens = f_row[i];    result += ((queens * (queens - 1) / 2);}
To check for diagonal conflicts, we have to observe the board little differently. First let’s try for main diagonals. See the following image where every board cell contains the sum of it’s row and column values.
If you look closely, you’ll see all the (main) diagonals has the same sum value. That is, if Queen A stands on a position where the value is 10, and Queen B is also somewhere with value 10, conflict occurs!
Just like rows, we will also use a frequency table. But this time twice the size. Because the maximum sum value we can have is the sum of highest row and highest column, N + N. Let’s denote it as f_mdiag[] (here mdiag refers to main diagonal).
We can populate it like that:
for (int i = 1; i <= N; i++){    int row = Queen[i];    int sum = row + i; // i is column value    f_mdiag[sum]++;}
For secondary diagonals, we have to flip the column indexing (or row, but not both). Now sum the row and column value for each cell and observe.
Again, the diagonals we are looking for, has the same sum values. So we need another frequency table, same size as last one.
Populating can be done like this:
for (int i = 1; i <= N; i++){    int row = Queen[i];    int sum = (N - row + 1) + i; // flipping is done by N - row + 1    f_sdiag[sum]++;}
Now we can iterate through both of these arrays and use (N * (N-1)) / 2 to generate number of conflicts in each diagonals and add them to result. Something like this (for f_mdiag[]):
for (int i = 1; i <= (N+N); i++){ // note here, N+N, not N    int queens = f_mdiag[i];    result += ((queens * (queens - 1))/2);}
Actually, the way we computed conflicts, all the settings are mutually exclusive to others. Main diagonal conflicts does not affect the computation of secondary diagonal conflicts or the row conflicts and vice versa — even if the same queen may be considered once as a row member and later as a diagonal member. So we can update all the frequencies (row, main and secondary diagonals) all in one single loop (even at the time of generating the board configuration!) and also compute the result in another single loop.
Check this for example:
for (int i=1; i<=N; i++){    Queen[i] = generate_random(1, N); // generating queen position     int val = Queen[i];    f_row[val]++;               // updating frequency of rows    f_mdiag[val + i]++;         // and main diagonals    f_sdiag[N - val + 1 + i]++; // and secondary diagonals}int result = 0;for (int i=1; i<=(N+N); i++){ // this loop runs from 1 to N+N    int x = 0, y = 0, z = 0;    if (i <= N) x = f_row[i];  //number of queens in ith row    y = f_mdiag[i];   //number of queens in ith main diagonal    z = f_sdiag[i];   //number of queens in ith secondary diagonal    result += (x * (x - 1)) / 2; // adding to result    result += (y * (y - 1)) / 2;    result += (z * (z - 1)) / 2;}
By this approach, the time complexity is O(N). The space complexity is also O(N).
Check Python code for this approach.
Hope this helps you!
Let me know your thoughts :)