Permutations excluding repeated characters

前端 未结 5 1134
無奈伤痛
無奈伤痛 2021-02-06 01:43

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

The problem description is as follows -

<
相关标签:
5条回答
  • 2021-02-06 02:23

    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
    
    0 讨论(0)
  • 2021-02-06 02:28

    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
        }
    } 
    
    0 讨论(0)
  • 2021-02-06 02:40

    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.

    0 讨论(0)
  • 2021-02-06 02:41

    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

    0 讨论(0)
  • 2021-02-06 02:45

    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:

    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.

    0 讨论(0)
提交回复
热议问题