题目
给定一棵二叉树,检查它是否左右镜像对称。
示例:
例如,二叉树 [1, 2, 2, 3, 4, 4, 3] 是对称的:
1
/ \
2 2
/ \ / \
3 4 4 3
而二叉树 [1, 2, 2, null, 3, null, 3] 不对称:
1
/ \
2 2
\ \
3 3
后续:
尝试分别用递归和迭代两种方式解决。
难度:容易
编程语言:C++
分析
程序框架为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| /** * 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: bool isSymmetric(TreeNode *root) { } };
|
思路可以借鉴 Leetcode 题解 - 107. Binary Tree Level Order Traversal II,先把二叉树转换为 vector<vector<TreeNode*>>
,每个元素是一个 vector<TreeNode*>
,里面是第 i 层的所有 TreeNode 指针。然后根据 vector 下标能比较方便地判断二叉树是否对称。
我们先算出 vector<vector<TreeNode*>>
,代码如下:
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
| vector<vector<TreeNode*>> levelTopDown(TreeNode *root) { vector<vector<TreeNode*>> vv; if (root == NULL) { return vv; } vector<TreeNode*> queue; vector<int> qLevel; queue.push_back(root); qLevel.push_back(1); for (int i = 0; i < queue.size(); i++) { TreeNode *node = queue[i]; if (node == NULL) { continue; } queue.push_back(node->left); qLevel.push_back(qLevel[i] + 1); queue.push_back(node->right); qLevel.push_back(qLevel[i] + 1); } qLevel.insert(qLevel.begin(), 0); for (int i = 0; i < queue.size(); i++) { if (qLevel[i] < qLevel[i + 1]) { vector<TreeNode*> vNewLevel; vv.push_back(vNewLevel); } vv[vv.size() - 1].push_back(queue[i]); } vv.erase(vv.end() - 1); return vv; }
|
在上面代码的第 21、24 行,把 node->left 和 node->right(不管是否为 NULL)都添加到 queue 中。这样会导致一个问题,示例的第一棵二叉树层数为 3,上面的代码运行后生成的 vector<vector<TreeNode*>>
为:
[
[1],
[2, 2],
[3, 4, 4, 3],
[null, null, null, null, null, null, null, null]
]
即是多出来的最底一层全为 NULL。所以在 return vv 前把 vv 最后一个 vector<TreeNode*>
去掉。
在根据 vv 判断二叉树是否对称前,我们先整理一下完全二叉树各节点的下标:
0
/ \
1 2
/ \ / \
3 4 5 6
第 i 层有 2^(i-1) 个节点:
- 第 1 层有 1 个节点
- 第 2 层有 2 个节点
- 第 3 层有 4 个节点
- 第 4 层有 8 个节点
所以第 i 层的最左节点下标,即是前 i-1 层所有节点个数之和 + 1 - 1(-1 是因为下标从 0 开始),即 2^0 + 2^1 + … + 2^(i-2) = 2^(i-1) - 1,详见等比数列求和公式。
那么第 i 层的最右节点下标,即是最左节点下标 + 第 i 层节点个数 - 1,即 2^(i-1) - 1 + 2^(i-1) - 1 = 2^i - 2。
我们用一个表格来整理清楚各个数据:
层数 该层节点个数 最左节点下标 最右节点下标
1 1 0 0
2 2 1 2
3 4 3 6
4 8 7 14
...
i 2^(i-1) 2^(i-1) - 1 2^i - 2
接下来就可以利用这些数据来判断二叉树是否对称了,思路是:
- 对于每一层,使用 leftIndex 和 rightIndex 记录该层的
vector<TreeNode*>
最左和最右节点下标
- 如果 leftIndex 和 rightIndex 节点都为 NULL,则它们相同,leftIndex++,rightIndex–
- 如果 leftIndex 和 rightIndex 只有一个为 NULL,则它们不相同,则整棵二叉树不对称,return false
- 如果 leftIndex 和 rightIndex 节点都不为 NULL,则判断 leftVal == rightVal,相同则 leftIndex++,rightIndex–,否则整棵二叉树不对称,return false
- 对于二叉树每一层,重复第 1 步。
代码如下:
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
| class Solution { public: bool isSymmetric(TreeNode *root) { if (root == NULL) { return true; } vector<vector<TreeNode*>> vv = levelTopDown(root); bool isSym = checkSymmetric(vv); return isSym; } private: vector<vector<TreeNode*>> levelTopDown(TreeNode *root) { vector<vector<TreeNode*>> vv; if (root == NULL) { return vv; } vector<TreeNode*> queue; vector<int> qLevel; queue.push_back(root); qLevel.push_back(1); for (int i = 0; i < queue.size(); i++) { TreeNode *node = queue[i]; if (node == NULL) { continue; } queue.push_back(node->left); qLevel.push_back(qLevel[i] + 1); queue.push_back(node->right); qLevel.push_back(qLevel[i] + 1); } qLevel.insert(qLevel.begin(), 0); for (int i = 0; i < queue.size(); i++) { if (qLevel[i] < qLevel[i + 1]) { vector<TreeNode*> vNewLevel; vv.push_back(vNewLevel); } vv[vv.size() - 1].push_back(queue[i]); } vv.erase(vv.end() - 1); return vv; } bool checkSymmetric(vector<vector<TreeNode*>>& vv) { int level = vv.size(); for (int i = 1; i <= level; i++) { int leftIndex = 0; int rightIndex = pow(2, i - 1) - 1; while (leftIndex <= rightIndex) { TreeNode *leftNode = vv[i - 1][leftIndex]; TreeNode *rightNode = vv[i - 1][rightIndex]; if (leftNode == NULL && rightNode == NULL) { leftIndex++; rightIndex--; continue; } if (leftNode == NULL || rightNode == NULL) { return false; } if (leftNode != NULL && rightNode != NULL) { if (leftNode->val == rightNode->val) { leftIndex++; rightIndex--; continue; } else { return false; } } } } return true; } };
|
提交到 Leetcode,Runtime Error! 导致错误的输入是 [9, 71, 71, null, 79, 79, null, -76, -43, -43, -76, null, -52, 21, null, null, 84, -52],即下面的二叉树:
经 debug 后,我发现上面算法的问题出在 levelTopDown,因为 checkSymmetric 时最左节点下标都是从 0 开始,所以如果二叉树某一层的最左节点下标,不是完全二叉树意义上的最左节点(如第 3 层的第一个 79,第 5 层的第一个 -52),就会导致该层的下标不准确,导致访问 rightIndex 元素时 out of range,导致 Runtime Error。
简单来讲,即是 checkSymmetric 需要形参 vv 在第 i 层有 2^(i-1) 个元素,而 levelTopDown 返回的 vv 不满足。
如果把第 44~52 行的 for 循环修改为:
1 2 3 4 5 6 7 8 9
| for (int i = 0; i < queue.size(); i++) { TreeNode *node = queue[i]; queue.push_back(node == NULL ? NULL : node->left); qLevel.push_back(qLevel[i] + 1); queue.push_back(node == NULL ? NULL : node->right); qLevel.push_back(qLevel[i] + 1); }
|
则满足第 i 层节点个数为 2^(i-1),但会导致死循环。以上图二叉树为例,在第 5 层叶子节点下面层的节点全为 NULL,一层一层生成,无法停止。现在我还没想到办法如何判断出第 6 层全为 NULL 后则停止 for 循环,并把第 6 层删除。
想到办法了,在每次遍历 i 时,先判断是否 qLevel[i-1] < qLevel[i],若是,则说是 queue[i] 是下一层的第一个节点。然后遍历上一层,如果全是 NULL,则 break 出 for 循环。
代码如下:
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 106 107 108 109 110
| #include <frequently-used-code-snippets.h> #include <math.h> using namespace std; class Solution { public: bool isSymmetric(TreeNode *root) { if (root == NULL) { return true; } vector<vector<TreeNode*>> vv = levelTopDown(root); bool isSym = checkSymmetric(vv); return isSym; } private: vector<vector<TreeNode*>> levelTopDown(TreeNode *root) { vector<vector<TreeNode*>> vv; if (root == NULL) { return vv; } vector<TreeNode*> queue; vector<int> qLevel; queue.push_back(root); qLevel.push_back(1); for (int i = 0; i < queue.size(); i++) { if (i > 0 && qLevel[i - 1] < qLevel[i]) { bool continueFor = false; for (int j = i - 1; j >= 0 && qLevel[j] == qLevel[i - 1]; j--) { if (queue[j] != NULL) continueFor = true; } } if (! continueFor) { break; } } TreeNode *node = queue[i]; queue.push_back(node == NULL ? NULL : node->left); qLevel.push_back(qLevel[i] + 1); queue.push_back(node == NULL ? NULL : node->right); qLevel.push_back(qLevel[i] + 1); } qLevel.insert(qLevel.begin(), 0); for (int i = 0; i < queue.size(); i++) { if (qLevel[i] < qLevel[i + 1]) { vector<TreeNode*> vNewLevel; vv.push_back(vNewLevel); } vv[vv.size() - 1].push_back(queue[i]); } vv.erase(vv.end() - 1); vv.erase(vv.end() - 1); return vv; } bool checkSymmetric(vector<vector<TreeNode*>>& vv) { int level = vv.size(); for (int i = 1; i <= level; i++) { int leftIndex = 0; int rightIndex = pow(2, i - 1) - 1; while (leftIndex <= rightIndex) { TreeNode *leftNode = vv[i - 1][leftIndex]; TreeNode *rightNode = vv[i - 1][rightIndex]; if (leftNode == NULL && rightNode == NULL) { leftIndex++; rightIndex--; continue; } if (leftNode == NULL || rightNode == NULL) { return false; } if (leftNode != NULL && rightNode != NULL) { if (leftNode->val == rightNode->val) { leftIndex++; rightIndex--; continue; } else { return false; } } } } return true; } };
|
提交到 Leetcode,Time Limit Exceeded! 导致超时的输入为 [6, 82, 82, null, 53, 53, null, -58, null, null, -58, null, -85, -85, null, -9, null, null, -9, null, 48, 48, null, 33, null, null, 33, 81, null, null, 81, 5, null, null, 5, 61, null, null, 61, null, 9, 9, null, 91, null, null, 91, 72, 7, 7, 72, 89, null, 94, null, null, 94, null, 89, -27, null, -30, 36, 36, -30, null, -27, 50, 36, null, -80, 34, null, null, 34, -80, null, 36, 50, 18, null, null, 91, 77, null, null, 95, 95, null, null, 77, 91, null, null, 18, -19, 65, null, 94, null, -53, null, -29, -29, null, -53, null, 94, null, 65, -19, -62, -15, -35, null, null, -19, 43, null, -21, null, null, -21, null, 43, -19, null, null, -35, -15, -62, 86, null, null, -70, null, 19, null, 55, -79, null, null, -96, -96, null, null, -79, 55, null, 19, null, -70, null, null, 86, 49, null, 25, null, -19, null, null, 8, 30, null, 82, -47, -47, 82, null, 30, 8, null, null, -19, null, 25, null, 49]
上面代码的时间复杂度是 O(2^n),因为最坏情况是二叉树每层只有一个节点,但为了保证 leftIndex 和 rightIndex 的正确性,要把该层所有 2^(i-1)-1 个节点全部都用 NULL 填充占位。所以当有 n 个节点,每个节点占一层时,n 层完全二叉树共有 2^0 + 2^1 + 2^2 + … + 2^(n-1) = 2^n - 1 个节点,所以时间复杂度为 O(2^n)。
#思路二
上面的思路好像走不通,那么就换个思路。要判断一棵二叉树是否对称,一个比较直观的思路是,对源二叉树生成一棵左右交换(即垂直旋转)的二叉树,如果两棵二叉树完全相同,那么源二叉树就是对称的。
代码如下:
1 2 3 4 5
| bool isSymmetric(TreeNode *root) { TreeNode *invertRoot = invertTree(root); bool isSym = isSameTree(root, invertRoot); return isSym; }
|
invertTree 可直接使用 Leetcode 题解 - 226. Invert Binary Tree 的代码。注意那是使用原地反转二叉树,在本题套用前,要先对二叉树作一份拷贝。
代码如下:
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
| TreeNode* invertTree(TreeNode *root) { TreeNode *copyRoot = copyTree(root); copyRoot = invertTreeInPlace(copyRoot); return copyRoot; } TreeNode* copyTree(TreeNode *root) { if (root == NULL) { return NULL; } TreeNode *copyRoot = new TreeNode(root->val); copyRoot->left = copyTree(root->left); copyRoot->right = copyTree(root->right); return copyRoot; } TreeNode* invertTreeInPlace(TreeNode *root) { if (root == NULL) { return NULL; } if (root->left == NULL && root->right == NULL) { return root; } TreeNode *left = invertTreeInPlace(root->left); TreeNode *right = invertTreeInPlace(root->right); TreeNode *temp = root->left; root->left = root->right; root->right = temp; return root; }
|
isSameTree 可直接使用 Leetcode 题解 - 100. Same Tree 的代码:
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
| bool isSameTree(TreeNode *p, TreeNode *q) { if (p == NULL && q == NULL) { return true; } if (p != NULL && q == NULL) { return false; } if (p == NULL && q != NULL) { return false; } else { if (p->val != q->val) { return false; } bool isSameLeft = isSameTree(p->left, q->left); bool isSameRight = isSameTree(p->right, q->right); if (isSameLeft && isSameRight) { return true; } return false; } }
|
整个程序的代码如下(测试数据仍是下图的二叉树):
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 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129
| #include <frequently-used-code-snippets.h> #include <math.h> using namespace std; class Solution { public: bool isSymmetric(TreeNode *root) { TreeNode *invertRoot = invertTree(root); bool isSym = isSameTree(root, invertRoot); return isSym; } private: TreeNode* invertTree(TreeNode *root) { TreeNode *copyRoot = copyTree(root); copyRoot = invertTreeInPlace(copyRoot); return copyRoot; } TreeNode* copyTree(TreeNode *root) { if (root == NULL) { return NULL; } TreeNode *copyRoot = new TreeNode(root->val); copyRoot->left = copyTree(root->left); copyRoot->right = copyTree(root->right); return copyRoot; } TreeNode* invertTreeInPlace(TreeNode *root) { if (root == NULL) { return NULL; } if (root->left == NULL && root->right == NULL) { return root; } TreeNode *left = invertTreeInPlace(root->left); TreeNode *right = invertTreeInPlace(root->right); TreeNode *temp = root->left; root->left = root->right; root->right = temp; return root; } bool isSameTree(TreeNode *p, TreeNode *q) { if (p == NULL && q == NULL) { return true; } if (p != NULL && q == NULL) { return false; } if (p == NULL && q != NULL) { return false; } else { if (p->val != q->val) { return false; } bool isSameLeft = isSameTree(p->left, q->left); bool isSameRight = isSameTree(p->right, q->right); if (isSameLeft && isSameRight) { return true; } return false; } } }; int main() { TreeNode A(9); TreeNode B(71); TreeNode C(71); TreeNode D(79); TreeNode E(79); TreeNode F(-76); TreeNode G(-43); TreeNode H(-43); TreeNode I(-76); TreeNode J(-52); TreeNode K(21); TreeNode L(84); TreeNode M(-52); A.left = &B; A.right = &C; B.right = &D; C.left = &E; D.left = &F; D.right = &G; E.left = &H; E.right = &I; F.right = &J; G.left = &K; H.right = &L; I.left = &M; Solution sol; cout << sol.isSymmetric(&A) << endl; }
|
提交到 Leetcode,Accepted! :) 运行时间为 9ms。