前言

数(据结构)算(法)再回顾,加深记忆抗遗忘。

基于遍历的简单问题

1
2
3
4
5
6
7
8
9
10
11
var isSameTree = function (p, q) {
// validate structure
if (!p && !q) return true;
if ((!p && q) || (p && !q)) return false;
// p and q are both non-null, then validate value
if (p.val === q.val) {
return isSameTree(p.left, q.left) && isSameTree(p.right, q.right);
}

return false;
};

对称二叉树解法一:修改isSameTree逻辑,将同侧分支判断改为对称侧分支判断。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var isSymmetricHelper = function (p, q) {
if (!p && !q) {
return true;
} else if (!p || !q) {
return false;
} else if (p.val !== q.val) {
return false;
} else {
return isSameTree(p.left, q.right) && isSameTree(p.right, q.left);
}
};
var isSymmetric = function (root) {
if (!root) return true;
return isSymmetricHelper(root.left, root.right);
};

对称二叉树解法二:层序遍历,判断每层是否对称。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var isSymmetric = function (root) {
const queue = [root];

while (root && queue.length) {
let len = queue.length;
const layer = [];
while (len) {
const node = queue.pop();
// tip: To insert a value even if node equal to null for keeping nodes' order
layer.push(node && node.val);
if (node) {
queue.unshift(node.left);
queue.unshift(node.right);
}
len--;
}
for (let i = 0; i < Math.floor(layer.length / 2); i++) {
if (layer[i] !== layer[layer.length - i - 1]) return false;
}
}

return true;
};

对称二叉树解法三:先或中序遍历时压入左、右两个节点,每次 pop 两个节点,判断两节点是否值等。

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
var isSymmetric = function (root) {
const stack = [];
let p = (q = root);

while (p || q || stack.length) {
while (p || q) {
stack.push(p, q);
p = p?.left;
q = q?.right;
}

const nodeQ = stack.pop();
const nodeP = stack.pop();
if (!nodeQ && !nodeP) {
continue;
} else if (!nodeQ || !nodeP) {
return false;
} else if (nodeQ.val !== nodeP.val) {
return false;
}
q = nodeQ.left;
p = nodeP.right;
}

return true;
};
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
// 翻转二叉树
var invertTree = function (root) {
if (!root) return root;

const temp = root.left;
root.left = root.right;
root.right = temp;

invertTree(root.left);
invertTree(root.right);

return root;
};
// 后序遍历迭代法翻转二叉树
var invertTree = function (root) {
const stack = [];
let prev = null;
let p = root;

while (p || stack.length) {
while (p) {
stack.push(p);
p = p.left;
}

const node = stack.pop();
if (!node.right || node.right === prev) {
const temp = node.left;
node.left = node.right;
node.right = temp;
prev = node;
} else {
stack.push(node);
p = node.right;
}
}

return root;
};

深度/高度问题

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
// 递归
var maxDepth = function (root) {
if (!root) return 0;
return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1;
};

// 后序遍历迭代实现
var maxDepth = function (root) {
const stack = [];
let prev = null;
let depth = 0;

while (root || stack.length) {
while (root) {
stack.push(root);
root = root.left;
}

const node = stack.pop();
if (!node.right || node.right === prev) {
depth = Math.max(depth, stack.length + 1);
prev = node;
} else {
stack.push(node);
root = node.right;
}
}

return depth;
};

注意求最小深度时,递归写法应考虑如下 case:

  1. 左、右都空:忽略子树深度,直接返回 1;
  2. 左孩子存在,右孩子空:应当忽略右子树的深度,否则会有 root.right = null,此时右子树不是叶子节点,但却被 min 函数计算了;
  3. 左孩子空,右孩子存在:应当忽略左子树的深度,同理;
  4. 左、右都存在:计算左、右子树深度;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 递归
var minDepth = function (root) {
if (!root) return 0;

if (!root.left && !root.right) return 1;

const dl = root.left ? minDepth(root.left) : Infinity;
const dr = root.right ? minDepth(root.right) : Infinity;

return Math.min(dl, dr) + 1;
};
// 更优雅
var minDepth = function (root) {
if (!root) return 0;

if (!root.left && !root.right) return 1;

const dl = minDepth(root.left);
const dr = minDepth(root.right);

if (!root.left || !root.right) return dl + dr + 1;

return Math.min(dl, dr) + 1;
};

路径问题

112

根-叶路径和问题较为简单,因为指定路径端点为根、叶节点,一次后序遍历就可以解决:

  1. 每次递归将 target - 当前节点值,传递给下一层递归,直到最后一层递归,也就是访问到叶节点;此时判断叶节点值是否等于 target 即可;
  2. 注意每层递归需要归并下层递归的判断结果,也就是左或右子树路径和是否满足 target,最终在递归结束后得出总结果;
  3. 注意叶节点的值等于 target 才符合题意,必须通过左右子树不为空的条件,来限定只有叶节点执行值的比较,否则会出现特殊 case:某个分支节点的值恰好等于 target,导致 return true,

本身很简单,但容易忽视根到叶这一特殊 Case:

  • root=[1,2], target = 1,根节点的值虽然等于 1,但根节点有一个左孩子,所以根节点不算叶子节点,应当返回 false;
  • root=[1], target = 1,根节点同时也是叶子节点,且树的路径定义中,单个节点也可以构成路径,符合题意,应当返回 true;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
bool hasPathSum(TreeNode* root, int targetSum) {
if(!root) return false;

bool result = hasPathSum(root->left, targetSum - root->val)
|| hasPathSum(root->right, targetSum - root->val);

bool isLeaf = !root->left && !root->right;

return result || isLeaf && root->val == targetSum;
}
};

唯一需要注意的是,上述代码中的isLeaf很容易漏掉。

113

同样是根节点到叶节点的路径求和,只不过需要记录路径,迭代实现较为方便。

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
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/

class Solution {
public:
int sum(vector<TreeNode*> arr) {
int result = 0;
for(int i = 0; i < arr.size(); i++) {
result += arr[i]->val;
}
return result;
}
public:
vector<int> nodeToInt(vector<TreeNode*> v) {
vector<int> result;
for(int i = 0; i < v.size(); i++) {
result.push_back(v[i]->val);
}
return result;
}
public:
vector<vector<int>> pathSum(TreeNode* root, int targetSum) {
vector<TreeNode*> stack;
vector<vector<int>> result;
TreeNode* prev = nullptr;

while(root || stack.size()) {
while(root) {
stack.push_back(root);
root = root->left;
}
TreeNode* node = stack.back();

if (node->right == nullptr || node->right == prev) {
bool isLeafNode = node->left == nullptr && node->right == nullptr;
if (isLeafNode && this->sum(stack) == targetSum) {
result.push_back(this->nodeToInt(stack));
}
stack.pop_back();
prev = node;
} else {
root = node->right;
}
}

return result;
}
};

129

求根节点到叶节点数字之和,该问题的完整描述是——求出根-叶路径上,从根到叶的数字按顺序从左到右排列,所组成的新数字之和。递归思路是:

  1. 数字拼接:每层递归的值 + 上层递归拼接的数字 * 10;
  2. 结果归并:左右子树分别递归处理,结果相加;
  3. 结束条件:如果当前是叶子节点,数字拼接后直接返回结果;如果当前是空指针(度为 1 的节点会出现该情况),则返回 0,0 在求和中不影响结果;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
using namespace std;
class Solution {
public:
int sumNumbers(TreeNode* root, int prefix = 0) {
if(root == nullptr) return 0;
if(root->left == nullptr && root->right == nullptr) return prefix + root->val;

int newPrefix = (prefix + root->val) * 10;

return sumNumbers(root->left, newPrefix) + sumNumbers(root->right, newPrefix);
}
};

257

同 113,换汤不换药,输出数组改为输出字符串;

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
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/

using namespace std;
class Solution {
public:
string nodesToString(vector<TreeNode*> v) {
string result = "";
for(int i = 0; i < v.size(); i++) {
if(i == 0) {
result = to_string(v[i]->val);
} else {
result = result + "->" + to_string(v[i]->val);
}
}

return result;
}
public:
vector<string> binaryTreePaths(TreeNode* root) {
vector<string> result;
vector<TreeNode*> stack;
TreeNode* prev = nullptr;

while(root != nullptr || stack.size() != 0) {
while(root != nullptr) {
stack.push_back(root);
root = root->left;
}
TreeNode* node = stack.back();
if(node->right == nullptr || node->right == prev) {
if(node->left == nullptr && node->right == nullptr) {
result.push_back(this->nodesToString(stack));
}
stack.pop_back();
prev = node;
} else {
root = node->right;
}
}

return result;
}
};

404

叶节点求和,也可以看作是叶节点单独组成的路径(长度为 1)之和,该题限定左叶节点,为了区分左右子树,可以引入一个 isLeft 变量用于标识左右;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
int sumOfLeftLeaves(TreeNode* root, bool isLeft = false) {
if (root == nullptr) return 0;
if (root->left == nullptr && root->right == nullptr) return isLeft ? root -> val : 0;

return sumOfLeftLeaves(root->left, true) + sumOfLeftLeaves(root->right);
}
};

基于二叉树性质的问题

222. 完全二叉树的节点个数