SamJ's blog

leetcode 做题记录(9)

Word count: 2.6kReading time: 13 min
2021/04/12 Share

剑指 Offer 31. 栈的压入、弹出序列

难度中等100

输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如,序列 {1,2,3,4,5} 是某栈的压栈序列,序列 {4,5,3,2,1} 是该压栈序列对应的一个弹出序列,但 {4,3,5,1,2} 就不可能是该压栈序列的弹出序列。

示例 1:

1
2
3
4
5
输入:pushed = [1,2,3,4,5], popped = [4,5,3,2,1]
输出:true
解释:我们可以按以下顺序执行:
push(1), push(2), push(3), push(4), pop() -> 4,
push(5), pop() -> 5, pop() -> 3, pop() -> 2, pop() -> 1

示例 2:

1
2
3
输入:pushed = [1,2,3,4,5], popped = [4,3,5,1,2]
输出:false
解释:1 不能在 2 之前弹出。

用辅助栈来模拟入栈和出栈的操作,对入栈序列,每次入栈一个,之后循环判断栈顶元素和当前出栈序列的元素是否相等,若相等,就出栈,并使出栈序列下标递增。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
public:
bool validateStackSequences(vector<int>& pushed, vector<int>& popped) {
stack<int> sim;
int k = 0; //指向poped
for(int i = 0; i < pushed.size(); ++i)
{
sim.push(pushed[i]);
while(!sim.empty() && sim.top() == popped[k])
{
++k;
sim.pop();
}
}
return sim.empty();
}
};

剑指 Offer 32 - I. 从上到下打印二叉树

难度中等52

从上到下打印出二叉树的每个节点,同一层的节点按照从左到右的顺序打印。

例如:
给定二叉树: [3,9,20,null,null,15,7],

1
2
3
4
5
  3
/ \
9 20
/ \
15 7

返回:

1
[3,9,20,15,7]

提示:

  1. 节点总数 <= 1000

层序遍历树,用队列辅助,没啥可说的。

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
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
vector<int> levelOrder(TreeNode* root) {
vector<int> res;
if(root == nullptr) return {};
queue<TreeNode*> q;
q.push(root);
TreeNode* temp = nullptr;
while(!q.empty())
{
temp = q.front();
if(temp->left) q.push(temp->left);
if(temp->right) q.push(temp->right);
res.push_back(temp->val);
q.pop();
}
return res;
}
};

剑指 Offer 32 - II. 从上到下打印二叉树 II

难度简单

从上到下按层打印二叉树,同一层的节点按从左到右的顺序打印,每一层打印到一行。

例如:
给定二叉树: [3,9,20,null,null,15,7],

1
2
3
4
5
  3
/ \
9 20
/ \
15 7

返回其层次遍历结果:

1
2
3
4
5
[
[3],
[9,20],
[15,7]
]

提示:

  1. 节点总数 <= 1000

自己搞了一个使用两个栈的做法,忘记了可以用q.size()了。。

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
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int>> res;
if(root == nullptr) return {};
queue<TreeNode*> q1; //记录上一层
queue<TreeNode*> q2; //下一层
q1.push(root);
TreeNode* temp = nullptr;
vector<int> tempRes;
while(!q1.empty() || !q2.empty())
{
while(!q1.empty())
{
temp = q1.front();
if(temp->left) q2.push(temp->left);
if(temp->right) q2.push(temp->right);
tempRes.push_back(temp->val);
q1.pop();
}
while(!q2.empty())
{
q1.push(q2.front());
q2.pop();
}
res.push_back(tempRes);
tempRes.clear();
}
return res;
}
};

使用一个栈的做法:

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
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int>> res;
if(root == nullptr) return {};
queue<TreeNode*> q1;
q1.push(root);
TreeNode* temp = nullptr;
while(!q1.empty())
{
vector<int> tempRes;
int n = q1.size();
for(int i = 0; i < n; ++i)
{
temp = q1.front();
q1.pop();
tempRes.push_back(temp->val);
if(temp->left) q1.push(temp->left);
if(temp->right) q1.push(temp->right);
}
res.push_back(tempRes);
}
return res;
}
};

剑指 Offer 32 - III. 从上到下打印二叉树 III

难度中等58

请实现一个函数按照之字形顺序打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右到左的顺序打印,第三行再按照从左到右的顺序打印,其他行以此类推。

例如:
给定二叉树: [3,9,20,null,null,15,7],

1
2
3
4
5
  3
/ \
9 20
/ \
15 7

返回其层次遍历结果:

1
2
3
4
5
[
[3],
[20,9],
[15,7]
]

提示:

  1. 节点总数 <= 1000

还是层序遍历,使用双端队列,奇数层前取后放(左→右),偶数层后取前放(右→左)。

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
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int>> res;
if(root == nullptr) return res;
bool flag = true;
deque<TreeNode*> dq;
dq.push_back(root);
while(!dq.empty())
{
int n = dq.size();
vector<int> tempRes;
TreeNode* temp;
if(flag)
{
for(int i = 0; i < n; ++i)
{
temp = dq.front();
dq.pop_front();
if(temp->left) dq.push_back(temp->left);
if(temp->right) dq.push_back(temp->right);
tempRes.push_back(temp->val);
}
}
else
{
for(int i = 0; i < n; ++i)
{
temp = dq.back();
dq.pop_back();
if(temp->right) dq.push_front(temp->right);
if(temp->left) dq.push_front(temp->left);
tempRes.push_back(temp->val);
}
}
flag = !flag;
res.push_back(tempRes);
}
return res;
}
};

剑指 Offer 33. 二叉搜索树的后序遍历序列

难度中等153

输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历结果。如果是则返回 true,否则返回 false。假设输入的数组的任意两个数字都互不相同。

参考以下这颗二叉搜索树:

1
2
3
4
5
    5
/ \
2 6
/ \
1 3

示例 1:

1
2
输入: [1,6,3,2,5]
输出: false

示例 2:

1
2
输入: [1,3,2,6,5]
输出: true

提示:

  1. 数组长度 <= 1000

要用递归的思想,最后一个是根节点,然后找到第一个大于根节点的,其后面是右子树,右子树均大于根,左子树均小于根,递归判断左子树和右子树。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
private:
bool DFS(int Start, int End, vector<int>& postorder) {
if(Start >= End) return true;
int Standard = postorder[End]; //最后一个为根节点,前半段小于根,后半段大于根
int Break = Start; //找到小于根和大于根的分界点
while(postorder[Break] < Standard) Break++;
for(int i = Break; i < End; i++) {
if(postorder[i] <= Standard) return false;
}
return DFS(Start, Break-1, postorder) && DFS(Break, End - 1, postorder);
}
public:
bool verifyPostorder(vector<int>& postorder) {
if(postorder.size() < 2) return true;
return DFS(0, postorder.size() - 1, postorder);
}
};

剑指 Offer 34. 二叉树中和为某一值的路径

难度中等111

输入一棵二叉树和一个整数,打印出二叉树中节点值的和为输入整数的所有路径。从树的根节点开始往下一直到叶节点所经过的节点形成一条路径。

示例:
给定如下二叉树,以及目标和 sum = 22

1
2
3
4
5
6
7
      5
/ \
4 8
/ / \
11 13 4
/ \ / \
7 2 5 1

返回:

1
2
3
4
[
[5,4,11,2],
[5,8,4,5]
]

提示:

  1. 节点总数 <= 10000

回溯的思想。满足条件的情况是,路径和为sum并且为叶子节点。

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
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
private:
vector<vector<int> > res;
vector<int> temp;
public:
vector<vector<int>> pathSum(TreeNode* root, int sum) {
if(!root) return {};
recursion(root, sum);
return res;
}
void recursion(TreeNode *root, int sum)
{
if(!root) return;
temp.push_back(root -> val);
sum -= root -> val;
if(sum == 0 && !root -> left && !root -> right)
res.push_back(temp);
recursion(root -> left, sum); // 左
recursion(root -> right, sum); // 右
temp.pop_back(); // 函数退出之前先弹出当前节点
}
};

想了好久才明白,自己的方法是败在了剪枝上(天天想这么多还搞错了是真的蠢)

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
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
vector<vector<int>> pathSum(TreeNode* root, int sum) {
vector<vector<int>> res;
vector<int> path;
dfs(root, 0, res, path, sum);
return res;
}

void dfs(TreeNode* node, int tempSum, vector<vector<int>>& res, vector<int> path, int target)
{
if(node == nullptr){
return;
}
path.push_back(node->val);
tempSum += node->val;
if(node->left == nullptr && node->right == nullptr && tempSum == target){
res.push_back(path);
return;
}
else
{
dfs(node->left, tempSum, res, path, target);
dfs(node->right, tempSum, res, path, target);
tempSum -= node->val;
}

}
};

剑指 Offer 35. 复杂链表的复制

难度中等125

请实现 copyRandomList 函数,复制一个复杂链表。在复杂链表中,每个节点除了有一个 next 指针指向下一个节点,还有一个 random 指针指向链表中的任意节点或者 null

示例 1:

img

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

示例 2:

img

1
2
输入:head = [[1,1],[2,1]]
输出:[[1,1],[2,1]]

示例 3:

img

1
2
输入:head = [[3,null],[3,0],[3,null]]
输出:[[3,null],[3,0],[3,null]]

示例 4:

1
2
3
输入:head = []
输出:[]
解释:给定的链表为空(空指针),因此返回 null。

这道题较为简单的一个思路是使用hash表记录老节点到新节点的映射,这一步是为了后面找random指针的指向而进行的。

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
/*
// Definition for a Node.
class Node {
public:
int val;
Node* next;
Node* random;

Node(int _val) {
val = _val;
next = NULL;
random = NULL;
}
};
*/
class Solution {
public:
Node* copyRandomList(Node* head) {
if(head == nullptr) return nullptr;
unordered_map<Node*, Node*> randMap; //记录老链表和新链表之间的映射
Node* newHead = new Node(head->val);
randMap[head] = newHead;
Node* cur = head;
Node* newCur = newHead;
cur = cur->next;
while(cur){
newCur->next = new Node(cur->val);
randMap[cur] = newCur->next;
cur = cur->next;
newCur = newCur->next;
}
cur = head;
newCur = newHead;
while(cur){
newCur->random = randMap[cur->random];
cur = cur->next;
newCur = newCur->next;
}
return newHead;
}
};

还有一种想法是在原链表上复制,这样不用记录在复制random指向的时候,可以在副本上完全拷贝。

1
1->2->3->null 变成了 1->1'->2->2'->3->3'->null

拷贝random的时候,直接新random = 原random->next,最后串联新链表,并且恢复旧链表。

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
class Solution {
public:
Node* copyRandomList(Node* head) {
if(head==NULL)return NULL;
Node*p=head,*q;
while(p){
q=p->next;
p->next=new Node(p->val);
p->next->next=q;
p=q;
}
p=head;
while(1){
if(p->random)
p->next->random=p->random->next;
if(p->next->next) p=p->next->next;
else break;
}
p=head;q=head->next;
Node *ret=head->next;
while(p&&q){
p->next=q->next;
p=p->next;
if(p)q->next=p->next;
else break;
q=q->next;
}
return ret;
}
};
CATALOG
  1. 1. 剑指 Offer 31. 栈的压入、弹出序列
  2. 2. 剑指 Offer 32 - I. 从上到下打印二叉树
  3. 3. 剑指 Offer 32 - II. 从上到下打印二叉树 II
  4. 4. 剑指 Offer 32 - III. 从上到下打印二叉树 III
  5. 5. 剑指 Offer 33. 二叉搜索树的后序遍历序列
  6. 6. 剑指 Offer 34. 二叉树中和为某一值的路径
  7. 7. 剑指 Offer 35. 复杂链表的复制