Longest increasing subsequence with binary search - python

I'm trying to implement some algorithm in python and I need help.
Given some array of integers,
I want to build BTS and find the Longest increasing subsequence.
The idea is to give index to each node(by order of inserting)
Next we want to take all the indices from the left tree and put them in stack
Next we want to check for each index in the above stack if we have in the tree index that is bigger than the current node, if yes we insert it to stack and update the value max which is the number of elements in our stack.
I need help at the point of scanning the tree and insert the elements into a stack.
here is my code so far:
class Node:
def __init__(self, key, index = -1):
self.right = None
self.left = None
self.key = key
self.index = index
def __str__(self):
return "key: %s, index: %s" % (str(self.key), str(self.index))
def insert(root, key, value=-1):
if root is None:
root = Node(key, value)
else:
if key < root.key:
root.left = insert(root.left, key, value)
elif key > root.key:
root.right = insert(root.right, key, value)
else:
pass
return root
def LeftSideIndices(root):
res = []
if root:
res = LeftSideIndices(root.left)
res.append(root.index)
return res
def InOrderWithInsert(root,A):
newStack = []
if root:
for i in range(0, len(A)):
newStack = upInOrder(root.left,A)
if root.index > A[i]:
newStack.append(root.key)
newStack = newStack + upInOrder(root.right, A)
return newStack
Example:
The right stack should be: s=[0,2,8,11]

Some general remarks :
providing a MWE is appreciated.
your code does not define upInOrder so we can't run part of it
nitpick: in the insert function your value parameter is passed to the index parameter of your Node constructor, the naming is confusing.
reformulating your question to make it explicit : "given a Binary Search Tree, find the Longest Increasing Subsequence"
there is a bug in your LeftSideIndices : it returns only indices of left (grand)children instead of the indices of the whole left-half of the tree, example :
bst = insert(insert(insert(None, 2, 0), 0, 1), 1, 2)
# val=2,idx=0
# /
# val=0,idx=1
# \
# val=1,idx=2
print(LeftSideIndices(bst)) # [1, 0]
for reference :
print("value | " + " | ".join(str(value).ljust(2) for value in A) + " |")
print("index | " + " | ".join(str(index).ljust(2) for index in range(len(A))) + " |")
# | value | 4 | 1 | 13 | 7 | 0 | 2 | 8 | 11 | 3 |
# | index | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
as said in the comments, another solution would be to not use a BST at all, and just search the LIS in A (which uses a much more common algorithm). I would even say that using a BST makes it pointlessly hard because the result has nothing to do with the BST structure, it is strictly defined by the insertion order, irregarding the datastructure considered.
There may be multiple different subsequences having the same length (being the longest possible), example :
bst = insert(insert(insert(insert(None, 1, 0), 22, 1), 14, 2), 15, 3)
# val=1,idx=0
# \
# val=22,idx=1
# /
# val=14,idx=2
# \
# val=15,idx=3
# Expected LIS = { (1, 22), (14, 15) }
I read several times your algorithm explanation (slightly reformatted) :
take all the indices from the left tree and put them in stack
check for each index in the stack if we have in the tree index that is bigger than the current node
if yes, insert it to stack and update the value max which is the number of elements in the stack.
I am not sure to understand your algorithm, and I don't think it works.
I am even not sure there is a simple solution to this problem.
The way your indices are scattered accross your tree prevent a node to know which solution(s) from its left and/or right sub-trees are interesting because there is too much holes in the index sequence in a sub-tree, the information at the node level is too much gappy.
Usually for trees algorithms, it is simple to apply some divide and conquer algorithm, but in this case I can't even tell how recursively the root node could tell which is the longest subsequence, given the results of its left and righ subtrees.
But in your case it looks to me extra hard to find how to implement what you want. And I can't convince myself there is actually an algorithm. So I hope you will be able to prove me wrong.

Related

Count Number of Good Nodes

problem statement
I am having trouble understanding what is wrong with my code and understanding the constraint below.
My pseudocode:
Traverse the tree Level Order and construct the array representation (input is actually given as a single root, but they use array representation to show the full tree)
iterate over this array representation, skipping null nodes
for each node, let's call it X, iterate upwards until we reach the root checking to see if at any point in the path, parentNode > nodeX, meaning, nodeX is not a good node.
increment counter if the node is good
Constraints:
The number of nodes in the binary tree is in the range [1, 10^5].
Each node's value is between [-10^4, 10^4]
First of all:
My confusion on the constraint is that, the automated tests are giving input such as [2,4,4,4,null,1,3,null,null,5,null,null,null,null,5,4,4] and if we follow the rules that childs are at c1 = 2k+1 and c2 = 2k+2 and parent = (k-1)//2 then this means that there are nodes with value null
Secondly:
For the input above, my code outputs 8, the expected value is 6, but when I draw the tree from the array, I also think the answer should be 8!
tree of input
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def goodNodes(self, root: TreeNode) -> int:
arrRepresentation = []
queue = []
queue.append(root)
# while queue not empty
while queue:
# remove node
node = queue.pop(0)
if node is None:
arrRepresentation.append(None)
else:
arrRepresentation.append(node.val)
if node is not None:
# add left to queue
queue.append(node.left)
# add right to queue
queue.append(node.right)
print(arrRepresentation)
goodNodeCounter = 1
# iterate over array representation of binary tree
for k in range(len(arrRepresentation)-1, 0, -1):
child = arrRepresentation[k]
if child is None:
continue
isGoodNode = self._isGoodNode(k, arrRepresentation)
print('is good: ' + str(isGoodNode))
if isGoodNode:
goodNodeCounter += 1
return goodNodeCounter
def _isGoodNode(self, k, arrRepresentation):
child = arrRepresentation[k]
print('child: '+str(child))
# calculate index of parent
parentIndex = (k-1)//2
isGood = True
# if we have not reached root node
while parentIndex >= 0:
parent = arrRepresentation[parentIndex]
print('parent: '+ str(parent))
# calculate index of parent
parentIndex = (parentIndex-1)//2
if parent is None:
continue
if parent > child:
isGood = False
break
return isGood
Recursion might be easier:
class Node:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
def good_nodes(root, maximum=float('-inf')):
if not root: # null-root
return 0
is_this_good = maximum <= root.val # is this root a good node?
maximum = max(maximum, root.val) # update max
good_from_left = good_nodes(root.left, maximum) if root.left else 0
good_from_right = good_nodes(root.right, maximum) if root.right else 0
return is_this_good + good_from_left + good_from_right
tree = Node(2, Node(4, Node(4)), Node(4, Node(1, Node(5, None, Node(5, Node(4), Node(4)))), Node(3)))
print(good_nodes(tree)) # 6
Basically, recursion traverses the tree while updating the maximum number seen so far. At each iteration, the value of a root is compared with the maximum, incrementing the counter if necessary.
Since you wanted to solve with breadth first search:
from collections import deque
class Solution:
def goodNodes(self,root:TreeNode)->int:
if not root:
return 0
queue=deque()
# run bfs with track of max_val till its parent node
queue.append((root,-inf))
res=0
while queue:
current,max_val=queue.popleft()
if current.val>=max_val:
res+=1
if current.left:
queue.append((current.left,max(max_val,current.val)))
if current.right:
queue.append((current.right,max(max_val,current.val)))
return res
I added the node and its max_value till its parent node. I could not add a global max_value, because look at this tree:
For the first 3 nodes, you would have this [3,1,4] and if you were keeping the max_val globally, max_val would be 4.
Now next node would be 3, leaf node on the left. Since max_node is 4, 3<4 would be incorrect so 3 would not be considered as good node. So instead, I keep track of max_val of each node till its parent node
The binary heap you provided corresponds to the folloring hierarchy:
tree = [2,4,4,4,None,1,3,None,None,5,None,None,None,None,5,4,4]
printHeapTree(tree)
2
/ \
4 4
/ / \
4 1 3
\
5
In that tree, only item value 1 has an ancestor that is greater than itself. The 6 other nodes are good, because they have no ancestor that are greater than themselves (counting the root as good).
Note that there are values in the list that are unreachable because their parent is null (None) so they are not part of the tree (this could be a copy/paste mistake though). If we replace these None values by something else to make them part of the tree, we can see where the unreachable nodes are located in the hierarchy:
t = [2,4,4,4,'*', 1,3,'*',None, 5,None, None,None,None,5,4,4]
printHeapTree(t)
2
__/ \_
4 4
/ \ / \
4 * 1 3
/ / \
* 5 5
/ \
4 4
This is likely where the difference between a result of 8 (not counting root as good) vs 6 (counting root as good) comes from.
You can find the printHeapTree() function here.

Bad Tree design, Data Structure

I tried making a Tree as a part of my Data Structures course. The code works but is extremely slow, almost double the time that is accepted for the course. I do not have experience with Data Structures and Algorithms but I need to optimize the program. If anyone has any tips, advices, criticism I would greatly appreciate it.
The tree is not necessarily a binary tree.
Here is the code:
import sys
import threading
class Node:
def __init__(self,value):
self.value = value
self.children = []
self.parent = None
def add_child(self,child):
child.parent = self
self.children.append(child)
def compute_height(n, parents):
found = False
indices = []
for i in range(n):
indices.append(i)
for i in range(len(parents)):
currentItem = parents[i]
if currentItem == -1:
root = Node(parents[i])
startingIndex = i
found = True
break
if found == False:
root = Node(parents[0])
startingIndex = 0
return recursion(startingIndex,root,indices,parents)
def recursion(index,toWhomAdd,indexes,values):
children = []
for i in range(len(values)):
if index == values[i]:
children.append(indexes[i])
newNode = Node(indexes[i])
toWhomAdd.add_child(newNode)
recursion(i, newNode, indexes, values)
return toWhomAdd
def checkHeight(node):
if node == '' or node == None or node == []:
return 0
counter = []
for i in node.children:
counter.append(checkHeight(i))
if node.children != []:
mostChildren = max(counter)
else:
mostChildren = 0
return(1 + mostChildren)
def main():
n = int(int(input()))
parents = list(map(int, input().split()))
root = compute_height(n, parents)
print(checkHeight(root))
sys.setrecursionlimit(10**7) # max depth of recursion
threading.stack_size(2**27) # new thread will get stack of such size
threading.Thread(target=main).start()
Edit:
For this input(first number being number of nodes and other numbers the node's values)
5
4 -1 4 1 1
We expect this output(height of the tree)
3
Another example:
Input:
5
-1 0 4 0 3
Output:
4
It looks like the value that is given for a node, is a reference by index of another node (its parent). This is nowhere stated in the question, but if that assumption is right, you don't really need to create the tree with Node instances. Just read the input into a list (which you already do), and you actually have the tree encoded in it.
So for example, the list [4, -1, 4, 1, 1] represents this tree, where the labels are the indices in this list:
1
/ \
4 3
/ \
0 2
The height of this tree — according to the definition given in Wikipedia — would be 2. But apparently the expected result is 3, which is the number of nodes (not edges) on the longest path from the root to a leaf, or — otherwise put — the number of levels in the tree.
The idea to use recursion is correct, but you can do it bottom up (starting at any node), getting the result of the parent recursively, and adding one to 1. Use the principle of dynamic programming by storing the result for each node in a separate list, which I called levels:
def get_num_levels(parents):
levels = [0] * len(parents)
def recur(node):
if levels[node] == 0: # this node's level hasn't been determined yet
parent = parents[node]
levels[node] = 1 if parent == -1 else recur(parent) + 1
return levels[node]
for node in range(len(parents)):
recur(node)
return max(levels)
And the main code could be as you had it:
def main():
n = int(int(input()))
parents = list(map(int, input().split()))
print(get_num_levels(parents))

Maximum Path Sum between 2 Leaf Nodes(GeeksForGeeks)

Given a binary tree in which each node element contains a number. Find the maximum possible sum from one leaf node to another.
Example 1:
Input :
3
/ \
4 5
/ \
-10 4
Output: 16
Explanation :
Maximum Sum lies between leaf node 4 and 5.
4 + 4 + 3 + 5 = 16.
Example 2:
Input :
-15
/ \
5 6
/ \ / \
-8 1 3 9
/ \ \
2 -3 0
/ \
4 -1
/
10
Output : 27
Explanation:
The maximum possible sum from one leaf node
to another is (3 + 6 + 9 + 0 + -1 + 10 = 27)
This is the solution:
'''
# Node Class:
class Node:
def _init_(self,val):
self.data = val
self.left = None
self.right = None
'''
res = -999999999
def maxPathSumUtil(root):
global res
if root is None:
return 0
if root.left is None and root.right is None:
return root.data
ls=maxPathSumUtil(root.left)
rs=maxPathSumUtil(root.right)
if root.left and root.right:
res=max(res,ls+rs+root.data)
return max(ls+root.data,rs+root.data) #Line: Problem
if root.left is None:
return rs+root.data
else:
return ls+root.data
def maxPathSum(root):
global res
res = -999999999
maxPathSumUtil(root)
return res
Can anyone tell me why do we use return max(ls+root.data,rs+root.data). And if we do use return max(ls+root.data,rs+root.data) for checking the maximum value then why do we use res=max(res,ls+rs+root.data) and not just res = max(ls+root.data,rs+root.data).
EDIT:
For example:
Let's take this tree for example:
10
/ \
8 2
/ \
3 5
In this, after recursive calls, ls becomes 3 and rs becomes 5.
res becomes ls+rs+root.data which is 3+5+8 = 16.
Then return max(ls+root.data,rs+root.data) which is max(11,13) = 13.
Now after this according to me the function should just return 13 but that does not happen. Even though return is not a recursive statement. How is the control flow of the code happening?
There are two things that are measured in parallel during execution:
ls+rs+root.data is the max path in the tree rooted by root, between two of the leaves below it. So it is (the value) of a leaf-to-leaf path
The function return value is the maximum path from root to any of the leaves below it. So it is (the value) of a root-to-leaf path
These are two different concepts and should not be mixed up.
Both ls and rs are function return values: ls represents the maximum path from root.left to a leaf. And in the same way rs represents the maximum path from root.right to a leaf.
ls+rs+root.data on the other hand, represents a path from leaf to leaf passing through root.
res should be updated if that latter expression is greater than res, hence the max().
But the function's return value should not represent a leaf-to-leaf path, but a root-to-leaf path. So that is why we have:
return max(ls+root.data,rs+root.data)
This tells the caller what the maximum root-to-leaf path is, not what the maximum leaf-to-leaf path is. The latter is used for determining res, not the function's return value.
I hope this clarifies the distinction between these two concepts and the roles they play in the algorithm.
The example
You presented this tree as example:
10
/ \
8 2
/ \
3 5
Indeed, when the function is called for the node 8, it:
sets res to 16 (the max path between two leaves below the node)
returns 13 (the max path from the node to one of its leaves)
You then ask:
Now after this according to me the function should just return 13 but that does not happen.
But it does happen like that. You should however not forget that this is the return value of maxPathSumUtil, not of maxPathSum. Also, this is not the top-level call of maxPathSumUtil. The value 13 is returned to another execution context of maxPathSumUtil, where root is the node 10. Then -- after another recursive call is made (with root equal to node 2), this top-level execution of the function maxPathSumUtil will:
set res to 25 (the max path between two leaves below the node 10)
return 23 (the max path from the node 10 to one of its leaves)
This toplevel call was made from within maxPathSum, which ignores the value returned by maxPathSumUntil.
It only takes the value of res (25), and returns that:
maxPathSumUtil(root) # notice that return value is ignored.
return res
At each node, we have to check whether that node's left and right child resulted in max path. But when we return, we need to return either the left or right path depending upon whichever is max.
Let's take this tree for example:
10
/ \
8 2
/ \
3 5
In this, after recursive calls, ls becomes 3 and rs becomes 5. res becomes ls+rs+root.data which is 3+5+8 = 16. So res(result) will be updated to 16, and return will be max(11,13) which is 13. Now this 13 value will be used by node 10 as ls(left value).

Find all unique nodes of a n-ary tree in all paths that reach a certain depth without recursion

I'm trying to write an algorithm in Python to get a unique list of all nodes in a tree where the path reaches a certain depth.
Each child has an unknown number of children prior to traversal
The children can be accessed via an iterable (e.g. for child in B.get_children())
For example, see this tree (asterisks mark node that should be included):
A*
|
-----
| |
B* C*
| |
| ---
| | |
D* E F*
/ | \ | \
G* H* I* J* K*
|
L
Let's say I'm trying to reach a depth of 3. I need a function that would yield the sequence [G, H, I, J, K, D, F, B, C, A] in any order.
Note the omission of:
E (doesn't reach depth of 3)
L (exceeds depth of 3)
I feel there is a way to get this list recursively. Something along the lines of:
def iterate_tree(path: List[T], all_nodes: Set[T]):
if len(path) == 4:
for node in path:
if node not in all_nodes:
all_nodes.add(node)
yield node
else:
for node in path[-1].get_children():
path = path.copy()
path.append(node)
yield from iterate_tree(path, all_nodes)
iterate_tree([A] ,set())
I don't know if the above works, but I think I can hack it from that. What I don't like about the (probably incorrect) solution is:
The recursion: I'm writing this for an unknown depth. I don't want a stack-overflow.
I really feel like there must be a way to do this without carrying around a set of previously yielded nodes.
I have to make a copy of path at each iteration so I don't mess up other branches of the recursion.
Any suggestions?
For point 1, you can use an explicit stack and loop instead of recursion.
For point 2, I'm not sure I see a problem with keeping a set of yielded nodes. Memory is cheap and if you need to detect duplicates, re-traversing the tree every time you yield is extremely expensive.
Furthermore, your implementation checks for uniqueness based on node hashability, but it's unclear how nodes compute their hash. I assume you're using Node.val for that. If you're hashing based on object reference, "uniqueness" seems pointless since you're guaranteed that a tree of Node objects is unique by identity. The example here doesn't show what a clash on uniqueness would entail. My implementation assumes the hash is object identity (as it should be) and that you can access the value for uniqueness separately using Node.val.
For point 3, if you're working recursively there's no need to copy the path list since you revisit the call frame and can append/pop on a single list. Iteratively, you can keep a parent_of dict alongside the nodes_yielded set that keeps a reference to the parent of each node. When we reach a node at the desired depth, we can walk the links in this dictionary to reconstruct the path, avoiding revisiting a branch more than once thanks to nodes_yielded. A second set, vals_yielded can be used to enforce uniqueness on the yields.
Lastly, I don't really know what your data structures are so in the interest of a minimal, complete example, I've provided something that should be adaptable for you.
import collections
def unique_nodes_on_paths_to_depth(root, depth):
parent_of = {root: None}
nodes_yielded = set()
vals_yielded = set()
stack = [(root, depth)]
while stack:
node, depth = stack.pop()
if depth == 0:
while node and node not in nodes_yielded:
if node.val not in vals_yielded:
vals_yielded.add(node.val)
yield node
nodes_yielded.add(node)
node = parent_of[node]
elif depth > 0:
for child in node.children:
parent_of[child] = node
stack.append((child, depth - 1))
if __name__ == "__main__":
"""
A*
|
-----
| |
B* C*
| |
| ---
| | |
D* E F*
/ | \ | \
G* H* I* J* K*
|
L
"""
Node = collections.namedtuple("Node", "val children")
root = Node("A", (
Node("B", (
Node("D", (
Node("G", ()),
Node("H", ()),
Node("I", ()),
))
,)),
Node("C", (
Node("E", ()),
Node("F", (
Node("J", ()),
Node("K", (
Node("L", ())
,)),
)),
))
))
print([x.val for x in unique_nodes_on_paths_to_depth(root, 3)])
# => ['K', 'F', 'C', 'A', 'J', 'I', 'D', 'B', 'H', 'G']

Maximum depth of a binary tree in python

I created a tuple from a binary tree and it looks like this:
tuple = (1,(2,(4,5,6),(7,None,8)),(3,9,(10,11,12)))
The tree structure becomes more clear by applying indentation:
(1,
(2,
(4,
5,
6
),
(7,
None,
8
)
),
(3,
9,
(10,
11,
12
)
)
)
I know how to find the maximum depth of the binary tree using recursive method, but I am trying to find the maximum depth using the tuple I created. Can anyone help me with how to do it?
Recursive method:
a = (1,(2,(4,5,6),(7,None,8)),(3,9,(10,11,12)));
def depth(x):
if(isinstance(x, int) or x == None):
return 1;
else:
dL = depth(x[1]);
dR = depth(x[2]);
return max(dL, dR) + 1;
print(depth(a));
The idea is to determine the depth of a tree by looking at its left and right subtree. If the node does not have subtrees, a depth of 1 is returned. Else it returns max(depth of right, depth of left) + 1
Here is a tricky but rather efficient solution, that will work, provided no elements of your data structure is a string containing '(' or ')'.
I would convert the tuple to a string, and parse it so as to count the depth of the parentheses.
string = str(myTuple)
currentDepth = 0
maxDepth = 0
for c in string:
if c == '(':
currentDepth += 1
elif c == ')':
currentDepth -= 1
maxDepth = max(maxDepth, currentDepth)
It gives the depth in a linear time with regards to the number of characters in the string into which the tuple was converted.
That number should be more or less proportional to the number of elements plus the depth, so you'd have a complexity somewhat equal to O(n + d).
I solve this with level order traversal. If you know level order traversal, this question and https://leetcode.com/problems/binary-tree-right-side-view/ question can be solved with same technique:
from collections import deque
class Solution:
def max_depth(self,root):
if not root:
return 0
level=0
q=deque([root])
while q:
# once we exhaust the for loop, that means we traverse all the nodes in the same level
# so after for loop increase level+=1
for i in range(len(q)):
node=q.popleft()
if node.left:
q.append(node.left)
if node.right:
q.append(node.right)
level+=1
return level
class Node(object):
def __init__(self, x):
self.val = x
self.left = None
self.right = None
class Solution(object):
def maxDepth(self, root):
if not root:
return 0
ldepth = self.maxDepth(root.left)
rdepth = self.maxDepth(root.right)
return max(ldepth, rdepth) + 1

Categories

Resources