版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
在开始之前,我觉得应该默念一遍奥卡姆剃刀原理:“如无必要,勿增实体”。我感觉这一条原则在做算法的时候特别重要:是什么就是什么,不应该增加额外的元素。当然,在需要空间换时间(或者空间换时间)的时候,增加辅助元素是有必要的,并不冲突。
为什么这么说呢,且看这两道题目:
给出一个 32 位的有符号整数,你需要将这个整数中每位上的数字进行反转。
假设我们的环境只能存储得下 32 位的有符号整数,则其数值范围为 [−2^31, 2^31 − 1]。请根据这个假设,如果反转后整数溢出,那么就返回 0。
判断一个整数是否是回文数。回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数;负数不是回文数。
其实这两道题从本质上是一样的,都是对数字的反转。
但是按照常规思路,第一反应是什么?我觉得很多人(比如我)都会想到,先把数字变成字符串,然后进行反转。这个思路看起来很合理,但仔细一想就会觉得不对,因为明明是数字之间的操作,为什么要变成字符串呢?而且在反转整数的时候,还要对负号进行额外的处理。
/**
* ------result------
* memory: 8 MB (90%)
* speed: 0 ms (100%)
*/
int reverse(int x) {
int res = 0;
while (x != 0) {
// INT32_MIN / -1会溢出,所以需要转换成long
// 至于为什么不用res跟INT32_MAX比较,因为不能确定res的符号,所以范围可能会错误
if (res != 0 &&
(long)INT32_MAX / res < 10 &&
(long)INT32_MIN / res < 10) {
return 0;
}
res = res * 10 + x % 10;
x /= 10;
}
return res;
}
其实这个做法有点取巧的意思,因为如果环境不支持long,这个做法就会失效;最好的做法是对最后一位进行判断。另外,我觉得涉及到倒序对数字的每一位进行操作的问题,秦九韶算法(也就是所谓的霍纳算法)都是值得考虑的,因为这个算法可以以O(n)的时间复杂度对每一位进行操作。
至于回文数,明明判断一下反转后的整数和反转之前是否相等就够了,为什么要引入字符串呢?但是因为回文数的特殊性,这题有特殊的解法:只反转一半,也就是修改一下反转结束的判断条件:
bool isPalindrome(int x) {
// 负数不是回文数
// 除了0,不存在最后一位为0的回文数
if (x < 0 ||
(x % 10 == 0 && x != 0)) {
return false;
}
int res = 0;
// 如果x <= res,说明已经过了一半了
while (x > res) {
res = res * 10 + x % 10;
x /= 10;
}
// 如果是奇数,到这里还需要额外除以10;因为过去了一位
return x == res || x == res / 10;
}
我感觉有时候是不是自己提高了算法题的难度……