【问题标题】:How to optimize my function that takes a positive integer and returns the next smaller positive integer?如何优化接受正整数并返回下一个较小正整数的函数?
【发布时间】:2017-02-03 05:19:03
【问题描述】:

我正在尝试编写一个函数,它接受一个正整数并返回下一个包含相同数字的较小正整数,当没有包含相同数字的较小数字时返回-1。

For example:

nextSmaller(21) == 12
nextSmaller(531) == 513
nextSmaller(2071) == 2017

我写了一个代码来解决这个问题,但我真的不知道如何进一步优化它。请你帮助我好吗?它在 repl.it 上运行得相当快,但是当我提交它时,它说它需要超过 1200 毫秒,并且即使所有测试都通过了也不允许我提交它。

function nextSmaller(n) {
var nArray= n.toString().split("")
var minimumNum = 1 + Array(nArray.length).join('0')
for(var i=n-1; i >= minimumNum; i--) {
  var newNumArray = i.toString().split('');
  var counter = 0;
  for (var j=0; j<newNumArray.length; j++) {
    if (nArray.indexOf(newNumArray[j]) > -1) {
        counter++
        nArray.splice(nArray.indexOf(newNumArray[j]), 1)
        if (counter === n.toString().split("").length) {
        return i;
        }
     } 
  }
       nArray = n.toString().split("");
       if (i === Number(minimumNum)) return -1;
  }
}

【问题讨论】:

  • 或许这更适合code review
  • 寻找模式来缩小问题空间。例如,您应该只检查输入中数字的排列。
  • 看起来这是 Codewars 上的一个 kata
  • 是的,它是代码战 JohanP 上的一个 kata。谢谢 E.Sundin,我会把它贴在那里。 4castle,我不明白你的意思,你能详细说明一下吗?
  • 如果你使用 c++,问题似乎在于找到下一个字典顺序更小的排列,你可以使用 next_permutation 和合适的比较函数cplusplus.com/reference/algorithm/next_permutation 或者你可以自己实现它。但是您可能需要处理在下一个较小排列中有前导零的特殊情况

标签: javascript string algorithm math numbers


【解决方案1】:

您的代码可以稍微优化一下,例如,您可以在内部循环中使用 break 语句,在您知道当前的数字不起作用时立即转到下一个数字(这应该让它运行大约一半的时间,但对于像91234567 这样的n 来说仍然很慢,并且在循环中使用变量而不是n.toString().split("").length,因此您只需要将n 转换为数组一次。

function nextSmaller(n) {
  var nArray = n.toString().split("")
  var length = nArray.length;
  var minimumNum = 1 + Array(length).join('0')
  for(var i=n-1; i >= minimumNum; i--) {
    var newNumArray = i.toString().split('');
    var counter = 0;
    for (var j=0; j<newNumArray.length; j++) {
      if (nArray.indexOf(newNumArray[j]) < 0)
          break;
      counter++
      nArray.splice(nArray.indexOf(newNumArray[j]), 1)
      if (counter === length) {
          return i;
      }
    }
    nArray = n.toString().split("");
  }
  return -1;
}

有一种非常有效的算法可以计算下一个排列,可以很容易地改用前一个排列(如果结果排列以0 开头,则返回-1)。我调整了this algorithm 来做到这一点:

[21,531,2071,912345678,9123545678,915345678].forEach( x => console.log( nextSmaller( x ) ) );

function nextSmaller(n) {
  
    const arr = ( n + '' ).split( '' ).map( Number );
  
    // Find longest non-decreasing suffix
    let i, prev = 9;
    for ( i = arr.length; i--; ) {
        if ( arr[ i ] > prev )
            break;
        prev = arr[ i ];
    }
  
    // If whole sequence is non-decreasing,
    // it is already the smallest permutation
    if ( i < 0 )
        return -1;
  
    const pivot_i = i;
    const pivot = arr[ pivot_i ];
  
    for ( i = arr.length; i--; ) {
        if ( arr[ i ] < pivot )
            break;
    }
  
    arr[ pivot_i ] = arr[ i ];
    arr[ i ] = pivot;

    if ( arr[ 0 ] === 0 )
        return -1;
  
    return +arr.slice( 0, pivot_i + 1 ).concat( arr.slice( pivot_i + 1 ).reverse( ) ).join('');
}

【讨论】:

  • “有一个非常有效的算法” 这是如何证实的? “非常有效”相比?
  • @guest271314 一看就知道。它所做的只是循环遍历数组最多两次并调用 slice、concat 和 reverse 几次(不是在循环中)。
  • 没有什么是“显而易见的”。明显也可能会产生误导。令人惊讶的是,当问题中提到“效率”时,答案中没有包含对提议方法的“效率”的实际可重复测量。尤其是可以使用工具来展示和证明“效率”的事实。时间是可以衡量的。 OP没有要求理论。证明事实Is it more efficient to use find() rather than > for child selector in jQuery?
  • @guest271314 OP没有提到“效率”;那是你。它们的阈值为 1.2 秒,但像这样的简单函数显然可以在该阈值内运行数千次,所以这并不重要。
  • 倾向于收集正在交流的内容。一些用户从字面上阅读术语“优化”。意义、展示和证明方法需要更少的时间来完成;这是事实。无论如何,也许 OP 对实际数字和确切事实不感兴趣;而是一种“优化”的“感觉”。在这里,数字很重要。数字不会说谎。没有可能与时钟协商。在迭代或创建 700k+ 或更多项目时,每一刻都很重要。每个配置文件都很重要。就此而言,所有的时间都至关重要。
【解决方案2】:

算法可能如下:

  1. 对于输入数字n,查找由same digitssort 中的一些permutations 组成的所有数字。例如,如果n=213,我们得到的排序列表为[123, 132, 213, 231, 312, 321]。 (例如,Permutations in JavaScript? 可以帮助您)。

  2. 在排序列表中找到数字nindex i。如果i&gt;0 返回index i-1 处的数字,否则返回-1(如果它是出现在排序列表第一个位置的最小数字)。

另一种替代算法可能如下:

递减数字n,直到找到具有完全相同数字的数字(以不同的顺序,您可以对数字进行排序并检查是否相等)。

最有效的将类似于@Paulpro(https://www.nayuki.io/page/next-lexicographical-permutation-algorithm) 提到的那个

  1. n 的十进制字符串表示中找到longest non-decreasing suffix
  2. 如果整个字符串 n 不递减,则返回 -1(不能更小)。
  3. 否则选择紧靠后缀开头的数字为pivot,并将其与suffix中的(the leftmost和)the largest数字交换,即smaller而不是pivot。返回此号码。

【讨论】:

  • 这个答案如何解决问题“如何优化我的功能”?如何将算法描述重现验证为比javascript@Answer 更有效?
  • 好吧,它被标记为algorithm所以想到发布一个我能想到的并且还发布了一些javascript实现的链接,在我看来它可以有效地实现,因为数量位数是有限的,排列不会很多,甚至我们可以优化生成部分排列列表,看到数字n就停止。
  • 我猜这与特定的编程语言无关,worst case 中该算法的时间和空间复杂度将是O(d!),其中dn 中的位数,由于d 不会很大(假设基于 OP 给出的示例),(部分)置换生成算法的任何标准实现都不会花费很多时间,javascript 没有什么特别之处我猜,它是另一种编程语言。
  • @guest271314 复杂性比实际花费的时间更重要。测量的时间取决于硬件,并没有告诉您运行时间如何随着输入长度的增加而增加,而复杂度类实际上告诉您该算法与其他算法的比较情况。我发的是O(d)
  • @SandipanDey 我希望我能这么说。总有一天我会的,哈哈。我有包含第 4A 卷的套装。我读了第 1 卷的所有内容,但我还没有读过其他的任何内容。我可能平均阅读了其中每篇的大约 40%。
猜你喜欢
  • 2013-09-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-09-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多