字符串

面试题01.09.字符串轮转

image-20220403170011293

1
2
3
4
5
6
7
8
9
10
11
+ 旋转词
+ 特点
+ 拼接两个相同的词
+ 滑动窗口,每个窗口都包含全部词
+ 判断是否为其子串
+ contains()方法
+ KMP
+ 两个数据结构拼接
+ 旋转
+ 链表相交
...

image-20220403170035618

image-20220403170044049

572、另一个树的子树

image-20220403170053397

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
+ 判断一棵树是否是另一棵树的子树	
+ 遍历(非层次遍历)
+ 暴力
+ 找到子树
+ 判断是否所有子节点都相等
+ 二叉树序列化
+ 遍历,序列化为字符串
+ 如果序列化后是其子串
+ 则为其子树
+ 序列化
+ 每个节点结束后加!(连在一起位数都无法判断)
+ 空节点值为#(保证构建的二叉树唯一)
+ 只有一个后序遍历无空节点,构建无数棵树
+ 如何反序列化?
+ 实现
+ 两个树序列化
+ 序列化(递归)
+ 结束条件:节点为空,序列化#
+ 序列化当前左子树
+ 序列化右子树
+ 拼接当前子树
+ StringBuilder拼接字符串
+ 判断是否为子串
+ contains()
+ 打印树
+ 测试KMP
+ contains改为KMP
+ 前序遍历bug
+ 解决:最前面再加个符号
+ 二叉树序列化字符串
+ 判断两个树的结构方面的

image-20220403170155652

image-20220403170204117

242、有效的字母异位词

image-20220403170225236

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
+ 两个字符串,所有字母都一样,字母的顺序不一样
+ 1、map统计字符
+ 统计每个字符的kv
+ 判断map中是否还有值
+ 2、数组统计字符(都是小写)轻量级
+ askii码进行计算
+ arr[26]统计所有字符的数量
+ 实现
+ 扫描数组
+ 一个字符串,进行相加
+ 一个字符串,进行相减
+ 若数量小于0,则直接返回false
+ 最后判断是否数组元素都为0
+ 优化
+ 继续空间换时间
+ charAt()一直在调方法
+ 字符串转为char[]数组
+ 直接用索引进行取值,效率很快

image-20220403170303602

151、翻转字符串里的单词

image-20220403170533557

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
+ 1、消除字符串中多余空格
+ 双指针
+ 若为字符,赋值到前面
+ 若为空格,且前面为字符,赋值空格
+ flag标记:前一个字符是否为空格
+ 2、获取字符串有效长度
+ 若最后为空格,则为cur的位置-1
+ 若最后为字符,则cur位置
+ 3、方法:翻转指定范围字符串
+ 左右边界为两个指针
+ 循环(指针不重合)
+ 左右交换指针值,指针归中
+ 4、对整个字符串逆序
+ 5、对每个单词进行逆序
+ 扫描数组,记录前一个空格位置pre(哨兵)
+ 发现空格时,结束了一个单词
+ 单词在前一个空格和后一个空格之间

image-20220403170618225

image-20220403170624055

image-20220403170632372

3、无重复字符的最长子串

image-20220403170644411

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
+ 类似动态规划
+ 以i结尾的最长无重复串
+ 维护最大值即为结果
+ 扫描字符
+ 求以i结尾的最长不重复串
+ 假设i-1的子串已经求出来(li到i-1不重复)
+ li为i-1子串的起始位置,pi为i字符上一次出现的位置
+ 如果li在pi右边
+ i的子串:li到i
+ 如果li在pi左边
+ i的子串:pi+1到i
+ 如果li==pi
+ i的子串:pi+1到i
+ 记录每个字符上一次出现的位置(map:k-v、arr[])
+ 实现
+ map
+ 扫描每个字符(从1开始)
+ 如果pi不存在(map中不存在),设置pi为-1,直接li为起始
+ getOrDefault()
+ 如果pi存在
+ li小于或等于pi才改li的值为li+1
+ 其他情况都li不变(li在右边)
+ 维护max记录i到li子串的长度
+ 优化
+ arr[26] + ascii
+ 想出来?
+ 之前有经验!!
+ 经历过,刷题越来越多
+ 扫一眼就知道用什么思路!

image-20220403170757314

image-20220403170803833

练习

5、最长回文子串

72、编辑距离

1143、32、1048

总结

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
+ 二叉树序列化
+ 序列化反序列化
+ 判断两棵树的的相同结构
+ 字符串查找子串
+ KMP
+ contains()
+ 字符统计
+ map
+ 数组
+ 结构拼接
+ 字符串拼接
+ 判断翻转
+ 链表拼接
+ 查找相交位置
+ 翻转字符串
+ 分割单词
+ 哨兵pre

动态规划

image-20220403170831008

面试题47、礼物的最大价值

image-20220403170842706

1
2
3
4
5
6
+ 动态规划
+ 状态定义:dp[i][j]:走到ij的最大价值
+ 初始条件:首行首列,不能放在在循环递推
+ 状态转移方程
+ 一行一行求
+ 依靠上和左的值

image-20220403170858115

image-20220403170902652

121、买卖股票的最佳时机

image-20220403171107378

1
2
3
4
5
6
7
8
9
10
11
12
+ 求前面最低买入,和后面最高卖出
+ 从卖的角度思考
+ 每一天都有可能卖,求其最大利润
+ 维护最大利润
+ 扫描一遍 + 双指针
+ 一个指针维护最小值
+ 一个指针遍历
+ 计算与最小值的利润
+ 动态规划
+ 求出相邻两天的价格差
+ 转为:求最大连续子序列和问题
+ dp[i]:以i结尾的最大子序列和

image-20220403171147347

image-20220403171154871

72、编辑距离

image-20220403171206720

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
+ 动态规划
+ 状态定义
+ dp i j :s1 [0,i)转为s2 [0,j)最少操作数
+ s1 的前i个字符转为 s2 前j个字符 最少操作数
+ 初始化
+ [0][j]、[i][0]
+ 最小操作数:i次(全部删除、插入)
+ 状态转移方程
+ 按行推导
+ dp[i][j]
+ 上、左、左上三种情况推得
+ 四种情况
+ dp[i-1][j]转换过来:差一个字符,+ 1
+ aa->bb = aaa -> bb + 1
+ 删除一个字符
+ dp[i][j-1]转换过来:差一个字符,+ 1
+ aa -> b = aa -> bb + 1
+ 插入一个字符
+ dp[i-1][j-1]转换过来:
+ 如果最后一个字符相等:相比之前操作一次,替换一个字符,+1
+ aa -> bb = aaa -> bbb + 1
+ bba -> bbb + 1
+ 换掉一个字符
+ 如果最后一个字符不相等:不操作
+ 最后一个字符不用动,只用操作前面的
+ aa -> bb = aab + bbb
+ 不用操作
+ 遍历,每次求最小转换情况
+ 状态转移方程
+ 考虑所有情况,可以从前面推导来的情况
+ 优化
+ 一维数组

image-20220403171325888

image-20220403171336257

image-20220403171344200

5、最长回文子串

image-20220403171355598

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
+ 暴力
+ 双指针遍历所有子串n²
+ 判断回文n
+ 记录最大长度

+ 动态规划
+ 状态定义
+ dp[i][j]:s[i,j]是否为回文串
+ 初始化
+ i = j : T
+ 状态转移方程
+ 子串长度为2,判断元素是否相同
+ 子串长度大于2
+ 如果[i+1,j-1]是回文串,且元素相等,那么是回文串
+ 左右都减去一个元素且为回文串,判断最后元素
+ 遍历方式
+ 无法从左往右
+ 由左下角推导的
+ 实现
+ 准备
+ str转换为char[]
+ dp[i][j]
+ 遍历(从下达到上,从左到右)
+ 最后一个字符必须两个相等 && (长度<=2 或者 子串已经回文)
+ 维护max
+ 优化
+ 一维数组

+ 扩展中心法
+ 以i为中心,向外拓展
+ 以字符间隙为中心,向外拓展
+ 实现
+ 遍历扫描
+ 不管是间隙为中心还是元素为中心,只要给左右元素的位置就行
+ 扫描字符为中心的回文子串
+ 扫描间隙为中心的回文子串
+ 左右扫描回文子串方法
+ 循环,条件(双指针移动且相等)
+ 双指针向外扩散(到边界出退出)
+ 维护最大值
+ 优化
+ 1、减少没必要的扫描
+ 已求得的最长子串长度大于剩下的字符长度
+ 2、中间有很多没必要的扩散
+ 实现
+ l和r左右扩散
+ 找到右边第一个不相等的元素为r
+ 左边相邻元素为l
+ 下一次i跳到r(跳到下一个不相等元素)
+ 中间的元素已经相等,没必要扩散
+ 可能是1个、2个、3个,中间相等的元素(不管多少个)已经构成了回文串
+ 只用从r和l的位置进行扩散就行!!!
+ 原理:
+ 核心:如果元素连续相等,变成一个拓展中心
+ 可以省去间隙的拓展中心
+ 因为每个都找右边第一个不相等的位置
+ 代码
+ 遍历数组
+ 找到右边第一个不相等的位置r
+ while 判断
+ 向左向右扩散
+ i -1、r
+ 循环双指针比较
+ 拓展结束。计算长度,开始索引位置

+ 马拉车算法
+ 原理
+ O(n)
+ 预处理
+ 头尾加特殊字符,元素之间加特殊字符
+ m[i]
+ 以i为拓展中心的最大回文子串的长度(不含#)
+ 以i为拓展中心的最大回文子串的右半部分或左半部分的长度(包含#)
+ 核心
+ l、r、c:前一个回文串,li回文串对称位置,i当前位置
+ 根据前面的回文串,进行对称,获取后面的回文串长度
+ 根据对称性,前面的回文长度等于后面的长度
+ 前提 i + m[i] < r,不能超过对称范围
+ 超过对称范围,外面的元素不一定对称
+ 对称范围内的,若前半部分是回文,则后半部分是回文且相等的
+ 可以保证范围内的都对称,超出的不知道
+ 如果恰好 i + m[i] = r
+ 不一定等于前面的(最少情况),r右边的元素不知,也可能回文
+ 拓展中心法,继续比较
+ 更新c、r、l等
+ 维护max
+ 总结:对称性质
+ 对称范围内的,和对称的回文串长度相等
+ 对称范围外的,需要拓展比较
+ 四种情况
+ i==r,拓展中心法
+ i < r
+ i + m[i] < r,相等
+ i + m[i] = r,最少值,拓展计算
+ i + m[i] > r,最少值,拓展计算
+ 实现
+ 预处理字符
+ 遍历,从第一个元素到最后一个元素
+ i==r
+ i<r,求出根据c求出li(对称)
+ 直接赋值,至少是m[li]
+ 拓展中心法
+ 双指针
+ 更新c、r等
+ 维护max:m[i]

暴力

image-20220403171759900

动态规划

image-20220403171835548

拓展中心

image-20220403171805084

拓展中心优化

image-20220403171901259

马拉车

image-20220403171915978

image-20220403171920775

image-20220403171927194

练习

47、72、5优化

1143、53、42、322、面试08.11

300、70、198、213、674、63、122、123、188、714

1235、943、516、376

总结

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
+ 三步
+ 状态定义
+ 初始值
+ 状态转移方程
+ 优化
+ 一维——>变量
+ 二维——>变量、数组
+ 注意递推关系
+ 后无效性、前推后
+ 考虑所有情况,可以从前面推导来的情况
+ 拓展中心法
+ 拓展中心法优化
+ 右边第一个不同元素
+ 马拉车算法
+ 回文串对称

二叉树

绝大多数题目解决:

递归 + 遍历(前序、中序、后序、层次)

236、二叉树的最近公共祖先

image-20220403172009431

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
+ 两个节点最近的公共祖先
+ 可以为节点本身
+ 递归实现
+ 方法功能:查找以root为根节点的p、q的最近公共祖先
+ 递归
+ 去左子树为根节点找
+ 去右子树为根节点找
+ 结束条件
+ 根节点为空,或者根节点为目标元素
+ 直接返回根节点
+ 错误
+ 如果左子树找到
+ 返回左子树
+ 如果右子树找到
+ 返回右子树
+ 如果左右子树都找不到,说明p、q节点在两个子树
+ root节点即为公共祖先,直接返回root
+ 分析递归方法
+ 如果pq在两边,递归左右子树不能返回null
+ 只有一个节点,应该直接返回该节点
+ 两边同时找到,说明根节点为公共祖先
+ 如果pq都存在,返回公共祖先
+ 正确
+ 左右子树都找到,返回root
+ 左子树或右子树找到,返回找到的节点
+ 都没找到,返回null
+ 原理
+ 向下找到节点就返回节点,找不到就返回null
+ 如果左右都找到,返回root
+ 如果找到一个,说明找到的是节点,继续向上返回
+ 如果为父子节点,父节点返回,其他路没有节点返回,父节点为祖先,符合

image-20220403172108264

image-20220403172113711

99、恢复二叉搜索树

image-20220403172122402

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
+ 二叉搜素树
+ 中序遍历升序
+ 错误交换后
+ 中序遍历,找到一个或两个逆序对
+ 实现
+ 递归中序遍历,找到错误节点
+ 退出条件:root为空
+ 递归左子树
+ 比较当前节点和上次遍历节点大小是否逆序
+ 准备两个指针标记错误节点
+ 递归右子树
+ 交换node
+ 交换值
+ 二叉树的Morris遍历
+ 使用前驱节点连线,进行回溯
+ 线索二叉树
+ Morris中序遍历
+ 动态线索二叉树
+ 把原来栈里的指针存放在了node里面
+ 实现
+ 循环(node不为空)
+ 若左子树不为空
+ 找到前驱节点
+ 左子树最右边
+ 判断前驱节点右指针
+ 为空(第一次)
+ 前驱节点指向当前node
+ 移动到左子树
+ 不为空(第二次来)
+ 打印当前节点,清空指针
+ 移动到右子树
+ 若左子树为空
+ 打印当前节点
+ 移动到右子树
+ Morris查找错误节点
+ 把打印改成find
+ Morris前序、中序、后序遍历
+ 空间复杂度O(1)
+ 动态线索树

image-20220403172244094

image-20220403172251620

image-20220403173035837