-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathpseudo-palindromic-paths-in-a-binary-tree.py
94 lines (83 loc) · 3.34 KB
/
pseudo-palindromic-paths-in-a-binary-tree.py
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
# 1457. Pseudo-Palindromic Paths in a Binary Tree
# 🟠 Medium
#
# https://leetcode.com/problems/pseudo-palindromic-paths-in-a-binary-tree/
#
# Tags: Bit Manipulation - Tree - Depth-First Search -
# Breadth-First Search - Binary Tree
import timeit
from typing import Optional, Set
from data import TreeNode, deserializeStringArrayToBinaryTree
# Use DFS to find unique paths between the root and the leaves. While
# traveling down the path, keep track of node values that we have seen
# using a set, each time we see a match for a value, we know that we
# can use it to construct two symmetrical elements in the palindrome and
# we remove the match from the set. When we arrive at a leaf, we must
# have zero or one element left in the set for the path to be a pseudo
# palindrome.
#
# Time complexity: O(n) - We visit each node once and perform O(1)
# operations.
# Space complexity: O(h) - Where h is the height of the tree, for the
# call stack. h could be equal to n if each node had a single child.
# The set uses O(1) because the values in the node can only be 1-9.
#
# Runtime: 351 ms, faster than 96%
# Memory Usage: 43.46 MB, less than 98.67%
class UsingSet:
def pseudoPalindromicPaths(self, root: Optional[TreeNode]) -> int:
# Root is guaranteed to not be null.
# Define a function that explores the tree using DFS.
def dfs(node: TreeNode, seen: Set[int]) -> int:
# Add this value to the seen set.
if node.val in seen:
seen.remove(node.val)
else:
seen.add(node.val)
# If the current node is not a leaf, keep exploring and
# return the result of exploring its children.
if node.left or node.right:
res = (dfs(node.left, seen) if node.left else 0) + (
dfs(node.right, seen) if node.right else 0
)
# If the current node is a leaf, the path formed from the
# root to it is a pseudo palindrome only if the values
# found on the way were all matched, or all except for one.
else:
res = int(len(seen) < 2)
# Reset the seen set to the caller's state.
if node.val in seen:
seen.remove(node.val)
else:
seen.add(node.val)
return res
# Initial call.
return dfs(root, set())
# TODO: add a solution using bits to keep track of the digits seen.
def test():
executors = [
UsingSet,
]
tests = [
["[2,3,1,3,1,null,1]", 2],
["[2,1,1,1,3,null,null,null,null,null,1]", 1],
["[9]", 1],
]
for executor in executors:
start = timeit.default_timer()
for _ in range(1):
for col, t in enumerate(tests):
sol = executor()
root = deserializeStringArrayToBinaryTree(t[0])
result = sol.pseudoPalindromicPaths(root)
exp = t[1]
assert result == exp, (
f"\033[93m» {result} <> {exp}\033[91m for"
+ f" test {col} using \033[1m{executor.__name__}"
)
stop = timeit.default_timer()
used = str(round(stop - start, 5))
cols = "{0:20}{1:10}{2:10}"
res = cols.format(executor.__name__, used, "seconds")
print(f"\033[92m» {res}\033[0m")
test()