检查一个数字是否可以被3整除。

19

编写代码确定一个数是否能被3整除。函数的输入是单个二进制位 0 或 1,如果接收到的数字是3的倍数的二进制表示,则输出应为1,否则为零。

示例:

input  "0":       (0)  output 1
inputs "1,0,0":   (4)  output 0
inputs "1,1,0,0": (6)  output 1

这是基于一个面试问题。我要求绘制逻辑门图,但既然在stackoverflow上,任何编程语言的答案都可以接受。如果提供硬件实现(例如Verilog),则会给予额外的奖励。

第一部分(易):第一个输入是最高有效位(MSB)。

第二部分(较难):第一个输入是最低有效位(LSB)。

第三部分(困难):哪个更快、更小,(a)还是(b)?(不是从大O角度理论上的,而是从实际上的速度/大小来看)。现在把速度较慢/较大的那个变得和速度较快/较小的一样快/小。


为什么这个被关闭了?另请参见构建/可视化电路以检查3的整除性 / [codegolf.se]。 - vzn
10个回答

28

有一个相当著名的技巧可以确定一个数是否是11的倍数,即交替相加和相减其十进制数字。如果最后得到的数字是11的倍数,则你开始的数字也是11的倍数:

47278    4 - 7 + 2 - 7 + 8 = 0,是11的倍数     (47278 = 11 * 4298)
52214    5 - 2 + 2 - 1 + 4 = 8,不是11的倍数 (52214 = 11 * 4746 + 8)

我们可以将同样的技巧应用于二进制数。一个二进制数是3的倍数,如果它的位的交替和也是3的倍数的话:

4   = 100       1 - 0 + 0 = 1,不是3的倍数
6   = 110       1 - 1 + 0 = 0,是3的倍数
78  = 1001110   1 - 0 + 0 - 1 + 1 - 1 + 0 = 0,是3的倍数
109 = 1101101   1 - 1 + 0 - 1 + 1 - 0 + 1 = 1,不是3的倍数

从最高位开始或者最低位开始都没有影响,所以下面这个 Python 函数同样适用于两种情况。它接受一个返回一位一位的二进制数的迭代器。multiplier交替选择1和2,而不是1和-1,以避免对负数取模。

def divisibleBy3(iterator):

    multiplier = 1
    accumulator = 0

    for bit in iterator:
        accumulator = (accumulator + bit * multiplier) % 3
        multiplier = 3 - multiplier

    return accumulator == 0

24

在这里...有些新的...如何检查任意长度(甚至数千位)的二进制数是否可以被3整除。

可被3整除状态机

-->((0))<---1--->()<---0--->(1)        ASCII representation of graph

从图片中开始。

  1. 你从双倍圆圈开始。
  2. 当你得到一个1或0时,如果数字在圆圈内,则留在该圆圈。但是,如果数字在线上,则沿着该线移动。
  3. 重复第二步,直到所有数字都被消耗完。
  4. 如果最终停在双倍圆圈中,则二进制数可被3整除。

您还可以使用此方法生成可被3整除的数字。我想将其转换为电路也不难。

以下是使用图形的一个示例...

11000000000001011111111111101可以被3整除(最后又回到双倍圆圈)

试试吧。

您还可以使用类似的技巧执行MOD 10,用于将二进制数转换为十进制数。(10个圆圈,每个圆圈都有两倍圆圈,表示模数为0到9的值)

编辑:这是针对从左到右的数字运行的,但是修改有限状态机接受反向语言并不困难。

注意:在图形的ASCII表示中,()表示单个圆圈,(())表示双倍圆圈。在有限状态机中,它们被称为状态,而双倍圆圈是接受状态(表示最终可被3整除的状态)。


你的有限状态机适用于从左到右和从右到左运行的位。看看吧。 - Eyal
为什么这个有效?背后的逻辑是什么? - scarface
@scarface 每个节点代表系统的状态(余数为0、1或2)。每次获得一个新数字,您可以计算出每个状态下余数如何随着该新数字而改变,并使用此信息来构建图中的边缘。 - clinux

12

最低有效位的状态表:

S I S' O
0 0 0  1
0 1 1  0
1 0 2  0
1 1 0  1
2 0 1  0
2 1 2  0

解释:0可以被3整除。 0 << 1 + 0 = 0。 如果S == 0,则重复使用S = (S << 1 + I) % 3O = 1

最高位状态表:

S I S' O
0 0 0  1
0 1 2  0
1 0 1  0
1 1 0  1
2 0 2  0
2 1 1  0

解释:0是3的倍数。0 >> 1 + 0 = 0。重复使用S = (S >> 1 + I) % 3O = 1,如果S == 0

S'与上述不同,但O的工作方式相同,因为在相同情况下(00和11),S'也等于0。由于O在两种情况下是相同的,所以O_LSB = O_MSB,因此要使MSB和LSB中较短的一个尽可能短,或者反之,只需使用两者中最短的一个即可。


2
是的,这就是我一直在寻找的。MSB和LSB的解决方案是相同的。几乎没有人能够想到这一点。 - Eyal

9
这里有一个简单的手工方法。 由于1 = 22 mod 3,因此对于每个正整数,我们得到1 = 22n mod 3。 此外,2 = 22n+1 mod 3。因此,可以通过计算奇数位上的1位数,将该数字乘以2,加上偶数位上的1位数并将它们添加到结果中,然后检查结果是否可被3整除,从而确定整数是否可被3整除。
例如:5710=1110012。 在奇数位上有2位,偶数位上有2位。2*2 + 2 = 6 可以被3整除。因此57可以被3整除。
这里还有一个解决问题c)的思路。如果反转二进制整数的位顺序,则所有位保持在偶数/奇数位置或所有位都改变。因此,如果且仅当n可被3整除时,反转整数n的位顺序会得到一个可被3整除的整数。因此,任何问题a)的解决方案都适用于问题b),反之亦然。嗯,也许这可以帮助找出哪种方法更快...

我使用的另一种仅涉及移位和减法的方法: (对于57) 111001 -> 11100-1 = 11011 11011 -> 1101-1 = 1100 1100 -> 110-0 = 110 110 -> 11-0 = 11 11 -> 1-1 = 0 可被整除(对于55) 110111 -> 11011-1 = 11010 11010 -> 1101-0 = 1101 1101 -> 110-1 = 101 101 -> 10-1 = 1 不可被整除 - Agguro

8
您需要使用模3算术运算来进行所有计算。这是方法。 MSB:
number=0
while(!eof)
    n=input()
    number=(number *2 + n) mod 3

if(number == 0)
    print divisible

LSB:

number = 0;
multiplier = 1;
while(!eof)
    n=input()
    number = (number + multiplier * n) mod 3
    multiplier = (multiplier * 2) mod 3

if(number == 0)
   print divisible

这是一个通用的想法...

现在,你需要明白为什么这是正确的。

当然,要自己完成作业;)


注意:乘数将始终是序列(1、2、1、2,...)。 - Anton Tykhyy
面试问题,非作业。这只是一个谜题,我已经知道答案了。第三部分怎么样? - Eyal
2
@Eyal Buddy,我已经给了您一个高效的解决方案......这已经完成了99%的工作。现在要对其进行优化。如果两个位的内存对您有影响,请尝试找到更多的技巧;) - Artyom
3
你希望别人在面试求职时解决一个难题,而你已经知道答案?这可真有点难度。你当时能解决这个问题吗? - Bjorn
不错的算法。你能告诉我们它来自哪里吗? - undefined
显示剩余2条评论

4
这里的想法是数字可以任意增长,这意味着你不能在这里使用“模3”,因为你的数字将超出整数类的容量。
我们需要注意数字发生的变化。如果你在向右添加位,实际上你正在左移一位并添加新的位。
左移相当于乘以2,而添加新位要么是加0要么是加1。假设我们从0开始,我们可以根据上一个数字的模3递归地进行这个过程。
last | input || next | example
------------------------------------
0    | 0     || 0    | 0 * 2 + 0 = 0
0    | 1     || 1    | 0 * 2 + 1 = 1
1    | 0     || 2    | 1 * 2 + 0 = 2
1    | 1     || 0    | 1 * 2 + 1 = 0 (= 3 mod 3)
2    | 0     || 1    | 2 * 2 + 0 = 1 (= 4 mod 3)
2    | 1     || 2    | 2 * 2 + 1 = 2 (= 5 mod 3)

现在让我们看看向左添加一点会发生什么。首先请注意:

22n mod 3 = 1

22n+1 mod 3 = 2

因此,根据当前迭代次数是奇数还是偶数,我们必须将模数加1或2。

last | n is even? | input || next | example
-------------------------------------------
d/c  | don't care | 0     || last | last + 0*2^n = last
0    | yes        | 1     || 0    | 0 + 1*2^n = 1 (= 2^n mod 3)
0    | no         | 1     || 0    | 0 + 1*2^n = 2 (= 2^n mod 3)
1    | yes        | 1     || 0    | 1 + 1*2^n = 2
1    | no         | 1     || 0    | 1 + 1*2^n = 0 (= 3 mod 3)
1    | yes        | 1     || 0    | 2 + 1*2^n = 0
1    | no         | 1     || 0    | 2 + 1*2^n = 1

接下来,将所有这些东西转换成查找表。为了提高速度,让表一次查看多个位。 - Brian
1
我在这里没有提供任何代码。你可以自由地按照你想要的方式实现它。 - Nathan Fellman
这帮助我自己构建了上面的机器(x2部分)。谢谢! - Flying_Banana

0
input  "0":       (0)  output 1
inputs "1,0,0":   (4)  output 0
inputs "1,1,0,0": (6)  output 1

这个最后一次输入应该是12,还是我误解了问题?


对于更一般的答案,这里是一个离散有限自动机(一般来说,可以创建一个DFA来描述任何n的倍数,而不仅仅是n=3) Q = q0 ... qn(在这种情况下,n为三) 转移函数Delta:D(Qn,i)= Q 2n+i mod n q0是接受状态。关键是要认识到每个新数字都会使数字加倍(i=0),或者加倍并加一(i=1)。然后,每个状态qn都是同余类,意思是:q0是数字mod n = 0,q1是数字mod n = 1,等等。为了辩护面试官,这个问题实际上确实帮助我完成了作业。 - jim
不包括格式。 - jim

0

实际上,最低有效位方法会使这更容易。在 C 中:

最高有效位方法:

/* 
Returns 1 if divisble by 3, otherwise 0
Note: It is assumed 'input' format is valid
*/
int is_divisible_by_3_msb(char *input) {
  unsigned value = 0;
  char *p = input;
  if (*p == '1') {
    value &= 1;
  }
  p++;
  while (*p) {
    if (*p != ',') {
      value <<= 1;
      if (*p == '1') {
        ret &= 1;
      }
    }
    p++;
  }
  return (value % 3 == 0) ? 1 : 0;
}

LSB 方法:

/* 
Returns 1 if divisble by 3, otherwise 0
Note: It is assumed 'input' format is valid
*/
int is_divisible_by_3_lsb(char *input) {
  unsigned value = 0;
  unsigned mask = 1;
  char *p = input;
  while (*p) {
    if (*p != ',') {
      if (*p == '1') {
        value &= mask;
      }
      mask <<= 1;
    }
    p++;
  }
  return (value % 3 == 0) ? 1 : 0;
}

就我个人而言,我很难相信其中一个会与另一个有显著的不同。


你的解决方案使用了O(n)的内存... 我很确定,这不是他们期望的。 - Artyom

0

如果一个数字的各个位数之和可以被3整除,那么这个数字也能被3整除。

因此,你可以将各个位数相加并得到总和:

  • 如果总和大于或等于10,请使用相同的方法
  • 如果总和为3、6或9,则该数字是可被3整除的
  • 如果总和不等于3、6或9,则它不能被3整除

然而,这个数字是以二进制表示的。当整数是以十进制表示时,您的方法有效。(为了好玩,它也可以用于十六进制) - Accipitridae
它也适用于二进制数。在这种情况下,您必须查看比特对: 00、01、10、11。 将比特对相加(注意,从右到左进行配对),并重复此过程,直到剩下两个比特位。如果剩余的比特位为00或11,则该数字是3的倍数且可被3整除。 - Agguro

-1

我认为Nathan Fellman在A和B部分是正确的(除了B部分需要一个额外的状态:您需要跟踪您的数字位置是奇数还是偶数)。

我认为C部分的诀窍是在每个步骤中否定last值。即,0变成0,1变成2,2变成1。


这是一个提示:你的第一句话是错误的。 - Eyal

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接