我在上次面试中遇到的一个问题是:

设计函数f,使得:f(f(n))==-n其中n是32位有符号整数;你不能用复数算术。如果你不能为整个数字范围设计这样的函数,那么就尽可能为最大范围设计它。

有什么想法吗?


当前回答

这里有一个证明,如果不使用额外信息(除了32位的int),那么对于所有数字,这样的函数都不可能存在:

我们必须使f(0)=0。(证明:假设f(0)=x,则f(x)=f(f(0))=-0=0。现在,-x=f(f(x))=f(0)=x,这意味着x=0。)

此外,对于任何x和y,假设f(x)=y。那么我们希望f(y)=-x。并且f(f(y))=-y=>f(-x)=-y。总结一下:如果f(x)=y,那么f(-x)=-y,f(y)=-x,f(-y)=x。

因此,我们需要将除0之外的所有整数分成4个集合,但我们有奇数个这样的整数;不仅如此,如果我们去掉没有正对应的整数,我们仍然有2(mod4)个数。

如果我们去掉剩下的2个最大数(通过abs值),我们可以得到函数:

int sign(int n)
{
    if(n>0)
        return 1;
    else 
        return -1;
}

int f(int n)
{
    if(n==0) return 0;
    switch(abs(n)%2)
    {
        case 1:
             return sign(n)*(abs(n)+1);
        case 0:
             return -sign(n)*(abs(n)-1);
    }
}   

当然,另一种选择是不遵守0,并获得我们删除的2个数字作为奖励。(但这只是一个愚蠢的假设。)

其他回答

我希望你改变2个最高有效位。

00.... => 01.... => 10.....

01.... => 10.... => 11.....

10.... => 11.... => 00.....

11.... => 00.... => 01.....

正如你所看到的,这只是一个补充,省去了进位。

我是怎么得到答案的?我的第一个想法就是需要对称。4转回到我开始的地方。起初我想,这是20比特的格雷码。然后我觉得标准二进制就足够了。

没有人说它必须是无国籍的。

int32 f(int32 x) {
    static bool idempotent = false;
    if (!idempotent) {
        idempotent = true;
        return -x;
    } else {
        return x;
    }
}

作弊,但不如很多例子。更糟糕的是,查看堆栈以查看调用者的地址是否为-f,但这将更具可移植性(虽然不是线程安全的……线程安全版本将使用TLS)。更邪恶的是:

int32 f (int32 x) {
    static int32 answer = -x;
    return answer;
}

当然,对于MIN_INT32的情况,这两种方法都不太有效,但除非允许返回更宽的类型,否则对此您几乎无能为力。

int f( int n ){
    return n==0?0:(n&1?n:-n)+(n<0?-1:1);
}

这对所有负数都是正确的。

    f(n) = abs(n)

因为两个互补整数的负数比正数多一个,所以f(n)=abs(n)比f(n(n)=n>0-n:n溶液,与f(n)=-abs(n)相同。一个接一个…:D

更新

不,这对一个以上的案例无效,因为我刚从李布的评论中认识到。。。abs(Int.Min)将溢出。。。

我也想过使用mod 2信息,但得出的结论是,它不起作用。。。到早期。如果操作正确,它将适用于除Int.Min之外的所有数字,因为这将溢出。

更新

我玩了一段时间,寻找一个很好的位操作技巧,但我找不到一个很不错的单行线,而mod 2解决方案适合一个。

    f(n) = 2n(abs(n) % 2) - n + sgn(n)

在C#中,这变成了以下内容:

public static Int32 f(Int32 n)
{
    return 2 * n * (Math.Abs(n) % 2) - n + Math.Sign(n);
}

要使其适用于所有值,必须将Math.Abs()替换为(n>0)+n:-n,并将计算包含在未选中的块中。然后,您甚至可以像未检查的否定一样将Int.Min映射到自身。

更新

受另一个答案的启发,我将解释函数是如何工作的,以及如何构造这样的函数。

让我们从头开始。函数f被重复应用于给定值n,产生一系列值。

    n => f(n) => f(f(n)) => f(f(f(n))) => f(f(f(f(n)))) => ...

这个问题要求f(f(n))=-n,即f的两个连续应用否定这个论点。另外两次应用f-总共四次-再次否定论点,再次产生n。

    n => f(n) => -n => f(f(f(n))) => n => f(n) => ...

现在有一个明显的长度为4的循环。代入x=f(n),并注意所获得的方程式f(f(f)n))=f(f f(x))=-x成立,得出以下结果。

    n => x => -n => -x => n => ...

所以我们得到一个长度为4的循环,有两个数字,两个数字被取反。如果将循环想象为矩形,则取反的值位于相反的角落。

构建这样一个循环的许多解决方案之一是从n开始的以下方法。

 n                 => negate and subtract one
-n - 1 = -(n + 1)  => add one
-n                 => negate and add one
 n + 1             => subtract one
 n

一个具体的例子是这样一个循环:+1=>-2=>-1=>+2=>+1。我们快完成了。注意到所构造的循环包含一个奇数正数,它的偶数后继数,并且两个数都是负数,我们可以很容易地将整数划分为许多这样的循环(2^32是四的倍数),并找到了满足条件的函数。

但我们有一个零的问题。循环必须包含0=>x=>0,因为零对自身求反。因为循环状态已经是0=>x,所以它遵循0=>x=>0=>x。这只是一个长度为2的循环,x在两次应用后变为自身,而不是变为-x。幸运的是,有一个案例解决了这个问题。如果X等于零,我们得到一个长度为1的循环,它只包含零,我们解决了这个问题,得出结论,零是f的不动点。

完成?几乎我们有2^32个数字,零是留下2^32-1个数字的固定点,我们必须将这个数字分成四个数字的循环。糟糕的是,2^32-1不是四的倍数-在任何长度为四的循环中都会保留三个数字。

我将使用范围从-4到+3的较小的3位带符号iteger集来解释解决方案的其余部分。我们用零结束了。我们有一个完整的循环+1=>-2=>-1=>+2=>+1。现在让我们构建从+3开始的循环。

    +3 => -4 => -3 => +4 => +3

出现的问题是+4不能表示为3位整数。我们可以通过将-3减为+3来获得+4,这仍然是一个有效的3位整数,但然后将1加上+3(二进制011)得到100个二进制。它被解释为无符号整数,它是+4,但我们必须将它解释为有符号整数-4。因此实际上,本例中的-4或一般情况下的Int.MinValue是整数算术否定的第二个不动点-0和Int.MinValue映射到它们自己。所以循环实际上如下。

    +3 =>    -4 => -3 => -4 => -3

这是一个长度为2的循环,另外+3通过-4进入循环。因此,-4在两个函数应用程序之后正确映射到自身,+3在两个功能应用程序之后被正确映射到-3,但-3在两个应用程序之后错误映射到自身。

所以我们构造了一个函数,它适用于除1以外的所有整数。我们能做得更好吗?不,我们不能。为什么?我们必须构造长度为4的循环,并且能够覆盖多达四个值的整个整数范围。剩下的值是必须映射到自身的两个固定点0和Int.MinValue,以及必须由两个函数应用程序相互映射的两个任意整数x和-x。

为了将x映射到-x,反之亦然,它们必须形成一个四循环,并且必须位于该循环的相对角。因此,0和Int.MinValue也必须位于相反的角落。这将正确映射x和-x,但在两个函数应用程序之后交换两个固定点0和Int.MinValue,并留下两个失败的输入。因此,不可能构造一个适用于所有值的函数,但我们有一个适用所有值(除了一个值)的函数,这是我们所能达到的最佳效果。

该问题表示“32位有符号整数”,但没有指定它们是2个补码还是1个补码。

如果使用1补码,则所有2^32值都出现在长度为4的循环中-不需要零的特殊情况,也不需要条件。

在C中:

int32_t f(int32_t x)
{
  return (((x & 0xFFFFU) << 16) | ((x & 0xFFFF0000U) >> 16)) ^ 0xFFFFU;
}

这项工作由

交换高位和低位16位块反转其中一个块

两次传递后,我们得到原始值的位逆。在一中补语表示等同于否定。

示例:

Pass |        x
-----+-------------------
   0 | 00000001      (+1)
   1 | 0001FFFF (+131071)
   2 | FFFFFFFE      (-1)
   3 | FFFE0000 (-131071)
   4 | 00000001      (+1)

Pass |        x
-----+-------------------
   0 | 00000000      (+0)
   1 | 0000FFFF  (+65535)
   2 | FFFFFFFF      (-0)
   3 | FFFF0000  (-65535)
   4 | 00000000      (+0)