前序
这是刷穿剑指offer系列的第一篇文章,在这里需要简单和大家说明下关于这个系列的一些说明。
数据结构学习顺序
如果大家购买了剑指offer-专项突破,那么大家肯定看到了关于这本书目录对应的数据结构。但前文也说了,这本书的价值更多的在于题目上,如果知道了题目,那么就没有太大的必要去买这本书了,那么怎么获取本书的目录与所有题目呢?这里和大家介绍下。
- 首先打开力扣首页并进入题库专栏
- 选择剑指offer专项突击版,即可获取到本书大纲与所有题目列表。
关于文章和解题
昨天的公众号文章介绍了咱们的刷题学习策略,再这里简单复述下:
- 每种数据结构、每道解题,都会讲解Python、Java对应的知识内容。
- 针对每种数据结构,大概会花 4 天的时间进行学习。
- 第一天介绍这个数据结构对应的Python、Java语言涉及到的知识点,然后给出一道简单题目完成入门。
- 之后的两天,每天会讲解本书中的两道题目,并在讲解题目的同时,针对这道题涉及到的知识点进行总结
- 最后一天,针对该数据结构以及这三天的题目,进行一个总结,并推荐力扣上同类型题目列表,供大家深入学习与验证这种数据结构的学习成果。
具体每天文章的更新时间,现在还不能确定,因为没有存稿,都是每天学习、总结后写文章,所以想固定一个时间比较难。更新时会在刷穿剑指offer的微信群通知大家,针对文章或者当天的题目有什么问题也欢迎大家在群里相互讨论。
如果现在还没有加入群的朋友,可以通过公众号、微信联系我,但为了避免打广告的马甲号,入群前请向我提供你的力扣用户名、个人主页链接、或者力扣个人二维码。我会关注你确认身份后,拉你入群。
好了,要说明的就这些内容了,让我们开始今天的算法学习之旅吧!
Day01-整数I
本以为二进制的考题很少,毕竟有些偏底层,但最近不管周赛还是群友提问,二进制的题目如雨后春笋的都冒出来了,所以借着本章的机会好好复习下这方面的知识吧。
对于Python玩家来说,数字只分两种:整型、浮点型,不是小数的都是整数真简单啊!但这里有一个Python的面试考点:复数(大家仅做了解即可。)
复数(Complex)是Python的内置类型,直接书写即可,复数由实部(real)和虚部(imag)构成,在 Python 中,复数的虚部以
j
或者J
作为后缀,具体格式为:a + bj
a 表示实部,b 表示虚部。
然而,对于Java来说,单说整数就细分为了byte、short、int、long。
这里简单总结下Java的整数类型:
数据类型 | 范围(^ 代表次方) | 字节数 |
byte 类型 | -128(-2 ^ 7) ~ 127(2 ^ 7 - 1) | 1字节,8位 |
short 类型 | -32768(-2 ^ 15)~ 32767(2 ^ 15 - 1) | 2字节,16位 |
int 类型 | -2,147,483,648(-2 ^ 31)~ 2,147,483,647(2 ^ 31 - 1) | 4字节,32位 |
long 类型 | -2 ^ 63 ~ 2 ^ 63 -1 (数值太长没必要记了...) | 8字节,64位 |
那么,在做整数题目时,Python与Java的差别是什么呢?
- Python: 放心大胆无脑冲!
- Java:在越界的边缘战战兢兢...
二进制转换
在讲解二进制的与、或、异或方法前,首先需要复习下Java和Python十进制和二进制的互转的api操作。
转换方式 | 语言 | api(函数) | 结果 |
十进制转二进制 | Java | Integer.toBinaryString(int: 100) | "1100100" |
十进制转二进制 | Python | bin(100) | "0b1100100" |
二进制转十进制 | Java | Integer.valueOf(s: "1100100",radix: 2) | 100 |
二进制转十进制 | Python | int('0b1100100',2) | 100 |
不管是Java还是Python在进行二进制转换后,得到的结果都是字符串,只是python会多一个"0b"的前缀。然而只有这么一点区别么?错了!
Java的二进制是存在符号位的,而Python是不存在符号位的,我们以-10举例:
Integer.toBinaryString(-10) -> "11111111111111111111111111110110"
bin(-10) -> "-0b101"
另外负数的二进制Python可以直接转化为十进制,但Java则不行…需要按位取反+1后再进行转化。
这里的语言差异需要大家留意!
与、或、异或
与、或、异或的运算都是将两个数字转化为二进制数后,按位比较的,运算规律Java与Python一致,具体如下图:
与 ( & ) | 0 & 0 = 0 | 1 & 0 = 0 | 0 & 1 = 0 | 1 & 1 = 1 |
或( | ) | 0 | 0 = 0 | 1 | 0 = 1 | 0 | 1 = 1 | 1 | 1 = 1 |
异或( ^ ) | 0 ^ 0 = 0 | 1 ^ 0 = 1 | 0 ^ 1 = 1 | 1 ^ 1 = 0 |
与、或 操作符合日常逻辑,异或操作大家只要记住,相同为0 ,不同为1即可。
PS: Java 在遇到与或是经常会顺带问问短路与 ( && ) 短路或( || )的区别,即遇到前半部分符合条件立即终止判断。
位移操作
位运算分 左位移、右位移,符号对应<< 和 >>,简单来说就是将数字 m 向左、右移动n位。
如果是左移,最左边的n位将被丢弃,同时使用n个0进行补充至字符串末尾。
如果是右移,字符串末尾的n位被丢弃,但处理左边位时默认补0?No,针对Java等有符号位的语言,需要补充符号位,即正数补0,负数补1!
以上就是针对整数这章,初步涉及到的算法知识,后续会根据题目,再进一步补充,好了,开始做题吧!
剑指Offer001.整数除法
https://leetcode-cn.com/problems/xoh6Oh/
难度:简单
题目:
给定两个整数 a 和 b ,求它们的除法的商 a/b ,要求不得使用乘号 '*'、除号 '/' 以及求余符号 '%' 。
注意:
- 整数除法的结果应当截去(truncate)其小数部分,例如:truncate(8.345) = 8 以及 truncate(-2.7335) = -2
- 假设我们的环境只能存储 32 位有符号整数,其数值范围是 [−2 ^ 31, 2 ^ 31−1]。本题中,如果除法结果溢出,则返回 2 ^ 31 − 1
提示:
- 2 ^ 31 <= a, b <= 2 ^ 31 - 1
- b != 0
示例:
示例 1: 输入:a = 15, b = 2 输出:7 解释:15/2 = truncate(7.5) = 7 示例 2: 输入:a = 7, b = -3 输出:-2 解释:7/-3 = truncate(-2.33333..) = -2 示例 3: 输入:a = 0, b = 1 输出:0 示例 4: 输入:a = 1, b = 1 输出:1
分析
首先这种不能使用xxx方法的题目,一般机试时是不会出的,因为限制不了编辑器,人工阅卷又需要耽误时间。
但正因为不会出现在机试中,所以在手撕算法时却会成为热门,所以请不要违背题意的直接使用乘、除提交题目。
拿示例一来说,一个最直观的方法就是循环使用a-b,计算减了多少次,即可得到结果。
但请注意a、b的取值范围,如果a为2 ^ 31−1,b为1那么仅一次就需要计算2 ^ 31−1次,所以这个思路是不可行的。
那么,既然不能从被除数上下手,能否通过除数进行操作呢?如果大家对二分搜索熟悉,相信很快能找到思路。
- 初始化返回值ret = 0
- 如果换成如果被除数大于除数,则除数扩大一倍
- 若被除数仍大于除数,这除数再次扩大一倍
- 直到除数下一次翻倍比被除数大时,将被除数减去除数,并将ret+=除数扩大的倍数,结束这一轮循环
- 重复2、3、4,直到被除数小于除数,终止循环并返回ret即可。
另外:在计算开始时,我们需要注意判断除数和被除数的正负号问题。如果是Python,只需要记录正负号即可。但对于Java而言,
由于负数的取值比正数大1,转换成正数计算会造成溢出,所以简单的方式是将数字转化为负数来进行比较。
解题:
class Solution: def divide(self, a: int, b: int) -> int: ret = 0 flag = False if (a > 0 and b > 0) or (a < 0 and b < 0) else True a, b = abs(a), abs(b) def calc(x, y): n = 1 while x > y << 1: y <<= 1 n <<= 1 return n, y while a >= b: cnt, val = calc(a, b) ret += cnt a -= val ret = -ret if flag else ret return ret - 1 if ret >= 2 ** 31 else ret
class Solution { public int divide(int a, int b) { int flag = 0; if (a > 0) { a = -a; flag += 1; } if (b > 0) { b = -b; flag += 1; } int ret = calc(a, b); if (flag != 1 && ret == Integer.MIN_VALUE) { ret++; } return flag == 1 ? ret : -ret; } private int calc(int a, int b) { int ret = 0; while (a <= b) { int cnt = 1; int val = b; while (val >= Integer.MIN_VALUE >> 1 && a <= val << 1) { cnt += cnt; val += val; } ret -= cnt; a -= val; } return ret; } }