剑指Offer-1
-- 本章重点导航 -- Leetcode-offer1
环境配置:
IDEA + LeetCode插件
插件的github上有相关issue,助你解决IDE的智能补全功能。
Day 1
JZ3Pro:数组中重复的数字
442. 数组中重复的数据:这是一道技巧题目,使用就地哈希思想。
可以不断交换,直到元素与下标相对应
灵活使用数据特征,用负数等充当
bool数组的标记效果
JZ4:有序二维数组查找@二分思想
==参:JZ11:二段有序数组最小值的二分查找法==
二分查找中,因为有序,mid实质是判断在哪段,缩小解空间。
而拓展二分思想,就是“分段思想”,从一个点的性质着手判断,位于哪段,缩小一段解空间。
//[
// [1, 4, 7, 11, 15],
// [2, 5, 8, 12, 19],
// [3, 6, 9, 16, 22],
// [10, 13, 14, 17, 24],
// [18, 21, 23, 26, 30]
//] 查找有无特定数字。
class Solution {
public boolean findNumberIn2DArray(int[][] matrix, int target) {
int n = matrix.length; if(n == 0) return false;
int m = matrix[0].length; if(m == 0) return false;
int tx = 0, ty = m - 1;
while (matrix[tx][ty] != target){
if( matrix[tx][ty] > target) ty--;
else tx++;
if(!inMatrix(tx,ty,n,m)) return false;
}
return true;
}
}你可以理解为,将矩阵旋转45度,得到类二叉搜索树;每次判断,剪枝一子树。
JZ5:替换字符串空格为“%20”@StringBuilder
拓展:如何就地替换? 先统计有多少个空格,对string
append相应个" "占位; 维护双指针,分别为完整的字符串末、非占位的字符串末。
JZ7:由先序、中序遍历序列重建二叉树@HashMap
本题主要学习了一种哈希优化递归方法,学习以下代码:
225:一个队列实现栈@Deque
用栈实现队列,需要两个栈,因为栈“LIFO”的特性,我们不能通过一个栈调转顺序。
而队列“LILO”的特性,使其 “先出队后入队”后,能达到调转顺序的效果 ,因此,可以通过一个队列实现栈。
以下学习一下Deque的使用方式:
Day 2
JZ12:矩阵路径@回溯法
解决一个回溯问题,实际上就是一个决策树的遍历过程,使用 isValid 函数剪枝。
回溯算法(DFS)模板框架如下:
对于isValid函数及剪枝策略,回溯法求最值时,可以通过贪心思想找到一个“较优解”,减去差于其的枝。(分支限界法)
本题套模板不难,注意入口。
JZ13:矩阵范围筛选@DFS/BFS
也可以使用回溯法解决的,学习一种操作: return 1+dfs()+dfs()...
要理解回溯是为了什么,怎么准备现场,要不要恢复现场,怎么恢复现场
BFS模板如下:
Day3
JZ14 绳子分段积最大@动态规划 快速幂求余
模板:
动态规划实际是“聪明地枚举”,和回溯类似,会遍历所有可能的情况,但是我们会保存已经计算过的信息。其中,也允许“剪枝”(不丢失最优解)属于贪心的范畴,可能提高了“溯源”的难度。
首先注意:动态规划不是说一上来就写状态转移方程(直接做“备忘录”),而是逐步分析,如下:
分析最优子结构 -> 递推公式 -> 自底向上求解(初始化、状态转移)
分析技巧: 递归树、状态转移图
调试技巧:打印 dp 数组
如果需要“溯源”,可以开一个新数组进行标记。
由于需要状态转移,一般数组下标从1开始,对应的dp数组也开有特别标识的“安全空间"。
注意状态转移的方向,利用了什么。
C++中,可使用
max({a,b,c})获得三个值的最值,Java似乎无类似机制。
大数求余思想
根据求余运算性质:$(xy) % a = (x%a \times y%a)%a$
结合二分快速幂 $x^a = x^{a/2} \times x^{a/2} \times x^{a -a/2\times2}$
即可达到快速幂求余的目的。
技巧:JAVA中的大数常量,可以使用
_分隔,如:998_244_353
JZ15 位运算
《Hacker’s Delight》给予我们大量用位运算获取信息的技巧,整理部分如下:
JZ17:打印从1到最大的n位数
递归填充全排列。
Day 4
JZ19:字符串匹配
Java的toCharArray,并没有arr[s.length] = ‘\0’ ,与访问器charAt一样,会抛出异常ArrayIndexOutOfBoundsException。
关键是,对两个串A和串B,明确用谁去匹配谁,把一个串当做另一个串的指针转移的条件,即“状态机”思想。
值得一提的是,本题可以使用动态规划的方式解决:
我们嫌参数名太长。既然是核心代码模式,你改短一点,也没什么所谓。
记bool f[i][j]代表 s 中的 1~i 字符和 p 中的 1~j 字符是否匹配,留以f[0][0]为true进入空串匹配成功,后分为各种情况进行状态转移。其中,对于p[j] 为 '*':读得 p[j - 1] 的字符, 可实际匹配 s 中 a 的个数是 0 个、1 个、2 个 ... 观察到(图源牛客题解):

JZ21:数组划分
划分条件可变时,使用函数指针;
本题可使用双指针中的快慢指针技巧,是这样的:
Day5
接下来的题目就涉及到指针了,这个时候要记得
访问元素前,记得特判null
顺便可以复习一下JAVA基础中的
optional类型。
遇到树的题目时,通用的思考方向:
先考虑遍历一遍二叉树(所有结点),然后考虑分治成子问题进行递归。
采用哪种遍历方式?只要明白:已遍历结点向未遍历结点传递信息即可。
回忆一下非递归中序遍历:
入栈顺序:先序遍历;
出栈顺序:中序遍历。
非递归后序遍历的实现:一种方法是,出栈时不马上弹出结点(处理的时机)。
复习:
poll- 删除并返回 ;peek- 仅访问
JZ26:树的子结构
分治:以树为单位判断固然极难,以结点为单位判断就很简单了。
JZ27:树的镜像
如果调用的方法会改变树的结构,请检查调用参数是不是你所期待的!
一种补丁:
Day 6
JZ29:顺时针打印矩阵@非方阵特殊处理
要准确判断四角应该判作哪个角,以达到正确的目标。非方阵中,比较麻烦的是四角,在此提供两个特别用例: $1 \space\space 2$ 和
JZ30/31:魔改栈
任何的魔改需求,都可以加一层“辅助层”解决。
本题可以复习一下“常量池“,对于数字类包装,[-128,127]有缓存,可用==判等值;而其以外的对象,不可使用==。其实,对于对象,任何时候都应该优先考虑使用方法。
JZ32:二叉树的花式遍历
本题不难,但是需要注意Java语法:
如果需要给一个对象的队列加入对象,这个对象参数传入时是浅拷贝,也就是说,后续对对象本身的修改,会直接影响已入队的对象的值。
提示:
1.类似List<Integer> tmpq = new LinkedList<Integer>(tmp);的操作,可以得到一个副本。
2. list.add(new LinkedList(alist)) ,可将 alist 的副本加入到 list 。
JZ33:后序遍历序列判断@单调栈
后序遍历倒序: [根|右|左] 。类似先序遍历的镜像,对应大小关系:根 ↑右↓左。
这个单调性方便我们寻找“右与左的交界”:
借助一个单调栈 stack 存储值递增的节点;每当遇到值递减的节点 $r_i$,则通过出栈来更新节点 $r_i$ 的父节点 root (因为顺序是根在前):这个时候处理完了一棵子树,结合根节点继续处理。
参考:递归和栈两种方式解决,最好的击败了100%的用户 - 二叉搜索树的后序遍历序列 - 力扣(LeetCode) (leetcode-cn.com)
因为,对于逆序:
挨着的两个数如果arr[i]<arr[i+1],那么arr[i+1]一定是arr[i]的右子节点;如果arr[i]>arr[i+1],那么arr[i+1]一定是arr[0]……arr[i]中某个节点的左子节点,并且这个值是大于arr[i+1]中最小的。
Day 7
JZ34:二叉树和为某值的路径@回溯且溯源
DFS路径溯源需要设计一些序列化容器的方法使用,根据多态理论可以得到你想要的方法:

本题可否使用BFS解决?
知道哪片叶子不难,怎么溯源路径呢:我们可以使用
map对应结点的parent和路径和。根据
parent不断向上层即可,最后需要翻转。
JZ35:复制盲盒链表@就地哈希
还记得吗,对于链表的题目,建议下笔画下来,再处理。
这里介绍一个神奇的思路:就地哈希。
由于本题中一个链表内有两个指针,进行复制时,很容易想到先按next指针复制一份链表(同时建立HashMap对应random指针),再通过哈希表进行新表的random连接。
我们有一个新想法:原地复制:A -> A’ -> B -> B’ -> …
一来沿着next直接得到了新链表,又可以非常方便地处理random,达到了就地哈希的效果。
JZ36:BST双向链表化
中序遍历进行处理即可,关键是遍历的“第一个节点、最后一个节点”有无特性。
你可以借助JZ37,练习二叉树的非递归遍历及复现。
按输出顺序的倒序入栈,中节点入栈时加入标志NULL节点。例子:
最后更新于
这有帮助吗?