11

I'm working on a Free Code Camp problem - http://www.freecodecamp.com/challenges/bonfire-no-repeats-please

The problem description is as follows -

Return the number of total permutations of the provided string that don't have repeated consecutive letters. For example, 'aab' should return 2 because it has 6 total permutations, but only 2 of them don't have the same letter (in this case 'a') repeating.

I know I can solve this by writing a program that creates every permutation and then filters out the ones with repeated characters.

But I have this gnawing feeling that I can solve this mathematically.

First question then - Can I?

Second question - If yes, what formula could I use?

To elaborate further -

The example given in the problem is "aab" which the site says has six possible permutations, with only two meeting the non-repeated character criteria:

aab aba baa aab aba baa

The problem sees each character as unique so maybe "aab" could better be described as "a1a2b"

The tests for this problem are as follows (returning the number of permutations that meet the criteria)-

"aab" should return 2
"aaa" should return 0
"abcdefa" should return 3600
"abfdefa" should return 2640
"zzzzzzzz" should return 0

I have read through a lot of post about Combinatorics and Permutations and just seem to be digging a deeper hole for myself. But I really want to try to resolve this problem efficiently rather than brute force through an array of all possible permutations.

I posted this question on math.stackexchange - https://math.stackexchange.com/q/1410184/264492

The maths to resolve the case where only one character is repeated is pretty trivial - Factorial of total number of characters minus number of spaces available multiplied by repeated characters.

  • "aab" = 3! - 2! * 2! = 2
  • "abcdefa" = 7! - 6! * 2! = 3600

But trying to figure out the formula for the instances where more than one character is repeated has eluded me. e.g. "abfdefa"

Community
  • 1
  • 1
John Behan
  • 574
  • 5
  • 20

5 Answers5

17

This is a mathematical approach, that doesn't need to check all the possible strings.

Let's start with this string:

abfdefa

To find the solution we have to calculate the total number of permutations (without restrictions), and then subtract the invalid ones.


TOTAL OF PERMUTATIONS

We have to fill a number of positions, that is equal to the length of the original string. Let's consider each position a small box. So, if we have

abfdefa

which has 7 characters, there are seven boxes to fill. We can fill the first with any of the 7 characters, the second with any of the remaining 6, and so on. So the total number of permutations, without restrictions, is:

7 * 6 * 5 * 4 * 3 * 2 * 1 = 7! (= 5,040)


INVALID PERMUTATIONS

Any permutation with two equal characters side by side is not valid. Let's see how many of those we have. To calculate them, we'll consider that any character that has the same character side by side, will be in the same box. As they have to be together, why don't consider them something like a "compound" character? Our example string has two repeated characters: the 'a' appears twice, and the 'f' also appears twice.

Number of permutations with 'aa' Now we have only six boxes, as one of them will be filled with 'aa':

6 * 5 * 4 * 3 * 2 * 1 = 6!

We also have to consider that the two 'a' can be themselves permuted in 2! (as we have two 'a') ways. So, the total number of permutations with two 'a' together is:

6! * 2! (= 1,440)

Number of permutations with 'ff' Of course, as we also have two 'f', the number of permutations with 'ff' will be the same as the ones with 'aa':

6! * 2! (= 1,440)


OVERLAPS

If we had only one character repeated, the problem is finished, and the final result would be TOTAL - INVALID permutations.

But, if we have more than one repeated character, we have counted some of the invalid strings twice or more times. We have to notice that some of the permutations with two 'a' together, will also have two 'f' together, and vice versa, so we need to add those back. How do we count them? As we have two repeated characters, we will consider two "compound" boxes: one for occurrences of 'aa' and other for 'ff' (both at the same time). So now we have to fill 5 boxes: one with 'aa', other with 'ff', and 3 with the remaining 'b', 'd' and 'e'. Also, each of those 'aa' and 'bb' can be permuted in 2! ways. So the total number of overlaps is:

5! * 2! * 2! (= 480)


FINAL SOLUTION

The final solution to this problem will be:

TOTAL - INVALID + OVERLAPS

And that's:

7! - (2 * 6! * 2!) + (5! * 2! * 2!) = 5,040 - 2 * 1,440 + 480 = 2,640

Lurai
  • 171
  • 2
  • 7
  • That's a really easy to understand explanation, thanks for it. I'm going to go back and try this out on the problem. – John Behan Mar 24 '16 at 01:04
  • 1
    Hi, first I would like to thank you for your explanation which is very clear. I tried it but I've got a little issue when my word got 2 repetitions of the same letter plus some other letters like this 'aaab' for example. I always end up with a final number too high. Do you have any idea where does it come from ? Thanks in advance – Orodan May 17 '16 at 19:44
  • When you have some character repeated more than twice, you must take into account all the grouping possibilities when counting invalid permutations. In 'aaab', the total number of perm. is 24. Then, you have some invalid perm. with the three 'a' in the same box (AAAB and BAAA) multiplied by the permutations of the 3 'a', which are 6 (total 12). You also have to add all the invalid perm. in which you place two 'a' in the same box. Those are 3x2x1 = 12. So, we have 12 invalid perm. with groups of 3, plus 12 invalid perms. with groups of 2. Total - Invalid = 24 - (12 + 12) = 0 – Lurai May 19 '16 at 17:52
  • @Lurai, how do you make sure that when placing two A's in the same box you do not end up with three A's in a row, as far as I understood it, that scenario might happen but then your calculations seem to add up ... – utxeee Nov 06 '16 at 22:16
  • Thanks! I have one question. Under OVERLAPS, you wrote "Also, each of those 'aa' and 'bb' can be permuted in 2! ways." I was wondering if you meant 'aa' and 'ff'. I can't make make sense of 'bb' since there is only one b. – Alexander Køpke Jan 17 '17 at 06:51
  • First of all, thanks for your explanation, @Lurai . Then I have one question: what if a have 'aaabb' ? That's the only one I couldn't solve yet. – NerdKvothe Mar 24 '17 at 21:18
2

It seemed like a straightforward enough problem, but I spent hours on the wrong track before finally figuring out the correct logic. To find all permutations of a string with one or multiple repeated characters, while keeping identical characters seperated:

Start with a string like:

abcdabc

Seperate the first occurances from the repeats:

firsts: abcd
repeats: abc

Find all permutations of the firsts:

abcd abdc adbc adcb ...

Then, one by one, insert the repeats into each permutation, following these rules:

  • Start with the repeated character whose original comes first in the firsts
    e.g. when inserting abc into dbac, use b first
  • Put the repeat two places or more after the first occurance
    e.g. when inserting b into dbac, results are dbabc and dbacb
  • Then recurse for each result with the remaining repeated characters

I've seen this question with one repeated character, where the number of permutations of abcdefa where the two a's are kept seperate is given as 3600. However, this way of counting considers abcdefa and abcdefa to be two distinct permutations, "because the a's are swapped". In my opinion, this is just one permutation and its double, and the correct answer is 1800; the algorithm below will return both results.

function seperatedPermutations(str) {
    var total = 0, firsts = "", repeats = "";
    for (var i = 0; i < str.length; i++) {
        char = str.charAt(i);
        if (str.indexOf(char) == i) firsts += char; else repeats += char;
    }
    var firsts = stringPermutator(firsts);
    for (var i = 0; i < firsts.length; i++) {
        insertRepeats(firsts[i], repeats);
    }
    alert("Permutations of \"" + str + "\"\ntotal: " + (Math.pow(2, repeats.length) * total) + ", unique: " + total);

    // RECURSIVE CHARACTER INSERTER
    function insertRepeats(firsts, repeats) {
        var pos = -1;
        for (var i = 0; i < firsts.length, pos < 0; i++) {
            pos = repeats.indexOf(firsts.charAt(i));
        }
        var char = repeats.charAt(pos);
        for (var i = firsts.indexOf(char) + 2; i <= firsts.length; i++) {
            var combi = firsts.slice(0, i) + char + firsts.slice(i);
            if (repeats.length > 1) {
                insertRepeats(combi, repeats.slice(0, pos) + repeats.slice(pos + 1));
            } else {
                document.write(combi + "<BR>");
                ++total;
            }
        }
    }

    // STRING PERMUTATOR (after Filip Nguyen)
    function stringPermutator(str) {
        var fact = [1], permutations = [];
        for (var i = 1; i <= str.length; i++) fact[i] = i * fact[i - 1];
        for (var i = 0; i < fact[str.length]; i++) {
            var perm = "", temp = str, code = i;
            for (var pos = str.length; pos > 0; pos--) {
                var sel = code / fact[pos - 1];
                perm += temp.charAt(sel);
                code = code % fact[pos - 1];
                temp = temp.substring(0, sel) + temp.substring(sel + 1);
            }
            permutations.push(perm);
        }
        return permutations;
    }
}

seperatedPermutations("abfdefa");

A calculation based on this logic of the number of results for a string like abfdefa, with 5 "first" characters and 2 repeated characters (A and F) , would be:

  • The 5 "first" characters create 5! = 120 permutations
  • Each character can be in 5 positions, with 24 permutations each:
    A**** (24)
    *A*** (24)
    **A** (24)
    ***A* (24)
    ****A (24)
  • For each of these positions, the repeat character has to come at least 2 places after its "first", so that makes 4, 3, 2 and 1 places respectively (for the last position, a repeat is impossible). With the repeated character inserted, this makes 240 permutations:
    A***** (24 * 4)
    *A**** (24 * 3)
    **A*** (24 * 2)
    ***A** (24 * 1)
  • In each of these cases, the second character that will be repeated could be in 6 places, and the repeat character would have 5, 4, 3, 2, and 1 place to go. However, the second (F) character cannot be in the same place as the first (A) character, so one of the combinations is always impossible:
    A****** (24 * 4 * (0+4+3+2+1)) = 24 * 4 * 10 = 960
    *A***** (24 * 3 * (5+0+3+2+1)) = 24 * 3 * 11 = 792
    **A**** (24 * 2 * (5+4+0+2+1)) = 24 * 2 * 12 = 576
    ***A*** (24 * 1 * (5+4+3+0+1)) = 24 * 1 * 13 = 312
  • And 960 + 792 + 576 + 312 = 2640, the expected result.

Or, for any string like abfdefa with 2 repeats:
formula for 2 repeats
where F is the number of "firsts".

To calculate the total without identical permutations (which I think makes more sense) you'd divide this number by 2^R, where R is the number or repeats.

  • Note: the code example doesn't work for strings with triple repeats, like `abacab`. The logic is similar though, so you should be able to adapt it quite easily. – m69's been on strike for years Aug 30 '15 at 04:01
  • Thanks for this, it helped to clear my head on what I needed to do – John Behan Aug 31 '15 at 07:38
  • So you got three useful answers to help you avoid having to brute-force it, and in the end you brute-forced it? That doesn't sound very ambitious. – m69's been on strike for years Aug 31 '15 at 12:22
  • True and fair comment, in the end I had spent far too much time on the problem then I should have and was slowly getting there but still far enough away from understanding and building a mathematical solution that would be durable for all cases. In the end I gave up on the maths approach, would @old monk's approach be counted as brute-force? Anyway, thanks for your input, it was interesting. If you're interested, here's the code I came up with, I could probably improve it a bit by doing the filtering during the generatePerms function - https://jsfiddle.net/jjmax/1zj6yxdz/3/ – John Behan Aug 31 '15 at 13:12
  • @JJMax I'd say Old Monk's method is "brute force with a smart sidekick". A fully brute force answer would be this: http://stackoverflow.com/questions/31505427/javascript-permutations/31506400#31506400 Checking doubles before the whole sequence is formed, makes it a bit smarter. In the case of multiple repeats, skipping to the next permutation when one double has been found also makes it a bit smarter. But the algorithm still goes in without much of a plan. – m69's been on strike for years Aug 31 '15 at 13:43
  • Yes, my code also creates a list of permutations first, which is not really the best solution; I did it this way to make the code easier to read (and to keep Filip Nguyen's code seperate), but putting insertRepeats() inside the stringPermutator() loop would be better. – m69's been on strike for years Aug 31 '15 at 14:13
  • @JJMax when I think about the recursive brute-force way, I think of adding the next character to all preceding permutations. For example, if I have `"ff"` as a preceding permutation, I would not want to discard it because in the next function call, I might get an `"faf"` along with `"aff"` and `"ffa"`; and then in the next call, the others might become `"afbf"` and `"fbfa"`...etc. I'm not sure how you can really tell during the recursion when to discard the thread. – גלעד ברקן Aug 31 '15 at 16:39
  • @גלעדברקן I started working on an algorithm that would allow `ff` if enough letters were left over to seperate the repeat later on, but mark it `f*f`, to let further recursions know that the spot marked `*` needed to be filled. I got it to work, but abandoned this line of thought in favour of the code posted above, because it proved too difficult to find only unique permutations. – m69's been on strike for years Aug 31 '15 at 18:34
  • 1
    @JJMax I've expanded this idea to an algorithm which can handle any number of repeats per character; see my answer to this question: http://stackoverflow.com/questions/25285792/generate-all-permutations-of-a-list-without-adjacent-equal-elements/32366585#32366585 – m69's been on strike for years Sep 03 '15 at 03:57
1

Well I won't have any mathematical solution for you here.

I guess you know backtracking as I percieved from your answer.So you can use Backtracking to generate all permutations and skipping a particular permutation whenever a repeat is encountered. This method is called Backtracking and Pruning.

Let n be the the length of the solution string, say(a1,a2,....an). So during backtracking when only partial solution was formed, say (a1,a2,....ak) compare the values at ak and a(k-1). Obviously you need to maintaion a reference to a previous letter(here a(k-1))

If both are same then break out from the partial solution, without reaching to the end and start creating another permutation from a1.

Atul Yadav
  • 1,992
  • 1
  • 13
  • 15
  • After reading through several answers and trying to figure out the math I resolved to solving the problem using your suggestion. In the end I used Heap's Algorithim to create all permutations then filtered out repeats with a regular expression. – John Behan Aug 31 '15 at 07:35
  • You don't have to go that far dear. While creating permutation itself...if a repeat is found then skip that particular permutation. You don't need to create all and then filter – Atul Yadav Aug 31 '15 at 07:38
  • Wait for a while. I am kinda busy..I will post the code...when I will get a little time for it – Atul Yadav Aug 31 '15 at 07:39
  • Thanks, I was thinking about that approach – John Behan Aug 31 '15 at 08:17
1

Here's one way to think about it, which still seems a bit complicated to me: subtract the count of possibilities with disallowed neighbors.

For example abfdefa:

There are 6 ways to place "aa" or "ff" between the 5! ways to arrange the other five 
letters, so altogether 5! * 6 * 2, multiplied by their number of permutations (2).

Based on the inclusion-exclusion principle, we subtract those possibilities that include
both "aa" and "ff" from the count above: 3! * (2 + 4 - 1) choose 2 ways to place both
"aa" and "ff" around the other three letters, and we must multiply by the permutation
counts within (2 * 2) and between (2).

So altogether,

7! - (5! * 6 * 2 * 2 - 3! * (2 + 4 - 1) choose 2 * 2 * 2 * 2) = 2640

I used the formula for multiset combinations for the count of ways to place the letter pairs between the rest.

A generalizable way that might achieve some improvement over the brute force solution is to enumerate the ways to interleave the letters with repeats and then multiply by the ways to partition the rest around them, taking into account the spaces that must be filled. The example, abfdefa, might look something like this:

afaf / fafa  =>  (5 + 3 - 1) choose 3  // all ways to partition the rest
affa / faaf  =>  1 + 4 + (4 + 2 - 1) choose 2  // all three in the middle; two in the middle, one anywhere else; one in the middle, two anywhere else
aaff / ffaa  =>  3 + 1 + 1  // one in each required space, the other anywhere else; two in one required space, one in the other (x2)

Finally, multiply by the permutation counts, so altogether:

2 * 2! * 2! * 3! * ((5 + 3 - 1) choose 3 + 1 + 4 + (4 + 2 - 1) choose 2 + 3 + 1 + 1) = 2640
גלעד ברקן
  • 23,602
  • 3
  • 25
  • 61
  • This feels like the best answer for the question but I couldn't implement it and ended up programming a solution with Heap's Algorithim that created all permutations and then filtered out the repeats. – John Behan Aug 31 '15 at 07:38
  • 1
    @JJMax Yes, it answers the mathematical version for a simple string, but my hunch is the inclusion-exclusion might end up too complicated for larger strings. I imagine the site might be looking for a semi brute force. – גלעד ברקן Sep 01 '15 at 22:54
  • @JJMax I added a combinatoric way based on partitions rather than inclusion-exclusion that I think might be generalizable and offer improvement over brute force (I realize it might not be relevant to you anymore, though.) – גלעד ברקן Sep 01 '15 at 22:56
0

Thanks Lurai for great suggestion. It took a while and is a bit lengthy but here's my solution (it passes all test cases at FreeCodeCamp after converting to JavaScript of course) - apologies for crappy variables names (learning how to be a bad programmer too ;)) :D

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

public class PermAlone {

    public static int permAlone(String str) {
        int length = str.length();
        int total = 0;
        int invalid = 0;
        int overlap = 0;
        ArrayList<Integer> vals = new ArrayList<>();
        Map<Character, Integer> chars = new HashMap<>();

        // obtain individual characters and their frequencies from the string
        for (int i = 0; i < length; i++) {
            char key = str.charAt(i);
            if (!chars.containsKey(key)) {
                chars.put(key, 1);
            }
            else {
                chars.put(key, chars.get(key) + 1);
            }
        }

        // if one character repeated set total to 0
        if (chars.size() == 1 && length > 1) {
            total = 0;
        }
        // otherwise calculate total, invalid permutations and overlap
        else {
            // calculate total
            total = factorial(length);

            // calculate invalid permutations
            for (char key : chars.keySet()) {
                int len = 0;
                int lenPerm = 0;
                int charPerm = 0;
                int val = chars.get(key);
                int check = 1;
                // if val > 0 there will be more invalid permutations to  calculate
                if (val > 1) {
                    check = val;
                    vals.add(val); 
                }
                while (check > 1) {
                    len = length - check + 1; 
                    lenPerm = factorial(len);
                    charPerm = factorial(check);
                    invalid = lenPerm * charPerm;
                    total -= invalid; 
                    check--; 
                }   
            }

            // calculate overlaps
            if (vals.size() > 1) {
                overlap = factorial(chars.size());
                for (int val : vals) {
                    overlap *= factorial(val);
                }
            }

            total += overlap;
        }
        return total;
    }

    // helper function to calculate factorials - not recursive as I was running out of memory on the platform :?
    private static int factorial(int num) {
        int result = 1;
        if (num == 0 || num == 1) {
            result = num;
        }
        else {
            for (int i = 2; i <= num; i++) {
                result *= i;
            }
        }
        return result;
    }

    public static void main(String[] args) {
        System.out.printf("For %s: %d\n\n", "aab", permAlone("aab")); //   expected 2
        System.out.printf("For %s: %d\n\n", "aaa", permAlone("aaa")); // expected 0
        System.out.printf("For %s: %d\n\n", "aabb", permAlone("aabb")); // expected 8
        System.out.printf("For %s: %d\n\n", "abcdefa", permAlone("abcdefa")); // expected 3600
        System.out.printf("For %s: %d\n\n", "abfdefa", permAlone("abfdefa")); // expected 2640
        System.out.printf("For %s: %d\n\n", "zzzzzzzz", permAlone("zzzzzzzz")); // expected 0
        System.out.printf("For %s: %d\n\n", "a", permAlone("a")); // expected 1
        System.out.printf("For %s: %d\n\n", "aaab", permAlone("aaab")); // expected 0
        System.out.printf("For %s: %d\n\n", "aaabb", permAlone("aaabb")); // expected 12
        System.out.printf("For %s: %d\n\n", "abbc", permAlone("abbc")); //expected 12
    }
} 
Kasia B M
  • 49
  • 1
  • 3