CodeWars -- Alphabetic Anagrams

1,867 阅读3分钟

Problem

Consider a “word” as any sequence of capital letters A-Z (not limited to just “dictionary words”). For any word with at least two different letters, there are other words composed of the same letters but in a different order (for instance, “STATIONARILY”/“ANTIROYALIST”, which happen to both be dictionary words; for our purposes “AAIILNORSTTY” is also a “word” composed of the same letters as these two). We can then assign a number to every word, based on where it falls in an alphabetically sorted list of all words made up of the same group of letters. One way to do this would be to generate the entire list of words and find the desired one, but this would be slow if the word is long. Given a word, return its number. Your function should be able to accept any word 25 letters or less in length (possibly with some letters repeated), and take no more than 500 milliseconds to run. To compare, when the solution code runs the 27 test cases in JS, it takes 101ms. For very large words, you’ll run into number precision issues in JS (if the word’s position is greater than 253). For the JS tests with large positions, there’s some leeway (.000000001%). If you feel like you’re getting it right for the smaller ranks, and only failing by rounding on the larger, submit a couple more times and see if it takes. Python, Java and Haskell have arbitrary integer precision, so you must be precise in those languages (unless someone corrects me). C# is using a long, which may not have the best precision, but the tests are locked so we can’t change it.

Sample

Sample words, with their rank:

  • ABAB = 2
  • AAAB = 1
  • BAAA = 4
  • QUESTION = 24572
  • BOOKKEEPER = 10743

Solution

思路:

  • 1、生成字母序的异位字母词列表然后去定位(计算机:WDNMD)
  • 2、只要知道前面有多少比该词大的即可,比如BAAA =》 { 3A, 1B }, 一号位为B则若一号位为A =》{ 2A, 1B } 则有 (2 + 1)! / (2!*1!)个比目标大,即多重集全排列计算(《组合数学》):

定理2.4.2 设S是多重集合,它有k种不同类型的对象,且每一种类型的有限重复数分别是n1,n2,…,nk。设S的大小为n=n1+n2+…+nk。则S的排列数目等于

证明: 给定多重集合S,它有k种类型对象,比如说a1,a2,…,ak,且重复数分别是n1,n2,…,nk,对象总数n=n1+n2+…+nk。我们想要这n个对象的排列数量。可以这样考虑这个问题。一共有n个位置,而我们想要在每一个位置放置S中的一个对象。首先,我们确定放置a1的位置。因为在S中a1的数量是n1,因此必须从n个位置的集合中取出n1个位置的子集。这样做的方法数是

下一步,要确定放置a2的位置。此时还剩下n-n1个位置,我们必须从中选取n2个位置来。这样做的方法数量是 再接下来我们有

种方法为a3选择位置。继续这样做下去,利用乘法原理,我们发现S的排列个数等于

使用定理2.3.1,我们看到上面这个数等于

消去分子分母上的相同因子,上面的数化简成为

之后递归即可。

Code

// 计算阶乘
function getFactorial(num) {
  return Array.apply(null, {
    length: num
  }).reduce((re, cur, index) => re * (index + 1 || 1), 1)
}

// 计算前方比目标大的数
function getBeforeCount(set, setIndex, permutation) {
  return Array.apply(null, {
    length: setIndex
  }).reduce((re, cur, index) => {
    const _set = {
      ...set
    }
    const setArr = Object.keys(_set)
    // 去除固定位
    _set[setArr[index]] -= 1
    const _count = Object.values(_set).reduce((_re, cur) => _re * (getFactorial(cur) || 1), 1)
    return (permutation / _count) + re
  }, 0)
}

function listPosition(str) {
  let pos = 1
  let _str = str
  str.split('').forEach((char, index) => {
    const _set = {}
    // 多重集
    _str.split('').sort().forEach(item => {
      if (_set[item]) {
        _set[item] += 1
      } else {
        _set[item] = 1
      }
    })

    const setArr = Object.keys(_set)
    // 多重集所处字母序
    const setIndex = setArr.indexOf(char)
    // 剩余坑位
    const solt = str.length - index - 1
    const permutation = getFactorial(solt)
    pos += getBeforeCount(_set, setIndex, permutation)
    _str = str.substr(index + 1)
  })
  return pos
}

Best Practices Code

function listPosition(word) {
    var indexer = {}; // D:3 B:1 A:0 C:2
    var counts = []; // 2 1 1 1

    var lettersCount = 0;
    word.split("").sort().forEach(function(x){
        if ( indexer[x] == undefined ) {
            indexer[x] = lettersCount;
            counts[lettersCount] = 0;
            lettersCount ++;
        }
    });

    var term = 1;
    var sum = term;
    word.split("").reverse().forEach(function(x, i){
        var step = i + 1, idx = indexer[x];
        counts[idx] ++;
        term /= counts[idx];
        for (var j = 0; j < idx; ++j) 
            if (counts[j] != 0) 
                sum += term * counts[j];
        term *= step;
    });
    return sum;
}