docs: def solve(self):
          """Runs the Dancing Links/Algorithm X solver on the Sudoku.
  
          This code has been adapted from http://www.cs.mcgill.ca/~aassaf9/python/algorithm_x.html
  
          :return: List of lists with the same size as the input Sudoku, representing solutions.
          :rtype: list
  
          """
  
          R, C = int(math.sqrt(len(self.sudoku))), int(math.sqrt(len(self.sudoku[0])))
          N = R * C
  
          X = ([("rc", rc) for rc in product(range(N), range(N))] +
               [("rn", rn) for rn in product(range(N), range(1, N + 1))] +
               [("cn", cn) for cn in product(range(N), range(1, N + 1))] +
               [("bn", bn) for bn in product(range(N), range(1, N + 1))])
          Y = dict()
  
          for r, c, n in product(range(N), range(N), range(1, N + 1)):
              b = (r // R) * R + (c // C)  # Box number
              Y[(r, c, n)] = [
                  ("rc", (r, c)),
                  ("rn", (r, n)),
                  ("cn", (c, n)),
                  ("bn", (b, n))]
  
          X, Y = self._exact_cover(X, Y)
  
          for i, row in enumerate(self.sudoku):
              for j, n in enumerate(row):
                  if n:
                      self._select(X, Y, (i, j, n))
  
          for solution in self._solve(X, Y, []):
              grid = copy.deepcopy(self.sudoku)
              for (r, c, n) in solution:
                  grid[r][c] = n
              yield grid