Deleting the maximum element in BST - binary-search-tree

Some of the code below seems too obvious, traversing the tree using its right most branch since that is where all the max values are.However, I don't understand a few things about this code I saw in Robert Sedgewick's Algorithms book.
public void deleteMax() {
if (isEmpty()) throw new NoSuchElementException("");
root = deleteMax(root);
assert check();
}
private Node deleteMax(Node x) {
if (x.right == null) return x.left;
x.right = deleteMax(x.right);
x.size = size(x.left) + size(x.right) + 1;
return x;
}
In the private method why do we return the left element if the right child of x is null ?From my understanding x would be the maximum if x has no right children and is the right most node we could go to.Also I don't understand when do we return x in the last line of the 2nd method.

If x doesn't have a right child then x is the maximum node. We "delete" it by returning x.left (the new max node). We return x after we've modified its right subtree.

Related

Remove duplicates from an unsorted linked list. I used ArrayList to solve

Given an unsorted linked list of N nodes. The task is to remove duplicate elements from this unsorted Linked List. When a value appears in multiple nodes, the node which appeared first should be kept, all others duplicates are to be removed.
Example 1:
Input:
N = 4
value[] = {5,2,2,4}
Output: 5 2 4
Explanation: Given linked list elements are
5->2->2->4, in which 2 is repeated only.
So, we will delete the extra repeated
elements 2 from the linked list and the
resultant linked list will contain 5->2->4
It's a question on GFG and I passed 405 test cases but on 406th test case it shows 'Time limit exceeded Error'. Is it like ArrayList is taking too long to traverse every element again and again?
Then I tried using HashSet with the same logic and it worked perfectly and passes all test cases.
class Solution
{
//Function to remove duplicates from unsorted linked list.
public Node removeDuplicates(Node head)
{
// Your code here
Node prev = head;
Node temp = head.next;
ArrayList<Integer> val = new ArrayList<>();
val.add(head.data);
while(temp != null){
if(val.contains(temp.data)){
prev.next = temp.next;
temp = temp.next;
}
else{
val.add(temp.data);
temp = temp.next;
prev = prev.next;
}
}
return head;
}
}
While your logic is indeed correct, it is not the most optimal solution.
Consider this example:
Input: 5 -> 2 -> 2 -> 2 -> 2 -> 3
Your Result steps would be:
5 -> 2 -> 2 -> 2 -> 2 -> 3
5 -> 2 -> 2 -> 2 -> 3
5 -> 2 -> 2 -> 3
5 -> 2 -> 3
Instead of doing the pointer writes so many times, you could keep going forward till you encounter the first not-seen-so-far node and make your pointers point to the new node directly.
Moreover, instead of using an ArrayList, you could use a Set. The .contains() method in ArrayList has a time complexity of O(n) where n is the number of elements in the ArrayList. As you do this operation M times for each loop, your order is going to be O(mn). Set has a time complexity of O(1) (Constant lookup time), So if you the do the operation M times, it will be M times O(1) which is whole lot faster.
Perhaps this code might help:
class Solution
{
//Function to remove duplicates from unsorted linked list.
public Node removeDuplicates(Node head)
{
if(head == NULL) { // BASE CASE
return head;
}
Node prev = head;
Node temp = head.next;
boolean shouldDelete = false;
Set<Integer> set = new HashSet<>();
set.add(head.data);
while(temp != null){
if(set.contains(temp.data)){
shouldDelete = true; // Set this to true to make sure any duplicates at the end is also taken care of.
}
else{
shouldDelete = false; // Set this to false as we have encountered a not-seen-so-far node.
set.add(temp.data);
prev.next = temp;
prev = temp;
}
temp = temp.next;
}
if(shouldDelete) { // There is duplicate elements in the end that has not been trimmed because we never encountered a not-seen-so-far element. So set it to NULL
prev.next = NULL;
}
return head;
}
}

How to print if the value inserted in a BST should be inserted in the left or right subtree

I'm inserting values in a BST and printing "left" if the value should be inserted in the left subtree or "right" if the value should be inserted in the right subtree but when I'm going to print, several right/left are printed, how can I fix it? I think it's due to recursion but I can't solve it without recursion.
BST insertion code:
Node *insert(Node **root, int key)
{
if(*root == NULL)
{
Node *newNode = (Node*)malloc(sizeof(Node));
if(newNode == NULL)
return NULL;
newNode->key= key;
newNode->left = NULL;
newNode->right = NULL;
(*root) = newNode;
return newNode;
}
if(key < (*root)->key){
printf("left ");
return insert(&((*root)->left),key);
}
else{
printf("right ");
return insert((&(*root)->right),key);
}
}
Example:
Inserting the values: 25 - 20 - 36 - 10 - 22 - 30
What I have got from the expected output is that the "left" or "right" indicates the relative position of the node with respect to its parent node.
If this is so, you can do this:
if(key < (*root)->key){
if ( (*root)->left==NULL)
printf("left ");
return insert(&((*root)->left),key);
}
else{
if ( (*root)->right==NULL)
printf("right ");
return insert((&(*root)->right),key);
}
The logic is, We will get to know whether it is left or right just 1 step before the insertion step. I just checked if the next node we are going to , is NULL or not.

Recurrence Relation for specific function

The task was to find the recurrence relation for this function and then find the complexity class for it as well. Provided is my work and the function. My question is, I feel like I'm missing some step in the recurrence relation and complexity class. Is this correct? The following code is in JavaScript.
function divideAndConquerSum(x){
if(x.length<1){
return 0;
}
if(x.length == 1){
return x[0];
}
var third = Math.floor((x.length-1)/3);
var next = (third *2)+1;
var y = x.slice(0, third+1);
var z = x.slice(third+1, next+1);
var a = x.slice(next+1, x.length);
return divideAndConquerSum(y)+divideAndConquerSum(z)+divideAndConquerSum(a);
}
///
work:
Checks if the array has length of zero...this is a constant time so : +1
Checks if the array has a length of 1, if so then return. Also constant time: +1
Splits the array into 3 The functionis is also constant no matter the size of n: +1
Function then adds its self three times but each time is a recursive call of 1/3 the size of n: 3T(1/3)
1+1+1+ 3T(1/3)
3T(1/3)
calls itself so we can define the relation as
3T(1/3)
9T(1/9)
27T(1/27)
this pattern can shown as
3log3n
so we have
1+1+1+ 3log3n
which is a complexity class of
O(log n)
is this right?

Binary Search Tree Iterator java

I did the LeetCode question Binary Search Tree Iterator. the following code is what I learned from others. One part I didn't understand which is cur = cur.right. Since I got the smallest value of cur.val. Why do I need to assign cur.right to cur? When I remove cur = cur.right, it said time limit exceeded. Could someone can help me to explain it?
public class BSTIterator {
Stack<TreeNode> stack;
TreeNode cur;
public BSTIterator(TreeNode root) {
stack = new Stack<>();
cur = root;
}
/** #return the next smallest number */
public int next() {
while (cur != null) {
stack.push(cur);
cur = cur.left;
}
cur = stack.pop();
int val = cur.val;
cur = cur.right; //why needed to assign cur.right to cur?
return val;
}
}
By thinking on the structure of a binary search tree we know that the left node is less than the parent (or middle) node, and that the right node is more than the parent (or middle) node. By setting the current node to be equal to the right node you are iterating through the tree in order from least to largest value. Note that if cur.right() doesn't exist then cur will be set to null and therefore not execute the while loop.
I submitted my code, it was successful.
https://leetcode.com/problems/binary-search-tree-iterator/
You can find it here.
https://github.com/yan-khonski-it/bst/blob/master/bst-core/src/main/java/com/yk/training/bst/iterators/BSTIterator.java
Explanation.
You have to use inorder which first visits the left sub-tree, then visit the current node, and then the right sub-tree, so you will iterate through all the elements in the ascending order.
Now, you have stack, which holds all the node that you should return in next call in the correct order.
next will remove the last element from the stack. Now you check the current node, if it has right subtree. If so, you need to iterate though left elements of the right subtree.
/**
* Find next node to be returned in {#link #next()}.
* Push it to stack.
*/
public void navigateLeftSubtree() {
stack.push(currentNode);
while (currentNode.left != null) {
currentNode = currentNode.left;
stack.push(currentNode);
}
}
In this case, (right sub tree is present for current node), you should put the right child of the current node into the stack. You don't want to put the current into the stack, if you have already visited it.
public int next() {
currentNode = stack.pop();
final int currentValue = currentNode.value;
if (currentNode.right != null) {
// Push root of the right subtree into stack.
currentNode = currentNode.right;
navigateLeftSubtree();
}
return currentValue;
}

questions about binary search tree

Show that every n-node binary search tree is not equally likely (assuming items are inserted in random order), and that balanced trees are more probable than straight-line trees.
How is it prove mathematical?
Number of possible tree configurations: see With ' N ' no of nodes, how many different Binary and Binary Search Trees possible?
Number of ways to get a single line, most imbalanced, deepest tree with n nodes: 2^(n-1)
Explanation:
2 ways to pick up first node (greatest or smallest)
X 2 ways to pick up second node (greatest or smallest among the n-1 remaining nodes
...
X 2 ways to pick up the (n-1)th node
X 1 way to pick up the last node
Number of ways to add n items to a binary tree in such a way that it is balanced:
Let g(n,m) denote the number of ways to merge two ordered lists by picking elements from one list or the other one at a time until both lists are empty. g(n,m) = g(n-1,m) + g(n,m-1) which happen to correspond to the numbers defined in the Pascal Triangle. That would give n+m chose n or n+m chose m = (n+m)! / n! (n+m-n)! = (n+m)!/(n! m!)
Example:
Number of ways to merge two ordered lists containing 2 elements each = 4!/(2! 2!) = 24 / 4 = 6
Assuming the two lists are as follows:
1 A
2 B
The six merging combinations are:
1 1 1 A A A
2 A A B 1 1
A 2 B 1 B 2
B B 2 2 2 B
Now, let f(n) denote the number of combinations in which n sorted elements can be added to a empty binary tree such that the binary tree is balanced. The only way to achieve that is to first add
the middle element if n is odd. That would be element ceiling(n/2). Each side would have n/2-1 elements.
either element n/2 or element n/2+1 if n is even. One side would have n/2-1 element, the other side n/2 elements and vice versa.
Once the middle element is selected, all elements to the left will always fall on the left subtree and all elements on the right will always fall on the right subtree. Assuming the elements on the right are ordered in such a way that the left subtree is balanced and the elements on the right are also ordered in such a way that the right subtree is balanced, merging the two lists in any way will always result in both subtree being balanced. That means that for each list of n and m elements that respectively fall on the left and right subtree such that both subtrees are balanced, there are (n+m)!/(n!m!) to merge them so as to achieve the same result.
That would give us (n+m)!/(n!m!) x f(n) x f(m)
We can now formulate this as follows:
Base cases:
f(1) = 1
f(2) = 2
General case:
f(n) = (n-1)!/[((n-1)/2)!]^2 x [f((n-1)/2)]^2 | n odd
f(n) = (n-1)!/((n/2-1)! x (n/2)!) x 2 x f(n/2-1) x f(n/2) | n even
Rest to transform this into a non recursive formula in terms of n. Maybe it would be easier to start with the easiest case where n = 2^m - 1 (i.e. removing a node and dividing by two will always give a whole number and will result in a completely balanced tree).
Note: I posted a related math question here: https://math.stackexchange.com/questions/460767/recurrent-relation-for-number-of-ways-to-get-a-balanced-n-binary-tree
Following is a C# console application that lists all the sequences in which nodes can be added to a binary tree so as to have it balanced (Run it here ):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace AllBalancedTrees
{
class Program
{
static void Main(string[] args)
{
char[] nodes = { 'A', 'B', 'C', 'D', 'E' };
AllBalancedTrees<char> AllBalancedTrees = new AllBalancedTrees<char>();
foreach (char[] a in AllBalancedTrees.allBalancedTreeCombinations(nodes))
{
foreach (char c in a)
{
Console.Write(c + " ");
}
Console.WriteLine();
}
Console.ReadKey();
}
}
class AllBalancedTrees<T>
{
public IEnumerable<T[]> allBalancedTreeCombinations(T[] nodes)
{
T[] result;
if (nodes.Length == 1)
{
yield return nodes;
}
else if (nodes.Length == 2)
{
yield return nodes;
T[] nodes2 = new T[2];
nodes2[0] = nodes[1];
nodes2[1] = nodes[0];
yield return nodes2;
}
else if (nodes.Length % 2 == 1)
{
int mid = (nodes.Length - 1) / 2;
T[] left = new T[mid];
T[] right = new T[mid];
Array.Copy(nodes, 0, left, 0, mid);
Array.Copy(nodes, mid + 1, right, 0, mid);
foreach (T[] l in allBalancedTreeCombinations(left))
{
foreach (T[] r in allBalancedTreeCombinations(right))
{
result = new T[nodes.Length];
result[0] = nodes[mid];
foreach (T[] a in allMergeCombinations(l, r))
{
Array.Copy(a, 0, result, 1, a.Length);
yield return result;
}
}
}
}
else
{
int mid = (nodes.Length) / 2;
T[] left = new T[mid];
T[] right = new T[mid - 1];
Array.Copy(nodes, 0, left, 0, mid);
Array.Copy(nodes, mid + 1, right, 0, mid - 1);
foreach (T[] l in allBalancedTreeCombinations(left))
{
foreach (T[] r in allBalancedTreeCombinations(right))
{
result = new T[nodes.Length];
result[0] = nodes[mid];
foreach (T[] a in allMergeCombinations(l, r))
{
Array.Copy(a, 0, result, 1, a.Length);
yield return result;
}
}
}
mid = nodes.Length / 2 - 1;
left = new T[mid];
right = new T[mid + 1];
Array.Copy(nodes, 0, left, 0, mid);
Array.Copy(nodes, mid + 1, right, 0, mid+1);
foreach (T[] l in allBalancedTreeCombinations(left))
{
foreach (T[] r in allBalancedTreeCombinations(right))
{
result = new T[nodes.Length];
result[0] = nodes[mid];
foreach (T[] a in allMergeCombinations(l, r))
{
Array.Copy(a, 0, result, 1, a.Length);
yield return result;
}
}
}
}
}
public IEnumerable<T[]> allMergeCombinations(T[] firstArray, T[] secondArray)
{
if (firstArray.Length == 0)
{
yield return secondArray;
}
else if (secondArray.Length == 0)
{
yield return firstArray;
}
else
{
T[] result;
T[] firstMinusOne;
firstMinusOne = new T[firstArray.Length - 1];
Array.Copy(firstArray, 1, firstMinusOne, 0, firstMinusOne.Length);
foreach (T[] a in allMergeCombinations(firstMinusOne, secondArray))
{
result = new T[firstArray.Length + secondArray.Length];
result[0] = firstArray[0];
Array.Copy(a, 0, result, 1, a.Length);
yield return result;
}
T[] secondMinusOne;
secondMinusOne = new T[secondArray.Length - 1];
Array.Copy(secondArray, 1, secondMinusOne, 0, secondMinusOne.Length);
foreach (T[] a in allMergeCombinations(firstArray, secondMinusOne))
{
result = new T[firstArray.Length + secondArray.Length];
result[0] = secondArray[0];
Array.Copy(a, 0, result, 1, a.Length);
yield return result;
}
}
}
}
}
Every n-node binary search tree is not equally likely because, if items are inserted in random order, there is a much greater probability that the input will not be in strictly increasing or decreasing order. As long as the items are not in ascending or descending order, a straight-line tree is an impossibility.
For example, in a tree of four records with keys 1, 2, 3, and 4, there are 4! or 24 ways for these items to be ordered as input. Only two of these ways can result in a straight-line tree (4, 3, 2, 1 and 1, 2, 3, 4). In this case the probability of a straight-line tree is approximately 8.3%, whereas the probability of a (somewhat) balanced tree is 91.6%. Clearly, the odds are against the chances of getting a straight-line tree for a result.