7

Consider the problem described here (reproduced below.) Can some better known NP-complete problem be reduced to it?

The problem:

There are a row of houses. Each house can be painted with three colors: red, blue and green. The cost of painting each house with a certain color is different. You have to paint all the houses such that no two adjacent houses have the same color. You have to paint the houses with minimum cost. How would you do it?

Note: The cost of painting house 1 red is different from that of painting house 2 red. Each combination of house and color has its own cost.

Puneet
  • 753
  • 3
  • 9
  • 24
  • 1
    @Sudhanshu: In my answer :-) – Knoothe Mar 26 '13 at 07:42
  • You can restate this as an instance of the shortest path problem. Each house/color combination is a vertex in a graph. Vertices that represent neighbouring houses connect except when they are of the same color. There are also separate start and end vertices. You can now assign cost to each edge and find the shortest path from start to end vertex. – n. m. could be an AI Nov 23 '14 at 05:58
  • @n.m. - Would you pls put some pseudo-code for your proposal to solve it by shortest path for withed graph way! – KGhatak Oct 19 '16 at 19:20

2 Answers2

30

No, it is not NP-hard (technically, "NP-complete" is the wrong term for this, as this is not a decision problem).

Dynamic programming works, and gives you an O(n) time algorithm. (n is the number of houses).

You maintain three arrays R[1..n], B[1..n], G[1..n], where R[i] is the minimum cost of painting houses 1, 2, 3 ... i such that i is colored Red. Similarly B[i] is min cost of painting 1, 2 ... i with i being colored Blue, and G[i] is with i being colored Green.

You can compute R[i+1]: R[i+1]= (Cost of painting house i+1 Red) + minimum {G[i], B[i]}.

Similarly B[i+1] and G[i+1] can be computed.

Ultimately you take the minimum of R[n], B[n] and G[n].

This is O(n) time and O(n) space.

For example consider the following cost matrix for the houses:

House #: 1   2   3
R      : 1   4   6
G      : 2  100  2
B      : 3  100  4

The algorithm is building the following matrix to get the answer:

Houses : 0  1   2    3
R      : 0  1   6   107
G      : 0  2  101   8
B      : 0  3  101  10

From the last column, where all 3 houses are painted, we can find the minimum cost, which is equal to 8 and corresponds to the combination [Green (2), Red (4), Green (2)].

Quick Python:

# rc = costs of painting red, bc of blue and gc of green.
def min_paint(rc, bc, gc):
    n, i = len(rc), 1
    r, b, g = [0]*n, [0]*n, [0]*n
    r[0], b[0], g[0] = rc[0], bc[0], gc[0]
    while i < n:
        r[i] = rc[i] + min(b[i-1], g[i-1])
        b[i] = bc[i] + min(r[i-1], g[i-1])
        g[i] = gc[i] + min(b[i-1], r[i-1])
        i += 1

    return min(r, b, g)

def main():
    print min_paint([1, 4, 6], [2, 100, 2], [3, 100, 4])

if __name__ == "__main__":
    main()

The output will be ([1, 6, 107], [2, 101, 8], [3, 101, 10]), which is a cost matrix leading to the solution.

Max Voitko
  • 1,542
  • 1
  • 17
  • 32
Knoothe
  • 1,218
  • 8
  • 14
  • 1
    It prints ([1, 6, 107], [2, 101, 8], [3, 101, 10]). What does that specify? – user1247412 May 14 '13 at 18:57
  • 7
    Of course, more generally this solution is not so much O(n) as it is O(n*c). But with only 3 colors specificied, that's O(n). Either way, not an NP-hard problem. – RichardPlunkett Nov 22 '13 at 10:17
  • @user1247412 it's the result matrix, as described above the code in python. – shlatchz Feb 17 '17 at 22:51
  • 1
    @RichardPlunkett isn't it O(n*c^2)? Because for each color you also have to calculate the min of the other colors, which is a double `for` loop - thus c^2. – shlatchz Feb 17 '17 at 22:53
  • The solution needn't be O(n) in space – the whole history of analyzed colours assingment is not necessary, we just need to keep the last column. Of course that will add an overhead of copying a 'current' column to the 'previous' one, but that is still O(n) in time, while memory usage drops to O(1). – CiaPan Jul 27 '21 at 11:19
4

The explanation by @Knoothe is spot-on, but I believe the implementation can be improved - it uses O(n) additional space for storing previous values, but we can do it in O(1) space by noticing that we only need the previous value for each color, not the whole array of values. Here's how:

def min_paint(rc, bc, gc):
    # `r` is the min cost of painting the current house
    # using color red; similarly for `b` and `g`
    r, b, g = 0, 0, 0
    for cr, cb, cg in zip(rc, bc, gc):
        # new value for `r` is current cost for `r` plus the
        # minimum cost for painting the previous house in one
        # of the other two colors; similarly for `b` and `g`
        r, b, g = cr + min(b, g), cb + min(r, g), cg + min(r, b)
    # answer is the min cost for painting the last house
    return min(r, b, g)

For example:

min_paint([1, 4, 6], [2, 100, 2], [3, 100, 4])
=> 8
Óscar López
  • 232,561
  • 37
  • 312
  • 386
  • 1
    If only the min cost value is desired, this is fine, but if the problem asks for the actual color values for each of the houses, you can't throw away the previously computed DP values. – Abhijit Sarkar Mar 10 '19 at 04:46