1 前言

题目来源于Leetcode。

重点:理清逻辑,忽略细节,模仿高手,五毒神掌

2 题目分析

题目很容易理解,先分成两个部分

  • 正数
  • 负数

先解决正数

最开始想到的是

int
char数组
long

唯一增加的就是,先判断整数是多少位

之后再判断溢出

然后解决负数
先使用一个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;

好了,这个程序已经优化了很多了,还有没有空间继续优化呢?

再优化,就只能在获取整数位数下手,将其直接变成反转的条件使用。

好吧……我做不下去了,看大神解法好了。

谈一谈收获

  1. 对待算法题,重点关注逻辑,对于防御性编程等细节可以不用深究
  2. 先把逻辑在纸面上搞清楚,再写代码!
  3. int、long等数据类型的大小,根据系统位数以及编译器决定,需要实际测试一下,尽量使用sizeof等通用的东西
  4. 计算位数部分,有一点动态规划的意思,很有趣。

毫无移位,按照我这么写算法题,可以凉凉了~~~~接下来,我来学习一下大神的解法吧。

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 第一掌

  1. 先正确理解题目
  2. 自己想,5分钟想出来就写
  3. 想不出来,就直接看世界大神答案,并且理解
  4. 然后大致理解背下来(理解代替记忆,如果不理解,就先记忆,多用用就理解了)
  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. 1个大容器与2个小容器
  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:整数反转相关推荐

  1. java算法int型整数反转的另类解法

    要求:输入一个int型整数,将这个整数中每位上的数字进行反转(除福符号位外)输出,若反转后的数字超过了int型范围,则输出0: 如:12340--------4321 -1234----------- ...

  2. 【每日一算法】整数反转

    微信改版,加星标不迷路! 每日一算法 - 整数反转 12 月 14 日 周 四 难度:简单.由于读者水平不一,所以从简单的做起,逐渐加难度. 题目:给出一个 32 位的有符号整数,你需要将这个整数中每 ...

  3. python整数反转_敲代码学Python:力扣简单算法之整数反转

    学习重点:整数逆序算法 力扣(LeetCode)原题​leetcode-cn.com ''' 功能:整数反转 来源:https://leetcode-cn.com/explore/featured/c ...

  4. 试题 算法训练 整数拆分

    试题 算法训练 整数拆分 资源限制 内存限制:256.0MB C/C++时间限制:1.0s Java时间限制:3.0s Python时间限制:5.0s 问题描述 对于给定的正整数S,将其拆分为正整数的 ...

  5. 整数反转leetcode java_【Java】【每日算法/刷穿 LeetCode】7. 整数反转(简单)

    首页 专栏 java 文章详情 0 [每日算法/刷穿 LeetCode]7. 整数反转(简单) 宫水三叶发布于 今天 12:10 题目描述 给出一个 32 位的有符号整数,你需要将这个整数中每位上的数 ...

  6. 算法训练:嘘,别人我不告诉TA

    算法训练:嘘,别人我不告诉他 算法 or 游戏 基础的设计能力:不知道如何下手怎么办? 基础的建模能力:数组.链表,以及改进的结构 解题技巧:也说不清楚,就是对这道题有 feel 呀! 攻略:新手.老 ...

  7. LeetCode实战:整数反转

    题目英文 Given a 32-bit signed integer, reverse digits of an integer. Example 1: Input: 123 Output: 321 ...

  8. 可由一个尾指针唯一确定的链表有_极客算法训练笔记(三),链表详细图解,别再逃避了朋友...

    目录 缓存引爆链表 链表单链表双向链表循环链表双向循环链表 LinkedHashMap实现LRU缓存,源码解析(JDK1.8) 算法 爬楼梯 算法 反转链表 算法 链表环检测 缓存引爆链表 存储结构 ...

  9. 算法训练 素因子去重

    算法训练 素因子去重 时间限制:1.0s 内存限制:256.0MB 问题描述 给定一个正整数n,求一个正整数p,满足p仅包含n的所有素因子,且每个素因子的次数不大于1 输入格式 一个整数,表示n 输出 ...

最新文章

  1. C++中前置声明介绍
  2. ECMAScript6入门--Class对象
  3. Java中给循环体起别名
  4. android nougat和安卓7.1,Android Nougat 7.1.2 先睹为快
  5. php中文网 日历,php小型日历类库
  6. 由单目标跟踪实现多目标跟踪的思想框架
  7. Windows Phone 模拟器 (WPR Alpha 0.0.1 WP7/8模拟器) XAP XNA文件使用教程
  8. python判断是否有重复单词_Python判断两个单词的相似度
  9. 哈佛国际评论学术写作挑战赛介绍
  10. 如果只想推广俄语语言市场该如何利用谷歌?
  11. Python 音频随机播放器脚本
  12. 荷兰国土不大,人口不多,为什么有那么多世界级大公司?
  13. 查询存储过程报错TDS协议流无效
  14. 搞明白这八个问题 Linux系统就好学多了
  15. 关系型到文档型的跨越:颠覆你对数据库数据模型的认识
  16. 最近在 vscode 中借助 gcc 编译器来配置 c
  17. GPS 入门 1 —— 基础知识[转]
  18. JS 关闭本页面,刷新父页面
  19. 【Markdown】Markdown画图
  20. IT痴汉的工作现状56-耳鸣

热门文章

  1. Fragment基础讲解
  2. JAVA 作业:图形界面
  3. ASP.NET2.0学习8--WebPart部件
  4. php 修改 wordpress,wordpress怎么编辑代码修改页面
  5. linux 广播命令,Linux基础命令---ping
  6. mysql权限表_MySQL 数据库赋予用户权限操作表
  7. 是否可以限制蓝牙传输距离_技术文章—关于蓝牙传输范围的常见误解
  8. mysql 重装,Windows系统中完全卸载MySQL数据库实现重装mysql
  9. 如何用c 控制mysql数据库_用C语言操作MySQL数据库
  10. wxpython的sizer_wxPython BoxSizer布局