【问题标题】:Sequence of numbers without repeating adjacent digits不重复相邻数字的数字序列
【发布时间】:2018-08-21 10:23:46
【问题描述】:

在给定基数的情况下,我想获得一个不包含在某个基数中重复的后续数字的数字序列。我用一个例子来解释自己:以 10 为底,序列将是 0, 1, ..., 10, 12, ... 21, 23, ... 等等。没有像 11 这样的数字, 125568, 220等

或者,等效地,我想告诉以 6 为底的数字 521 包含重复的重复项(以 10 为底的 521 = 以 6 为底的 2225)。

有没有办法迭代十进制数,使其在给定基数中的表示不包含任何重复的重复项?我的意思是,以 6 为基数,20 后面的(十进制)数字是 22(因为 20 是 32,22 是 34)。

当然,所有这些都不需要在给定的基数中转换索引并检查是否有重复的数字。

[编辑] 有一种方法可以判断一个十进制数(可以说是索引)在某个基数中是否有重复的连续数字,这样我就可以在生成序列时跳过它?

【问题讨论】:

  • 听起来像是 XY 问题。 “完全转化”有什么问题?
  • (什么是 XY 问题?)我不想进行两次相同的计算,或者如果可以避免的话,我不想浪费时间进行基本转换和数组搜索。
  • 以 2 种形式保存计数器:二进制和十六进制。将它们都增加到下一个数字。这种方式不需要转换。可以通过跳过子范围来优化它,但说来话长。
  • What is the XY problem?。 “判断以 6 为底的数字 521 包含重复重复”的能力并不等同于生成 一系列数字
  • 为什么不获取两个基数中的数字。然后检查必要的基础并删除两者中的那些索引。在我的脑海中,我猜有一种数学方法可以计算出一个基数是否是另一个基数的倍数。

标签: algorithm math number-theory


【解决方案1】:

生成一系列数字

要检查单个数字是否在某个基数中包含相等的相邻数字,请在下面进一步查看我的初始答案。如果要生成在某个基数中相邻数字不相等的数字范围,最好将检查构建到生成器中,而不是检查所有数字。

下面的代码示例创建一个特定基数中每个数字的值的表,并将数字零创建为一个数字数组。具有相同基数的每个后续调用都会增加数字,同时跳过相等的相邻数字。

function next(base, reset) {
    // If this is the first call, or if the base has been changed, a
    // new table with the values of digits in this base is generated.
    if (base !== next.base) {
        initialize_table();
        reset = true;
    }
    // If reset flag is set, re-initialize array of digits to zeros.
    if (reset) {
        initialize_digits();
        return 0;
    }
    increment_digits();
    return calculate_value();

    // The distinct parts of the algorithm have been split into 
    // seperate functions for clarity; for speed, integrate them.
    function initialize_table() {
        // The base, digit value table and array of digits are stored
        // as static variables, to be re-used between function calls.
        next.base = base;
        next.table = [];
        // Set a maximum for the values in the table (here it's 32-bit
        // unsigned integers); this determines the size of the table.
        // No overflow checking is done; add this if required.
        for (var pos = 0, val = 1, step = 1; val < 4294967296; pos++){
            next.table[pos] = [0];
            for (var digit = 1; digit < base; digit++, val += step) {
                next.table[pos][digit] = val;
            }
            step = val;
        }
    }
    function initialize_digits() {
        next.digit = [];
        for (var pos = 0; pos < next.table.length; pos++) {
            next.digit[pos] = 0;
        }
    }
    function increment_digits() {
        // Find the lowest digit that can be incremented.
        // No overflow checking is done; add if required.
        var pos = 0;
        while (next.digit[pos] == base-1 ||
               next.digit[pos] == base-2 &&
               next.digit[pos+1] == base-1) {
            ++pos;
        }
        // Add 1, or 2 if adding 1 would equal the next digit.
        while (++next.digit[pos] == next.digit[pos+1]);
        // Set the lower digits to 0 or 1 alternatingly.
        for (pos-- ; pos >= 0; pos--) {
            next.digit[pos] = (next.digit[pos+1] == 0) ? 1 : 0;
        }
    }
    function calculate_value() {
        // Look up value for each digit in the table and add up.
        var val = 0;
        for (pos = 0; pos < next.digit.length; pos++) {
            val += next.table[pos][next.digit[pos]];
        }
        return val;
    }
}

for (var b = 9; b > 1; b--) {
    document.write(b + " &rarr; ");
    for (var i = 0; i < 25; i++)
        document.write(next(b) + " ");
    document.write("...<br>");
}

检查单个号码

最有效的方法(特别是如果您想检查具有相同基数的多个数字)是首先制作该基数中每个数字的值的表格,然后根据该数字从高到低检查数字.这样您就可以避免计算量很大的除法和模数,并且您可以在找到两个相同的数字后立即返回,而无需进行完整的基本转换。


假设基数为 6,数字为 16 位无符号整数,最大值为 65535,则表格为:

position: 0     1     2     3     4     5     6
digit:
  0  ->   0     0     0     0     0     0     0
  1  ->   1     6    36   216  1296  7776 46656
  2  ->   2    12    72   432  2592 15552     -
  3  ->   3    18   108   648  3888 23328     -
  4  ->   4    24   144   864  5184 31104     -
  5  ->   5    30   180  1080  6480 38880     -

假设我们正在检查数字 49876;我们首先检查第 6 位数字的值:

49876 >= 46656 ? yes
-> digit at position 6 is 1

然后我们从 49876 中减去 46656 得到 3220,然后继续检查第 5 位数字的值:

3220 >= 38880 ? no
3220 >= 31104 ? no
3220 >= 23328 ? no
3220 >= 15552 ? no
3220 >=  7776 ? no
-> digit at position 5 is 0

这个数字与前一个不同。我们从数字中减去任何内容,因为数字为零;然后我们转到位置 4 的数字值:

3220 >= 6480 ? no
3220 >= 5184 ? no
3220 >= 3888 ? no
3220 >= 2592 ? yes
-> digit at position 4 is 2

这个数字也和前一个不同。我们从 3220 中减去 2592 得到 628;然后我们转到位置 3 的数字值:

628 >= 1080 ? no
628 >=  864 ? no
628 >=  648 ? no
628 >=  432 ? yes
-> digit at position 3 is 2

这与前一个数字相同,因此我们知道该数字在基数 6 中具有两个相同的相邻数字。


该表可以仅使用加法构建,并且相对较小(最多 16×16 = 256 个元素,用于检查 base-16 中的 64 位整数),并且可以重复用于检查同一个中的多个数字根据。仅通过比较和减法来检查数字;绝对没有模数,除法甚至乘法,所以它应该非常快。

如果数字包含相同的相邻数字,则算法可以在检查所有数字之前返回答案。如果没有相同的相邻数字,则检查所有数字,这实际上是一个完整的基本转换;但至少它有效地完成了。

【讨论】:

  • 谢谢m69!我将研究该算法以更好地了解它的工作原理
  • 你也有数学证明吗?
  • @mauro 证明这种方法或使用筛子的想法?这种方法只是对基本转换和手动递增存储为单独数字的数字的一种改编,因此没有太多要证明的地方。如果看起来不清楚,我可以在代码中添加更多 cmets。
【解决方案2】:

对数字集结果数组的递归迭代生成器和逐位回溯将给出数字的自然排序。

生成器是具有类成员的类:

int length;
int curVal[10];
boolean digits[10];

这里 curVal 包含部分准备好的数字。

低位索引对应高位,pos是从高位到低位的从0开始的索引。

digits 是一个集合(如果您愿意,也可以是布尔数组)来跟踪使用的数字。

生成器驱动函数原型如下:

void generate(int pos)

在里面你做一个从 0 到 9 的简单循环,如果 digit 不在数字集中,把它放进去,添加到 curVal[pos] 并调用 generate(pos+1)。返回后从 set 和 curVal 中删除 cur 数字。

填完最后一位后,打印/入队号码以输出流/队列/列表。

除了 curVal 之外,您还可以跟踪二进制表示。每次将 digit 放入 curVal 时,预先计算 10^i 并将 precalc[length-pos-1]*digit 添加到二进制值,如果将其拉回则减去。

另外你需要单独处理第一个数字,因为最高位不能为0。自然可以在主循环内部通过适当的长度完成

【讨论】:

  • 好的,如果我理解正确的话,这正是我想避免的复杂性
【解决方案3】:

这是我能想到的最简单的解决方案:

 private boolean check(long t, long base) {
    long x = -1;
    long y;
    while (t > 0) {
      y = t % base;
      if (y == x) {
        return false;
      }
      x = y;
      t = t / base;
    }
    return true;
  }

【讨论】:

  • 在您的解决方案中有一个我想避免的隐藏转换。
  • 您想根据以特定基数表示的数字的数字来评估条件。您无法避免某种“隐藏”转换。即使您可以找到某种公式,该公式也会在其中的某处包含“基础”(可能多次),这样它也将包含“隐藏”转换。
  • 如果是这样的答案那真是可惜了……你确定没有别的办法吗?
  • 不,我不确定确定,但我会在这上面下个小赌注。测试重复后续数字不是你可以用一个简单的公式来做的事情。我什至会以同样的方式在二进制中执行此操作:将位向右移动并检查重复值。但这也是对二进制的“隐藏”转换:)
  • 你可能会得到某种筛子来做到这一点,但它会非常复杂并且占用大量内存,所以它可能不值得追求。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-10-30
  • 2017-12-05
  • 2021-07-03
  • 1970-01-01
  • 2019-06-17
  • 1970-01-01
相关资源
最近更新 更多