1
\$\begingroup\$

I'm posting my code for a LeetCode problem. If you'd like to review, please do so. Thank you for your time!

Problem

A game on an undirected graph is played by two players, Mouse and Cat, who alternate turns.

The graph is given as follows: graph[a] is a list of all nodes b such that ab is an edge of the graph.

Mouse starts at node 1 and goes first, Cat starts at node 2 and goes second, and there is a Hole at node 0.

During each player's turn, they must travel along one edge of the graph that meets where they are. For example, if the Mouse is at node 1, it must travel to any node in graph[1].

Additionally, it is not allowed for the Cat to travel to the Hole (node 0.)

Then, the game can end in 3 ways:

  • If ever the Cat occupies the same node as the Mouse, the Cat wins.
  • If ever the Mouse reaches the Hole, the Mouse wins.
  • If ever a position is repeated (ie. the players are in the same position as a previous turn, and it is the same player's turn to move), the game is a draw.

Given a graph, and assuming both players play optimally, return 1 if the game is won by Mouse, 2 if the game is won by Cat, and 0 if the game is a draw.

Inputs

[[2,5],[3],[0,4,5],[1,4,5],[2,3],[0,2,3]]
[[1,3],[0],[3],[0,2]]
[[2,3],[3,4],[0,4],[0,1],[1,2]]
[[2,5],[3],[0,4,5],[1,4,5],[2,3],[0,2,3],[2,3],[3,4],[0,4],[0,1],[1,2]]
[[2,5],[3],[0,4,5],[1,4,5],[2,3],[0,2,3],[2,3],[3,4],[0,4],[0,1],[1,2],[6,4,3],[6,4,2],[1,4,3],[2,4,3]]

Outputs

0
1
1
2
2

Code

#include <vector>
#include <queue>

class Solution {

    static constexpr int mouse_turn = 0;
    static constexpr int cat_turn = 1;
    static constexpr int draw = 0;
    static constexpr int mouse_wins = 1;
    static constexpr int cat_wins = 2;

public:
    static const int catMouseGame(const std::vector<std::vector<int>> &graph) {
        const int size = graph.size();
        std::vector<std::vector<std::vector<int>>> states(
            size,
            std::vector<std::vector<int>>(size, std::vector<int>(2, draw))
        );

        std::vector<std::vector<std::vector<int>>> indegree_paths(
            size,
            std::vector<std::vector<int>>(size, std::vector<int>(2))
        );

        std::queue<std::vector<int>> queue;

        for (int i_ind = 0; i_ind < size; i_ind++) {
            if (i_ind) {
                states[0][i_ind][mouse_turn] = states[0][i_ind][cat_turn] = mouse_wins;
                queue.push(std::vector<int> {0, i_ind, mouse_turn, mouse_wins});
                queue.push(std::vector<int> {0, i_ind, cat_turn, mouse_wins});
                states[i_ind][i_ind][mouse_turn] = states[i_ind][i_ind][cat_turn] = cat_wins;
                queue.push(std::vector<int> {i_ind, i_ind, mouse_turn, cat_wins});
                queue.push(std::vector<int> {i_ind, i_ind, cat_turn, cat_wins});
            }

            for (int j_ind = 0; j_ind < size; j_ind++) {
                indegree_paths[i_ind][j_ind][mouse_turn] = graph[i_ind].size();
                indegree_paths[i_ind][j_ind][cat_turn] = graph[j_ind].size();

                if (find(graph[j_ind].begin(), graph[j_ind].end(), 0) != graph[j_ind].end()) {
                    indegree_paths[i_ind][j_ind][cat_turn]--;
                }
            }
        }

        while (!queue.empty()) {
            const int mouse_state = queue.front()[0];
            const int cat_state = queue.front()[1];
            const int turn = queue.front()[2];
            const int final_state = queue.front()[3];
            queue.pop();
            const int prev_turn = not turn;

            if (mouse_turn == prev_turn) {
                for (const auto &mouse : graph[mouse_state]) {
                    if (states[mouse][cat_state][prev_turn] == draw) {
                        if (mouse_wins == final_state) {
                            states[mouse][cat_state][prev_turn] = mouse_wins;

                        } else {
                            indegree_paths[mouse][cat_state][prev_turn]--;

                            if (not indegree_paths[mouse][cat_state][prev_turn]) {
                                states[mouse][cat_state][prev_turn] = cat_wins;
                            }
                        }

                        if (states[mouse][cat_state][prev_turn] != draw) {
                            queue.push(
                                std::vector<int> {mouse, cat_state, prev_turn, states[mouse][cat_state][prev_turn]}
                            );
                        }
                    }
                }

            } else {
                for (const auto &cat : graph[cat_state]) {
                    if (not cat) {
                        continue;
                    }

                    if (states[mouse_state][cat][prev_turn] == draw) {
                        if (cat_wins == final_state) {
                            states[mouse_state][cat][prev_turn] = cat_wins;

                        } else {
                            indegree_paths[mouse_state][cat][prev_turn]--;

                            if (not indegree_paths[mouse_state][cat][prev_turn]) {
                                states[mouse_state][cat][prev_turn] = mouse_wins;
                            }
                        }


                        if (states[mouse_state][cat][prev_turn] != draw) {
                            queue.push(
                                std::vector<int> {mouse_state, cat, prev_turn, states[mouse_state][cat][prev_turn]}
                            );
                        }
                    }
                }
            }
        }

        return states[1][2][mouse_turn];
    }

};

References

\$\endgroup\$

2 Answers 2

2
\$\begingroup\$
    for (int i_ind = 0; i_ind < size; i_ind++) {
        if (i_ind) {

is a known anti-pattern. if (i_ind) is guarantied to fail at the first iteration, and is guaranteed to succeed at all the rest of them. Move the special case out of the loop, and start the loop with int i_ind = 1;. To keep the code DRY, make the block

        for (int j_ind = 0; j_ind < size; j_ind++) {
            indegree_paths[i_ind][j_ind][mouse_turn] = graph[i_ind].size();
            indegree_paths[i_ind][j_ind][cat_turn] = graph[j_ind].size();

            if (find(graph[j_ind].begin(), graph[j_ind].end(), 0) != graph[j_ind].end()) {
                indegree_paths[i_ind][j_ind][cat_turn]--;
            }
        }

into a function. As a side note, it is always a good idea to avoid naked loops.


Speaking of DRY, the if (mouse_turn == prev_turn) and else clauses are pretty much identical. You should try to factor them into a function as well. Let me repeat the no-naked-loops mantra.

It is very unclear why he else clause tests for not cat, but the if clause does not have a symmetric not mouse test. That said, I don't see how these tests may ever fail.


I am not sure that std::vector holding 4 values of a completely unrelated nature is a right approach. Names are better than indices. Consider a

struct state {
    int mouse_state;
    int cat_state;
    int turn; // Even maybe `bool turn;`
    int final_state;
};

I do not endorse using not.

\$\endgroup\$
0
2
\$\begingroup\$

Code Review

static constexpr int mouse_turn = 0;
static constexpr int cat_turn = 1;
static constexpr int draw = 0;
static constexpr int mouse_wins = 1;
static constexpr int cat_wins = 2;

Prefer to use enum for this:

enum Turn  {Mouse, Cat};
enum State {Draw, MouseWins, CatWins};
\$\endgroup\$

Not the answer you're looking for? Browse other questions tagged or ask your own question.