两条像面试用的编程问题,和我的囧事
2010-03-04 00:57 Milo Yip 阅读(12643) 评论(62) 编辑 收藏 举报昨天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)会做成浮点问题,所以我就改为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | template < typename t> inline T even(T x) { return x % 2 == 0 ? -1 : 1; } template < typename t> inline T sgn(T x) { if (x > 0) return 1; else if (x < 0) return -1; else return 0; } template < typename t> struct f1 { T operator()(T x) { return even(x) * x + sgn(x); } }; |
这个函数非常简单,可体现数学之美。郑老师也写了另一个比较少代码的实现:
1 2 3 4 5 6 7 8 9 10 11 | template < typename T> struct f2 { T operator()(T x) { if (x == 0) return 0; else if (x > 0) return x & 1 ? x + 1 : 1 - x; else return x & 1 ? x - 1 : -x - 1; } }; |
测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | #include <limits> #include <iostream> using namespace std; template < typename T, typename F> void test(F f) { cout << "[" << ( int )numeric_limits<T>::min() << "," << ( int )numeric_limits<T>::max() << "]" << endl; T x = numeric_limits<T>::min(); do { T y = f(f(x)); if (y != (T)-x) cout << ( int )x << " " << ( int )y << endl; x++; } while (x != numeric_limits<T>::min()); cout << endl; } void main() { test< signed char >(f1< signed char >()); test< signed short >(f1< signed short >()); test< int >(f1< int >()); test< signed char >(f2< signed char >()); test< signed short >(f2< signed short >()); test< int >(f2< int >()); } |
f1和f2的执行结果相同:
1 2 3 4 5 6 7 8 | [-128,127] 127 127 [-32768,32767] 32767 32767 [-2147483648,2147483647] 2147483647 2147483647 |
这结果说明,除了x为整数的上限时,结果正确。但因为没有额外的状态,相信这个边界问题应该不能解决。
第二题
第二题比较简单,只需要利用-(-x) = x的特点,无论x为正或负,经过这两次映射,总会有一次为正数,一次为负数。所以可以写一个函数,在x为正数时(或负数时)计算其倒数:
1 2 3 | float g( float x) { return x > 0 ? -1.0f / x : -x; } |
这个函数在x=0时无定义。
后记
我的数学不好,今天囧了。但是回想起来,如果我当初没有尝试去解决这个问题,就不会学到这些思考方式。这个小代价还是很值得的。
以上用了程序去测试正确性,应该也可以用数学归纳法去证明,读者可以试试看。
最后感谢郑老师的教导。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· .NET Core 托管堆内存泄露/CPU异常的常见思路
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验