备忘:
第一遍没做出来(或者做的时候比较吃力的题目):
17,打印从1到最大的n位数,overflow函数(模拟字符串加法),自己没写出来
18,题目二,删除链表中的重复数字,debug了无数遍,考虑不全,反思!
19,正则表达式匹配,自己不会写
23,快慢指针的经典题,多做几遍。
24,反转链表,二刷通过
25,递归循环都可以,二刷通过
26,也是递归,二刷通过
二刷不会做的题目(需要加强练习的):
17,
18,
19,正则表达式的题目
23,快慢指针的经典题,多做几遍。
文章目录
第三章 高质量的代码
3.3 代码的完整性
面试题16:数值的整数次方
【题目描述】:
给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。不能使用库函数,同时不需要考虑大数问题。
【解题思路】:
【常规解法】
使用如下公式递归求解,关键步骤是:1. exponent除以2操作,2. 求余操作(判断奇偶数)
需要考虑几种特殊情况:
- base=0时,无论exponent为多少,返回0
- exponent=0时,返回1
- exponent<0时,先算正数次幂,然后求倒数
- exponent>0时,正常计算。
【改进版做法】(使用位运算)
用位运算中的右移操作代替除以2操作,用位与运算符代替求余运算符来判断一个数是奇数还是偶数。
面试题17:打印从1到最大的n位数
【题目描述】:
输入数字n,按顺序打印出从1到最大的n位十进制数。比如输入3,则打印出1、2、3一直到最大的3位数即999。
【解题思路】:
方法1:常规思路(for循环)
for循环,从1开始打印,但是缺点是,当n很大的时候,会溢出。
方法2:使用字符串模拟数字加法,避免溢出问题
主要有两步:
- 在字符串表达的数字上模拟加法(关键!)
- 把字符串表达的数字打印出来(高位如果是0,则不打0)
面试题18:删除链表的节点
题目一:在O(1)时间删除链表结点
【题目描述】:
给定单向链表的头指针和一个结点指针,定义一个函数在O(1)时间删除该 结点。
【解题思路】:
方法1:常规思路 O(n)
从头到尾遍历链表,当找到待删除节点时,将其删除即可。
方法2:
由于已经给定了待删除的节点,那么它的下一个节点其实我们是已知的,直接用下一个节点的内容复制到待删除的节点上,覆盖原有内容,再把下一个节点删除即可。
根据待删节点p的位置,可以分成以下两种情况:
- 待删节点p有后续节点(表明该节点不是尾节点)
直接将p.next内容覆盖当前节点p的内容,再把p.next节点删除掉即可,无需遍历链表。 - 待删节点没有后续节点(表明该节点是尾节点)
又可分为两种情况:
a)该节点既是头结点又是尾节点(即链表只有一个节点)
需要将该节点删除的同时,将头结点指针置空。
b)链表中有多个节点,要删的是尾节点
这时候需要从头遍历链表,遍历到该节点进行删除操作。
题目二:删除链表所有重复的节点
【题目描述】:
在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5
【解题思路】:
参考 https://blog.youkuaiyun.com/gangstudyit/article/details/80623477
https://blog.youkuaiyun.com/sixingmiyi39473/article/details/79420241
这一题debug了无数遍,哭。。。
把出bug的测试用例粘过来:
- [1,1,1,1,1,1]——输出None
- [1,1,1,1,1,2]——输出[2]
- [1,1,2,2,3,3,4,4]——输出None
- [1,1,2,3,3,4,5,5] ——输出[2,4]
面试题19:正则表达式匹配(重要)
【题目描述】:
请实现一个函数用来匹配包括'.'
和'*'
的正则表达式。模式中的字符'.'
表示任意一个字符,而'*'
表示它前面的字符可以出现任意次(包含0次)。 在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串"aaa"
与模式"a.a"
和"ab*ac*a"
匹配,但是与"aa.a"
和"ab*a"
均不匹配
【解题思路】:
https://www.cnblogs.com/yanmk/p/9196535.html (代码清楚 python)
https://www.cnblogs.com/xuanxufeng/p/6914472.html (思路详细)
分析:这道题的核心其实在于分析’’,对于’.‘来说,它和任意字符都匹配,可把其当做普通字符。对于’'的分析,我们要进行分情况讨论,当所有的情况都搞清楚了以后,就可以写代码了。
在每轮匹配中,Patttern第二个字符是’*'时:
- 第一个字符不匹配(’.‘与任意字符视作匹配),那么’'只能代表匹配0次,比如’ba’与’aba’,字符串不变,模式向后移动两个字符,然后匹配剩余字符串和模式
- 第一个字符匹配,那么’'可能代表匹配0次,1次,多次,比如’aaa’与’aaaa’、‘aba’与’aba’、'aaaba’与’aba’。匹配0次时,字符串不变,模式向后移动两个字符,然后匹配剩余字符串和模式;匹配1次时,字符串往后移动一个字符,模式向后移动2个字符;匹配多次时,字符串往后移动一个字符,模式不变;
而当Patttern第二个字符不是’*'时,情况就简单多了:
- 如果字符串的第一个字符和模式中的第一个字符匹配,那么在字符串和模式上都向后移动一个字符,然后匹配剩余字符串和模式。
- 如果字符串的第一个字符和模式中的第一个字符不匹配,那么直接返回false。
面试题20:表示数值的字符串(重要)
【题目描述】:
请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串"+100",“5e2”,"-123",“3.1416"和”-1E-16"都表示数值。 但是"12e",“1a3.14”,“1.2.3”,"±5"和"12e+4.3"都不是。
【解题思路】:
表示数值的字符串遵循模式A[.[B]][e|EC]
,其中A为整数部分,B为小数部分,e或者E为指数符号,C为指数部分。
- 小数可以没有整数部分,例如.123等于0.123;因此A不是必须的。如果一个数没有整数部分,那么小数部分不能为空
- 小数点后面可以没有数字,例如233.等于233.0;
- A和C前面可以含有正负号,但是B前面不能有正负号;
- 当e或E前面没有数字时,整个字符串不能表示数字,例如.e1、e1;
- 当e或E后面没有整数时,整个字符串不能表示数字,例如12e、12e+5.4;
具体步骤:
首先尽可能多的扫描1~9的数位(有可能在起始处有正负号),如果遇到小数点,则开始扫描小数部分的B部分,如果遇到e或者E,则开始扫描指数的C部分。
面试题21:调整数组顺序使奇数位于偶数前面
【题目描述】:
输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分。(并保证奇数和奇数,偶数和偶数之间的相对位置不变。)
【解题思路】:
如果没有括号中的限制条件,解决思路很简单:只需要维护两个指针i和j,i从头开始,j从尾开始,如果i指的是偶数,j指的是奇数,则将两个数值交换,当i和j相遇时,停止。但是这样的解法并不能保证奇数和偶数的相对位置不变。
要想保证原有次序,只能顺次移动或相邻交换。 这里使用相邻交换:
- 设置一个i指针,从头开始遍历;
- 对于每个固定的i,内层设置一个循环j,j从尾开始遍历直到i停止,如果相邻两个元素,前面的是偶数后面的是奇数,则交换这两个相邻元素的顺序。每执行完一次内层循环,可以将最前面的奇数“归位”。
- i++,继续下一轮遍历。
考虑这道题的可扩展解法
如果把题目改成把数组中的数按照大小分为两部分,所有负数在非负数的前面,如何操作?
如果把题目改成把数组中的数分成两部分,能被3整除的数都在不能被3整除的数前面,如何操作?
解决方案:将判断奇数偶数这个逻辑框架抽离出来,封装成函数,大的逻辑框架不需要改动。即可实现可扩展解法。
面试题22:链表中倒数第k个节点
【题目描述】:
输入一个链表,输出该链表中倒数第k个结点。
【解题思路】:
方法1:(需要遍历两次)
倒数第k个节点就是正数的第n-k+1个节点。直接往前走n-k+1步即可。但是由于只给了链表的头节点,并不知道n是多少,所以只能遍历两次:
- 第一次遍历,获取链表长度n,
- 第二次,然后从头结点开始,正向走n-k+1步。
方法2:(遍历一次)
正确做法:设置两个指针i和j,先保持第二个指针j不动,指针i往前走k-1步,这时两个指针的距离为k-1,;接下来两个指针同时往前走,当第一个指针i到达链表尾的时候,第二个指针j正好指向倒数第k个节点。
【Attention】注意边界条件的处理方法:
- 空链表
- k为0
- k大于整个链表长度
对于空链表或者k=0的情况,直接返回null即可。对于k大于整个链表长度的情况,需要在第一个指针走k-1步的循环中多加一个判断。
相关题目:求链表的中间节点
【题目描述】:
如果链表中的节点总数为奇数,则返回中间节点;如果节点总数为偶数,则返回中间两个节点的任意一个。
【解题思路】:
定义快慢指针,同时从链表的头节点出发,快指针一次走两步,慢指针一次走一步,当快指针到链表尾部时,慢指针正好在中间。
面试题23:链表中环的入口节点(快慢指针)(重要)
【题目描述】:
一个链表中包含环,请找出该链表的环的入口结点。如图,环的入口节点是3.
这道题目分三个小步骤:
- 先判断链表是否有环
方法:快慢指针,快指针一次走两步,慢指针一次走一步,如果有环,两个指针终将相遇,且相遇一定发生在环内部节点上,返回这个内部节点给第2步 - 如果链表有环,找到环的节点个数
方法:以第1步的节点为起点,绕环一周,即可获得环内的节点个数n,将n传递给第3步 - 找到环的入口
方法:双指针,定义两个指针p1和p2都指向链表的头结点,指针p1先独自走n步,然后p1和p2一起向前走,p1和p2的相遇点就是环的入口节点。(当p2走到环的入口节点时,p1已经绕环一圈正好回到了入口节点)
--------------2019.7.6二刷:----------------------
https://blog.youkuaiyun.com/qq_20141867/article/details/80931915
上述的2和3可以合为一步,即:
- 快慢指针,如果能相遇,说明有环,而且相遇的点一定在环内
- 慢指针回到表头, 快指针留在相遇点,二者同步往前,下一次相遇就是入口节点。
特殊用例:
- 输入链表不含环的情况
- 输入链表为空,或者只有一个节点的情况。
面试题24:反转链表
【题目描述】:
定义一个函数,输入一个链表的头结点,反转该链表并输出反转后链表的头结点。
【解题思路】:
本题的关键点:链表不能断!在修改next指针之前,一定要先把节点保存下来
设置三个指针pre,p,pnext,分别指向前一个,当前,后一个节点。在修改当前节点p的next指针时,一定要先将pnext节点保存下来,不然会出现链表断裂问题。这题主要考察指针的用法,思路虽简单,但其实不好写。
特殊用例:
- 输入链表为空
- 输入链表只有一个节点。
面试题25:合并两个排序的链表(递归)
【题目描述】:
输入两个单调递增的链表,合并这两个链表,并使新链表中的节点仍然是递增排序的。
【解题思路】:
设置两个指针p1和p2,分别指向两个排序链表的表头,然后比较指针所指位置的值大小,小的那个值成为合并链表的表头,对应的指针后移,直至遍历完两个链表。
----19.07.06 二刷------
不用递归也可以实现。
特殊用例:
- 两个链表有多个节点
- 存在节点值相等的情况
- 两个链表的一个或者两个头结点为空。
- 两个链表只有一个节点。
面试题26:树的子结构(递归)(重要)
【题目描述】:
输入两棵二叉树A和B,判断B是不是A的子结构。例如下图中的两棵二叉树,由于A中有一部分子树的结构和B是一样的,因此B是A的子结构。
【解题思路】:
主要分为两个部分,
- 第一步:遍历A,搜寻B的根节点在A的什么位置,找到匹配的值之后,再进行第二步操作,
- 第二步:判断匹配点下面的子树是否与B相同。
总结:用两个函数来实现,一个是搜寻匹配根节点的,一个是用来匹配子树的。两个函数都可以递归实现。
-----------2019.7.6 二刷------------
写了不借助辅助函数的版本。
特殊用例:
- B不是A的子结构(返回False的情况)
- 两个二叉树的一个或两个根节点为空
- 二叉树的所有节点都没有左子树或右子树