我上次面试时收到的一个问题:
设计一个函数
f
,使得:f(f(n)) == -n
当
n
是一个32位的有符号整数时,你不能使用复数运算。如果你不能为所有数字范围设计这样的函数,那就尽可能地为最大范围设计。
有什么想法吗?
您没有说明他们期望的是什么类型的语言...这里是一个静态解决方案(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()
class C a b | a->b where { f :: a->b }
; instance C Int (()->Int) where { f=const.negate }
; instance C (()->Int) Int where { f=($()) }
。 - leftaroundaboutf(n) = math.copysign(1, n) - (-1)**n * n
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会自动将整数提升为任意长度的长整型。在其他语言中,最大的正整数将溢出,因此它适用于除了最大的正整数以外的所有整数。
要使其适用于实数,您需要将 (-1)ⁿ 中的 n 替换为 { 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));
}
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进行计算,然后把我们删掉的那两个数作为奖励。(但这只是一个愚蠢的假设。)n = -2147483648
(最小值); 在这种情况下,您不能对 n
取绝对值,结果将是未定义的(或异常)。 - Kirk Broadhurst由于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));
}
或者,您可以滥用预处理器:
#define f(n) (f##n)
#define ff(n) -n
int main()
{
int n = -42;
cout << "f(f(" << n << ")) = " << f(f(n)) << endl;
}
这对所有负数都成立。
f(n) = abs(n)
对于二进制补码整数,负数比正数多一个,因此 f(n) = abs(n)
比 f(n) = n > 0 ? -n : n
多适用一种情况,而后者与 f(n) = -abs(n)
相同。恰好多了一种情况... :D
更新
不,它并不比 litb 的评论更适用于一种情况...abs(Int.Min)
将会溢出...
我也考虑过使用模 2 信息,但得出结论:不可行...还为时过早。如果正确实现,它将适用于除 Int.Min
以外的所有数字,因为这个将会溢出。
更新
我试着找一个好的位操作技巧,但是我没有找到一个漂亮的单行代码,而模 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
并将计算包含在一个 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) => ...现在有一个明显的长度为四的循环。将
x=f(n)
代入,并注意等式 f(f(f(n)))=f(f(x))=-x
成立,则得到以下结果。
n => x => -n => -x => n => ...因此,我们得到了一个长度为四的循环,其中包含两个数字和这两个数字的反向值。如果你将这个循环想象成一个矩形,那么取反的值就位于对角线上。
n => 取反并减一 -n - 1 = -(n + 1) => 加一 -n => 取反并加一 n + 1 => 减一 n一个具体的例子是这样一个循环:
+1 => -2 => -1 => +2 => +1
。我们已经快完成了。注意到构造的循环包含一个奇正数、它的偶数后继以及这两个数的相反数,我们可以轻松地将整数分成许多这样的循环(2^32
是四的倍数),并找到满足条件的函数。0 => x => 0
,因为零被取反为它本身。而且因为循环状态已经是 0 => x
,所以得到 0 => x => 0 => x
。这只是一个长度为二的循环,x
在两次应用后变成它本身,而不是变成 -x
。幸运的是,有一种情况可以解决这个问题。如果 X
等于零,我们得到一个长度为一的循环,只包含零,我们解决了这个问题,得出零是 f
的一个固定点。2^32
个数字,零是一个固定点,留下 2^32 - 1
个数字,我们必须将这个数字分成长度为四的循环。不好的是,2^32 - 1
不是四的倍数——还剩下三个数字不在任何长度为四的循环中。-4
到 +3
的三位有符号整数的更小集合来解释解决方案的其余部分。我们已经处理完了零。我们有一个完整的循环 +1 => -2 => -1 => +2 => +1
。现在让我们构造以 +3
开始的循环。问题是 +4
不能被表示为3位整数。我们可以通过将 -3
变为 +3
(仍然是一个有效的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
在两个函数应用后错误地映射到自身。0
和 Int.MinValue
,它们必须映射到自身,并且两个任意整数 x
和 -x
必须通过两个函数应用相互映射。为将 x
映射到 -x
以及将其反过来,它们必须形成一个四边形循环,并且必须位于该循环的对角处。因此,0
和 Int.MinValue
也必须位于对角位置。这将正确地映射 x
和 -x
,但在两次函数应用后交换两个固定点 0
和 Int.MinValue
,并留下两个失败的输入。因此,无法构建适用于所有值的函数,但我们有一个适用于除一个值之外的所有值的函数,这是我们能够达到的最好结果。float
与int
)。许多答案描述的“4元环”需要4个状态,可以表示为2个状态的每个维度。这个答案的问题在于它需要额外的处理空间(仅适用于-64..63,但需要-128..127的空间),并且没有明确说明书面公式! - Kirk Broadhurst适用于除 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));
}
x => -x
的固定点,因此将0
发送到0
,将-2147483648
发送到-2147483648
。对于其余的数字,请按上面图片中的箭头进行操作。正如SurDin的答案及其评论所示,在这种情况下,将有两个数字,即2147483647
和-2147483647
,没有其他剩余的数字可以交换。 - Jeppe Stig Nielsen问题没有说明函数 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
将会成立。
显然,这种方法可以扩展到适用于更广泛的数字范围...
对于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;
}