This is a classic brute force problem. We have a space of possible solutions, namely all strings of 'c's and 'd's of a given length. We have to count all those that satisfy certain conditions. The method I advocate, due to its simplicity, is called generate-and-test. As it sounds, we will generate all potential strings, and test each for viability. To help explain the details of this process, I will illustrate two distinct methods that produce the correct results. In the first method, we use binary strings to represent possible stroke sequences. 1 will represent 'c', and 0 will represent 'd'. This works, since every sequence corresponds to exactly one binary string. For example, "cdcdcccddd" will correspond to "1010111000". The reason we introduce binary strings in the first place, is since we can loop through them in a very natural manner. Java code follows:
public int howMany(int numLength, String[] forbidden, int allowed) {Notice how digitMask will assume the values 1,10,100,1000,... in sequence. Using the bitwise-and operation, digitMask allows me to test whether a given bit is 1 in gen. After correspond has been built, I loop through looking for substrings that match elements of forbidden. My final if statement tests for whether the string has few enough forbidden sequences. Also, the char[] buffer was used to avoid building a lot of Strings. Even with that slight optimization, this method just barely runs in time (approx. 6 seconds on some test cases).
int ret = 0; //to be returned
for (int gen = 0; gen < (1<<numLength); gen++) { //Generate 000... through 111...
char[] buffer = new char[numLength];
for (int digitMask = 1,j=0; digitMask < (1<<numLength); digitMask *= 2,j++) { //loop through bits
if ( (digitMask & gen) != 0 ) buffer[j]= 'c'; //test mask against binary string
else buffer[j]= 'd';
}
String correspond = new String(buffer);
int countForb = 0; //counts forbidden sequences
for (int charPos = 0; charPos < correspond.length(); charPos++)
for (int forbIndex = 0; forbIndex < forbidden.length; forbIndex++)
if (correspond.startsWith(forbidden[forbIndex],charPos)) countForb++;
if (countForb < allowed) ret++;
}
return ret;
}
public int howMany(int numLength, String[] forbidden, int allowed) {The recursive function rec tries each possible character ('c' or 'd') for each position. It is also optimized to look for forbidden sequences at each recursion level. This way a generated string with a bad prefix will be ignored early on, thus accelerating the process. Understanding the recursive method may require more thought for some. When I think about how this function works, I consider how it behaves on a given index. Basically, when rec is called and index is k, it considers strings that have either 'c' or 'd' in position k. The concept lurking between the lines of code states that all strings can be divided in to 2 groups. Strings with 'c' in position k, and strings with 'd' in position k.
return rec(0,numLength,"",allowed, forbidden);
}
int rec(int index, int numLength, String curr, int allowed, String[] forbidden) {
if (index == numLength) return 1; //Base case
int ret = 0; //to be returned
for (char stroke = 'c'; stroke<='d'; stroke++) {
int newAllowed = allowed;
String newCurr = curr+stroke;
for (int forbIndex = 0; forbIndex < forbidden.length; forbIndex++) {
if (newCurr.endsWith(forbidden[forbIndex])) newAllowed++;
}
if (newAllowed <= 0) continue;
ret+=rec(index+1, numLength, newCurr, newAllowed, forbidden);
}
return ret;
}