您的位置:首页 > 职场人生

两条像面试用的编程问题,和我的囧事

2010-04-08 17:50 330 查看
昨天meta网友在某论坛写了两条编程题目:

设计一个函数f, 使得它满足:f(f(x))=-x,这里输入参数为32位整型
设计一个函 数g, 满足:g(g(x))=1/x, x是浮点数
以下是一些反面的解答,可澄清这两条个题目:

meta提供了同事的解答,但该解答用了static local variable来區分办调用次数。这函数有副作用,且不是thread-safe。因此这不是好答案。
Sweating和Kng Zhu网友利用语言特性,第一次调用函数时,输出第二种型别,使第二次执行该函数时,用型别来检测这是第二次调用。这个方法其实等同写两个签名不一样的函 数,和题目有出入,并不是正确答案。
Wang Feng网友利用复数去解题。不过如果输入输出能增加一个变量,不如直接用该变量来储存调用次数,就不需用复数了。

我的囧事

第一个回覆这帖子的是网友Atry,他解答了问题(1),但没有写当中的逻辑(其实和本文的解法思路一样)。

另外,有网友认为两条问题是无解的,我也是当中一员。因此,今天午饭时间就发了以下的错误证明:

假设一个函数 f 存在,x 为32-bit整数,f(x)) = -x
设 y = f(x)
f(f(x)) = -x ⇔ f(y) = –x
变换变量, f(y) = -x ⇔ f(x) = -y ⇔ y = -f(x)
y = f(x) = -f(x)
第5步只是当y=0才成立,和f的值域矛盾,按反证法,函数f不存在。

Lu JunZhu和Hongzhang Liu网友指出第4行有错误。Hongzhang Liu更套用同样思路,可以错误地证明,f(f(x))=4x中的f是不存在的。那就肯定是我的错了,囧rz。我当时没想到错在那,就去请教郑晖老师。

郑老师指出,只有自由变量(Free Variable)才可以置换(Subsititue)。上述证明中,x和y不是自由变量。 之后,郑老师独立做了一个解答,我编程序来测试(虽然郑老师认为应该用证明方式)。以下我尝试把郑老师的解答,加上我的理解去演译一个正确答案。

问题分析

这两问题的难点在于,函数不能储存额外状态。

我们首先分析问题(1),设y=f(x),则

1. f(x) = y

2. f(y) = -x

如果再把结果-x再应用一次f 函数,f(-x) = ?

因为之前 f(y)=-x,而按题目定义,f(f(y))=-y ,所以f(-x) = -y。我们可以列出:

3. f(-x) = -y

4. f(-y) = x

我们可以发现,4次函数映射之后,会变成一个循环。也就是说,

x → y → –x → –y → x→…

我们只要把数字分为四类,就可以实现这个循环。x和-x的分别是正负号,我们可以再利用数字的奇偶性,这两个正交属性可以产生4个组合。这个循环就 可变成

正奇→ 正偶→ 负奇→ 负偶→ 正奇→ …

可以看到,这个排列的正负号是每两次更改。接下来,就要想一个函数,满足这个变化。郑老师说他经过几次推敲,得到:



实现

在编程时,由于用(int)pow(-1,x)会做成浮点问题,所以我就改为:

view sourceprint?
01
template
<
typename
t>
02
inline
T even(T x) {
03
return
x % 2 == 0 ? -1 : 1;
04
}
05
06
template
<
typename
t>
07
inline
T sgn(T x) {
08
if
(x > 0)
09
return
1;
10
else
if
(x < 0)
11
return
-1;
12
else
13
return
0;
14
}
15
16
template
<
typename
t>
17
struct
f1 {
18
T  operator()(T x) {
19
return
even(x) * x +  sgn(x);
20
}
21
};
这个函数非常简单,可体现数学之美。郑老师也写了另一个比较少代码的实现:

view sourceprint?
01
template
<
typename
T>
02
struct
f2 {
03
T  operator()(T x) {
04
if
(x == 0)
05
return
0;
06
else
if
(x > 0)
07
return
x & 1  ? x + 1 : 1 - x;
08
else
09
return
x & 1  ? x - 1 : -x - 1;
10
}
11
};

测试

view sourceprint?
01
#include <limits>
02
#include  <iostream>
03
04
using
namespace
std;
05
06
template
<
typename
T,
typename
F>
07
void
test(F f) {
08
cout   <<
"["
<< (
int
)numeric_limits<T>::min() <<
","
<< (
int
)numeric_limits<T>::max()   <<
"]"
<< endl;
09
T  x = numeric_limits<T>::min();
10
do
{
11
T y = f(f(x));
12
if
(y != (T)-x)
13
cout << (
int
)x <<
" "
<< (
int
)y << endl;
14
x++;
15
}
while
(x !=  numeric_limits<T>::min());
16
17
cout << endl;
18
}
19
20
void
main() {
21
test<
signed
char
>(f1<
signed
char
>());
22
test<
signed
short
>(f1<
signed
short
>());
23
test<
int
>(f1<
int
>());
24
25
test<
signed
char
>(f2<
signed
char
>());
26
test<
signed
short
>(f2<
signed
short
>());
27
test<
int
>(f2<
int
>());
28
}
f1和f2的执行结果相同:

view sourceprint?
1
[-128,127]
2
127 127
3
4
[-32768,32767]
5
32767 32767
6
7
[-2147483648,2147483647]
8
2147483647 2147483647
这结果说明,除了x为整数的上限时,结果正确。但因为没有额外的状态,相信这个边界问题应该不能解决。

第二题

第二题比较简单,只需要利用-(-x) = x的特点,无论x为正或负,经过这两次映射,总会有一次为正数,一次为负数。所以可以写一个函数,在x为正数时(或负数时)计算其倒数:

view sourceprint?
1
float
g(
float
x) {
2
return
x > 0 ? -1.0f / x  : -x;
3
}
这个函数在x=0时无定义。

后记

我的数学不好,今天囧了。但是回想起来,如果我当初没有尝试去解决这个问题,就不会学到这些思考方式。这个小代价还是很值得的。

以上用了程序去测试正确性,应该也可以用数学归纳法去证明,读者可以试试看。

最后感谢郑老师的教导。

原文链接:http://www.cnblogs.com/miloyip/archive/2010/03/04/1677902.html

《编程之美》,IT人求职面试必读

《编程之美》豆瓣主页

《编程之美》互动网购买链接
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: