If there is if (cubeMatrix.length != word.length()) return false;
, and every side letter of the cube is unique (i.e. no two sides of a cube have the same letter), then the time complexity of your algorithm is O(SN - S + 1 S!) when N >= S, and O(S N!) when N <= S. Here S is the number of cube sides, and N is the number of cubes.
In brief, you make the recursive call only then there is a unused letter in the word corresponding to the cube side letter, so, in the worst case the number of times you make the recursive call is not more than the number of word letters left unused. And the number of unused word letters decreases with increasing of the recursion depth, and eventually this number becomes less than the number of cube sides. That's why, in the final recursion depths the complexity becomes factorial.
A bit more details
Let's introduce f(n) that is how many times you call findWordExists
with cubeNumber
= n. We also introduce g(n) that is how many times findWordExists
with cubeNumber
= n recursively calls itself (but now with cubeNumber
= n + 1).
f(0) = 1, because you call findWordExists
non-recursively only once.
f(n) = f(n - 1) g(n - 1) when n > 0.
We know that g(n) = min { S, N - n }, because, as I already pointed out, findWordExists
is called recursively no more times than the number of letters left — the if (frequency > 0)
check is responsible for this — and the number of letters left equals to the number of cubes left, i.e. N - n.
Now we can calculate how many times findWordExists
is called in total:
f(0) + f(1) + ... + f(N) =
= 1 + g(0) + g(0) g(1) + ... + g(0) g(1) ... g(N - 1) =
= 1 + S + S2 + ... + SN - S + SN - S (S - 1) + SN - S (S - 1) (S - 2) + ... + SN - S (S - 1) (S - 2) ... 1 =
= O(SN - S S!).
But every findWordExists
call (except finals) iterate over each side, so we need to multiply the number of findWordExists
calls by the number of sides: S O(SN - S S!) = O(SN - S + 1 S!) — and that is our time complexity.
Better Algorithm
Actually, your problem is a bipartite matching problem, so there are much more efficient algorithms than brute force, e.g. Kuhn’s algorithm.
The complexity of Kuhn’s algorithm is O(N M), where N is the number of vertices, and M is the number of edges. In your case, N is the number of cubes, and M is just N2, so the complexity in your case could be O(N3). But you also need to iterate over all the sides of all the cubes, so if the number of cube sides is greater than N2, then the complexity is O(N S), where S is the number of cube sides.
Here is a possible implementation:
import java.util.*;
public class CubeFind {
private static boolean checkWord(char[][] cubes, String word) {
if (word.length() != cubes.length) {
return false;
}
List<Integer>[] cubeLetters = getCubeLetters(cubes, word);
int countMatched = new BipartiteMatcher().match(cubeLetters, word.length());
return countMatched == word.length();
}
private static List<Integer>[] getCubeLetters(char[][] cubes, String word) {
int cubeCount = cubes.length;
Set<Character>[] cubeLetterSet = new Set[cubeCount];
for (int i = 0; i < cubeCount; i++) {
cubeLetterSet[i] = new HashSet<>();
for (int j = 0; j < cubes[i].length; j++) {
cubeLetterSet[i].add(cubes[i][j]);
}
}
List<Integer>[] cubeLetters = new List[cubeCount];
for (int i = 0; i < cubeCount; i++) {
cubeLetters[i] = new ArrayList<>();
for (int j = 0; j < word.length(); j++) {
if (cubeLetterSet[i].contains(word.charAt(j))) {
cubeLetters[i].add(j);
}
}
}
return cubeLetters;
}
public static void main(String[] args) {
char[][] m = {{'e', 'a', 'l'} , {'x', 'h' , 'y'}, {'p' , 'q', 'l'}, {'l', 'h', 'e'}};
System.out.println("Expected true, Actual: " + CubeFind.checkWord(m, "hell"));
System.out.println("Expected true, Actual: " + CubeFind.checkWord(m, "help"));
System.out.println("Expected false, Actual: " + CubeFind.checkWord(m, "hplp"));
System.out.println("Expected false, Actual: " + CubeFind.checkWord(m, "hplp"));
System.out.println("Expected false, Actual: " + CubeFind.checkWord(m, "helll"));
System.out.println("Expected false, Actual: " + CubeFind.checkWord(m, "hel"));
}
}
class BipartiteMatcher {
private List<Integer>[] cubeLetters;
private int[] letterCube;
private boolean[] used;
int match(List<Integer>[] cubeLetters, int letterCount) {
this.cubeLetters = cubeLetters;
int cubeCount = cubeLetters.length;
int countMatched = 0;
letterCube = new int[letterCount];
Arrays.fill(letterCube, -1);
used = new boolean[cubeCount];
for (int u = 0; u < cubeCount; u++) {
if (dfs(u)) {
countMatched++;
Arrays.fill(used, false);
}
}
return countMatched;
}
boolean dfs(int u) {
if (used[u]) {
return false;
}
used[u] = true;
for (int i = 0; i < cubeLetters[u].size(); i++) {
int v = cubeLetters[u].get(i);
if (letterCube[v] == -1 || dfs(letterCube[v])) {
letterCube[v] = u;
return true;
}
}
return false;
}
}