【算法训练】DAY1:整数反转
1 前言
题目来源于Leetcode。
重点:理清逻辑,忽略细节,模仿高手,五毒神掌
2 题目分析
题目很容易理解,先分成两个部分
- 正数
- 负数
先解决正数
最开始想到的是
唯一增加的就是,先判断整数是多少位。
之后再判断溢出
然后解决负数
先使用一个bool变量保存符号,如果是负数,则取绝对值,再按正数进行运算,之后再加上符号,再判断溢出。
整体思维非常容易想到,分治思想,下面是代码。
3 自己想
int reverse(int x) {// 不管正负数,全变正数bool isNegativeNumber = false;int xAbsoluteValue = 0;if (x < 0) {isNegativeNumber = true;if (x != INT_MIN)xAbsoluteValue = -x;elsereturn 0;}else {isNegativeNumber = false;xAbsoluteValue = x;}// 判断整数多少位【动态的】int xTemporary = xAbsoluteValue;int count = 0;for (int i = 0; i < sizeof(int)*8; i++) { // 【注意】字节数*8if (xTemporary == 0) {break;}else {count++;}xTemporary /= 10;}// 反转long long xNew = 0; // 不要用long,它在32位下也是4字节xTemporary = xAbsoluteValue;for (int i = 0; i < count; i++) {xNew = xNew * 10 + xTemporary % 10;xTemporary /= 10;}// 符号回归if (isNegativeNumber) {xNew = -xNew;}// 判断溢出if (xNew < INT_MIN || xNew > INT_MAX) {return 0;}else{return xNew;}}
运行结果还可以,就是系统本身不稳定,有时候是4ms,这不重要,重要的是,这种做法太啰嗦了,我先尝试按照这个思路优化一下。
去掉符号转换,这部分没有也一样
注意使用long long
,而不是long
,32位下前者8字节,后者和int一样4个字节
判断溢出,使用一行代码搞定,取缔if else
,使用三元运算符
// 判断整数多少位【动态的】int xTemporary = x;int count = 0;for (int i = 0; i < sizeof(int) * 8; i++) { // 【注意】字节数*8if (xTemporary == 0) {break;}else {count++;}xTemporary /= 10;}// 反转long long xNew = 0; xTemporary = x;for (int i = 0; i < count; i++) {xNew = xNew * 10 + xTemporary % 10;xTemporary /= 10;}return (xNew < INT_MIN || xNew > INT_MAX) ? 0 : xNew;
好了,这个程序已经优化了很多了,还有没有空间继续优化呢?
再优化,就只能在获取整数位数下手,将其直接变成反转的条件使用。
好吧……我做不下去了,看大神解法好了。
谈一谈收获
- 对待算法题,重点关注逻辑,对于防御性编程等细节可以不用深究
- 先把逻辑在纸面上搞清楚,再写代码!
- int、long等数据类型的大小,根据系统位数以及编译器决定,需要实际测试一下,尽量使用sizeof等通用的东西
- 计算位数部分,有一点动态规划的意思,很有趣。
毫无移位,按照我这么写算法题,可以凉凉了~~~~接下来,我来学习一下大神的解法吧。
4 看大神做法,直接模仿学会
4.1 大神一
long long res = 0;
while (x) {res = res * 10 + x % 10;x /= 10;
}
return (res < INT_MIN || res > INT_MAX) ? 0 : res;
这个大神与我的思路类似,只不过比我的又进一步优化,我们学习一下。
这里最重要的一点,不需要判断多少位,也不需要暂存,不用管循环次数,循环结束的条件,就是x = 0
。
这也不是本质,这题的本质是数学问题。
- 是
1234
变成4321
的问题 - 是
1234
提取出每一个数字的问题
来看看我的算法中愚蠢的点。
// 判断整数多少位【动态的】int xTemporary = x;int count = 0;for (int i = 0; i < sizeof(int) * 8; i++) { // 【注意】字节数*8if (xTemporary == 0) {break;}else {count++;}xTemporary /= 10;}// 反转long long xNew = 0; xTemporary = x;for (int i = 0; i < count; i++) {xNew = xNew * 10 + xTemporary % 10;xTemporary /= 10;}
关注一下两个循环的条件
- 循环32次,确定位数
- 根据位数再反转
事实上,我想的是,先确定好位数,这样就不用每次都循环32次了,但是,我在确定位数的时候,还是循环了32次……蠢到家……
虽然不是每次循环32次,但是,这种程序结构无疑是垃圾的,尽管是双重保险,但是没有必要阿,我们警惕一下!
值得警惕的结构
抛开题目本身,我们看一看这个结构
for (int i = 0; i < sizeof(int) * 8; i++) { // 【注意】字节数*8if (xTemporary == 0) {break;}else {count++;}xTemporary /= 10;
}
for循环中,嵌套一个通过if判断的跳出循环的装置,我们来改进一下
while(xTemporary){ xTemporary /= 10;count++;}
嗯,这俩功能完全一样,但是显然后者更加简洁
现在,我们是通过中介count来完成程序,那么,可以去掉中间商吗?
当然可以!
既然,xTemporary /= 10
就可以作为终止条件,我们直接用就好了,没必要再管中间商,忽略它!
看一下我们刚才优化的本质,将x /= 10;
和while(x)
二者配合,作为循环终止条件,因此,我们进一步优化。
// 判断整数多少位【动态的】int xTemporary = x;int count = 0;while(xTemporary){ xTemporary /= 10;count++;}// 反转long long xNew = 0; while(xTemporary) {xNew = xNew * 10 + xTemporary % 10;xTemporary /= 10;}
这样一来,你很容易发现,第一个循环完全没有用,直接删掉。
int reverse(int x) {long long xNew = 0; while(x) {xNew = xNew * 10 + x % 10;x /= 10;}return (xNew < INT_MIN || xNew > INT_MAX) ? 0 : xNew;
}
我们,成功将自己的烂程序一步步优化成了大神的程序。
为了程序的通用性,我们稍改一下
int reverse(int x) {long long xNew = 0; while(x != 0) {xNew = xNew * 10 + x % 10;x /= 10;}return (xNew < INT_MIN || xNew > INT_MAX) ? 0 : xNew;
}
因为只有C/C++使用0和false是一样的,但是Java就不允许,只能使用布尔值。
4.2 大神二
int reverse(int x) {int d = 0;while (x){if (d > INT_MAX / 10 || d < INT_MIN / 10)return 0;d = d * 10 + x % 10;x = x / 10;}return d;
}
我们分析大神的思路,我先缓缓下跪了!
在后面五毒神掌第二掌会分析。
5 收获
5.1 一个重要结构的优化
for循环内,通过if跳出的时候,可以优化。
for(int i = 0;i < sizeof(int)*8;i++){if(x){break; }x /= 10
}
while(x){x /= 10
}
5.2 去掉“中间商”的方法
对于一些共性的东西,不再单独列出中间结果,直接得到最终答案。
5.3 算法的本质是数学问题
这个数学表达式其实是这么来的
- 先分治,拆解为数字+权重的形式,本质是硬件思维
- 再调换数字的权重
至于最终的表达式,需要一点点优化过来。
我们需要知道,对于int x;
- 求最低位的数字:
x % 10
- 降维,降低数量级:
x / 10
(利用int直接抹掉小数点)
第一次的算法(使用伪代码)
while(遍历每一位的数字){number[i] = x % 10;x /= 10;
}
这是很容易想到的,那么,我们保存了每一位数字,怎么保存它的权重?真的有必要保存权重吗?
显然没有必要,我们试一下就知道,可以直接一边处理旧数字,一边计算新数字。
newX = 0;
while(遍历每一位的数字){number[i] = x % 10;x /= 10;newX = newX*10 + number[i];
}
这已经是最小单元,没法解释,自己试一下吧。
然后你会发现number[i]
是多余的,并且遍历的条件就是x != 0
。
long long newX = 0;
while(x != 0){newX = newX*10 + x % 10;x /= 10;
}
至于为什么用long long
,这叫先假想结果,因为结果会溢出,所以只能用long long
了。
5.4 一些衍生的题目
5.4.1 求整数位数
所有整数均可。
int reverse(int x) {int count = 0;while (x){x /= 10;count++;}return count;
}
5.4.2 求整数的每一位
void reverse(int x) {int count = 0;int xTemporary = x;while (xTemporary){xTemporary /= 10;count++;}int *everyNumber = new int[count];for (int i = 0; i < count; i++) {everyNumber[i] = x % 10;x /= 10;}for (int i = 0; i < count; i++) {cout << everyNumber[i] << endl;}
}
注意
char与int转换,记得差一个'0'
int i = 4;
char a = i + '0';
cout << a << endl;
6 五毒神掌
五毒神掌是什么?
关注代码逻辑和结构层面的细节
目标导向,一天一个,完全搞定300题
6.1 第一掌
- 先正确理解题目
- 自己想,5分钟想出来就写
- 想不出来,就直接看世界大神答案,并且理解
- 然后大致理解背下来(理解代替记忆,如果不理解,就先记忆,多用用就理解了)
- 边抄边背的方式写代码
自己的思路不能只有一种,每种都要尝试。
重点关注逻辑!画图+手算分析
6.1.1 自己思考的过程
题目很简单,就是整数反转,需要注意
- 正负数问题
- 反转后溢出问题:用
long long
存储
之后用几个数字试一试,研究一下数学公式,先写正确,再不断优化。
int reverse(int x) {long long xNew = 0;while (x != 0) {xNew = xNew * 10 + x % 10;x /= 10;}return (xNew < INT_MIN || xNew > INT_MAX) ? 0 : xNew;
}
6.1.2 大神的代码
public int reverse(int x)
{int result = 0;while (x != 0){int tail = x % 10;int newResult = result * 10 + tail;if ((newResult - tail) / 10 != result){ return 0; }result = newResult;x = x / 10;}return result;
}
基于我的思路,如果可能溢出,就直接使用更大的容器取存储数据,然后看看有没有超过小容器的值,那么,如果没有更大的容器,又该怎么办?
没有大容器,那就用2个小容器,比较新值和旧值。
对于重点公式x1新 = x1旧 * 10 + x % 10
,我们知道,在数学公式中,进行等价变形,等式应该相等,也就是等式(x1新 - x%10) / 10 = x1旧
成立。
但是对于计算机不同,如果第一个公式计算过程有溢出,就会丢失数据,那么第二个公式就不成立。
这也就是我们判断的重点:If overflow exists, the new result will not equal previous one.
如果溢出存在,那么,使用新值运算反过来得到的旧值,就不是原来的那个旧值。
代码如下:
int reverse(int x) {int xNew1 = 0; // 旧值int xNew2 = 0; // 新值while (x) {xNew2 = xNew1 * 10 + x % 10;if ((xNew2 - x % 10) / 10 != xNew1)return 0;xNew1 = xNew2;x /= 10;}return xNew2;
}
事实上,在Leetcode编译器,上面的写法是错误的!
新的收获:使用经典的测试用例
不得不说……任何的算法,在使用大量测试用例测试之前,都不一定完美,例如上面的算法,如果使用INT_MAX
作为测试用例,对于能够进行溢出检测的严格编译器来说,会出现报错(不过C++编译器一般不检测……),那么,报错的原因是什么?
我们看一下xNew2 = xNew1 * 10 + x % 10;
,试想一下,我们刚才假定这个过程中,编译器是允许溢出后直接截断,但不会报错,那么现在,我们假定,编译器不允许溢出的发生,我们又该怎么办?
【思维修炼】“治未病”思想:在问题发生之前处理掉
对于xNew2 = xNew1 * 10 + x % 10;
,我们需要在溢出发生前,就检测出来,因此有以下程序
int reverse(int x) {int xNew = 0;while(x != 0){if(xNew > INT_MAX/10 || xNew < INT_MIN/10) return 0;xNew = xNew * 10 + x % 10;x /= 10;}return xNew;}
更严格来说,是不是需要把x % 10
也“治未病”呢?显然不需要,因为不存在一个数字,乘10后没有溢出,但是再+1就溢出了。
思考:为什么不是
>=
?
因为,对于极限数字214748364
(也就是INT_MAX / 10
),乘10之后,再加上x % 10
是不可能溢出的(可以想象,如果溢出,那x % 10
的结果需要 >7,那么,在这个数反转之前,就已经溢出了,所以不可能)。
经典测试案例 + 严格编译器 = 优秀算法
对于经典测试案例,例如本题,可以有
123
-123
INT_MAX
INT_MIN
1230000
这些提交前的测试案例,足够描述各种情况了。
6.1.3 小结
- 1个大容器与2个小容器
- 算法与数学公式
6.2 第二掌
把大神的代码完全不看的情况下写出来。
- 搞定
自己的代码,多种写法,不断优化到极致。
6.3 第三掌
过了24 小时的时间以后,再次重复做题
不同解法的熟练程度 ——> 专项练习
- 新的收获
6.3.1 整数反转图解——安检排队模型
如果你从动态的角度去看一下,是不是像一个U型排队区人员流动的样子?
想一想你过安检排队的情形。
怎么样,是不是瞬间记住了这个整数反转模型?
int reverse(int x) {int xNew = 0;while(x){if(xNew > INT_MAX/10 || xNew < INT_MIN/10) return 0; // 预测xNew = xNew*10 + x%10;x /= 10;}return xNew;}
通过预测提高性能
这是伟大计算机思想之一,应用广泛,例如指令操作的分支预测,在本题中,溢出的检测就使用了预测思想。
6.4 第四掌
过了一周之后: 反复回来练习相同的题目
6.5 第五掌
面试前一周恢复性的训练,所有题目全都刷一遍
【算法训练】DAY1:整数反转相关推荐
- java算法int型整数反转的另类解法
要求:输入一个int型整数,将这个整数中每位上的数字进行反转(除福符号位外)输出,若反转后的数字超过了int型范围,则输出0: 如:12340--------4321 -1234----------- ...
- 【每日一算法】整数反转
微信改版,加星标不迷路! 每日一算法 - 整数反转 12 月 14 日 周 四 难度:简单.由于读者水平不一,所以从简单的做起,逐渐加难度. 题目:给出一个 32 位的有符号整数,你需要将这个整数中每 ...
- python整数反转_敲代码学Python:力扣简单算法之整数反转
学习重点:整数逆序算法 力扣(LeetCode)原题leetcode-cn.com ''' 功能:整数反转 来源:https://leetcode-cn.com/explore/featured/c ...
- 试题 算法训练 整数拆分
试题 算法训练 整数拆分 资源限制 内存限制:256.0MB C/C++时间限制:1.0s Java时间限制:3.0s Python时间限制:5.0s 问题描述 对于给定的正整数S,将其拆分为正整数的 ...
- 整数反转leetcode java_【Java】【每日算法/刷穿 LeetCode】7. 整数反转(简单)
首页 专栏 java 文章详情 0 [每日算法/刷穿 LeetCode]7. 整数反转(简单) 宫水三叶发布于 今天 12:10 题目描述 给出一个 32 位的有符号整数,你需要将这个整数中每位上的数 ...
- 算法训练:嘘,别人我不告诉TA
算法训练:嘘,别人我不告诉他 算法 or 游戏 基础的设计能力:不知道如何下手怎么办? 基础的建模能力:数组.链表,以及改进的结构 解题技巧:也说不清楚,就是对这道题有 feel 呀! 攻略:新手.老 ...
- LeetCode实战:整数反转
题目英文 Given a 32-bit signed integer, reverse digits of an integer. Example 1: Input: 123 Output: 321 ...
- 可由一个尾指针唯一确定的链表有_极客算法训练笔记(三),链表详细图解,别再逃避了朋友...
目录 缓存引爆链表 链表单链表双向链表循环链表双向循环链表 LinkedHashMap实现LRU缓存,源码解析(JDK1.8) 算法 爬楼梯 算法 反转链表 算法 链表环检测 缓存引爆链表 存储结构 ...
- 算法训练 素因子去重
算法训练 素因子去重 时间限制:1.0s 内存限制:256.0MB 问题描述 给定一个正整数n,求一个正整数p,满足p仅包含n的所有素因子,且每个素因子的次数不大于1 输入格式 一个整数,表示n 输出 ...
最新文章
- C++中前置声明介绍
- ECMAScript6入门--Class对象
- Java中给循环体起别名
- android nougat和安卓7.1,Android Nougat 7.1.2 先睹为快
- php中文网 日历,php小型日历类库
- 由单目标跟踪实现多目标跟踪的思想框架
- Windows Phone 模拟器 (WPR Alpha 0.0.1 WP7/8模拟器) XAP XNA文件使用教程
- python判断是否有重复单词_Python判断两个单词的相似度
- 哈佛国际评论学术写作挑战赛介绍
- 如果只想推广俄语语言市场该如何利用谷歌?
- Python 音频随机播放器脚本
- 荷兰国土不大,人口不多,为什么有那么多世界级大公司?
- 查询存储过程报错TDS协议流无效
- 搞明白这八个问题 Linux系统就好学多了
- 关系型到文档型的跨越:颠覆你对数据库数据模型的认识
- 最近在 vscode 中借助 gcc 编译器来配置 c
- GPS 入门 1 —— 基础知识[转]
- JS 关闭本页面,刷新父页面
- 【Markdown】Markdown画图
- IT痴汉的工作现状56-耳鸣
热门文章
- Fragment基础讲解
- JAVA 作业:图形界面
- ASP.NET2.0学习8--WebPart部件
- php 修改 wordpress,wordpress怎么编辑代码修改页面
- linux 广播命令,Linux基础命令---ping
- mysql权限表_MySQL 数据库赋予用户权限操作表
- 是否可以限制蓝牙传输距离_技术文章—关于蓝牙传输范围的常见误解
- mysql 重装,Windows系统中完全卸载MySQL数据库实现重装mysql
- 如何用c 控制mysql数据库_用C语言操作MySQL数据库
- wxpython的sizer_wxPython BoxSizer布局