I am looking at LeetCode problem 572. Subtree of Another Tree:
Given the roots of two binary trees root and subRoot, return true if there is a subtree of root with the same structure and node values of subRoot and false otherwise.
A subtree of a binary tree tree is a tree that consists of a node in tree and all of this node's descendants. The tree tree could also be considered as a subtree of itself.
Example 1
Input: root = [3,4,5,1,2], subRoot = [4,1,2]
Output: true
Example 2
Input: root = [3,4,5,1,2,null,null,null,null,0], subRoot = [4,1,2]
Output: false
My Code
class Solution:
def isSubtree(self, root: Optional[TreeNode], subRoot: Optional[TreeNode]) -> bool:
if not root and not subRoot:
return True
if root==None or subRoot==None:
return False
if root.val == subRoot.val and self.isSubtree(root.left,subRoot.left) and self.isSubtree(root.right,subRoot.right):
return True
return self.isSubtree(root.left,subRoot) or self.isSubtree(root.right,subRoot)
Input Failing:
Input
[3,4,5,1,null,2]
[3,1,2]
Output True
Expected False
The problem with your algorithm is that it allows the check to skip some nodes.
The failing test case represents these trees:
root: 3 subRoot: 3
/ \ / \
4 5 1 2
/ /
1 2
In the first call of your function, the roots have equal values, so then this recursive call is made:
root: 4 subRoot: 1
/
1
Now you can already see that here the subRoot is a sub tree of root, but this fact should not be used here, since we are half-way a tree matching procedure where the original roots already matched, so we are not allowed now to skip over the node 4, yet that is what will happen.
The real problem is that you are trying to cover two recursive needs in one recursive function:
find a node in root where subRoot matches with all its descendants
verify that all descendants match.
You need to split this into two independent recursive functions. For instance:
class Solution:
def isSubtree(self, root: Optional[TreeNode], subRoot: Optional[TreeNode]) -> bool:
def recurMatch(root, subRoot):
return not (subRoot or root) or (
subRoot and root and
root.val == subRoot.val and
recurMatch(root.left, subRoot.left) and
recurMatch(root.right, subRoot.right)
)
def recurSearch(root):
return root and (
recurMatch(root, subRoot) or
recurSearch(root.left) or
recurSearch(root.right)
)
return bool(recurSearch(root))
Faster?
The above solution will only perform "average" on LeetCode. You could improve by relying on fast string comparison: stringify both trees and check whether the second string occurs within the first. And the shorter the serialisation you can generate, the faster the algorithm will work.
Spoiler:
class Solution:
def isSubtree(self, root, subRoot):
def stringify(root):
return f"[{stringify(root.left) if root.left else ''}{chr(root.val+10128)}{stringify(root.right) if root.right else ''}]"
return stringify(subRoot) in stringify(root)
Related
I have looked at this code line by line maybe 100 times and I am stumped. Why doesn't this work????
Problem: input of [5,4,6,null,null,3,7] (this is a BST with 5 being the root and 4 and 6 being its left and right nodes)
returns True when it should return False (3 should be not be to the right of parent node 5). False should be returned at the first nested if statement in the while loop.
def isValidBST(self, root: Optional[TreeNode]) -> bool:
if not root:
return True
# BFS method
current_node = root
queue = []
queue.append(current_node)
while len(queue) > 0:
current_node = queue.pop(0)
if current_node.left:
if (current_node.val > current_node.left.val) and (root.val > current_node.left.val):
queue.append(current_node.left)
else:
return False
if current_node.right:
if (current_node.val < current_node.right.val) and (root.val < current_node.right.val):
queue.append(current_node.right)
else:
return False
return True
The tree for which your code fails is:
5
/ \
4 6
/ \
3 7
When current_node is 6, your code checks that root.val > current_node.left.val, which means it checks that 5 > 3. This is true. And so it wrongly concludes there is no problem there, yet there is.
You may now think to just change the direction of that comparison, but there is a logical error in your approach: it is not enough to compare the root with every node in its subtree. For instance, in the following tree you should detect that 3 is violating the BST property, even though it is not in conflict with the root:
2
\
5
\
6
/
3
The conflict is with the value 5, which is neither the parent of 3, nor the root.
In conclusion, a BFS traversal is not really an ideal tool to verify a BST. Although it surely can be done, it is more natural to use DFS, as it allows to carry down the (accumulated) limitations that will apply to every node in the subtree.
For Leetcode 105 I've got the O(N^2) solution working and I'm attempting to optimize to an O(N) solution but the output tree is the wrong structure.
Given two integer arrays preorder and inorder where preorder is the preorder traversal of a binary tree and inorder is the inorder traversal of the same tree, construct and return the binary tree.
My original O(N^2) solution.
def buildTree(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]:
preorder_queue = deque(preorder)
def helper(preorder, inorder):
if not preorder or not inorder:
return None
root = TreeNode(preorder.popleft())
root_index = inorder.index(root.val)
root.left = helper(preorder, inorder[:root_index])
root.right = helper(preorder, inorder[root_index+1:])
return root
return helper(preorder_queue, inorder)
The slow operation is here is the inorder.index(root.val) since an index operation in a list is O(N). I'm trying to optimize this by storing a map of Node values -> indexes in my solution below.
def buildTree(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]:
preorder_queue = deque(preorder)
cache = {}
for i, node_val in enumerate(inorder):
cache[node_val] = i
def helper(preorder, inorder):
if not preorder or not inorder:
return None
root = TreeNode(preorder.popleft())
root_index = cache[root.val]
root.left = helper(preorder, inorder[:root_index])
root.right = helper(preorder, inorder[root_index+1:])
return root
return helper(preorder_queue, inorder)
However for the input preorder = [3,9,20,15,7] and inorder = [9,3,15,20,7] my output is giving me [3,9,20,null,null,15,null,7] instead of the correct tree structure [3,9,20,null,null,15,7]. Can anyone explain what's wrong with the cache solution?
The reason for the different output is this:
In the first version the value of root_index is taken from the local name inorder, which in general is just a slice of the original inorder (see how the recursive call is made with such a slice as argument). So the value of root_index is an index that is meaningful to the slice, but not to the overall, initial inorder list. Yet the second version of the code will give root_index an index that is meaningful in the initial inorder list, not to the local slice of it.
You can fix that by not creating a local inorder name (as parameter), but instead pass start and end indices which define which slice should be assumed, without actually creating it. That way the code can continue with the initial inorder list and use the indexing that the cache offers.
So:
def helper(preorder, start, end):
if not preorder or start >= end:
return None
root = TreeNode(preorder.popleft())
root_index = cache[root.val]
root.left = helper(preorder, start, root_index)
root.right = helper(preorder, root_index+1, end)
return root
return helper(preorder_queue, 0, len(inorder))
I came across the following problem.
You are given the root of a binary tree with n nodes.
Each node is uniquely assigned a value from 1 to n.
You are also given an integer startValue representing
the value of the start node s,
and a different integer destValue representing
the value of the destination node t.
Find the shortest path starting from node s and ending at node t.
Generate step-by-step directions of such path as a string consisting of only the
uppercase letters 'L', 'R', and 'U'. Each letter indicates a specific direction:
'L' means to go from a node to its left child node.
'R' means to go from a node to its right child node.
'U' means to go from a node to its parent node.
Return the step-by-step directions of the shortest path from node s to node t
Example 1:
Input: root = [5,1,2,3,null,6,4], startValue = 3, destValue = 6
Output: "UURL"
Explanation: The shortest path is: 3 → 1 → 5 → 2 → 6.
Example 2:
Input: root = [2,1], startValue = 2, destValue = 1
Output: "L"
Explanation: The shortest path is: 2 → 1.
I created the solution by finding the least common ancestor and then doing a depth-first-search to find the elements, Like this:-
# Definition for a binary tree node.
# class TreeNode(object):
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution(object):
def getDirections(self, root, startValue, destValue):
"""
:type root: Optional[TreeNode]
:type startValue: int
:type destValue: int
:rtype: str
"""
def lca(root):
if root == None or root.val == startValue or root.val == destValue:
return root
left = lca(root.left)
right = lca(root.right)
if left and right:
return root
return left or right
def dfs(root, value, path):
if root == None:
return ""
if root.val == value:
return path
return dfs(root.left, value, path + "L") + dfs(root.right, value, path + "R")
root = lca(root)
return "U"*len(dfs(root, startValue, "")) + dfs(root, destValue, "")
The solution runs good, however for a very large input it throws "Memory Limit Exceeded" error, can anyone tell me how I can optimise the solution, or what might I be doing that could be getting me into it ?
The reason you're getting a memory limit exceeded is the arguments to the dfs function. Your 'path' variable is a string that can be as large as the height of the tree (which can be the size of the whole tree if it's unbalanced).
Normally that wouldn't be a problem, but path + "L" creates a new string for every recursive call of the function. Besides being very slow, this means that your memory usage is O(n^2), where n is the number of nodes in the tree.
For example, if your final path is "L" * 1000, your call stack for dfs will look like this:
Depth 0: dfs(root, path = "")
Depth 1: dfs(root.left, path = "L")
Depth 2: dfs(root.left.left, path = "LL")
...
Depth 999: path = "L"*999
Depth 1000: path = "L"*1000
Despite all those variables being called path, they are all completely different strings, for a total memory usage of ~(1000*1000)/2 = 500,000 characters at one time. With one million nodes, this is half a trillion characters.
Now, this doesn't happen just because strings are immutable; in fact, even if you were using lists (which are mutable), you'd still have this problem, as path + ["L"] would still be forced to create a copy of path.
To solve this, you need to have exactly one variable for the path stored outside of the dfs function, and only append to it from the recursive dfs function. This will ensure you only ever use O(n) space.
def dfs(root, value, path):
if root is None:
return False
if root.val == value:
return True
if dfs(root.left, value, path):
path.append("L")
return True
elif dfs(root.right, value, path):
path.append("R")
return True
return False
root = lca(root)
start_to_root = []
dfs(root, startValue, start_to_root)
dest_to_root = []
dfs(root, destValue, dest_to_root)
return "U" * len(start_to_root) + ''.join(reversed(dest_to_root))
I am working on the LeetCode problem 104. Maximum Depth of Binary Tree:
Given the root of a binary tree, return its maximum depth.
A binary tree's maximum depth is the number of nodes along the longest path from the root node down to the farthest leaf node.
My attempt is not working: I first add the root to a queue (if root is not None), and then process it, by adding its children to the queue.
While doing this, I keep a counter, and each time I add a child node, I increment the counter by 1. When both left and right child exist, I will only increment the counter by 1.
from collections import deque
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
class Solution:
def max_depth(self,root):
counter = 1
queue = deque
if not root:
return 0
else:
queue.append(root)
while queue:
root = queue.popleft()
if root.left:
queue.append(root.left)
counter +=1
if root.right:
queue.append(root.right)
if root.left:
continue
else:
counter +=1
return counter
However, when I run the above on LeetCode, for an input of say [3,9,20,null,null,15,7], I get 'None' as a result.
Is it because I have structured the function to not take a list as an input?
Is it because I have structured the function to not take a list as an input?
No. This may be confusing, but on LeetCode the raw list representation of the input is translated to an instance of TreeNode before your function is called. So you should never have to deal with this list structure. It is merely the common input format that LeetCode uses across the different programming languages. But the conversion to the target language's data structure is done for you before your implementation is called.
Your code produces an error on the first call of queue.append because of this line:
queue = deque
This is wrong, as this makes queue a synonym for the class deque. But it should be an instance of it, so do:
queue = deque()
With that fix, the function does not return None.
However, its logic is not correct:
I keep a counter, and each time I add a child node, I increment the counter by 1. When both left and right child exist, I will only increment the counter by 1.
This practically means that you count the number of nodes that have at least one child, i.e. you count the number of internal nodes of the tree.
This is not correct. For instance, the following tree has 7 internal nodes:
___ 10 __
/ \
5 14
/ \ / \
1 8 12 20
/ \ / \ / \ / \
0 2 6 9 11 13 18 22
Obviously, 7 is not the correct answer. It should be 4 in this case.
Your queue-based solution will visit the nodes level by level, but you don't have any information about when you pass from one level to the next.
You can solve this by using two (standard) lists: the first list will have all the nodes from one level, and the second list will collect those from the next level. When that is done you know you have processed one level. Then you make the second list the first, and empty the second. Then you can restart this process for as long as there are nodes to process:
class Solution:
def maxDepth(self, root: TreeNode) -> int:
counter = 0
queue = []
if root:
queue.append(root)
while queue:
counter +=1
nextlevel = []
for root in queue:
if root.left:
nextlevel.append(root.left)
if root.right:
nextlevel.append(root.right)
queue = nextlevel
return counter
Making it a bit more compact, it can be:
class Solution:
def maxDepth(self, root: TreeNode) -> int:
counter = 0
if root:
queue = [root]
while queue:
counter +=1
queue = [root.left for root in queue if root.left
] + [root.right for root in queue if root.right]
return counter
You can also go for a depth-first traversal instead of the breadth-first traversal you were going for:
class Solution:
def maxDepth(self, root: TreeNode) -> int:
return 1 + max(self.maxDepth(root.left),
self.maxDepth(root.right)) if root else 0
I wrote this algorithm for a coding challenge on HackerRank to determine if a give binary tree is a BST. Yet in some cases when the tree is not a BST my algorithm returns True anyway. I couldn't find what was wrong, everything seems ok, right? Or is there something I don't know about BSTs?
Node is defined as:
class node:
def __init__(self, data):
self.data = data
self.left = None
self.right = None
My algorithm is
def checkBST(root):
if not root:
return True
else:
if root.left and root.left.data >= root.data:
return False
if root.right and root.right.data <= root.data:
return False
return checkBST(root.left) and checkBST(root.right)
A binary search tree has all the nodes on the left branch less than the parent node, and all the nodes on the right branch greater than the parent node.
So your code fails on a case like this:
5
/ \
4 7
/ \
2 6
It's not a valid BST because if you searched for 6, you'd follow the right branch from the root, and subsequently fail to find it.
Take a look at a picture below for which your code is giving an incorrect answer:
Where are you going wrong:
1. if an element exists on the left subtree if there exists a node with a value greater than root.
2. if an element exists on the right subtree if there exists a node with a value smaller than root.
You should try this approach :
if not root:
return True
else:
if root.left and maximumOfSubtree(root.left) >= root.data:
return False
if root.right and minimumOfSubtree(root.right) <= root.data:
return False
return checkBST(root.left) and checkBST(root.right)
so the problem is to determine whether the given tree is a BST or not.
the best way to find out is through an inorder traversal.
Do In-Order Traversal of the given tree and store the result in a temp array.
Check if the temp array is sorted in ascending order, if it is, then the tree is BST.
this can be the approach
def check_binary_search_tree_(root):
visited = []
def traverse(node):
if node.left: traverse(node.left)
visited.append(node.data)
if node.right: traverse(node.right)
traverse(root)
fc = {}
for i in visited:
if i in fc:
return False
else:
fc[i]=1
m = sorted(visited)
if visited==m:
return True
return False
refer to this for other methods https://www.geeksforgeeks.org/a-program-to-check-if-a-binary-tree-is-bst-or-not/
method 1 and method 2 are similar to your approach so it will help you to understand that too.