我在上次面试中遇到的一个问题是:
设计函数f,使得:f(f(n))==-n其中n是32位有符号整数;你不能用复数算术。如果你不能为整个数字范围设计这样的函数,那么就尽可能为最大范围设计它。
有什么想法吗?
我在上次面试中遇到的一个问题是:
设计函数f,使得:f(f(n))==-n其中n是32位有符号整数;你不能用复数算术。如果你不能为整个数字范围设计这样的函数,那么就尽可能为最大范围设计它。
有什么想法吗?
当前回答
作为一名数学家,我想分享我对这个有趣问题的看法。我认为我有最有效的解决方案。
如果我没记错的话,只需翻转第一位,就可以将有符号的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的平方根。
如果我只是单独展示了伪代码,而没有冗长的前奏,那么它看起来就像脱口而出的兔子,我想解释一下我是如何得到解决方案的。
其他回答
我试着打高尔夫,这是罗德里克·查普曼的回答。
无分支:74个字符
int f(int i){return(-((i&1)<<1)|1)*i-(-((i>>>31)<<1)|1)*(((i|-i)>>31)&1);}
带有分支,Java风格:58个字符
int f(int i){return i==0?0:(((i&1)==0?i:-i)+(i>0?-1:1));}
带分支,C样式:52个字符
int f(int i){return i?(((i&1)?-i:i)+(i>0?-1:1)):0;}
经过快速但有效的基准测试后,分支版本在我的机器上的速度提高了33%。(正数和负数的随机数据集,足够的重复,并防止编译器在预热时优化代码。)考虑到非分支版本中的操作数量以及可能的良好分支预测,这并不奇怪,因为函数被调用了两次:f(f(i))。当我将基准更改为度量:f(I)时,分支版本只快28%。我认为这证明了分支预测在第一种情况下确实有一些好处。更多证明:当使用f(f(f)(f(i)))进行测试时,分支版本的速度会快42%。
这个怎么样?
int nasty(int input)
{
return input + INT_MAX/2;
}
这个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
我相信这符合所有要求。没有什么规定参数必须是32位有符号整数,只有你传入的值“n”是。
long long f(long long n)
{
int high_int = n >> 32;
int low_int = n & 0xFFFFFFFF;
if (high_int == 0) {
return 0x100000000LL + low_int;
} else {
return -low_int;
}
}
事实上,这些问题更多的是关于面试官与规范、设计、错误处理、边界案例以及为解决方案选择合适的环境等进行斗争,而不是关于实际解决方案。然而::)
这里的函数是围绕封闭的4循环思想编写的。如果函数f只允许落在有符号的32位整数上,那么上面的各种解决方案都将起作用,除了其他人指出的三个输入范围数。minint永远不会满足函数方程,因此如果这是一个输入,我们将引发一个异常。
在这里,我允许Python函数操作并返回元组或整数。任务规范承认这一点,它只指定函数的两个应用程序应该返回一个与原始对象相等的对象,如果它是int32。(我会询问有关规范的更多细节)
这使得我的轨道可以很好且对称,并且可以覆盖所有输入整数(minint除外)。我最初设想的循环是访问半整数值,但我不想陷入舍入错误。因此是元组表示。这是一种将复杂旋转作为元组隐藏的方式,而不使用复杂的算术机制。
注意,在调用之间不需要保留任何状态,但调用者确实需要允许返回值为元组或int。
def f(x) :
if isinstance(x, tuple) :
# return a number.
if x[0] != 0 :
raise ValueError # make sure the tuple is well formed.
else :
return ( -x[1] )
elif isinstance(x, int ) :
if x == int(-2**31 ):
# This value won't satisfy the functional relation in
# signed 2s complement 32 bit integers.
raise ValueError
else :
# send this integer to a tuple (representing ix)
return( (0,x) )
else :
# not an int or a tuple
raise TypeError
因此,将f应用于37两次得到-37,反之亦然:
>>> x = 37
>>> x = f(x)
>>> x
(0, 37)
>>> x = f(x)
>>> x
-37
>>> x = f(x)
>>> x
(0, -37)
>>> x = f(x)
>>> x
37
将f两次应用于零得到零:
>>> x=0
>>> x = f(x)
>>> x
(0, 0)
>>> x = f(x)
>>> x
0
我们处理一个问题没有解决方案的情况(在int32中):
>>> x = int( -2**31 )
>>> x = f(x)
Traceback (most recent call last):
File "<pyshell#110>", line 1, in <module>
x = f(x)
File "<pyshell#33>", line 13, in f
raise ValueError
ValueError
如果你认为函数通过模拟乘以i的90度旋转打破了“无复杂算术”规则,我们可以通过扭曲旋转来改变这一点。这里元组表示半整数,而不是复数。如果你在数字线上追踪轨道,你会得到满足给定函数关系的非相交循环。
f2: n -> (2 abs(n) +1, 2 sign( n) ) if n is int32, and not minint.
f2: (x, y) -> sign(y) * (x-1) /2 (provided y is \pm 2 and x is not more than 2maxint+1
练习:通过修改f来实现这个f2。还有其他解决方案,例如,中间着落点是有理数而不是半整数。有一个分数模块可能很有用。你需要一个符号函数。
这个练习让我真正体会到了动态类型语言的乐趣。我在C中看不到这样的解决方案。