Найти i-ю перестановку в javascript

Для данного массива arr размера n и индекса 0<=i<n! Я хочу вернуть i-ю перестановку.

Я был в состоянии написать метод, который получает все перестановки:

function permute (arr) {
  var permutations = [];
  if (arr.length === 1) {
    return [ arr ];
  }

  for (var i = 0; i <  arr.length; i++) { 
    var subPerms = permute(arr.slice(0, i).concat(arr.slice(i + 1)));
    for (var j = 0; j < subPerms.length; j++) {
      subPerms[j].unshift(arr[i]);
      permutations.push(subPerms[j]);
    }
  }
  return permutations;
}

Как мне обрезать его, чтобы получить только одну ветвь рекурсии?

Ответ 1

Вы можете использовать факториал длины массива в качестве помощника для получения целевой перестановки. По сути, этот алгоритм вычисляет индексы массива, по которым повторно собирается результат.

function getN(n, array) {
    var f,
        l = array.length,
        indices = [];

    array = array.slice();
    while (l--) {
        f = factorial(l);
        indices.push(Math.floor(n / f));
        n %= f;
    }
    return indices.map(function (i) {
        return array.splice(i, 1)[0];
    });
}

function factorial(num) {
    var result = 1;
    while (num) {
        result *= num;
        num--;
    }
    return result;
}

var i, l,
    array = [1, 2, 3, 4];

for (i = 0, l = factorial(array.length); i < l; i++) {
    console.log(i, '>', getN(i, array).join(' '));
}
.as-console-wrapper { max-height: 100% !important; top: 0; }

Ответ 2

Здесь менее затратный в вычислительном отношении ответ: отслеживайте используемые элементы, используя массив логических флагов. Это может показаться небольшим улучшением, так как вам все равно придется сканировать массив флагов, чтобы найти искомый элемент, что приведет к производительности O (N ^ 2). Однако вы можете получить некоторое улучшение, если воспользуетесь расположением элементов в массиве:

function getN(n, array) {
    var f,
        l = array.length,
        indices = [];

    //Instead of using splice() to remove elements that are
    //already in the permutation, I'll use an array of bit flags
    //to track used elements.
    var indexMask = [];
    indexMask.length = array.length;
    var indexMaskInit = 0;
    for(indexMaskInit = 0; indexMaskInit < indexMask.length;
            indexMaskInit++)    {
        indexMask[indexMaskInit] = true;
    }

    array = array.slice();
    while(l--) {
        f = factorial(l);
        indices.push(Math.floor(n / f));
        n %= f;
    }

    var toReturn = [];
    toReturn.length = array.length;
    //Now, extract the elements using the array of indices.
    var numUsed;
    for(numUsed = 0; numUsed < indices.length; numUsed++)   {
        //factIndex is the index in the set of elements that have
        //not been selected yet.
        var factIndex = indices[numUsed];
        var elementFound = false;
        //This code searches for the element by assuming that some
        //elements have already been selected.  The number of used
        //elements can be found using the index of the outer loop.
        //By counting the number of used elements at the beginning
        //of the array, it may be possible to calculate an offset
        //that can be used to find the desired element.
        if(factIndex > 2 * numUsed)
        {
            var offset = 0, scanIndex;
            //Boundary condition:  no elements have been used yet,
            //in which case we can use factIndex directly.
            if(numUsed === 0)   {
                elementFound = true;
            }
            else    {
                //Loop to 2*numUsed, to avoid a linear search.
                for(scanIndex = 0; scanIndex < 2 * numUsed;
                        scanIndex++)    {
                    if(!indexMask[scanIndex])   {
                        offset++;
                    }
                    //Boundary condition:  all used elements have
                    //been found.
                    if(offset >= numUsed)   {
                        elementFound = true;
                        break;
                    }
                }
            }
            if(elementFound)    {
                factIndex += offset;
            }
        }
        //This code starts at the end of the array and counts the
        //number of used elements.
        else if ((indices.length - 1 - factIndex) > 2 * numUsed)    {
            var offset = 0, scanIndex;
            //Boundary condition:  no elements have been used yet,
            //in which case we can use factIndex directly.
            if(numUsed === 0)   {
                elementFound = true;
            }
            else    {
                var n = indices.length - 1;
                //Loop to 2*numUsed, to avoid a linear search.
                for(scanIndex = n; scanIndex > n - 2 * numUsed;
                        scanIndex--)    {
                    if(!indexMask[scanIndex])   {
                        offset++;
                    }
                    //Boundary condition:  all used elements have
                    //been found.
                    if(offset >= numUsed)   {
                        elementFound = true;
                        break;
                    }
                }
            }
            if(elementFound)    {
                factIndex += (numUsed - offset);
            }
        }
        //If the number of used elements is not much greater than
        //factIndex, or they do not all cluster at the beginning
        //of the array, it may be better to run a linear search.
        if(!elementFound)
        {
            var searchIndex = 0, i;
            for(i = 0; i < indexMask.length; i++)   {
                if(indexMask[i])    {
                    if(searchIndex >= factIndex)    {
                        break;
                    }
                    else    {
                        searchIndex++;
                    }
                }
            }
            factIndex = i;
        }
        toReturn[numUsed] = array[factIndex];
        indexMask[factIndex] = false;
    }
    return toReturn;
}

function factorial(num) {
    var result = 1;
    while (num) {
        result *= num;
        num--;
    }
    return result;
}

var i, l;
var array = [1, 2, 3, 4];
//var array = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"];

for (i = 0, l = factorial(array.length); i < l; i++) {
    console.log(i, getN(i, array).join(' '));
}

Ответ 3

Попробуй (алгоритм отсюда)

function perm(n,arr) {
    let r=[],i=1;
    while (n) { r.unshift(n%i); n=n/i++|0 }
    let s= i-1 ? arr.splice(-r.length) : arr;    
    return arr.concat(r.map( x=> s.splice(x,1)[0] ));
}

// TEST
for(let i=0; i<4*3*2*1; i++) console.log( i, perm(i,[1,2,3,4]).join() );