I would like to understand how to solve the Codility ArrayRecovery challenge, but I don't even know what branch of knowledge to consult. Is it combinatorics, optimization, computer science, set theory, or something else?
Edit:
The branch of knowledge to consult is constraint programming, particularly constraint propagation. You also need some combinatorics to know that if you take k numbers at a time from the range [1..n], with the restriction that no number can be bigger than the one before it, that works out to be
(n+k-1)!/k!(n-1)! possible combinations
which is the same as the number of combinations with replacements of n things taken k at a time, which has the mathematical notation . You can read about why it works out like that here.
Peter Norvig provides an excellent example of how to solve this kind of problem with his Sudoku solver.
You can read the full description of the ArrayRecovery problem via the link above. The short story is that there is an encoder that takes a sequence of integers in the range 1 up to some given limit (say 100 for our purposes) and for each element of the input sequence outputs the most recently seen integer that is smaller than the current input, or 0 if none exists.
input 1, 2, 3, 4 => output 0, 1, 2, 3
input 2, 4, 3 => output 0, 2, 2
The full task is, given the output (and the range of allowable input), figure out how many possible inputs could have generated it. But before I even get to that calculation, I'm not confident about how to even approach formulating the equation. That is what I am asking for help with. (Of course a full solution would be welcome, too, if it is explained.)
I just look at some possible outputs and wonder. Here are some sample encoder outputs and the inputs I can come up with, with * meaning any valid input and something like > 4 meaning any valid input greater than 4. If needed, inputs are referred to as A1, A2, A3, ... (1-based indexing)
Edit #2
Part of the problem I was having with this challenge is that I did not manually generate the exactly correct sets of possible inputs for an output. I believe the set below is correct now. Look at this answer's edit history if you want to see my earlier mistakes.
output #1: 0, 0, 0, 4
possible inputs: [>= 4, A1 >= * >= 4, 4, > 4]
output #2: 0, 0, 0, 2, 3, 4 # A5 ↴ See more in discussion below
possible inputs: [>= 2, A1 >= * >=2, 2, 3, 4, > 4]
output #3: 0, 0, 0, 4, 3, 1
possible inputs: none # [4, 3, 1, 1 >= * > 4, 4, > 1] but there is no number 1 >= * > 4
The second input sequence is very tightly constrained compared to the first just by adding 2 more outputs. The third sequence is so constrained as to be impossible.
But the set of constraints on A5 in example #2 is a bit harder to articulate. Of course A5 > O5, that is the basic constraint on all the inputs. But any output > A4 and after O5 has to appear in the input after A4, so A5 has to be an element of the set of numbers that comes after A5 that is also > A4. Since there is only 1 such number (A6 == 4), A5 has to be it, but it gets more complicated if there is a longer string of numbers that follow. (Editor's note: actually it doesn't.)
As the output set gets longer, I worry these constraints just get more complicated and harder to get right. I cannot think of any data structures for efficiently representing these in a way that leads to efficiently calculating the number of possible combinations. I also don't quite see how to algorithmically add constraint sets together.
Here are the constraints I see so far for any given An
An > On
An <= min(Set of other possible numbers from O1 to n-1 > On). How to define the set of possible numbers greater than On?
Numbers greater than On that came after the most recent occurrence of On in the input
An >= max(Set of other possible numbers from O1 to n-1 < On). How to define the set of possible numbers less than On?
Actually this set is empty because On is, by definition, the largest possible number from the previous input sequence. (Which it not to say it is strictly the largest number from the previous input sequence.)
Any number smaller than On that came before the last occurrence of it in the input would be ineligible because of the "nearest" rule. No numbers smaller that On could have occurred after the most recent occurrence because of the "nearest" rule and because of the transitive property: if Ai < On and Aj < Ai then Aj < On
Then there is the set theory:
An must be an element of the set of unaccounted-for elements of the set of On+1 to Om, where m is the smallest m > n such that Om < On. Any output after such Om and larger than Om (which An is) would have to appear as or after Am.
An element is unaccounted-for if it is seen in the output but does not appear in the input in a position that is consistent with the rest of the output. Obviously I need a better definition than this in order to code and algorithm to calculate it.
It seems like perhaps some kind of set theory and/or combinatorics or maybe linear algebra would help with figuring out the number of possible sequences that would account for all of the unaccounted-for outputs and fit the other constraints. (Editor's note: actually, things never get that complicated.)
The code below passes all of Codility's tests. The OP added a main function to use it on the command line.
The constraints are not as complex as the OP thinks. In particular, there is never a situation where you need to add a restriction that an input be an element of some set of specific integers seen elsewhere in the output. Every input position has a well-defined minimum and maximum.
The only complication to that rule is that sometimes the maximum is "the value of the previous input" and that input itself has a range. But even then, all the values like that are consecutive and have the same range, so the number of possibilities can be calculated with basic combinatorics, and those inputs as a group are independent of the other inputs (which only serve to set the range), so the possibilities of that group can be combined with the possibilities of other input positions by simple multiplication.
Algorithm overview
The algorithm makes a single pass through the output array updating the possible numbers of input arrays after every span, which is what I am calling repetitions of numbers in the output. (You might say maximal subsequences of the output where every element is identical.) For example, for output 0,1,1,2 we have three spans: 0, 1,1 and 2. When a new span begins, the number of possibilities for the previous span is calculated.
This decision was based on a few observations:
For spans longer than 1 in length, the minimum value of the input
allowed in the first position is whatever the value is of the input
in the second position. Calculating the number of possibilities of a
span is straightforward combinatorics, but the standard formula
requires knowing the range of the numbers and the length of the span.
Every time the value of the
output changes (and a new span beings), that strongly constrains the value of the previous span:
When the output goes up, the only possible reason is that the previous input was the value of the new, higher output and the input corresponding to the position of the new, higher output, was even higher.
When an output goes down, new constraints are established, but those are a bit harder to articulate. The algorithm stores stairs (see below) in order to quantify the constraints imposed when the output goes down
The aim here was to confine the range of possible values for every span. Once we do that accurately, calculating the number of combinations is straightforward.
Because the encoder backtracks looking to output a number that relates to the input in 2 ways, both smaller and closer, we know we can throw out numbers that are larger and farther away. After a small number appears in the output, no larger number from before that position can have any influence on what follows.
So to confine these ranges of input when the output sequence decreased, we need to store stairs - a list of increasingly larger possible values for the position in the original array. E.g for 0,2,5,7,2,4 stairs build up like this: 0, 0,2, 0,2,5, 0,2,5,7, 0,2, 0,2,4.
Using these bounds we can tell for sure that the number in the position of the second 2 (next to last position in the example) must be in (2,5], because 5 is the next stair. If the input were greater than 5, a 5 would have been output in that space instead of a 2. Observe, that if the last number in the encoded array was not 4, but 6, we would exit early returning 0, because we know that the previous number couldn't be bigger than 5.
The complexity is O(n*lg(min(n,m))).
Functions
CombinationsWithReplacement - counts number of combinations with replacements of size k from n numbers. E.g. for (3, 2) it counts 3,3, 3,2, 3,1, 2,2, 2,1, 1,1, so returns 6 It is the same as choose(n - 1 + k, n - 1).
nextBigger - finds next bigger element in a range. E.g. for 4 in sub-array 1,2,3,4,5 it returns 5, and in sub-array 1,3 it returns its parameter Max.
countSpan (lambda) - counts how many different combinations a span we have just passed can have. Consider span 2,2 for 0,2,5,7,2,2,7.
When curr gets to the final position, curr is 7 and prev is the final 2 of the 2,2 span.
It computes maximum and minimum possible values of the prev span. At this point stairs consist of 2,5,7 then maximum possible value is 5 (nextBigger after 2 in the stair 2,5,7). A value of greater than 5 in this span would have output a 5, not a 2.
It computes a minimum value for the span (which is the minimum value for every element in the span), which is prev at this point, (remember curr at this moment equals to 7 and prev to 2). We know for sure that in place of the final 2 output, the original input has to have 7, so the minimum is 7. (This is a consequence of the "output goes up" rule. If we had 7,7,2 and curr would be 2 then the minimum for the previous span (the 7,7) would be 8 which is prev + 1.
It adjusts the number of combinations. For a span of length L with a range of n possibilities (1+max-min), there are possibilities, with k being either L or L-1 depending on what follows the span.
For a span followed by a larger number, like 2,2,7, k = L - 1 because the last position of the 2,2 span has to be 7 (the value of the first number after the span).
For a span followed by a smaller number, like 7,7,2, k = L because
the last element of 7,7 has no special constraints.
Finally, it calls CombinationsWithReplacement to find out the number of branches (or possibilities), computes new res partial results value (remainder values in the modulo arithmetic we are doing), and returns new res value and max for further handling.
solution - iterates over the given Encoder Output array. In the main loop, while in a span it counts the span length, and at span boundaries it updates res by calling countSpan and possibly updates the stairs.
If the current span consists of a bigger number than the previous one, then:
Check validity of the next number. E.g 0,2,5,2,7 is invalid input, becuase there is can't be 7 in the next-to-last position, only 3, or 4, or 5.
It updates the stairs. When we have seen only 0,2, the stairs are 0,2, but after the next 5, the stairs become 0,2,5.
If the current span consists of a smaller number then the previous one, then:
It updates stairs. When we have seen only 0,2,5, our stairs are 0,2,5, but after we have seen 0,2,5,2 the stairs become 0,2.
After the main loop it accounts for the last span by calling countSpan with -1 which triggers the "output goes down" branch of calculations.
normalizeMod, extendedEuclidInternal, extendedEuclid, invMod - these auxiliary functions help to deal with modulo arithmetic.
For stairs I use storage for the encoded array, as the number of stairs never exceeds current position.
#include <algorithm>
#include <cassert>
#include <vector>
#include <tuple>
const int Modulus = 1'000'000'007;
int CombinationsWithReplacement(int n, int k);
template <class It>
auto nextBigger(It begin, It end, int value, int Max) {
auto maxIt = std::upper_bound(begin, end, value);
auto max = Max;
if (maxIt != end) {
max = *maxIt;
}
return max;
}
auto solution(std::vector<int> &B, const int Max) {
auto res = 1;
const auto size = (int)B.size();
auto spanLength = 1;
auto prev = 0;
// Stairs is the list of numbers which could be smaller than number in the next position
const auto stairsBegin = B.begin();
// This includes first entry (zero) into stairs
// We need to include 0 because we can meet another zero later in encoded array
// and we need to be able to find in stairs
auto stairsEnd = stairsBegin + 1;
auto countSpan = [&](int curr) {
const auto max = nextBigger(stairsBegin, stairsEnd, prev, Max);
// At the moment when we switch from the current span to the next span
// prev is the number from previous span and curr from current.
// E.g. 1,1,7, when we move to the third position cur = 7 and prev = 1.
// Observe that, in this case minimum value possible in place of any of 1's can be at least 2=1+1=prev+1.
// But if we consider 7, then we have even more stringent condition for numbers in place of 1, it is 7
const auto min = std::max(prev + 1, curr);
const bool countLast = prev > curr;
const auto branchesCount = CombinationsWithReplacement(max - min + 1, spanLength - (countLast ? 0 : 1));
return std::make_pair(res * (long long)branchesCount % Modulus, max);
};
for (int i = 1; i < size; ++i) {
const auto curr = B[i];
if (curr == prev) {
++spanLength;
}
else {
int max;
std::tie(res, max) = countSpan(curr);
if (prev < curr) {
if (curr > max) {
// 0,1,5,1,7 - invalid because number in the fourth position lies in [2,5]
// and so in the fifth encoded position we can't something bigger than 5
return 0;
}
// It is time to possibly shrink stairs.
// E.g if we had stairs 0,2,4,9,17 and current value is 5,
// then we no more interested in 9 and 17, and we change stairs to 0,2,4,5.
// That's because any number bigger than 9 or 17 also bigger than 5.
const auto s = std::lower_bound(stairsBegin, stairsEnd, curr);
stairsEnd = s;
*stairsEnd++ = curr;
}
else {
assert(curr < prev);
auto it = std::lower_bound(stairsBegin, stairsEnd, curr);
if (it == stairsEnd || *it != curr) {
// 0,5,1 is invalid sequence because original sequence lloks like this 5,>5,>1
// and there is no 1 in any of the two first positions, so
// it can't appear in the third position of the encoded array
return 0;
}
}
spanLength = 1;
}
prev = curr;
}
res = countSpan(-1).first;
return res;
}
template <class T> T normalizeMod(T a, T m) {
if (a < 0) return a + m;
return a;
}
template <class T> std::pair<T, std::pair<T, T>> extendedEuclidInternal(T a, T b) {
T old_x = 1;
T old_y = 0;
T x = 0;
T y = 1;
while (true) {
T q = a / b;
T t = a - b * q;
if (t == 0) {
break;
}
a = b;
b = t;
t = x; x = old_x - x * q; old_x = t;
t = y; y = old_y - y * q; old_y = t;
}
return std::make_pair(b, std::make_pair(x, y));
}
// Returns gcd and Bezout's coefficients
template <class T> std::pair<T, std::pair<T, T>> extendedEuclid(T a, T b) {
if (a > b) {
if (b == 0) return std::make_pair(a, std::make_pair(1, 0));
return extendedEuclidInternal(a, b);
}
else {
if (a == 0) return std::make_pair(b, std::make_pair(0, 1));
auto p = extendedEuclidInternal(b, a);
std::swap(p.second.first, p.second.second);
return p;
}
}
template <class T> T invMod(T a, T m) {
auto p = extendedEuclid(a, m);
assert(p.first == 1);
return normalizeMod(p.second.first, m);
}
int CombinationsWithReplacement(int n, int k) {
int res = 1;
for (long long i = n; i < n + k; ++i) {
res = res * i % Modulus;
}
int denom = 1;
for (long long i = k; i > 0; --i) {
denom = denom * i % Modulus;
}
res = res * (long long)invMod(denom, Modulus) % Modulus;
return res;
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Only the above is needed for the Codility challenge. Below is to run on the command line.
//
// Compile with: gcc -std=gnu++14 -lc++ -lstdc++ array_recovery.cpp
//
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#include <string.h>
// Usage: 0 1 2,3, 4 M
// Last arg is M, the max value for an input.
// Remaining args are B (the output of the encoder) separated by commas and/or spaces
// Parentheses and brackets are ignored, so you can use the same input form as Codility's tests: ([1,2,3], M)
int main(int argc, char* argv[]) {
int Max;
std::vector<int> B;
const char* delim = " ,[]()";
if (argc < 2 ) {
printf("Usage: %s M 0 1 2,3, 4... \n", argv[0]);
return 1;
}
for (int i = 1; i < argc; i++) {
char* parse;
parse = strtok(argv[i], delim);
while (parse != NULL)
{
B.push_back(atoi(parse));
parse = strtok (NULL, delim);
}
}
Max = B.back();
B.pop_back();
printf("%d\n", solution(B, Max));
return 0;
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Only the above is needed for the Codility challenge. Below is to run on the command line.
//
// Compile with: gcc -std=gnu++14 -lc++ -lstdc++ array_recovery.cpp
//
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#include <string.h>
// Usage: M 0 1 2,3, 4
// first arg is M, the max value for an input.
// remaining args are B (the output of the encoder) separated by commas and/or spaces
int main(int argc, char* argv[]) {
int Max;
std::vector<int> B;
const char* delim = " ,";
if (argc < 3 ) {
printf("Usage: %s M 0 1 2,3, 4... \n", argv[0]);
return 1;
}
Max = atoi(argv[1]);
for (int i = 2; i < argc; i++) {
char* parse;
parse = strtok(argv[i], delim);
while (parse != NULL)
{
B.push_back(atoi(parse));
parse = strtok (NULL, delim);
}
}
printf("%d\n", solution(B, Max));
return 0;
}
Let's see an example:
Max = 5
Array is
0 1 3 0 1 1 3
1
1 2..5
1 3 4..5
1 3 4..5 1
1 3 4..5 1 2..5
1 3 4..5 1 2..5 >=..2 (sorry, for a cumbersome way of writing)
1 3 4..5 1 3..5 >=..3 4..5
Now count:
1 1 2 1 3 2 which amounts to 12 total.
Here's an idea. One known method to construct the output is to use a stack. We pop it while the element is greater or equal, then output the smaller element if it exists, then push the greater element onto the stack. Now what if we attempted to do this backwards from the output?
First we'll demonstrate the stack method using the c∅dility example.
[2, 5, 3, 7, 9, 6]
2: output 0, stack [2]
5: output 2, stack [2,5]
3: pop 5, output, 2, stack [2,3]
7: output 3, stack [2,3,7]
... etc.
Final output: [0, 2, 2, 3, 7, 3]
Now let's try reconstruction! We'll use stack both as the imaginary stack and as the reconstituted input:
(Input: [2, 5, 3, 7, 9, 6])
Output: [0, 2, 2, 3, 7, 3]
* Something >3 that reached 3 in the stack
stack = [3, 3 < *]
* Something >7 that reached 7 in the stack
but both of those would've popped before 3
stack = [3, 7, 7 < x, 3 < * <= x]
* Something >3, 7 qualifies
stack = [3, 7, 7 < x, 3 < * <= x]
* Something >2, 3 qualifies
stack = [2, 3, 7, 7 < x, 3 < * <= x]
* Something >2 and >=3 since 3 reached 2
stack = [2, 2 < *, 3, 7, 7 < x, 3 < * <= x]
Let's attempt your examples:
Example 1:
[0, 0, 0, 2, 3, 4]
* Something >4
stack = [4, 4 < *]
* Something >3, 4 qualifies
stack = [3, 4, 4 < *]
* Something >2, 3 qualifies
stack = [2, 3, 4, 4 < *]
* The rest is non-increasing with lowerbound 2
stack = [y >= x, x >= 2, 2, 3, 4, >4]
Example 2:
[0, 0, 0, 4]
* Something >4
stack [4, 4 < *]
* Non-increasing
stack = [z >= y, y >= 4, 4, 4 < *]
Calculating the number of combinations is achieved by multiplying together the possibilities for all the sections. A section is either a bounded single cell; or a bound, non-increasing subarray of one or more cells. To calculate the latter we use the multi-choose binomial, (n + k - 1) choose (k - 1). Consider that we can express the differences between the cells of a bound, non-increasing sequence of 3 cells as:
(ub - cell_3) + (cell_3 - cell_2) + (cell_2 - cell_1) + (cell_1 - lb) = ub - lb
Then the number of ways to distribute ub - lb into (x + 1) cells is
(n + k - 1) choose (k - 1)
or
(ub - lb + x) choose x
For example, the number of non-increasing sequences between
(3,4) in two cells is (4 - 3 + 2) choose 2 = 3: [3,3] [4,3] [4,4]
And the number of non-increasing sequences between
(3,4) in three cells is (4 - 3 + 3) choose 3 = 4: [3,3,3] [4,3,3] [4,4,3] [4,4,4]
(Explanation attributed to Brian M. Scott.)
Rough JavaScript sketch (the code is unreliable; it's only meant to illustrate the encoding. The encoder lists [lower_bound, upper_bound], or a non-increasing sequence as [non_inc, length, lower_bound, upper_bound]):
function f(A, M){
console.log(JSON.stringify(A), M);
let i = A.length - 1;
let last = A[i];
let s = [[last,last]];
if (A[i-1] == last){
let d = 1;
s.splice(1,0,['non_inc',d++,last,M]);
while (i > 0 && A[i-1] == last){
s.splice(1,0,['non_inc',d++,last,M]);
i--
}
} else {
s.push([last+1,M]);
i--;
}
if (i == 0)
s.splice(0,1);
for (; i>0; i--){
let x = A[i];
if (x < s[0][0])
s = [[x,x]].concat(s);
if (x > s[0][0]){
let [l, _l] = s[0];
let [lb, ub] = s[1];
s[0] = [x+1, M];
s[1] = [lb, x];
s = [[l,_l], [x,x]].concat(s);
}
if (x == s[0][0]){
let [l,_l] = s[0];
let [lb, ub] = s[1];
let d = 1;
s.splice(0,1);
while (i > 0 && A[i-1] == x){
s =
[['non_inc', d++, lb, M]].concat(s);
i--;
}
if (i > 0)
s = [[l,_l]].concat(s);
}
}
// dirty fix
if (s[0][0] == 0)
s.splice(0,1);
return s;
}
var a = [2, 5, 3, 7, 9, 6]
var b = [0, 2, 2, 3, 7, 3]
console.log(JSON.stringify(a));
console.log(JSON.stringify(f(b,10)));
b = [0,0,0,4]
console.log(JSON.stringify(f(b,10)));
b = [0,2,0,0,0,4]
console.log(JSON.stringify(f(b,10)));
b = [0,0,0,2,3,4]
console.log(JSON.stringify(f(b,10)));
b = [0,2,2]
console.log(JSON.stringify(f(b,4)));
b = [0,3,5,6]
console.log(JSON.stringify(f(b,10)));
b = [0,0,3,0]
console.log(JSON.stringify(f(b,10)));
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"
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
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.
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
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.
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
}
}
I have below merge sort program in algorithms book, it is mentioned that The main problem is that merging two sorted lists requires linear extra memory, and the additional work spent copying to the temporary array and back, throughout the algorithm, has the effect of slowing down the sort considerably. This copying can be avoided by judiciously switching the roles of "a" and "tmp_array" at alternate levels of the recursion.
My question is what does author mean "copying can be avoided by judiciously switching the roles of a and tmp_array at alternate levels of the recursion" and how it is possible in following code? Request to show an example how we can achieve this?
void mergesort( input_type a[], unsigned int n ) {
input_type *tmp_array;
tmp_array = (input_type *) malloc( (n+1) * sizeof (input_type) );
m_sort( a, tmp_array, 1, n );
free( tmp_array );
}
void m_sort( input_type a[], input_type tmp_array[ ], int left, int right ) {
int center;
if( left < right ) {
center = (left + right) / 2;
m_sort( a, tmp_array, left, center );
m_sort( a, tmp_array, center+1, right );
merge( a, tmp_array, left, center+1, right );
}
}
void merge( input_type a[ ], input_type tmp_array[ ], int l_pos, int r_pos, int right_end ) {
int i, left_end, num_elements, tmp_pos;
left_end = r_pos - 1;
tmp_pos = l_pos;
num_elements = right_end - l_pos + 1;
/* main loop */
while( ( 1_pos <= left_end ) && ( r_pos <= right_end ) )
if( a[1_pos] <= a[r_pos] )
tmp_array[tmp_pos++] = a[l_pos++];
else
tmp_array[tmp_pos++] = a[r_pos++];
while( l_pos <= left_end ) /* copy rest of first half */
tmp_array[tmp_pos++] = a[l_pos++];
while( r_pos <= right_end ) /* copy rest of second half */
tmp_array[tmp_pos++] = a[r_pos++];
/* copy tmp_array back */
for(i=1; i <= num_elements; i++, right_end-- )
a[right_end] = tmp_array[right_end];
}
I'm going to assume that, without looking at this code, it is performing merge sort by declaring a contiguous block of memory the same size as the original.
So normally merge sort is like this:
split array in half
sort half-arrays by recursively invoking MergeSort on them
merge half-arrays back
I'm assuming it's recursive, so no copies will be done before we're sorting sub-arrays of size 2. Now what happens?
_ means it is memory we have available, but we don't care about the data in it
original:
8 5 2 3 1 7 4 6
_ _ _ _ _ _ _ _
Begin recursive calls:
recursive call 1:
(8 5 2 3) (1 7 4 6)
_ _ _ _ _ _ _ _
recursive call 2:
((8 5) (2 3)) ((1 7) (4 6))
_ _ _ _ _ _ _ _
recursive call 3:
(((8) (5))((2) (3)))(((1) (7))((4) (6)))
_ _ _ _ _ _ _ _
Recursive calls resolving with merging, PLUS COPYING (uses more memory, or alternatively is 'slower'):
merge for call 3, using temp space:
(((8) (5))((2) (3)))(((1) (7))((4) (6))) --\ perform merge
(( 5 8 )( 2 3 ))(( 1 7 )( 4 6 )) <--/ operation
UNNECESSARY: copy back:
(( 5 8 )( 2 3 ))(( 1 7 )( 4 6 )) <--\ copy and
_ _ _ _ _ _ _ _ --/ ignore old
merge for call 2, using temp space:
(( 5 8 )( 2 3 ))(( 1 7 )( 4 6 )) --\ perform merge
( 2 3 5 8 )( 1 4 6 7 ) <--/ operation
UNNECESSARY: copy back:
( 2 3 5 8 )( 1 4 6 7 ) <--\ copy and
_ _ _ _ _ _ _ _ --/ ignore old
merge for call 1, using temp space:
( 2 3 5 8 )( 1 4 6 7 ) --\ perform merge
1 2 3 4 5 6 7 8 <--/ operation
UNNECESSARY: copy back:
1 2 3 4 5 6 7 8 <--\ copy and
_ _ _ _ _ _ _ _ --/ ignore old
What the author is suggesting
Recursive calls resolving with merging, WITHOUT COPYING (uses less memory):
merge for call 3, using temp space:
(((8) (5))((2) (3)))(((1) (7))((4) (6))) --\ perform merge
(( 5 8 )( 2 3 ))(( 1 7 )( 4 6 )) <--/ operation
merge for call 2, using old array as temp space:
( 2 3 5 8 )( 1 4 6 7 ) <--\ perform merge
(( 5 8 )( 2 3 ))(( 1 7 )( 4 6 )) --/ operation (backwards)
merge for call 1, using temp space:
( 2 3 5 8 )( 1 4 6 7 ) --\ perform merge
1 2 3 4 5 6 7 8 <--/ operation
There you go: you don't need to do copies as long as you perform each "level" of the merge-sort tree in lock-step, as shown above.
You may have a minor issue of parity, also as demonstrated above. That is, your result may be in your temp_array. You either have three options for dealing with this:
returning the temp_array as the answer, and release the old memory (if your application is fine with that)
perform a single array copy operation, to copy temp_array back into your original array
allow yourself to consume a mere twice-as-much memory, and perform a single cycle of merges from temp_array1 to temp_array2 then back to original_array, then release temp_array2. The parity issue should be resolved.
This is not necessarily "faster":
additional work spent copying to the temporary array and back
This is actually not the core reason why it's 'faster' per se. It is obviously not asymptotically faster, nor necessarily even faster. There is a notion of latency vs. throughput. Generally running time is measured in latency, because extra garbage work (like releasing memory) may be done asynchronously. You don't necessarily need to copy "back" to the original array depending on your language. However, if you are repeating something many times on memory-bound hardware in a garbage-collected language, the garbage collection can occasionally be forced to spike if the GC algorithm is a poor choice for what you are doing (or if this is C, maybe you are waiting to allocate). Thus if you were to create extra memory in a GC language, it should not really count against you. Granted, this may cause you not to take advantage of cache properly if you use too much memory. You'd have to benchmark it yourself, very carefully for your use case.
I do not recommend creating random temporary arrays for each step though, as that would make memory O(N log(N)) and this is a trivial optimization.
Minor notes on in-placeness:
Also, the reason you can't naively do it in-place is because while you are merging two sorted sub-arrays, the new result sorted sub-array may take arbitrarily many from one input array before spontaneously swap to the other array. For example, as you can see we need a buffer because our input arrays might get split into fragments:
( 4 6 7 8 10)(1 2 3 5 9 11)(... other sub-arrays)
( 1)(6 7 8 10)(4)(2 3 5 9 11)(...
( 1 2)(7 8 10)(4 6)(3 5 9 11) ...
( 1 2 3)(8 10)(4 6 7)(5 9 11)
( 1 2 3 4(10)(8)(6 7)(5 9 11) ooph :-(
( 1 2 3 4 5)(8)(6 7)(10)(9 11) ooph
You might be able to so cleverly in-place if you do some weird variant of the kth-statistic median-of-medians algorithm, performing your merge into the middle of the two arrays rather than the start (merging from a specifically chosen element outwards left/decreasing and right/increasing simultaneously). I'm not sure how one would implement that though, or if the hunch is true.
(very minor note: Perhaps those who are familiar with sorting algorithms should be careful of comparing a traditional swap traditional swap operation involving a tmp variable in a register, which is two reads-from-cache and two writes-to-cache, to not-in-place copying to other bits of memory, without a per-operation counting argument.)
Certainly, OP's method is extremely simple to code for only twice as much memory.
Start by thinking of merge sort in this way.
0: Consider the input array A0 as a collection of ordered sequences of
length 1.
1: Merge each consecutive pair of sequences from A0, constructing a
new temporary array A1.
2: Merge each consecutive pair of sequences from A1, constructing a
new temporary array A2.
...
Finish when the last iteration results in a single sequence.
Now, you can obviously get away with just a single temporary array by doing this:
0: Consider the input array A0 as a collection of ordered sequences of
length 1.
1: Merge each consecutive pair of sequences from A0, constructing a
new temporary array A1.
2: Merge each consecutive pair of sequences from A1, overwriting A0
with the result.
3: Merge each consecutive pair of sequences from A0, overwriting A1
with the result.
...
Finish when the last iteration results in a single sequence.
Of course, you can be even smarter than this. If you want to be nicer to the cache, you might decide to sort top-down, rather than bottom-up. In this case, it hopefully becomes obvious what your textbook means when it refers to tracking the role of the arrays at different levels of recursion.
Hope this helps.
Here is my implementation without extra copies.
public static void sort(ArrayList<Integer> input) {
mergeSort(input, 0, input.size() - 1);
}
/**
* Sorts input and returns inversions number
* using classical divide and conquer approach
*
* #param input Input array
* #param start Start index
* #param end End index
* #return int
*/
private static long mergeSort(ArrayList<Integer> input, int start, int end) {
if (end - start < 1) {
return 0;
}
long inversionsNumber = 0;
// 1. divide input into subtasks
int pivot = start + (int) Math.ceil((end - start) / 2);
if (end - start > 1) {
inversionsNumber += mergeSort(input, start, pivot);
inversionsNumber += mergeSort(input, pivot + 1, end);
}
// 2. Merge the results
int offset = 0;
int leftIndex = start;
int rightIndex = pivot + 1;
while (leftIndex <= pivot && rightIndex <= end) {
if (input.get(leftIndex + offset) <= input.get(rightIndex)) {
if (leftIndex < pivot) {
leftIndex++;
} else {
rightIndex++;
}
continue;
}
moveElement(input, rightIndex, leftIndex + offset);
inversionsNumber += rightIndex - leftIndex - offset;
rightIndex++;
offset++;
}
return inversionsNumber;
}
private static void moveElement(ArrayList<Integer> input, int from, int to) {
assert 0 <= to;
assert to < from;
assert from < input.size();
int temp = input.get(from);
for (int i = from; i > to; i--) {
input.set(i, input.get(i - 1));
}
input.set(to, temp);
}
Look at the very last part of the merge function. What if, instead of copying that data, you just used the knowledge that the sorted part is now in tmp_array instead of a when the function returns, and a is available for use as a temp.
Details are left as an exercise for the reader.