Get sum of left leaves of a binary tree using recursion - binary-tree

I have the following code but there seems to be an issue in this code:
private boolean isLeaf(TreeNode node) {
if (node == null)
return false;
if (node.left == null && node.right == null)
return true;
return false;
}
public int sumOfLeftLeaves(TreeNode root) {
if (root == null)
return 0;
if (isLeaf(root.left))
return root.left.val;
return sumOfLeftLeaves(root.left) + sumOfLeftLeaves(root.right);
}
For the input [3, 9, 20, null, null, 15, 7, 2, null, null, null, 3, 2, null, null, null, 3] I get 9 using the code above but the answer should be 12 i.e. 9 + 3.
What is missing from this code?
The input array represents a binary tree where if a parent is at position i then its left child is at 2 * i + 1 and right child is at 2 * i + 2.

First problem :
[3, 9, 20, null, null, 15, 7, 2, null, null, null, 3, 2, null, null, null, 3]
3 is the root, it has 9 and 20 as children.
9 doesn't have any children, 20 has 15 and 7.
Where does the 2 belong?
Here's the tree in Ruby :
Node (0) : 3
Node (2) : 20
Node (6) : 7
Node (14) : nil
Node (13) : nil
Node (5) : 15
Node (12) : 2
Node (11) : 3
Node (1) : 9
Node (4) : nil
Node (10) : nil
Node (9) : nil
Node (3) : nil
Node (8) : nil
Node (7) : 2
Node (16) : 3
Node (15) : nil
Second problem :
A node could have a leaf on the left but a branch on the right :
public int sumOfLeftLeaves(TreeNode root) {
if (root == null)
return 0;
if (isLeaf(root.left))
return root.left.val+sumOfLeftLeaves(root.right);
return sumOfLeftLeaves(root.left) + sumOfLeftLeaves(root.right);
}

Maintain a sum variable that actually adds the node's values.
if(!root)
return 0;
if(root->left && root->left->left == NULL && root->left->right == NULL) //checking for leaf
sum += root->left->val;
sum += (sumOfLeftLeaves(root->left) + sumOfLeftLeaves(root->right));
return sum;

Related

Having a hard time visualizing the call order of binary tree recursion

I am trying to visualize recursion for this particular problem solution below, and have placed 2 console.logs, but I still can’t understand it.
The problem:
Node depths
Here is my solution. It works though I don't fully understand why.
function helper(root, depth) {
console.log(depth, ‘step 1’)
if(root === null) {
console.log(depth, ‘step 2’)
return 0;
}
if(root.left === null && root.right === null) {
console.log(depth, ‘step 3’)
return depth
}
console.log(depth, ‘step 4’)
const leftSum = helper(root.left, depth + 1);
const rightSum = helper(root.right, depth + 1);
const totalSum = depth + leftSum + rightSum;
return totalSum;
}
function nodeDepths(root) {
return helper(root, 0);
}
In particular, for this input:
{
"tree": {
"nodes": [
{"id": "1", "left": "2", "right": null, "value": 1},
{"id": "2", "left": null, "right": null, "value": 2}
],
"root": "1"
}
}
For the case where depth is equal to 1 and node.value is 2, the algorithm order goes like below
0 step 1
0 step 4
1 step 1
1 step 3
1 step 1
1 step 2
My question is why at depth === 1, the code doesn’t go to step 4 after step 3 but goes back up to step 1 instead? And when a number is returned from the call stack, why is that number added to the sum of a branch (but not minus, multiply or divide)?
Thank you in advance! I’ve been stumped on this for the past 3 days.
I tried console.log out the callstack and expected it to go to step 4 after step 3 for when depth is equal to 1.
You recurse twice, in the code here:
const leftSum = helper(root.left, depth + 1);
const rightSum = helper(root.right, depth + 1);
The four lines that are prefixed with depth 1 combine the two calls, two lines from each.
The first call is on the left child of the root, which is a valid leaf node, so you see step 3 printed, since that's the base case for leaf nodes. The second recursion, on the right child, is running on a null value, and bails out in step 2, another base case.
It might help you better understand the recursion if you printed the root value, along with the depth.
As for why the depths are being added up, that I can't help you with. I just seems to be the task assigned in this exercise. I don't think there's any utility to that sum, unlike, for example, the maximum depth (which can be used to balance a tree, which helps make some algorithms more efficient).
To understand the execution flow, I would suggest to change what you actually log. Logging "step" may not be enough to get a clear view. I would suggest to log something when a recursive call is about to be made, and when execution comes back from that recursive call. And also indicate which of the two recursive calls it is (left or right). Finally, use depth to indent the output -- indented output gives a better clue as to what is happening.
(NB: that data structure your mentioned is not ready to be passed to your function, as it doesn't have node instances as values for left or right -- so I added function that converts it)
function helper(root, depth) {
let indent = " ".repeat(depth);
console.log(`${indent}helper(root.id==${root?.id}):`);
indent += " ";
if(root === null) {
console.log(`${indent}Root is null. return 0`);
return 0;
}
if(root.left === null && root.right === null) {
console.log(`${indent}This node is a leaf; return ${depth} (depth).`);
return depth
}
console.log(`${indent}Making recursive call to the left.`);
const leftSum = helper(root.left, depth + 1);
console.log(`${indent}Back from left recursive call. leftSum==${leftSum}.`);
console.log(`${indent}Making recursive call to the right`);
const rightSum = helper(root.right, depth + 1);
console.log(`${indent}Back from right recursive call. rightSum==${rightSum}.`);
const totalSum = depth + leftSum + rightSum;
console.log(`${indent}All done for this node. totalSum=${totalSum}. Return it.`);
return totalSum;
}
function nodeDepths(root) {
return helper(root, 0);
}
// Added this function to convert data structure to nested nodes
function createTree({tree: { nodes, root }}) {
const map = new Map(nodes.map(obj => [obj.id, { ...obj }]));
for (const obj of map.values()) {
obj.left = map.get(obj.left) ?? null;
obj.right = map.get(obj.right) ?? null;
}
return map.get(root);
}
const data = {
"tree": {
"nodes": [
{"id": "1", "left": "2", "right": null, "value": 1},
{"id": "2", "left": null, "right": null, "value": 2}
],
"root": "1"
}
};
const root = createTree(data);
nodeDepths(root);
I hope with this kind of logging the process becomes clearer.

How does the Root Method Work in Quick-Union? [duplicate]

I've been studying the quick union algorithm. the code below was the example for the implementation.
Can someone explain to me what happens inside the root method please?
public class quickUnion {
private int[] id;
public void QuickUnionUF(int N){
id = new int [N];
for(int i = 0; i < N; i++){
id[i] = i;
}
}
private int root(int i){
while (i != id[i]){
i = id[i];
}
return i;
}
public boolean connected(int p, int q){
return root(p) == root(q);
}
public void union(int p, int q){
int i = root(p);
int j = root(q);
id[i] = j;
}
}
The core principle of union find is that each element belongs to a disjoint set of elements. This means that, if you draw a forest (set of trees), the forest will contain all the elements, and no element will be in two different trees.
When building these trees, you can imagine that any node either has a parent or is the root. In this implementation of union find (and in most union find implementations), the parent of each element is stored in an array at that element's index. Thus the element equivalent to id[i] is the parent of i.
You might ask: what if i has no parent (aka is a root)? In this case, the convention is to set i to itself (i is its own parent). Thus, id[i] == i simply checks if we have reached the root of the tree.
Putting this all together, the root function traverses, from the start node, all the way up the tree (parent by parent) until it reaches the root. Then it returns the root.
As an aside:
In order for this algorithm to get to the root more quickly, general implementations will 'flatten' the tree: the fewer parents you need to get through to get to the root, the faster the root function will return. Thus, in many implementations, you will see an additional step where you set the parent of an element to its original grandparent (id[i] = id[id[i]]).
The main point of algorithm here is: always keep root of one vertex equals to itself.
Initialization: Init id[i] = i. Each vertex itself is a root.
Merge Root:
If we merge root 5 and root 6. Assume that we want to merge root 6 into root 5. So id[6] = 5. id[5] = 5. --> 5 is root.
If we continue to merge 4 to 6. id[4] = 4 -> base root. id[6] = 5. -> not base root. We continue to find: id[5] = 5 -> base root. so we assign id[4] = 6
In all cases, we always keep convention: if x is base root, id[x] == x That is the main point of algorithm.
From Pdf file provided in the course Union find
Root of i is id[id[id[...id[i]...]]].
according to the given example
public int root(int p){
while(p != id[p]){
p = id[p];
}
return p;
}
lets consider a situation :
The elements of id[] would look like
Now lets call
root(3)
The dry run of loop inside root method is:
To understand the role of the root method, one needs to understand how this data structure is helping to organise values into disjoint sets.
It does so by building trees. Whenever two independent values 𝑝 and 𝑞 are said to belong to the same set, 𝑝 is made a child of 𝑞 (which then is the parent of 𝑝). If however 𝑝 already has a parent, then we first move to that parent of 𝑝, and the parent of that parent, ...until we find an ancestor which has no parent. This is root(p), lets call it 𝑝'. We do the same with 𝑞 if it has a parent. Let's call that ancestor 𝑞'. Finally, 𝑝' is made a child 𝑞'. By doing that, we implicitly make the original 𝑝 and 𝑞 members of the same tree.
How can we know that 𝑝 and 𝑞 are members of the same tree? By looking up their roots. If they happen to have the same root, then they are necessarily in the same tree, i.e. they belong to the same set.
Example
Let's look at an example run:
QuickUnionUF array = new QuickUnionUF(10);
This will create the following array:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
This array represents edges. The from-side of an edge is the index in the array (0..9), and the to-side of the same edge is the value found at that index (also 0..9). As you can see the array is initialised in a way that all edges are self-references (loops). You could say that every value is the root of its own tree (which has no other values).
Calling root on any of the values 0..9, will return the same number, as for all i we have id[i] == i. So at this stage root does not give us much.
Now, let's indicate that two values actually belong to the same set:
array.union(2, 9);
This will result in the assignment id[2] = 9 and so we get this array:
[0, 1, 9, 3, 4, 5, 6, 7, 8, 9]
Graphically, this established link be represented as:
9
/
2
If now we call root(2) we will get 9 as return value. This tells us that 2 is in the same set (i.e. tree) as 9, and 9 happens to get the role of root of that tree (that was an arbitrary choice; it could also have been 2).
Let's also link 3 and 4 together. This is a very similar case as above:
array.union(3, 4);
This assigns id[3] = 4 and results in this array and tree representation:
[0, 1, 9, 4, 4, 5, 6, 7, 8, 9]
9 4
/ /
2 3
Now let's make it more interesting. Let's indicate that 4 and 9 belong to the same set:
array.union(4, 9);
Still root(4) and root(9) just return those same numbers (4 and 9). Nothing special yet... The assignment is id[4] = 9. This results in this array and graph:
[0, 1, 9, 4, 9, 5, 6, 7, 8, 9]
9
/ \
2 4
/
3
Note how this single assignment has joined two distinct trees into one tree. If now we want to check whether 2 and 3 are in the same tree, we call
if (connected(2, 3)) /* do something */
Although we never said 2 and 3 belonged to the same set explicitly, it should be implied from the previous actions. connected will now use calls to root to imply that fact. root(2) will return 9, and also root(3) will return 9. We get to see what root is doing... it is walking upwards in the graph towards the root node of the tree it is in. The array has all the information needed to make that walk. Given an index we can read in the array which is the parent (index) of that number. This may have to be repeated to get to the grandparent, ...etc: It can be a short or long walk, depending how many "edges" there are between the given node and the root of the tree it is in.
/**
* Quick Find Java Implementation Eager's Approach
*/
package com.weekone.union.quickfind;
import java.util.Random;
/**
* #author Ishwar Singh
*
*/
public class UnionQuickFind {
private int[] itemsArr;
public UnionQuickFind() {
System.out.println("Calling: " + UnionQuickFind.class);
}
public UnionQuickFind(int n) {
itemsArr = new int[n];
}
// p and q are indexes
public void unionOperation(int p, int q) {
// displayArray(itemsArr);
int tempValue = itemsArr[p];
if (!isConnected(p, q)) {
itemsArr[p] = itemsArr[q];
for (int i = 0; i < itemsArr.length; i++) {
if (itemsArr[i] == tempValue) {
itemsArr[i] = itemsArr[q];
}
}
displayArray(p, q);
} else {
displayArray(p, q, "Already Connected");
}
}
public boolean isConnected(int p, int q) {
return (itemsArr[p] == itemsArr[q]);
}
public void connected(int p, int q) {
if (isConnected(p, q)) {
displayArray(p, q, "Already Connected");
} else {
displayArray(p, q, "Not Connected");
}
}
private void displayArray(int p, int q) {
// TODO Auto-generated method stub
System.out.println();
System.out.print("{" + p + " " + q + "} -> ");
for (int i : itemsArr) {
System.out.print(i + ", ");
}
}
private void displayArray(int p, int q, String message) {
System.out.println();
System.out.print("{" + p + " " + q + "} -> " + message);
}
public void initializeArray() {
Random random = new Random();
for (int i = 0; i < itemsArr.length; i++) {
itemsArr[i] = random.nextInt(9);
}
}
public void initializeArray(int[] receivedArr) {
itemsArr = receivedArr;
}
public void displayArray() {
System.out.println("INDEXES");
System.out.print("{p q} -> ");
for (int i : itemsArr) {
System.out.print(i + ", ");
}
System.out.println();
}
}
Main Class:-
/**
*
*/
package com.weekone.union.quickfind;
/**
* #author Ishwar Singh
*
*/
public class UQFClient {
/**
* #param args
*/
public static void main(String[] args) {
int[] arr = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
int n = 10;
UnionQuickFind unionQuickFind = new UnionQuickFind(n);
// unionQuickFind.initializeArray();
unionQuickFind.initializeArray(arr);
unionQuickFind.displayArray();
unionQuickFind.unionOperation(4, 3);
unionQuickFind.unionOperation(3, 8);
unionQuickFind.unionOperation(6, 5);
unionQuickFind.unionOperation(9, 4);
unionQuickFind.unionOperation(2, 1);
unionQuickFind.unionOperation(8, 9);
unionQuickFind.connected(5, 0);
unionQuickFind.unionOperation(5, 0);
unionQuickFind.connected(5, 0);
unionQuickFind.unionOperation(7, 2);
unionQuickFind.unionOperation(6, 1);
}
}
Output:
INDEXES
{p q} -> 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
{4 3} -> 0, 1, 2, 3, 3, 5, 6, 7, 8, 9,
{3 8} -> 0, 1, 2, 8, 8, 5, 6, 7, 8, 9,
{6 5} -> 0, 1, 2, 8, 8, 5, 5, 7, 8, 9,
{9 4} -> 0, 1, 2, 8, 8, 5, 5, 7, 8, 8,
{2 1} -> 0, 1, 1, 8, 8, 5, 5, 7, 8, 8,
{8 9} -> Already Connected
{5 0} -> Not Connected
{5 0} -> 0, 1, 1, 8, 8, 0, 0, 7, 8, 8,
{5 0} -> Already Connected
{7 2} -> 0, 1, 1, 8, 8, 0, 0, 1, 8, 8,
{6 1} -> 1, 1, 1, 8, 8, 1, 1, 1, 8, 8,

Scala - Shortest Path Between Two Nodes Recursive Algorithm

I am implementing Dijkstra's shortest path algorithm recursivingly in Scala, but I am having some trouble. I am getting the incorrect output for nodes 3 to 2, called like this, shortestPath(3, 2, x, BitSet.empty). This outputs 6, but the correct answer should be 7. I cannot seem to figure out what's wrong with my code.
var x = ListBuffer(ListBuffer(0, 2, 3, 4),
ListBuffer(2, 0, 0, 0),
ListBuffer(3, 0, 0, 0),
ListBuffer(4, 0, 0, 0))
My code is here shown below.
def shortestPath(cur: Int, dest: Int, graph: ListBuffer[ListBuffer[Int]], visited: BitSet) :Int = {
val newVisited = visited + cur
if(cur == dest) 0
else {
var pathLength = for(i <- graph(cur).indices; if(!visited(i) && graph(cur)(i) > 0)) yield {
graph(cur)(i) + shortestPath(i, dest, graph, newVisited)
}
if (pathLength.isEmpty) 0 else pathLength.min
}
}
As pointed out by obourgain, the critical error of the code is at interpreting the min-distance as 0 when two nodes are not connected.
The min-distance between two nodes should be infinity if they are disconnected, this is because the cost of two disconnected nodes must be greater than the cost of any connected nodes, and one simple fix to your code is to identify infinity with Int.MaxValue.
def shortestPath(cur: Int, dest: Int, graph: ListBuffer[ListBuffer[Int]], visited: BitSet) :Int = {
val newVisited = visited + cur
if(cur == dest) 0
else {
var pathLength = for(i <- graph(cur).indices; if(!visited(i) && graph(cur)(i) > 0)) yield {
val sLen = shortestPath(i, dest, graph, newVisited)
if (graph(cur)(i) > Int.MaxValue - sLen) Int.MaxValue else graph(cur)(i) + sLen // change #1
}
if (pathLength.isEmpty) Int.MaxValue else pathLength.min // change #2
}
}
This modification will give the expected answer Int = 7 when invoking shortestPath(3, 2, x, new BitSet()).
The code commented with "change #1" is to prevent integer overflow when the destination node is not reachable by the neighbor node (thus the min-distance is Int.MaxValue), and the code commented with "change #2" is to treat the min-distance between two nodes as "infinite" when they are disconnected.
The error is on the last line:
if (pathLength.isEmpty) 0 else pathLength.min
If pathLength.isEmpty, it means the two points are not connected. However, the function returns 0, which is interpreted as a connection with weight 0.

remove elements from link list whose sum equals to zero

Given a list in form of linked list, I have to canceled out all the resources whose sum up to 0(Zero) and return the remaining list.
Like
6 -6 3 2 -5 4 returns 4
8 10 4 -1 -3 return 8 10
I only need algorithm to solve this question.
this is actually the classic subset sum problem which is NP-complete
see on wiki or google it to see articles about that
The following function just prints the nodes except the ones which are canceled out, you can make it push to a new list and return.
void printExCancel( node* head )
{
node* start = head;
node* end;
while ( start )
{
bool mod = false;
int sum = 0;
end = start;
while ( end )
{
sum += end->data;
if ( sum == 0 )
{
start = end;
mod = true;
break;
}
end = end->next;
}
if ( mod == false ) {
//push to new list
printf( "%d\n", start->data );
}
//else {
// call funtion to delete from start to end
//}
start = start->next;
}
}
Assumption: Only consecutive elements when summed to zero can be removed.
Approach Followed:
1. Push the non-zero elements of the link list to a stack.
2. On occurrence of a non zero element:
(a) Iterate stack, pop each element and keep adding to the non-zero element.
(b) Keep adding the pop element to a list.
(c) If the value is zero (that means then by now you have removed ) break stack iteration.
(d) If stack is Empty & the sum != 0 add the list elements to stack along with the non-zero one
Try Following Code:
public class ElementSumNonZero {
private static Node head;
private static class Node {
int data;
Node next;
Node(int d) {
data = d;
next = null;
}
}
private void removeNonZeroElements(Node root) {
Node start = root;
Stack<Node> stack = new Stack<>();
boolean flag = false;
List<Node> list = new ArrayList<>();
while (start != null) {
if (start.data > 0)
stack.push(start);
else {
int sum = start.data;
flag = false;
while (!stack.isEmpty()) {
Node temp = stack.pop();
sum += temp.data;
if (sum == 0) {
flag = true;
list.clear();
break;
}
list.add(temp);
}
if (!flag) {
list.forEach(i -> stack.add(i));
stack.add(start);
}
}
start = start.next;
}
stack.forEach(i -> System.out.print(i.data +" -> "));
System.out.println("NULL");
}
// Driver program to test above functions
public static void main(String[] args) {
ElementSumNonZero list = new ElementSumNonZero();
ElementSumNonZero.head = new Node(6);
ElementSumNonZero.head.next = new Node(-6);
ElementSumNonZero.head.next.next = new Node(8);
ElementSumNonZero.head.next.next.next = new Node(4);
ElementSumNonZero.head.next.next.next.next = new Node(-12);
ElementSumNonZero.head.next.next.next.next.next = new Node(9);
ElementSumNonZero.head.next.next.next.next.next.next = new Node(8);
ElementSumNonZero.head.next.next.next.next.next.next.next = new Node(-8);
list.removeNonZeroElements(head);
}
}
Test 0
original: {6, -6,6, 8, 4, -12, 9, 8, -8}
canceled out: {9}
Test 1
original: {4, 6, -10, 8, 9, 10, -19, 10, -18, 20, 25}
canceled out: {20, 25}
We can create the resultant stack into a link list and return from "removeNonZeroElements" method.
Please correct me and suggest ways we can make this code efficient.
Following python code also passes both the testcases:
class Node():
def __init__(self,data):
self.data = data
self.next = None
class Linkedlist():
def __init__(self):
self.head = None
def append(self,data):
new_node = Node(data)
h = self.head
if self.head is None:
self.head = new_node
return
else:
while h.next!=None:
h = h.next
h.next = new_node
def remove_zeros_from_linkedlist(self, head):
stack = []
curr = head
list = []
while (curr):
if curr.data >= 0:
stack.append(curr)
else:
temp = curr
sum = temp.data
flag = False
while (len(stack) != 0):
temp2 = stack.pop()
sum += temp2.data
if sum == 0:
flag = True
list = []
break
elif sum > 0:
list.append(temp2)
if not flag:
if len(list) > 0:
for i in range(len(list)):
stack.append(list.pop())
stack.append(temp)
curr = curr.next
return [i.data for i in stack]
if __name__ == "__main__":
l = Linkedlist()
l.append(4)
l.append(6)
l.append(-10)
l.append(8)
l.append(9)
l.append(10)
l.append(-19)
l.append(10)
l.append(-18)
l.append(20)
l.append(25)
print(l.remove_zeros_from_linkedlist(l.head))
'''Delete the elements in an linked list whose sum is equal to zero
E.g-->> 6 -6 8 4 -12 9 8 -8
the above example lists which gets canceled :
6 -6
8 4 -12
8 -8
o/p : 9
case 3 : 4 6 -10 8 9 10 -19 10 -18 20 25
O/P : 20 25'''
#v_list=[6 ,-6, 8, 4, -12, 9, 8, -8]
#Building Nodes
class Node():
def __init__(self,value):
self.value=value
self.nextnode=None
#Class Linked List for Pointing Head and Tail
class LinkedList():
def __init__(self):
self.head=None
def add_element(self,value):
node=Node(value)
if self.head is None:
self.head=node
return
crnt_node=self.head
while True:
if crnt_node.nextnode is None:
crnt_node.nextnode=node
break
crnt_node=crnt_node.nextnode
def print_llist(self):
crnt_node=self.head
v_llist=[]
while True:
print(crnt_node.value,end='->')
v_llist.append(crnt_node.value) # storing data into list
if crnt_node.nextnode is None:
break
crnt_node=crnt_node.nextnode
print('None')
return v_llist
def print_modified_llist(self):
p_add=0
v_llist=self.print_llist()
#going till the second last element of list and then trying to print requested o/p
for i in range(len(v_llist)-1):
p_add=p_add+v_llist[i]
if v_llist[-1]>0 and p_add>0:
print(p_add,v_llist[-1])
elif v_llist[-1]<0 and p_add>0:
print(p_add+v_list[-1])
elif v_llist[-1]<0 and p_add<0:
print(v_llist[-1],p_add)
sll=LinkedList()
sll.add_element(4)
sll.print_llist()
sll.add_element(6)
sll.print_llist()
sll.add_element(-10)
sll.print_llist()
sll.add_element(8)
sll.print_llist()
sll.add_element(9)
sll.print_llist()
sll.add_element(10)
sll.print_llist()
sll.add_element(-19)
sll.print_llist()
sll.add_element(10)
sll.print_llist()
sll.add_element(-18)
sll.print_llist()
sll.add_element(20)
sll.print_llist()
sll.add_element(25)
sll.print_llist()
sll.print_modified_llist()
Remove elements with consecutive sum = K.
In your case K = 0
Append Node with value zero at the starting of the linked list.
Traverse the given linked list.
During traversal store the sum of the node value till that node with the
reference of the current node in an unordered_map.
If there is Node with value (sum – K) present in the unordered_map then delete
all the nodes from the node corresponding to value (sum – K) stored in map to the
current node and update the sum as (sum – K).
If there is no Node with value (sum – K) present in the unordered_map, then
stored the current sum with node in the map.

How to use binary search to find the first non-null element in an array?

Assume I have an array of consecutive data and consecutive nulls, e.g.:
0, 3, 1, 2, null, null, null
How to use binary search idea to find index of the first null element?
Same as regular binary search only treat NULL like the value infinity (max int) and everything else like the value 0. That way the array looks like
0, 0, 0, 0, MAX_INT, MAX_INT, MAX_INT
At which point run normal binary search for the first MAX_INT on it
Do a binary search as follows:
Start in the middle (as one does with binary search)
When you find a not-null, binary search to the right
When you find a null, check the element to the left
If it's null, binary search to the left
If it's not-null (or there is no element to the left, since we're already at the left-most element) we found the index of the first null
Some simple pseudo-code:
int search(start, end):
// terminating check - should always be the first null
if start == end
// sanity check - make sure it's correct
assert(input[start] == null && (start == 0 || input[start-1] != null))
return start
mid = (start+end)/2
if input[mid] != null
return search(mid, end) // search right
else if mid == 0 || input[mid-1] != null // check element to left
return mid // found
else
return search(start, mid) // search left
Simple brute force with O(n) is likely ideal here since it is equally likely to miss first occurrence of Null when divide and conquer the collection.
This is an example solution written in Go, with O(log(n)) time complexity:
package main
import "fmt"
func findIn(array []int) {
fmt.Printf("%v ", array)
if len(array) == 0 {
fmt.Println("Not Found")
return
}
for low, high, mid := 0, len(array), len(array)/2; ; {
// terminate search when high meets mid
if high-mid == 1 {
if array[mid] == -1 {
fmt.Println(mid)
return
}
if high == len(array) {
fmt.Println("Not Found")
return
}
fmt.Println(high)
return
}
// search lower half if middle is -1, otherwise search upper half
if array[mid] == -1 {
high = mid
mid = low + (mid-low)/2
} else {
low = mid
mid = mid + (high-mid)/2
}
}
fmt.Println()
}
func main() {
findIn([]int{})
findIn([]int{8})
findIn([]int{-1})
findIn([]int{8, -1, -1, -1})
findIn([]int{8, 9, 7, -1, -1, -1})
findIn([]int{8, 9, 7, 6, 5, 4, -1, -1})
}
Output:
[] Not Found
[8] Not Found
[-1] 0
[8 -1 -1 -1] 1
[8 9 7 -1 -1 -1] 3
[8 9 7 6 5 4 -1 -1] 6
Please feel free to suggest a better/shorter solution.

Resources