给定一个数字 n,列出所有 n 位数字,使得每个数字都没有重复数字

Posted

技术标签:

【中文标题】给定一个数字 n,列出所有 n 位数字,使得每个数字都没有重复数字【英文标题】:Given a number n, list all n-digit numbers such that each number does not have repeating digits 【发布时间】:2019-02-04 20:32:26 【问题描述】:

我正在尝试解决以下问题。给定一个整数 n,列出所有 n 位数字,使每个数字都没有重复数字。

例如,如果n为4,那么输出如下:

0123 0124 0125 ... 9875 9876 4位数字的总数是5040

我目前的方法是蛮力。我可以生成所有 n 位数字,然后使用 Set 列出所有没有重复数字的数字。不过,我很确定有一种更快、更好、更优雅的方式来做到这一点。

我正在使用 Java 编程,但我可以阅读 C 源代码。

谢谢

【问题讨论】:

你能说出你到底在尝试什么蛮力方法吗?如果您包含生成组合例程(函数或方法)会更好 这看起来像一个简单的练习。这里的人太好了,无法为您提供解决方案。我建议你给出你的尝试(代码)并询问你卡在哪里,哪里出了问题,或者为什么效率不够等等...... @Nin 完全同意你的看法。但在解释问题之前,这些家伙已经给了她直截了当的答案。一个简单的谷歌搜索和 geeksforgeeks 也会这样做 @Maribell,我们可以避免前导零吗? 您可能需要澄清您是否想要所有数字最多 n 位或只需要 n 位。您在示例中使用了 0123,在您的 n=4 示例中为 n-1 位。这会将可接受的答案更改为 9*9*8*7... 之类的序列... 【参考方案1】:

从数学上讲,第一个数字有 10 个选项,第二个数字有 9,第三个数字有 8,而 7 第四次。所以,10 * 9 * 8 * 7 = 5040

以编程方式,您可以使用一些组合逻辑生成这些。使用函数式方法通常会使代码更简洁;这意味着递归地构建一个新字符串,而不是尝试使用 StringBuilder 或数组来不断修改现有字符串。

示例代码

以下代码将生成排列,无需重复使用数字,无需任何额外的集合或映射/等。

public class LockerNumberNoRepeats 
    public static void main(String[] args) 
        System.out.println("Total combinations = " + permutations(4));
    

    public static int permutations(int targetLength) 
        return permutations("", "0123456789", targetLength);
    

    private static int permutations(String c, String r, int targetLength) 
        if (c.length() == targetLength) 
            System.out.println(c);
            return 1;
        

        int sum = 0;
        for (int i = 0; i < r.length(); ++i) 
            sum += permutations(c + r.charAt(i), r.substring(0,i) + r.substring(i + 1), targetLength);
        
        return sum;
    

输出:

...
9875
9876
Total combinations = 5040

说明

从@Rick 的评论中提取这一点,因为它说得很好,有助于澄清解决方案。

所以要解释这里发生的事情 - 它正在递归一个接受三个参数的函数:我们已经使用的数字列表(我们正在构建的字符串 - c),我们尚未使用的数字列表(字符串 r)和目标深度或长度。然后当一个数字被使用时,它被添加到 c 并从 r 中移除,用于后续的递归调用,所以你不需要检查它是否已经被使用过,因为你只传入那些没有被使用过的。

【讨论】:

所以解释一下这里发生了什么 - 它正在递归一个接受三个参数的函数:我们已经使用的数字列表(我们正在构建的字符串 - c),我们的数字列表尚未使用(字符串 r)和目标深度或长度。然后当一个数字被使用时,它会被添加到 c 并从 r 中删除,以供后续递归调用,所以你不需要检查它是否已经被使用,因为你只传入那些没有被使用过的。不错的技巧。 @John 非常感谢,这很有魅力,比我的蛮力方法快得多。 @Rick - 很棒的解释。我打算早上回去加一个。我会用你的名字将它包含在解决方案中。 @MaribellPinaGriego - 完全没问题。仅供参考,可能有一种使用堆栈 + while 循环的迭代方法,使用基本相同的逻辑会更快。递归解决方案通常是最容易理解且代码最少的,但它们很少是最快的,因为所有嵌套函数调用都不是最优的。 从技术上讲,第一个数字有 9 个选项,因为 0 会使它成为 n-1 位数字。但这个问题让人困惑。【参考方案2】:

很容易找到一个公式。即

如果n=110 变体。

如果n=29*10 变体。

如果n=38*9*10 变体。

如果n=47*8*9*10 变体。

【讨论】:

【参考方案3】:

注意这里的对称性:

0123
0124
...
9875
9876

9876 = 9999 - 123

9875 = 9999 - 124

所以对于初学者来说,你可以把工作分成两半。

您可能会找到一个覆盖场景的正则表达式,如果一个数字在同一个字符串中出现两次,那么它匹配/失败。

正则表达式是否会更快,谁知道?

特别是对于四位数字,您可以嵌套 For 循环:

for (int i = 0; i < 10; i++) 
   for (int j = 0; j < 10; j++) 
       if (j != i) 
           for (int k = 0; k < 10; k++) 
               if ((k != j) && (k != i)) 
                   for (int m = 0; m < 10; m++) 
                       if ((m != k) && (m != j) && (m != i)) 
                           someStringCollection.add((((("" + i) + j) + k) + m));

(等)

或者,对于更通用的解决方案,这是递归的便利性的一个很好的例子。例如。您有一个函数,该函数获取先前数字的列表和所需的深度,如果所需的数字的数量小于深度,则只有十次迭代的循环(通过您要添加的数字的每个值),如果数字在列表中不存在,然后将其添加到列表中并递归。如果您处于正确的深度,只需连接列表中的所有数字并将其添加到您拥有的有效字符串集合中。

【讨论】:

【参考方案4】:

回溯法也是一种蛮力法。

private static int pickAndSet(byte[] used, int last) 
    if (last >= 0) used[last] = 0;
    int start = (last < 0) ? 0 : last + 1;
    for (int i = start; i < used.length; i++) 
        if (used[i] == 0) 
            used[i] = 1;
            return i;
        
    
    return -1;


public static int get_series(int n) 
    if (n < 1 || n > 10) return 0;
    byte[] used = new byte[10];
    int[] result = new int[n];

    char[] output = new char[n];

    int idx = 0;
    boolean dirForward = true;
    int count = 0;
    while (true) 
        result[idx] = pickAndSet(used, dirForward ? -1 : result[idx]);
    if (result[idx] < 0)   //fail, should rewind.
      if (idx == 0) break;      //the zero index rewind failed, think all over.

      dirForward = false;
      idx --;
      continue;
     else //forward.
        dirForward = true;
    

    idx ++;
    if (n == idx) 
        for (int k = 0; k < result.length; k++) output[k] = (char)('0' + result[k]);
        System.out.println(output);
        count ++;
        dirForward = false;
        idx --;
    
    
    return count;

【讨论】:

以上是关于给定一个数字 n,列出所有 n 位数字,使得每个数字都没有重复数字的主要内容,如果未能解决你的问题,请参考以下文章

1056. 组合数的和(15)

1056. 组合数的和(15)

给定一个以 3 为底的 N 位数字,生成所有以可并行方式具有 k 位不同的以 3 为底的 N 位数字

PAT——1056. 组合数的和

删数问题-贪心

删数问题