【剑指offer】解题思路16-26

本文深入解析了一系列经典算法题目,包括打印从1到最大的n位数、删除链表中的重复数字、正则表达式匹配等,提供了详细的解题思路与代码实现,尤其强调了面试中常见的链表和字符串操作技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

备忘:
第一遍没做出来(或者做的时候比较吃力的题目):
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. 求余操作(判断奇偶数)
在这里插入图片描述
需要考虑几种特殊情况:

  1. base=0时,无论exponent为多少,返回0
  2. exponent=0时,返回1
  3. exponent<0时,先算正数次幂,然后求倒数
  4. exponent>0时,正常计算。

【改进版做法】(使用位运算)
用位运算中的右移操作代替除以2操作,用位与运算符代替求余运算符来判断一个数是奇数还是偶数。
 

面试题17:打印从1到最大的n位数

【题目描述】:
输入数字n,按顺序打印出从1到最大的n位十进制数。比如输入3,则打印出1、2、3一直到最大的3位数即999。

【解题思路】:
方法1:常规思路(for循环)
for循环,从1开始打印,但是缺点是,当n很大的时候,会溢出。
方法2:使用字符串模拟数字加法,避免溢出问题
主要有两步:

  1. 在字符串表达的数字上模拟加法(关键!
  2. 把字符串表达的数字打印出来(高位如果是0,则不打0)

 

面试题18:删除链表的节点

题目一:在O(1)时间删除链表结点

【题目描述】:
给定单向链表的头指针和一个结点指针,定义一个函数在O(1)时间删除该 结点。

【解题思路】:
方法1:常规思路 O(n)
从头到尾遍历链表,当找到待删除节点时,将其删除即可。
方法2:
由于已经给定了待删除的节点,那么它的下一个节点其实我们是已知的,直接用下一个节点的内容复制到待删除的节点上,覆盖原有内容,再把下一个节点删除即可。

根据待删节点p的位置,可以分成以下两种情况:

  1. 待删节点p有后续节点(表明该节点不是尾节点)
    直接将p.next内容覆盖当前节点p的内容,再把p.next节点删除掉即可,无需遍历链表。
  2. 待删节点没有后续节点(表明该节点是尾节点)
    又可分为两种情况:
    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,1]——输出None
  2. [1,1,1,1,1,2]——输出[2]
  3. [1,1,2,2,3,3,4,4]——输出None
  4. [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相遇时,停止。但是这样的解法并不能保证奇数和偶数的相对位置不变。
要想保证原有次序,只能顺次移动或相邻交换。 这里使用相邻交换:

  1. 设置一个i指针,从头开始遍历;
  2. 对于每个固定的i,内层设置一个循环j,j从尾开始遍历直到i停止,如果相邻两个元素,前面的是偶数后面的是奇数,则交换这两个相邻元素的顺序。每执行完一次内层循环,可以将最前面的奇数“归位”。
  3. i++,继续下一轮遍历。
考虑这道题的可扩展解法

如果把题目改成把数组中的数按照大小分为两部分,所有负数在非负数的前面,如何操作?
如果把题目改成把数组中的数分成两部分,能被3整除的数都在不能被3整除的数前面,如何操作?

解决方案:将判断奇数偶数这个逻辑框架抽离出来,封装成函数,大的逻辑框架不需要改动。即可实现可扩展解法。
 

面试题22:链表中倒数第k个节点

【题目描述】:
输入一个链表,输出该链表中倒数第k个结点。

【解题思路】:

方法1:(需要遍历两次)
倒数第k个节点就是正数的第n-k+1个节点。直接往前走n-k+1步即可。但是由于只给了链表的头节点,并不知道n是多少,所以只能遍历两次:

  1. 第一次遍历,获取链表长度n,
  2. 第二次,然后从头结点开始,正向走n-k+1步。

方法2:(遍历一次)
正确做法:设置两个指针i和j,先保持第二个指针j不动,指针i往前走k-1步,这时两个指针的距离为k-1,;接下来两个指针同时往前走,当第一个指针i到达链表尾的时候,第二个指针j正好指向倒数第k个节点。

【Attention】注意边界条件的处理方法:

  1. 空链表
  2. k为0
  3. k大于整个链表长度
    对于空链表或者k=0的情况,直接返回null即可。对于k大于整个链表长度的情况,需要在第一个指针走k-1步的循环中多加一个判断。
相关题目:求链表的中间节点

【题目描述】:
如果链表中的节点总数为奇数,则返回中间节点;如果节点总数为偶数,则返回中间两个节点的任意一个。

【解题思路】:
定义快慢指针,同时从链表的头节点出发,快指针一次走两步,慢指针一次走一步,当快指针到链表尾部时,慢指针正好在中间。

 

面试题23:链表中环的入口节点(快慢指针)(重要)

【题目描述】:
一个链表中包含环,请找出该链表的环的入口结点。如图,环的入口节点是3.

这道题目分三个小步骤:

  1. 先判断链表是否有环
    方法:快慢指针,快指针一次走两步,慢指针一次走一步,如果有环,两个指针终将相遇,且相遇一定发生在环内部节点上,返回这个内部节点给第2步
  2. 如果链表有环,找到环的节点个数
    方法:以第1步的节点为起点,绕环一周,即可获得环内的节点个数n,将n传递给第3步
  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可以合为一步,即:

  1. 快慢指针,如果能相遇,说明有环,而且相遇的点一定在环内
  2. 慢指针回到表头, 快指针留在相遇点,二者同步往前,下一次相遇就是入口节点。

特殊用例:

  • 输入链表不含环的情况
  • 输入链表为空,或者只有一个节点的情况。
     

面试题24:反转链表

【题目描述】:
定义一个函数,输入一个链表的头结点,反转该链表并输出反转后链表的头结点。

【解题思路】:
本题的关键点:链表不能断!在修改next指针之前,一定要先把节点保存下来
设置三个指针pre,p,pnext,分别指向前一个,当前,后一个节点。在修改当前节点p的next指针时,一定要先将pnext节点保存下来,不然会出现链表断裂问题。这题主要考察指针的用法,思路虽简单,但其实不好写。

特殊用例:

  1. 输入链表为空
  2. 输入链表只有一个节点。
     

面试题25:合并两个排序的链表(递归)

【题目描述】:
输入两个单调递增的链表,合并这两个链表,并使新链表中的节点仍然是递增排序的。

【解题思路】:
设置两个指针p1和p2,分别指向两个排序链表的表头,然后比较指针所指位置的值大小,小的那个值成为合并链表的表头,对应的指针后移,直至遍历完两个链表。

----19.07.06 二刷------
不用递归也可以实现。

特殊用例:

  1. 两个链表有多个节点
  2. 存在节点值相等的情况
  3. 两个链表的一个或者两个头结点为空。
  4. 两个链表只有一个节点。
     

面试题26:树的子结构(递归)(重要)

【题目描述】:
输入两棵二叉树A和B,判断B是不是A的子结构。例如下图中的两棵二叉树,由于A中有一部分子树的结构和B是一样的,因此B是A的子结构。

【解题思路】:
主要分为两个部分,

  • 第一步:遍历A,搜寻B的根节点在A的什么位置,找到匹配的值之后,再进行第二步操作,
  • 第二步:判断匹配点下面的子树是否与B相同。

总结:用两个函数来实现,一个是搜寻匹配根节点的,一个是用来匹配子树的。两个函数都可以递归实现。

-----------2019.7.6 二刷------------
写了不借助辅助函数的版本。

特殊用例:

  1. B不是A的子结构(返回False的情况)
  2. 两个二叉树的一个或两个根节点为空
  3. 二叉树的所有节点都没有左子树或右子树
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值