
¶剑指offer – 数据结构
¶剑指 Offer 05. 替换空格
Difficulty: 简单
请实现一个函数,把字符串 s 中的每个空格替换成"%20"。
示例 1:
1  | 输入:s = "We are happy."  | 
限制:
0 <= s 的长度 <= 10000
Solution
方法一:正序修改
1  | class Solution {  | 
时间复杂度:$O(N)$,N 为 s 的长度。
空间复杂度:$O(N)$,res 所需要的额外空间。
方法二:反向修改
1  | class Solution {  | 
时间复杂度:$O(N)$,N 为 s 的长度。
空间复杂度:$O(1)$,由于原地扩展 s 的长度,因此使用常数的额外空间。
¶剑指 Offer 06. 从尾到头打印链表
Difficulty: 简单
输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)。
示例 1:
1  | 输入:head = [1,3,2]  | 
限制:
0 <= 链表长度 <= 10000
Solution
方法一:正序输入,反转数组
1  | class Solution {  | 
时间复杂度:$O(N)$,N 为链表长度,一次遍历链表。
空间复杂度:$O(N)$,res 所需要的额外空间。
方法二:辅助栈
1  | class Solution {  | 
时间复杂度:$O(N)$,N 为链表长度,一次遍历链表。
空间复杂度:$O(N)$,stk 所需要的额外空间。
方法三:递归
1  | class Solution {  | 
时间复杂度:$O(N)$,N 为链表长度,一次遍历链表。
空间复杂度:$O(N)$,系统递归所需要使用的栈空间。
¶剑指 Offer 09. 用两个栈实现队列
Difficulty: 简单
用两个栈实现一个队列。队列的声明如下,请实现它的两个函数 appendTail 和 deleteHead ,分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead 操作返回 -1 )
示例 1:
1  | 输入:  | 
示例 2:
1  | 输入:  | 
提示:
1 <= values <= 10000最多会对 appendTail、deleteHead 进行 10000 次调用
Solution
方法一:每次delete,都进行倒出倒入操作
1  | class CQueue {  | 
**时间复杂度:**插入操作 $O(1)$,删除操作 $O(N)$
空间复杂度:$O(N)$
方法二:仅输出栈空调用倒入操作,无倒回操作
1  | class CQueue {  | 
**时间复杂度:**插入操作 $O(1)$,删除操作 $O(N)$
空间复杂度:$O(N)$
¶剑指 Offer 20. 表示数值的字符串
Difficulty: 中等
请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。
数值(按顺序)可以分成以下几个部分:
- 若干空格
 - 一个 小数 或者 整数
 - (可选)一个 
'e'或'E',后面跟着一个 整数 - 若干空格
 
小数(按顺序)可以分成以下几个部分:
- (可选)一个符号字符(
'+'或'-') - 下述格式之一:
- 至少一位数字,后面跟着一个点 
'.' - 至少一位数字,后面跟着一个点 
'.',后面再跟着至少一位数字 - 一个点 
'.',后面跟着至少一位数字 
 - 至少一位数字,后面跟着一个点 
 
整数(按顺序)可以分成以下几个部分:
- (可选)一个符号字符(
'+'或'-') - 至少一位数字
 
部分数值列举如下:
["+100", "5e2", "-123", "3.1416", "-1E-16", "0123"]
部分非数值列举如下:
["12e", "1a3.14", "1.2.3", "+-5", "12e+5.4"]
示例 1:
1  | 输入:s = "0"  | 
示例 2:
1  | 输入:s = "e"  | 
示例 3:
1  | 输入:s = "."  | 
示例 4:
1  | 输入:s = " .1 "  | 
提示:
1 <= s.length <= 20s仅含英文字母(大写和小写),数字(0-9),加号'+',减号'-',空格' '或者点'.'。
Solution
1  | class Solution {  | 
时间复杂度: $O(N)$,其中 N 为字符串 s 的长度,一次遍历即可。
空间复杂度:$O(1)$,mp 和 state 使用常数大小的额外空间。
状态转移图见:
¶剑指 Offer 24. 反转链表
Difficulty: 简单
定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。
示例:
1  | 输入: 1->2->3->4->5->NULL  | 
限制:
0 <= 节点个数 <= 5000
Solution
方法一:双指针
1  | class Solution {  | 
时间复杂度:$O(N)$,N 为链表长度,一次遍历链表。
空间复杂度:$O(1)$,pre, cur, post 所需要常数级的额外空间。
方法二:递归
1  | class Solution {  | 
时间复杂度:$O(N)$,N 为链表长度,一次遍历链表。
空间复杂度:$O(N)$,遍历链表的递归深度达到 N,系统使用 O(N) 大小的额外空间。
¶剑指 Offer 30. 包含min函数的栈
Difficulty: 简单
定义栈的数据结构,请在该类型中实现一个能够得到栈的最小元素的 min 函数在该栈中,调用 min、push 及 pop 的时间复杂度都是 O(1)。
示例:
1  | MinStack minStack = new MinStack();  | 
提示:
- 各函数的调用总次数不超过 20000 次
 
Solution
方法一:vector 实现
1  | class MinStack {  | 
方法二:stack 实现
1  | class MinStack {  | 
时间复杂度:$O(1)$,push, pop, top, min,皆是 $O(1)$。
空间复杂度:$O(N)$,需要额外的 vector 空间,或者 stack 空间。
¶剑指 Offer 35. 复杂链表的复制
Difficulty: 中等
请实现 copyRandomList 函数,复制一个复杂链表。在复杂链表中,每个节点除了有一个 next 指针指向下一个节点,还有一个 random 指针指向链表中的任意节点或者 null。
示例 1:

1  | 输入:head = [[7,null],[13,0],[11,4],[10,2],[1,0]]  | 
示例 2:

1  | 输入:head = [[1,1],[2,1]]  | 
示例 3:

1  | 输入:head = [[3,null],[3,0],[3,null]]  | 
示例 4:
1  | 输入:head = []  | 
提示:
-10000 <= Node.val <= 10000Node.random为空(null)或指向链表中的节点。- 节点数目不超过 1000 。
 
Solution
方法一:哈希表
1  | class Solution {  | 
时间复杂度:$O(N)$,N 为链表长度,两次遍历链表。
空间复杂度:$O(N)$,哈希表所需的额外空间。
方法二:拼接 + 拆分
1  | class Solution {  | 
时间复杂度:$O(N)$,N 为链表长度,三次遍历链表。
空间复杂度:$O(1)$,节点引用变量所需的额外空间。
¶剑指 Offer 58 - II. 左旋转字符串
Difficulty: 简单
字符串的左旋转操作是把字符串前面的若干个字符转移到字符串的尾部。请定义一个函数实现字符串左旋转操作的功能。比如,输入字符串"abcdefg"和数字2,该函数将返回左旋转两位得到的结果"cdefgab"。
示例 1:
1  | 输入: s = "abcdefg", k = 2  | 
示例 2:
1  | 输入: s = "lrloseumgh", k = 6  | 
限制:
1 <= k < s.length <= 10000
Solution
1  | class Solution {  | 
时间复杂度:$O(N)$,N 为 s 的长度,共两次遍历 s。
空间复杂度:$O(1)$,C++ 原地字符串操作,使用常数大小额外空间。
¶剑指 Offer 59 - I. 滑动窗口的最大值
Difficulty: 困难
给定一个数组 nums 和滑动窗口的大小 k,请找出所有滑动窗口里的最大值。
示例:
1  | 输入: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3  | 
提示:
你可以假设 k 总是有效的,在输入数组不为空的情况下,1 ≤ k ≤ 输入数组的大小。
Solution
方法一:优先队列
1  | class Solution {  | 
时间复杂度:$O(N\log N)$,其中 N 是 nums 的长度,nums 单调递增情况,优先队列包含所有元素,没有元素被移除,下一个元素放入优先队列的时间复杂度为 $O(\log N)$,因此总的时间复杂度为 $O(n \log N)$。
空间复杂度:$O(N)$,nums 单调递增情况下会存下所有元素。
方法二:单调队列(存元素)
1  | class Solution {  | 
方法三:单调队列(存元素索引)
1  | class Solution {  | 
两种方法的时间空间复杂度相同。
时间复杂度:$O(N)$,其中 N 是 nums 的长度,每个位置的元素或者索引恰好被放入队列一次,并且最多被弹出队列一次。
空间复杂度:$O(K)$,超出 K,会有 pop 操作,所以队列中的元素个数不会超过 K;
¶剑指 Offer 59 - II. 队列的最大值
Difficulty: 中等
请定义一个队列并实现函数 max_value 得到队列里的最大值,要求函数max_value、push_back 和 pop_front 的均摊时间复杂度都是O(1)。
若队列为空,pop_front 和 max_value 需要返回 -1
示例 1:
1  | 输入:  | 
示例 2:
1  | 输入:  | 
限制:
1 <= push_back,pop_front,max_value的总操作数 <= 100001 <= value <= 10^5
Solution
1  | class MaxQueue {  | 
时间复杂度:$O(1)$,max_value(), push_back(),pop_front() 方法的均摊时间复杂度均为 $O(1)$
空间复杂度:$O(N)$,如果连续存入递增序列,maxQ 中存 N 个元素,使用 $O(N)$ 的额外空间。
¶剑指 Offer 67. 把字符串转换成整数
Difficulty: 中等
写一个函数 StrToInt,实现把字符串转换成整数这个功能。不能使用 atoi 或者其他类似的库函数。
首先,该函数会根据需要丢弃无用的开头空格字符,直到寻找到第一个非空格的字符为止。
当我们寻找到的第一个非空字符为正或者负号时,则将该符号与之后面尽可能多的连续数字组合起来,作为该整数的正负号;假如第一个非空字符是数字,则直接将其与之后连续的数字字符组合起来,形成整数。
该字符串除了有效的整数部分之后也可能会存在多余的字符,这些字符可以被忽略,它们对于函数不应该造成影响。
注意:假如该字符串中的第一个非空格字符不是一个有效整数字符、字符串为空或字符串仅包含空白字符时,则你的函数不需要进行转换。
在任何情况下,若函数不能进行有效的转换时,请返回 0。
说明:
假设我们的环境只能存储 32 位大小的有符号整数,那么其数值范围为 [−231, 231 − 1]。如果数值超过这个范围,请返回 INT_MAX (231 − 1) 或 INT_MIN (−231) 。
示例 1:
1  | 输入: "42"  | 
示例 2:
1  | 输入: " -42"  | 
示例 3:
1  | 输入: "4193 with words"  | 
示例 4:
1  | 输入: "words and 987"  | 
示例 5:
1  | 输入: "-91283472332"  | 
Solution
方法一:遍历
1  | class Solution {  | 
时间复杂度:$O(N)$,其中 N 为字符串长度,遍历字符串;
空间复杂度:$O(N)$,删除首尾空格后需要建立新字符串,最差情况下占用 $O(N)$ 额外空间。
注意:该方法的越界判断十分巧妙!
方法二:先找数字首位,本质相同,只不过通过 STL 算法来实现。
1  | class Solution {  | 
时间复杂度:$O(N)$,其中 N 为字符串长度,遍历字符串;
空间复杂度:$O(N)$,中间变量 temp, stringstream 对象等所需的额外空间。
¶参考资料
图解算法数据结构 - LeetBook - 力扣(LeetCode)全球极客挚爱的技术成长平台 (leetcode-cn.com)