Skip to content
dsa450for five year olds

Graph · 36 of 43

M-Colouring Problem

With only m crayons, decide whether a map can be coloured so that no two neighbouring countries match.

Simple way
time O(m^V * (V + E))
room O(V)
Clever way
time O(m^V * V)
room O(V)

The problem, simply

You get a graph with V countries (dots) and some borders (lines), and a number m. Can you give every country one of m colours so that two countries sharing a border never have the same colour?

Answer true or false. Many versions also ask you to print one colouring that works.

A story to remember it

Picture it

graph
01234

Five countries with m = 3. Countries 0, 1, 4 touch each other, and so do 1, 3, 4. Country 2 only touches 3.

Examples

The picture above

In
V = 5, m = 3, edges = [[0,1],[0,4],[1,3],[1,4],[2,3],[3,4]]
Out
true: [red, blue, blue, red, green]

The first try paints country 2 red, which forces 3 to green and leaves nothing for 4. After backing up, 2 becomes blue and everything fits.

A square with one diagonal

In
V = 4, m = 3, edges = [[0,1],[1,2],[2,3],[3,0],[0,2]]
Out
true: [red, blue, green, blue]

Country 3 touches 0 (red) and 2 (green), so it takes blue. It cannot be red, because 3 and 0 share a border.

Too few crayons

In
V = 4, m = 2, edges = [[0,1],[1,2],[2,3],[3,0],[0,2]]
Out
false

Countries 0, 1 and 2 all touch each other, so they need three different colours.

Everyone touches everyone

In
V = 4, m = 3, all 6 pairs are borders
Out
false

Four countries that all touch need four colours.

No borders

In
V = 3, m = 1, edges = []
Out
true

With no borders, one crayon is enough.

Way 1: the simple way

Try every way to colour the map, then check each one. With m colours and V countries there are m^V ways.

  1. Give country 0 each colour in turn, and for each, give country 1 each colour, and so on, without looking at borders.
  2. When all countries have a colour, check every border. If no border joins two equal colours, answer true.
  3. If no way passes, answer false.
java
import java.util.*;

class Solution {
    public boolean graphColoring(int V, int[][] edges, int m) {
        return tryAll(new int[V], 0, V, edges, m);
    }

    private boolean tryAll(int[] colour, int u, int V, int[][] edges, int m) {
        if (u == V) {                                  // everyone has a colour: check now
            for (int[] e : edges) if (colour[e[0]] == colour[e[1]]) return false;
            return true;
        }
        for (int c = 1; c <= m; c++) {
            colour[u] = c;                             // no checking yet
            if (tryAll(colour, u + 1, V, edges, m)) return true;
        }
        return false;
    }
}

How long it takes: m^V full colourings, each checked against all borders, O(m^V * (V + E)). Extra room: the colour array and the recursion, O(V).

Way 2: the clever way

Check as you go. Before giving a country a colour, look at its neighbours. If one of them already has that colour, do not even try it. A bad choice is caught at the country where it goes wrong, not after the whole map is painted, so whole branches of tries are skipped. This is backtracking.

  1. solve(u): if u == V, everyone is coloured, return true.
  2. For each colour c from 1 to m: if no neighbour of u has c, set colour[u] = c and call solve(u + 1). If it returns true, return true.
  3. Otherwise erase colour[u] and try the next colour.
  4. If no colour works, return false so the country before can change.

Paint, get stuck, erase, try again

01 / 12

graph
01234
treechoices tried
start

We have 3 crayons: red, blue and green. Colour the countries in order 0 to 4. The choice tree records every crayon we put down.

m
3
  • looking now
  • checking
  • done
  • answer
  • rejected
  • path
java
import java.util.*;

class Solution {
    public boolean graphColoring(int V, int[][] edges, int m) {
        List<List<Integer>> adj = new ArrayList<>();
        for (int i = 0; i < V; i++) adj.add(new ArrayList<>());
        for (int[] e : edges) {
            adj.get(e[0]).add(e[1]);
            adj.get(e[1]).add(e[0]);
        }
        return solve(adj, new int[V], 0, m);          // 0 means no colour yet
    }

    private boolean solve(List<List<Integer>> adj, int[] colour, int u, int m) {
        if (u == colour.length) return true;          // every country is coloured
        for (int c = 1; c <= m; c++) {
            if (safe(adj, colour, u, c)) {
                colour[u] = c;
                if (solve(adj, colour, u + 1, m)) return true;
                colour[u] = 0;                        // erase and try the next crayon
            }
        }
        return false;                                 // stuck: let the caller change
    }

    private boolean safe(List<List<Integer>> adj, int[] colour, int u, int c) {
        for (int v : adj.get(u)) if (colour[v] == c) return false;
        return true;
    }
}

How long it takes: in the worst case the search still tries about m^V paths, and each step checks one country's neighbours, so O(m^V * V). In practice the early checks cut away most branches. Extra room: the colour array and the recursion, O(V).

Watch out for

  • A graph in pieces. Colouring country by country in number order handles every piece, because every country gets its turn.
  • Counting colours from 0 while also using 0 for "no colour". Use 1 to m for colours, or -1 for empty.
  • Borders listed once. Add each border to both countries' lists.
  • When m >= V, the answer is always true: every country can have its own colour. When m == 1, the answer is true only if there are no borders.

What interviewers ask next

  • How do you make it faster in practice? Colour the country with the fewest colours left first (most constrained first), and colour countries with many neighbours early.
  • When m = 2, is there a fast way? Yes. Breadth first search can check two-colouring (bipartite) in O(V + E). For m >= 3 no fast method is known; it is NP complete.
  • How do you find the smallest m that works? Try m = 1, 2, 3, ... with this search and stop at the first true. That number is the chromatic number.
  • Is greedy colouring enough? Greedy is fast and always valid, but it can use more colours than needed, so it cannot prove that m colours are impossible.
  • Sudoku and exam timetables are the same idea: each cell or exam is a country, each conflict is a border, and the digits or time slots are the colours.

Remember this

Grown-up words: countries are vertices, borders are edges, crayons are colours, checking a colour before using it is the safety check, erasing and retrying is backtracking, and the smallest number of crayons that works is the chromatic number.