设计函数f(f(n))== -n


841

我上次面试时遇到的一个问题:

设计一个函数f,使得:

f(f(n)) == -n

n32位有符号整数在哪里?您不能使用复数算法。

如果无法为整个数字范围设计这样的函数,请为最大范围设计。

有任何想法吗?


2
这次面试是做什么工作的?
tymtam '17

Answers:


377

怎么样:

f(n)=符号(n)-(-1)n * n

在Python中:

def f(n): 
    if n == 0: return 0
    if n >= 0:
        if n % 2 == 1: 
            return n + 1
        else: 
            return -1 * (n - 1)
    else:
        if n % 2 == 1:
            return n - 1
        else:
            return -1 * (n + 1)

Python自动将整数提升为任意长度的long。在其他语言中,最大的正整数将溢出,因此它将适用于除那个整数以外的所有整数。


为了使之成为真正的数字工作,你需要更换ñ在(-1)ñ{ ceiling(n) if n>0; floor(n) if n<0 }

在C#中(除了在溢出情况下,任何double都适用):

static double F(double n)
{
    if (n == 0) return 0;

    if (n < 0)
        return ((long)Math.Ceiling(n) % 2 == 0) ? (n + 1) : (-1 * (n - 1));
    else
        return ((long)Math.Floor(n) % 2 == 0) ? (n - 1) : (-1 * (n + 1));
}

10
因为-1 * 0仍为0,所以被-1打破
Joel Coehoorn

3
不,不是。f(-1)=0。f(0)= 1
1800信息

5
它被破坏为1。f(1)=0。f(0)= 1
1800信息

18
嗯,用偶数和奇数保存状态,我应该想到这一点。
未知

38
我认为最重要的不是实际功能(有无数种解决方案),而是构建此类功能的过程。
pyon

440

您没有说他们期望使用哪种语言...这是一个静态解决方案(Haskell)。基本上搞乱了两个最高有效位:

f :: Int -> Int
f x | (testBit x 30 /= testBit x 31) = negate $ complementBit x 30
    | otherwise = complementBit x 30

在动态语言(Python)中,这要容易得多。只需检查参数是否为数字X并返回返回-X的lambda:

def f(x):
   if isinstance(x,int):
      return (lambda: -x)
   else:
      return x()

23
很酷,我喜欢这个... JavaScript中的相同方法:var f = function(n){return(typeof n =='function')吗?n():function(){return -n; }
马克·雷诺夫

可能仅仅是我的Haskell生锈了,但您是否检查了(f 0)?至少在我们使用环绕算法处理32位int时(在求反操作中),这看起来应该产生与(f 0x80000000)相同的结果。那将是不好的。
达里乌斯·培根

11
一般的面试官甚至会知道lambda结构什么吗?
杰里米·鲍威尔

4
当然,这种类型的作弊伎俩也适用在Haskell,即使它是静态的:class C a b | a->b where { f :: a->b }; instance C Int (()->Int) where { f=const.negate }; instance C (()->Int) Int where { f=($()) }
左右20:39左右行驶

4
什么?您从哪儿得到typeof f(n)==='function'的想法,特别是在其中n是数字并且您期望返回数字的情况下?我不了解实例如何在这里适用。我的Python说得不好,但是在这种情况下,在JS中检查函数类型的参数显然是错误的。仅数字解适用于此处。f是函数,f(n)是数字。
哈利

284

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

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

此外,对于任何xy,假设f(x) = y。那我们要f(y) = -x。和f(f(y)) = -y => f(-x) = -y。总结一下:如果f(x) = y,则f(-x) = -yf(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个数字作为奖励。(但这真是愚蠢。)


29
我不敢相信我必须深入阅读才能找到一个很好的程序解决方案,该解决方案无需使用全局变量或混淆代码的技巧就可以处理负数。如果我能多次投票赞成你,我会的。
凯尔·西梅克

很好的观察,在任何n个带符号的位中都有奇数个非零整数。
Andres Jaan Tack

这也是我的答案,但要注意边缘情况n = -2147483648(最小值);abs(n)在这种情况下,您将无法做到,结果将是不确定的(或例外)。
柯克·布罗德赫斯特

1
@ a1kmm:对不起,上面的-2³²应该是-2³¹。无论如何,f(0)≠0(所以f(0)=-2?3)的情况实际上是更简单的情况,因为我们证明了这两个与其余部分无关。我们需要考虑的另一种情况是f(0)= 0,但是对于某些x≠0,x≠-2³¹,f(x)=-2³¹。在那种情况下,f(-2 13)= f(f(x))=-x(注意-x不能为-2 3 -1,因为不存在这样的x)。进一步令f(-x)= y。然后f(y)= f(f(-x))= x。同样,y不能为-2 3-1(因为f(y)= x,但f(-2 3-1)=-x,并且x不为0)。因此,-23-1 = f(x)= f(f(y))=-y,这是不可能的。因此,确实必须将0和-2¹¹与其余部分断开连接(而不是其他任何图像)。
ShreevatsaR 2013年

1
@will没有符号零,如果(按照我的假设)我们正在谈论二进制补码32位整数。
goffrie 2013年

146

感谢C ++中的重载:

double f(int var)
{
 return double(var);
} 

int f(double var)
{
 return -int(var);
}

int main(){
int n(42);
std::cout<<f(f(n));
}

4
不幸的是,由于名称修饰,您称为“ f”的函数实际上具有怪异的名称。
pyon

1
我曾想到过类似的事情,但是在C语言中,这被扔掉了……干得好!
Liran Orevi

@Rui Craverio:由于作者选择使用var关键字作为变量名,因此在.NET 3.5及更高版本中将无法使用。
Kredns

72
从技术上来说...这不是问题的要求。您定义了2个f()函数,f(int)和f(float),并且问题询问“设计函数f()...”
elcuco

2
@elcuco当然,从技术上讲,但是从逻辑上讲,它是一个具有多个重载的函数(您可以使用f(f(42))进行此操作)。由于该定义没有说明任何有关参数和返回值的内容,因此我很难接受它作为技术定义。
Marek Toman 2013年

135

或者,您可以滥用预处理器:

#define f(n) (f##n)
#define ff(n) -n

int main()
{
  int n = -42;
  cout << "f(f(" << n << ")) = " << f(f(n)) << endl;
}

那么,您将成为Konrad“ Le Chiffre” Rudolph吗?我去买外套。是的,我知道整个“ void main”,但是添加了“ return 0;”。就是这么多的额外努力;-)
Skizz 2009年

25
@Skizz,即使使用int返回值,在c ++中也不需要从main返回0。因此,通过正确执行操作,您实际上少输入一个字符!
丹·奥尔森,2009年

10
Skizz总是滥用预处理程序:D
Arnis Lapsa,2009年

23
这不是一个函数..所以这不是有效的解决方案
smerlin 2010年

3
@smerlin:从技术上讲,它是一个内联函数,它返回一个内联函数:两者的主体在编译时或在编译之前就已展开。没有比这更有效率的了。
乔恩·普迪

103

所有负数都是这样。

    f(n)=绝对值(n)

由于补码整数的负数比正数多,f(n) = abs(n)因此比f(n) = n > 0 ? -n : n与相同的解在多一种情况下有效f(n) = -abs(n)。让你一个...:D

更新

不,它在一种情况下是无效的,因为我刚刚被litb的评论所认可... abs(Int.Min)只会溢出...

我也考虑过使用mod 2信息,但得出的结论是,它直到早期都行不通。如果操作正确,它将适用于所有数字,除非Int.Min会溢出。

更新

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

    f(n)= 2n(绝对(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并将计算包括在unchecked块中。然后,您甚至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(x)) = -x成立,得出以下结果。

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

因此,我们得到了一个长度为4的循环,其中包含两个数字,而两个数字为负。如果将循环想象为矩形,则取反的值位于相对的角上。

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

 n =>求反,然后减去1
-n-1 =-(n + 1)=>加一
-n =>取反并加一个
 n + 1 =>减一
 ñ

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

但是我们有一个零问题。循环必须包含,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位带符号iteger集来解释解决方案的其余部分+3。我们完成了零。我们有一个完整的周期+1 => -2 => -1 => +2 => +1。现在让我们构造从开始的循环+3

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

出现的问题+4是不能表示为3位整数。我们将+4取反-3,即+3-仍然是有效的3位整数-,然后将其加到+3(binary 011)将产生100二进制。解释为无符号整数,+4但是我们必须将其解释为有符号整数-4。因此,实际上-4对于此示例或Int.MinValue一般情况,是整数算术求反的第二个固定点- 0Int.MinValue映射到它们自己。因此,周期实际上如下。

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

这是长度为2 +3的循环,另外通过进入循环-4。其结果是-4经过两个功能应用正确映射到自身,+3正确映射到-3经过两次的功能应用,但-3经过两级功能的应用被错误地映射到自身。

因此,我们构造了一个功能,该功能适用​​于除一个整数之外的所有整数。我们可以做得更好吗?不,我们不可以。为什么?我们必须构造长度为4的循环,并且能够覆盖整个整数范围(最多四个值)。剩余值是两个固定点0Int.MinValue必须被映射到自身和两个任意的整数x,并-x必须由两个功能的应用程序被映射到彼此。

为了映射x-x反之亦然,它们必须形成四个周期,并且必须位于该周期的相对角。结果0Int.MinValue也必须处于相反的角落。这将正确映射x-x交换两个固定点,0Int.MinValue在两个函数应用程序之后进行交换,并为我们提供两个失败的输入。因此,不可能构造一个适用于所有值的函数,但是我们拥有一个适用于除一个值之外的所有值的函数,这是我们可以实现的最佳结果。


不符合标准:abs(abs(n))!= -n
Dan Olson

就像他说的那样,对于所有负数都可以做到。那就是问题的一部分:如果您不能提出一个通用的方案,那么请提出一个适用范围最广的方案。
jalf

此答案至少与Marj Synowiec和Rowland Shaw的答案一样好,它仅适用于不同范围的数字
1800信息

19
杜德(Dude),您也可以摆脱“更新”,而写一个一致的正确答案。底部3/4(“灵感来自另一个答案”)很棒。
Andres Jaan Tack

1
我真的很喜欢负数的绝对值解决方案。简单易懂。
托尔比约恩Ravn的安德森

97

使用复数,可以有效地将数字取负的任务分为两个步骤:

  • 将n乘以i,得到n * i,即n逆时针旋转90°
  • 再乘以i,得到-n

很棒的是,您不需要任何特殊的处理代码。乘以我就可以了。

但是您不可以使用复数。因此,您必须使用部分数据范围以某种方式创建自己的虚轴。由于您需要的假想(中间)值与初始值一样多,因此只剩下一半的数据范围。

我假设下一个带符号的8位数据,试图在下图上形象化。您必须将其缩放为32位整数。初始n的允许范围是-64至+63。这是函数对正数n的作用:

  • 如果n在0..63(初始​​范围)内,则函数调用添加64,将n映射到范围64..127(中间范围)
  • 如果n在64..127(中间范围)中,则该函数从64减去n,将n映射到范围0 ..- 63

对于负数n,该函数使用中间范围-65 ..- 128。

替代文字


4
@geschema,您使用什么工具来创建这些漂亮的图形?
jwfearn

10
抱歉,该问题明确指出没有复数。
Rui Craveiro 09年

6
@Liran:我使用了OmniGraffle(仅限Mac)
geschema,

39
+1我认为这是最好的答案。我认为人们的阅读不够,因为他们都指出问题在于不能使用复数。我读了整本书,然后您用复数描述了解决方案,从而为提出问题的非复杂解决方案奠定了基础。做得非常好。
jrista

1
@jrista所有解决方案都使用第二维,这就是所有“复数”的真正含义(大多数使用奇数和偶数,&上面使用floatvs int)。许多答案描述的“ 4元环”需要4个状态,可以将其表示为2维,每个维具有2个状态。这个答案的问题是,它需要额外的处理空间(对于-64..63仅“有效”,但需要-128..127的空间),并且没有明确说明书写的公式!
柯克·布罗德赫斯特

65

除int.MaxValue和int.MinValue以外的作品

    public static int f(int x)
    {

        if (x == 0) return 0;

        if ((x % 2) != 0)
            return x * -1 + (-1 *x) / (Math.Abs(x));
        else
            return x - x / (Math.Abs(x));
    }

画报


不知道为什么这被否决了。对于什么输入失败?
罗德里克·查普曼

为什么不使用signum函数呢?!
2011年

1
图像真的很好。因为这两个数字是求反运算符的固定点,所以“ 发送0至” 0和“ -2147483648至” 。对于其余数字,请遵循上图中的箭头。从SurDin的答案及其评论中可以明显看出,在这种情况下将有两个数字,并且没有其他可交换的数字。-2147483648x => -x2147483647-2147483647
Jeppe Stig Nielsen

它看起来像一个笑脸-有很多皱纹
Anshul

48

问题没有说明该函数的输入类型和返回值f必须是什么(至少不是您介绍它的方式)...

...只是当n是32位整数时 f(f(n)) = -n

所以,怎么样

Int64 f(Int64 n)
{
    return(n > Int32.MaxValue ? 
        -(n - 4L * Int32.MaxValue):
        n + 4L * Int32.MaxValue);
}

如果n是32位整数,则该语句f(f(n)) == -n为true。

显然,这种方法可以扩展到适用于更大范围的数字...


2
鬼 字符数限制。
乔·菲利普斯

2
是的,我正在研究类似的方法。你打败了我。+1 :)
jalf

1
非常聪明!这与使用复数非常接近(并且实际上与之相同),这将是显而易见的理想解决方案,但明确禁止使用。在允许的数字范围之外工作。
Kirk Broadhurst,2009年

48

对于javascript(或其他动态类型的语言),您可以让函数接受一个int或一个对象,然后返回另一个。即

function f(n) {
    if (n.passed) {
        return -n.val;
    } else {
        return {val:n, passed:1};
    }
}

给予

js> f(f(10))  
-10
js> f(f(-10))
10

或者,您可以在强类型语言中使用重载,尽管这可能会违反规则,即

int f(long n) {
    return n;
}

long f(int n) {
    return -n;
}

后者并不意味着需要“ a”(单一)功能。:)
Drew

删除答案的后半部分,这是正确的答案。
jmucchiello

@Drew,这就是为什么我提到它可能会违反规则
cobbal

2
在JavaScript中,函数是对象,因此可以保留状态。
Nosredna

1
IMO:函数f(n){return n.passed?-n.val:{val:n,已通过:1}}更具可读性,并且更短。
SamGoody 2013年

46

根据您的平台,某些语言可以使您保持功能状态。VB.Net,例如:

Function f(ByVal n As Integer) As Integer
    Static flag As Integer = -1
    flag *= -1

    Return n * flag
End Function

IIRC,C ++也允许这样做。我怀疑他们正在寻找其他解决方案。

另一个想法是,由于它们没有定义对函数的第一次调用的结果,因此您可以使用奇/偶数来控制是否反转符号:

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

将所有偶数的数量加一,从所有奇数的数量中减一。两次调用的结果大小相同,但是一次调用甚至是我们交换符号的结果。在某些情况下,此操作将不起作用(-1,max或min int),但它的效果比迄今为止建议的任何其他功能都要好得多。


1
我相信它确实适用于MAX_INT,因为这总是很奇怪。它不适用于MIN_INT和-1。
Airsource Ltd,2009年

9
如果有副作用,它不是功能。

12
这在数学上可能是正确的,但是在编程中却无关紧要。所以问题是他们正在寻找数学解决方案还是编程解决方案。但是考虑到它是编程工作……
Ryan Lundy

+1我打算用“ static int x”在C中发布一个,实现一个带有否定输出的FIFO。但这已经足够接近了。
phkahler

2
@nos:是的,只是参照透明。
克拉克·加贝尔

26

利用JavaScript异常。

function f(n) {
    try {
        return n();
    }
    catch(e) { 
        return function() { return -n; };
    }
}

f(f(0)) => 0

f(f(1)) => -1


我怀疑之前是否曾使用过这样的例外... :)
NoBugs 2013年

+1开箱即用的想法。凉!但是在生产代码中,为了安全起见,我会使用typeof。

21

对于所有32位值(警告为-0为-2147483648)

int rotate(int x)
{
    static const int split = INT_MAX / 2 + 1;
    static const int negativeSplit = INT_MIN / 2 + 1;

    if (x == INT_MAX)
        return INT_MIN;
    if (x == INT_MIN)
        return x + 1;

    if (x >= split)
        return x + 1 - INT_MIN;
    if (x >= 0)
        return INT_MAX - x;
    if (x >= negativeSplit)
        return INT_MIN - x + 1;
    return split -(negativeSplit - x);
}

基本上,您需要将每个-x => x => -x循环与ay => -y => y循环配对。所以我将球拍的两侧配对了split

例如对于4位整数:

0 => 7 => -8 => -7 => 0
1 => 6 => -1 => -6 => 1
2 => 5 => -2 => -5 => 2
3 => 4 => -3 => -4 => 3

21

一个C ++版本,可能会稍微改变规则,但适用于所有数字类型(浮点数,整数,双精度数),甚至是使一元负号过载的类类型:

template <class T>
struct f_result
{
  T value;
};

template <class T>
f_result <T> f (T n)
{
  f_result <T> result = {n};
  return result;
}

template <class T>
T f (f_result <T> n)
{
  return -n.value;
}

void main (void)
{
  int n = 45;
  cout << "f(f(" << n << ")) = " << f(f(n)) << endl;
  float p = 3.14f;
  cout << "f(f(" << p << ")) = " << f(f(p)) << endl;
}

好主意。另一种选择是,您可能会丢失该结构,而是让一个函数返回一个指针,另一个函数取消引用并取反。
2009年

20

x86 asm(AT&T风格):

; input %edi
; output %eax
; clobbered regs: %ecx, %edx
f:
    testl   %edi, %edi
    je  .zero

    movl    %edi, %eax
    movl    $1, %ecx
    movl    %edi, %edx
    andl    $1, %eax
    addl    %eax, %eax
    subl    %eax, %ecx
    xorl    %eax, %eax
    testl   %edi, %edi
    setg    %al
    shrl    $31, %edx
    subl    %edx, %eax
    imull   %ecx, %eax
    subl    %eax, %edi
    movl    %edi, %eax
    imull   %ecx, %eax
.zero:
    xorl    %eax, %eax
    ret

检查代码,传递所有可能的32位整数,错误-2147483647(下溢)。


19

使用全局变量...但是是这样吗?

bool done = false
f(int n)
{
  int out = n;
  if(!done)
  {  
      out = n * -1;
      done = true;
   }
   return out;
}

3
不确定这是提问者的意图,但+1表示“开箱即用”。
Liran Orevi

5
您应该始终说“ done =!done”,而不是有条件地说“ done = true”,这样您的函数可以被多次使用。
克里斯·卢兹

@Chris,因为将done设置为true是在if(!done)块内部,所以它等效于done =!done,但是!done不需要进行计算(或编译器优化,如果足够聪明的话)。 。
nsayer 2010年

1
我的第一个想法就是使用全局变量来解决这个问题,尽管感觉就像在欺骗这个特定问题。但是,我要指出的是,考虑到问题的规范,全局变量解决方案是最好的解决方案。使用全局变量可以很容易地了解正在发生的事情。我同意完成=完成会更好。只需将其移出if子句即可。
阿德拉斯

3
从技术上讲,任何维持状态的不是功能,而是状态机。根据定义,函数始终为相同的输入提供相同的输出。
特德·霍普

19

该Perl解决方案适用于整数,浮点数和字符串

sub f {
    my $n = shift;
    return ref($n) ? -$$n : \$n;
}

尝试一些测试数据。

print $_, ' ', f(f($_)), "\n" for -2, 0, 1, 1.1, -3.3, 'foo' '-bar';

输出:

-2 2
0 0
1 -1
1.1 -1.1
-3.3 3.3
foo -foo
-bar +bar

但这并不能保持它的整数。实际上,您实际上是将全局变量数据存储在int“ n”本身中...除非它不是int,否则您将无法做到这一点。例如,如果n是一个字符串,我可以使548变成“ First_Time_548”,然后在下一次运行该函数时... if(prefix == First_Time_“)用”-“替换” First_Time_“
Albert Renshaw

@AlbertRenshaw不知道从哪里得到这些想法。(1)这里绝对没有涉及全局变量。(2)如果给函数一个int值,则返回一个int值–或引用一个int值,如果您将该函数调用的次数为奇数次。(3)也许最根本的是Perl。出于所有实际目的,整数和字符串是完全可以互换的。看起来像数字的字符串在大多数情况下都可以很好地用作数字,并且数字会在需要时高兴地字符串化。
FMc

抱歉,我似乎不太了解perl,似乎您使用的是全球阵列哈哈
Albert Renshaw 2014年

18

没有人说过f(x)必须是同一类型。

def f(x):
    if type(x) == list:
        return -x[0]
    return [x]


f(2) => [2]
f(f(2)) => -2

16

我实际上并没有在尝试解决问题本身,但是确实有一些评论,因为问题指出这个问题是(工作?)面试的一部分:

  • 我首先会问:“为什么需要这样的功能?这是更大的问题吗?” 而不是尝试现场解决实际提出的问题。这说明了我如何思考以及如何解决此类问题。谁知道?甚至可能首先是在面试中提出问题的实际原因。如果答案是“没关系,请假设它是必需的,然后告诉我如何设计此功能”。然后,我将继续这样做。
  • 然后,我将编写将使用的C#测试用例代码(显而易见的是:从int.MinValueint.MaxValue,然后n对该范围调用中的每个循环进行循环,f(f(n))并检查结果为-n),然后告诉我将使用“测试驱动开发”来获得此功能。
  • 只有面试官继续要求我解决所提出的问题,我才真正开始在面试过程中尝试编写伪代码,以尝试获得某种答案。但是,如果面试官能够表明公司的状况,我真的不认为我会跳槽工作。

哦,此答案假设面试是针对C#编程相关职位的。如果面试是针对与数学相关的职位,那当然是一个愚蠢的答案。;-)


7
您很幸运,他们要求输入32 int,如果是64位,则在运​​行测试后,采访将永远不会继续;-)
alex2k8

的确,如果我什至要真正写出该测试并在面试中运行它的话。;-)我的观点:我尽量不要在面试中达到这一点。在我看来,编程更多是“思考方式”,而不是“他如何编写代码行”。
peSHIr

7
在实际的采访中不要遵循这个建议。面试官希望您真正回答问题。对问题的相关性提出质疑不会给您带来任何好处,但可能会使面试官感到恼火。设计简单的测试不会使您更接近答案,也无法在面试中进行测试。如果您获得了更多信息(32位),请尝试弄清楚这可能是有用的。
Stefan Haustein 2013年

当我要求提供更多信息(可能会质疑其问题在过程中的相关性)时感到烦恼的面试官并不是我一定要与之合作的面试官。因此,我将在面试中不断提出类似的问题。如果他们不喜欢,我可能会结束采访,以免再浪费我们两个时间。不喜欢“我只遵从命令”的思维定式。你做..?
peSHIr

16

我会更改2个最高有效位。

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

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

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

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

如您所见,它只是一个附加项,省去了进位。

我是怎么得到答案的?我的第一个想法只是对称性的需要。4转回到我的起点。起初我以为是2位格雷码。然后我认为实际上标准二进制就足够了。


这种方法的问题在于,它不能与二进制补码负数一起使用(这是每个现代CPU都使用的)。这就是为什么我删除了相同的答案。
Tamas Czinege 09年

该问题指定了32位带符号整数。此解决方案不适用于32位有符号整数的2的补码或1的补码表示形式。它仅适用于符号和大小表示形式,这在现代计算机中非常罕见(浮点数除外)。
Jeffrey L Whitledge,09年

1
@DrJokepu-哇,六个月后-金克斯!
Jeffrey L Whitledge,09年

您是否只需要在函数内部将数字转换为正负号表示,执行转换,然后再转换回本机整数表示,然后再返回?
Bill Michell

我喜欢您通过引入一个虚构的位基本上实现了复数:)
jabirali

16

这是受要求启发的解决方案,或声称不能使用复数来解决此问题。

乘以-1的平方根是一个想法,这似乎只是失败了,因为-1在整数上没有平方根。但是,使用诸如mathematica之类的程序可以得出以下等式

(1849436465 2 +1)mod(2 32 -3)= 0。

这几乎与平方根为-1一样好。函数的结果必须是有符号整数。因此,我将使用修改后的模运算mods(x,n),它返回与最接近0的x模n一致的整数y。只有很少的编程语言具有suc模运算,但可以轻松定义。例如在python中,它是:

def mods(x, n):
    y = x % n
    if y > n/2: y-= n
    return y

使用上面的方程,现在可以解决问题

def f(x):
    return mods(x*1849436465, 2**32-3)

这满足f(f(x)) = -x范围内的所有整数。的结果也在此范围内,但当然计算将需要64位整数。[-231-2, 231-2]f(x)


13

C#的范围为2 ^ 32-1个数字,除(Int32.MinValue)以外的所有int32数字

    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

对于f(0)为1073741824.f(1073741824)=0。f(f(1073741824))= 1073741824
Dinah 2009年

您通常可以证明,对于任何位大小的二进制补码整数类型,该函数必须至少对两个输入值无效。
悠闲的

12

本质上,该函数必须将可用范围划分为大小为4的循环,其中-n在n循环的另一端。但是,0必须是大小为1的循环的一部分,否则为0->x->0->x != -x。由于单独存在0,因此在我们的范围内必须有3个其他值(其大小是4的倍数),而不是在4个元素的适当循环中。

我选择了这些额外的怪异值是MIN_INTMAX_INTMIN_INT+1。此外,MIN_INT+1MAX_INT正确映射,但会卡在此处而不重新映射。我认为这是最好的折衷方案,因为它具有仅极限值无法正常工作的良好特性。而且,这意味着它将对所有 BigInts 适用。

int f(int n):
    if n == 0 or n == MIN_INT or n == MAX_INT: return n
    return ((Math.abs(n) mod 2) * 2 - 1) * n + Math.sign(n)

12

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

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而言,这两种方法都无法很好地发挥作用,但是除非允许您返回更广泛的类型,否则您几乎无能为力。


您可以“升级”它以询问地址(是的,您必须通过ref \作为指针来获得它)-在C语言中,例如:int f(int&n){static int * addr =&n; 如果(addr ==&n){return -n; } return n; }
IUnknownPointer

11

我可以想象将第31位用作假想(i)位将是支持整个范围一半的方法。


这将比当前的最佳答案更复杂,但效果不明显
1800信息

1
@ 1800信息:另一方面,域[-2 ^ 30 + 1,2 ^ 30-1]是连续的,从数学的角度来看更吸引人。
Jochen Walter,2009年

10

适用于n = [0 .. 2 ^ 31-1]

int f(int n) {
  if (n & (1 << 31)) // highest bit set?
    return -(n & ~(1 << 31)); // return negative of original n
  else
    return n | (1 << 31); // return n with highest bit set
}

10

问题指出“ 32位有符号整数”,但没有指定它们是二进制补码还是二进制补

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

在C中:

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

这是由

  1. 交换高和低16位块
  2. 反转其中一个模块

经过两次传递后,我们得到原始值的按位倒数。补码表示等于否定。

例子:

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)

1
跨不同架构的字节顺序呢?
史蒂文(Steven)2009年

1
所有的算术都是32位的。我不处理单个字节,因此字节顺序不会影响它。
finnw

这听起来很接近。您可以假定输入为2补码。因此,您将转换为符号位表示形式。现在,根据最后一位,您可以翻转第一位和最后一位,或者仅翻转最后一位。基本上,您只对偶数取反,并一直循环偶数/奇数。因此,您在2次通话后从奇数回到奇数,甚至偶数。最后,您转换回2补码。在下面的某个地方发布了此代码。
Stefan Haustein 2013年

9

:D

boolean inner = true;

int f(int input) {
   if(inner) {
      inner = false;
      return input;
   } else {
      inner = true;
      return -input;
   }
}

5
可能还会使您讨论为什么全局变量如果没有将您赶出面试,那么为什么不好?
palswim


7

作为数学家,我想就这个有趣的问题分享我的观点。我认为我有最有效的解决方案。

如果我没记错的话,只需翻转第一位就可以对一个有符号的32位整数求反。例如,如果n = 1001 1101 1110 1011 1110 0000 1110 1010,则-n = 0001 1101 1110 1011 1110 0000 1110 1010。

那么,我们如何定义一个函数f,该函数采用一个带符号的32位整数并返回另一个带符号的32位整数,并且该属性取f两次与翻转第一位相同?

让我改写这个问题,而不必提及整数等算术概念。

我们如何定义一个函数f,该函数接受一个零序列和一个长度为32的序列,并返回一个零序列和一个长度相同的序列,而属性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的平方根的情况。

如果我只是单独提供伪代码而没有冗长的前奏,那似乎就像是一只兔子,我想解释一下我是如何得到解决方案的。


6
是的,这是一个有趣的问题。你知道你的数学。但这是计算机科学的问题。因此,您需要学习计算机。符号幅度表示法是允许的,但是大约在60年前就已经过时了。2的补码是最流行的。
Windows程序员

5
这是函数两次应用时对两个位所做的操作:(a,b)->(-b,a)->(-a,-b)。但是,我们试图到达(-a,b),而不是(-a,-b)。
buti-oxa,2009年

@ buti-oxa,你是对的。这两位操作应该是:00-> 01-> 10-> 11->00。但是然后我的算法假设符号幅度表示现在不受欢迎,如Windows程序员所说,所以我认为我的算法没什么用。
Yoo 2009年

那么,他不能只执行两次步骤而不是一次吗?
Nosredna,2009年

4
buti-oxa是完全正确的:函数在两次调用后甚至没有翻转第一位,而是翻转了前两位。翻转所有位更接近2的补码,但这并不完全正确。
redtuna 2010年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.