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

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

有什么想法吗?


当前回答

我不知道这是否完全正确,但一个简单的标志不起作用吗?在C语言中,使用静态局部变量,我成功地做到了这一点:

int main()
{
    int n = -256; // 32-bit signed integer
    printf("%d", f(f(n)));
}

int f(int n){
    static int x = 0; // not returning negative;
    switch(x){
        case 0:
            x = 1;
            return n;
            break;

        case 1:
            x = 0;
            return -n;
            break;
        default:
            return -999;
            break;
    }
}

其他回答

好问题!

这花了我大约35秒的时间思考并写下:

int f(int n){
    static int originalN=0;
    if (n!=0)
        originalN=n;
    return n-originalN;
}

上述问题不要求函数只能接受32位整数,只要求给定的n是32位整数。

红宝石:

def f( n )
  return 0 unless n != 0 
  ( n == n.to_i ) ? 1.0 / n : -(n**-1).to_i
end

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

    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位整数取反。例如,如果n=1001 1101 1110 1011 1110 0000 1110 1010,则-n=0001 1101 11101 1011 11100 0000 1110 010。

那么,我们如何定义一个函数f,它接受一个带符号的32位整数,并返回另一个有符号的32位数整数,该函数的属性是:接受两次f与翻转第一位相同?

让我重新表述这个问题,而不提整数之类的算术概念。

我们如何定义一个函数f,它接受长度为32的一系列0和1,并返回长度相同的一系列零和1,同时具有两次接受f与翻转第一位相同的性质?

观察:如果你能回答32位情况的上述问题,那么你也可以回答64位情况、100位情况等。你只需将f应用于前32位。

现在,如果你能回答2位案例的问题,哇!

是的,改变前2位就足够了。

这是伪代码

1. take n, which is a signed 32-bit integer.
2. swap the first bit and the second bit.
3. flip the first bit.
4. return the result.

备注:步骤2和步骤3可以概括为(a,b)-->(-b,a)。看起来很眼熟?这应该会让你想起平面的90度旋转以及乘以-1的平方根。

如果我只是单独展示了伪代码,而没有冗长的前奏,那么它看起来就像脱口而出的兔子,我想解释一下我是如何得到解决方案的。

C#表示2^32-1个数字的范围,所有整数32(int32.MinValue除外)

    Func<int, int> f = n =>
        n < 0
           ? (n & (1 << 30)) == (1 << 30) ? (n ^ (1 << 30)) : - (n | (1 << 30))
           : (n & (1 << 30)) == (1 << 30) ? -(n ^ (1 << 30)) : (n | (1 << 30));

    Console.WriteLine(f(f(Int32.MinValue + 1))); // -2147483648 + 1
    for (int i = -3; i <= 3  ; i++)
        Console.WriteLine(f(f(i)));
    Console.WriteLine(f(f(Int32.MaxValue))); // 2147483647

打印:

2147483647
3
2
1
0
-1
-2
-3
-2147483647