I found this question intriguing, and since I teach myself Haskell, I decided to try my hand at implementing a solution in this language.
I was thinking about branching and snapping, but could not find a suitable way to bind solutions, so I just did the pruning, discarding boards that break the rules.
My algorithm works starting with an empty board. It puts every possible color of the tower in the first empty slot and in each case (each color), and then recursively calls itself. Recursive calls try each color in the second slot, again recursive, until the board is full.
As soon as each tower is located, I check the located tower and all its neighbors to make sure that they obey the rules, treating any empty neighbors as wild cards. Therefore, if the white tower has four empty neighbors, I consider this to be valid. If the placement is not valid, I am not recursing at that placement, effectively trimming the entire opportunity tree below it.
As the code is written, I generate a list of all possible solutions, and then look through the list to find the best one. In fact, thanks to Haskell's lazy evaluation, list items are generated as the search function needs them, and since they are never mentioned again, they immediately become available for garbage collection, so even using 5x5 memory is pretty small ( 2 MB).
Performance is pretty good. On my 2.1 GHz laptop, the compiled version of the program solves the 4x4 problem in ~ 50 seconds using a single core. I am now using the 5x5 example to find out how long it will take. Since functional code is fairly easy to parallelize, I will also experiment with parallel processing. There is a parallel Haskell compiler that not only extends the work to several cores, but also to several machines, and this is a very parallelizable problem.
Here is my code so far. I understand that you specified Java or PHP, and Haskell is completely different. If you want to play with it, you can change the definition of the variable "bnd" just above the bottom to set the size of the board. Just set it ((1,1), (x, y)), where x and y are the number of columns and rows, respectively.
import Array import Data.List
Also, remember that this is my first Haskell non-trivial program. I am sure that this can be done much more elegantly and concisely.
Update . Since there was still a lot of time to make 5x5 with 5 colors (I waited 12 hours and it was not over yet), I looked again at how to use the restriction to trim more search trees.
My first approach was to evaluate the upper border of a partially filled board, assuming that each empty cell is filled with a white tower. Then I modified the solution function to track the best result and ignore any board whose upper bound is less than the best result.
This helped some by reducing the 4x4x5 board from 23 to 15 seconds. To improve it, I changed the function of the upper bound to assume that each Void is filled with the best possible tower in accordance with the existing non-empty contents of the cell. This helped a lot, reducing the 4x4x5 time to 2 seconds.
Running on 5x5x5 took 2600 seconds, providing the following board:
GBGRB RBWYG YGRBR BWYGY GRBRB
with a score of 730.
I can make one more modification and find all the boards with the maximum score, and not just one.