I have solved the following Leetcode problem.
Given a binary tree, check whether it is a mirror of itself (ie, symmetric around its center). For example, this binary tree [1,2,2,3,4,4,3] is symmetric.
Link: https://leetcode.com/problems/symmetric-tree/
I have made a simple iterative solution that takes \$O(n)\$ time and \$O(n)\$ space since we have to parse through each node, which is initialized as a class and each class contains the node's values, and pointers to the left and right child of the node. We compare if the node values at each level form a palindromic list (we store all the node values in a running list) or not. Here \$n\$ denotes the number of nodes in the tree. I have assumed the binary tree is complete and any missing node is initialized with a NONE
variable. The code terminates when I have reached a level in the tree where each node is a NONE
, meaning nothing has to be analyzed at this level, and if an error isn't found in one of the previous nodes (an error is raised when the nodes at each level don't form a palindromic list), we return True.
The code takes a whopping 1500 ms to run on Leetcode and uses around 150 MB of storage! I think about ~200 test cases are run in the background. Running the code on a single tree (of different sizes) makes the code run in about ~30-40ms.
Should I be concerned? Are the other significant ways to optimize the code/approach? I think even if the approach is correct, the implementation may be throwing off the time, and I'm not the most savvy coder. I'm new to learning algorithms and their implementation as well, so I'd appreciate some feedback.
Edit:
Here's my analysis of the run time of the algorithm. Assume the tree is a complete binary tree since each missing node can be thought of a node with a NONE
class associated to it. Assume the tree has \$k\$ (starting from level 0) levels and a total of \$n = 2^{k+1} - 1\$ nodes. For example, the tree [1|2,2|3,4,4,3]
, where a |
indicates a level has changed, has \$2\$ levels with \$ 2^{3} - 1 = 7 \$ nodes.
The outer while loop terminates when we check the condition of the while loop when we have reached level \$k + 1\$ where this level can be thought as being comprised of all NONE
nodes, meaning the tree doesn't extend till this level. So it runs only when the running variable \$l\$ ranges from \$0\$ to \$k\$, or a total of \$k + 1\$ times which is \$\Theta ( \lg (n+1)) = \Theta ( \lg n)\$, where \$\lg\$ is log base 2. In the while loop, we have that for each value of \$l\$, the first for loop runs for a total of \$2^{l}\$ times since each level has (at most) \$2^{l}\$ nodes. The additional for loop runs for only \$2\$ times so all in all, for each value of \$l\$ there are \$O(2^{l})\$ iterations. All other operations take constant time, so the running cost of the algorithm is,
$$ \begin{align} O\big(\sum_{l = 0}^{k + 1} 2^{l} \big) &= O\big(\sum_{l = 0}^{\Theta (\lg n)} 2^{l} \big) \\ &= O\big(2^{\Theta (\lg n) + 1 } - 1 \big ) \\ &= O\big(2^{\Theta (\lg n) + 1 } \big) \\ &= O\big(2^{\Theta (\lg n) } \big) \\ &= \Theta (n) \\ &= O(n) \end{align} $$
def isSymmetric(root):
if root == None:
return True
else:
t = [root]
l = 0
d = {None:-1}
while d[None] < 2**l:
d[None] = 0
n = []
v = []
for node in t:
if node == None:
d[None] = d[None] + 2
v.append(None)
v.append(None)
n.append(None)
n.append(None)
else:
for child in [node.left,node.right]:
n.append(child)
if child == None:
d[None] = d[None] + 1
v.append(None)
else:
v.append(child.val)
l = l + 1
if d[None] == 2**l:
return True
else:
a = v[0:2**(l-1)]
b = v[2**(l-1):2**l]
b.reverse()
if a != b:
return False
t = n