0

I've been trying to solve this question. The problem statement is bit of a customized heap binary tree if you compare it with ADT definition of heap binary tree. In Heap binary tree you always do deleteMax/deletetMin depending upon the kind of heap tree it is but here they want you to delete a specific node instead in this problem.

Well my solution is failing only one test case which is happening when I delete a value which is a leaf node:

Here is the effort I've made so far while writing the Heap class. Although the source code is big but you might want to focus on the DeleteSpecificValueFromHeap method where I'm facing the problem.

I've implemented the heap binary tree using an array (List in C# are backed by arrays). Consider the current state of a heap binary tree in the array:

-1 12 5 13 20 6 7

The heap binary tree looks something like this:

        -1
    /        \
   12         5
  /   \     /    \
13    20    6     7

Now I want to delete node with value 13. This case fails in my heap binary tree. Can you point out on how to fix it? DeleteSpecificValueFromHeap method is the one which is struggling currently.

public class Heap
{
    List<int> items;

    public int Root
    {
        get { return items[0]; }
    }

    public Heap()
    {
        items = new List<int>();
    }

    public void Insert(int item)
    {
        items.Add(item);

        if (items.Count <= 1)
            return;

        var i = items.Count - 1;

        while (i > 0)
        {
            var parentNodeValue = items[(i - 1) / 2];
            var newNodeValue = items[i];

            if (newNodeValue < parentNodeValue)
            {
                //we need to percolate up this node. so swap it with parent.
                items[(i - 1) / 2] = newNodeValue;
                items[i] = parentNodeValue;
                //update the position of newly inserted node after swapping
                i = (i - 1) / 2;
            }
            else
                break;
        }
    }

    public void DeleteSpecificValueFromHeap(int val)
    {
        for (var i = 0; i < items.Count; i++)
        {
            if (items[i] == val)
            {
                items[i] = items[items.Count - 1];
                items.RemoveAt(items.Count - 1);
                //reheapify : percolate down this node ith position

                var leftChildIndex = (i * 2) + 1;
                var rightChildIndex = (i * 2) + 2;



                while (leftChildIndex <= items.Count - 1) //chilren are there in the array.
                {
                    //child nodes of node at ith position
                    var child1Value = items[leftChildIndex];

                    if (rightChildIndex <= items.Count - 1)
                    {
                        var child2Value = items[rightChildIndex];
                        var currentNodeValue = items[i];
                        if (child1Value < child2Value)
                        {
                            //swap ith node with child 1
                            items[i] = child1Value;
                            items[leftChildIndex] = currentNodeValue;
                            i = leftChildIndex;
                        }
                        else
                        {
                            items[i] = child2Value;
                            items[rightChildIndex] = currentNodeValue;
                            i = rightChildIndex;
                        }
                    }
                    else
                    {
                        //case of only one child
                        var currentNodeValue = items[i];
                        items[i] = child1Value;
                        items[leftChildIndex] = currentNodeValue;
                        i = leftChildIndex;
                    }
                    leftChildIndex = (i * 2) + 1;
                    rightChildIndex = (i * 2) + 2;
                }
                break;
            }
        }

    }

Update:

I changed my DeleteSpecificValueFromHeap method as below as per @Raudel's recommendation after which the test case I've mentioned in the post is ok now but test case # 9 on the link is still failing. I'm really sorry for not being able to provide the input cases as it has .1 million inputs which will not be possible to put here. I need an eagle eye now who can dry run my code and help me if there is still anything wrong?

public void DeleteSpecificValueFromHeap(int val)
        {
            for (var i = 0; i < items.Count; i++)
            {
                if (items[i] == val)
                {
                    items[i] = items[items.Count - 1];

                    if (i == items.Count - 1)
                    {
                        //you are deleting the right most leaf node at the lowest level
                        //so nothing needs to be done apart from deleting the node.
                        items.RemoveAt(items.Count - 1);
                        break;
                    }

                    items.RemoveAt(items.Count - 1);

                    if (i == 0)
                        //it is the root node. The only option is to percolate down.
                        PercolateDownTheNode(i);
                    else
                    {
                        var parentNodeValue = items[(i - 1) / 2];
                        if (items[i] < parentNodeValue)
                            PercolateUpTheNode(i);
                        else
                            PercolateDownTheNode(i);
                    }

                    break;
                }
            }

        }

        private void PercolateDownTheNode(int i)
        {
            //reheapify : percolate down this node ith position
            var leftChildIndex = (i * 2) + 1;
            var rightChildIndex = (i * 2) + 2;

            while (leftChildIndex <= items.Count - 1) //chilren are there in the array.
            {
                //child nodes of node at ith position
                var child1Value = items[leftChildIndex];

                if (rightChildIndex <= items.Count - 1)
                {
                    var child2Value = items[rightChildIndex];
                    var currentNodeValue = items[i];
                    if (child1Value < child2Value)
                    {
                        //swap ith node with child 1
                        items[i] = child1Value;
                        items[leftChildIndex] = currentNodeValue;
                        i = leftChildIndex;
                    }
                    else
                    {
                        items[i] = child2Value;
                        items[rightChildIndex] = currentNodeValue;
                        i = rightChildIndex;
                    }
                }
                else
                {
                    //case of only one child
                    var currentNodeValue = items[i];
                    items[i] = child1Value;
                    items[leftChildIndex] = currentNodeValue;
                    i = leftChildIndex;
                }
                leftChildIndex = (i * 2) + 1;
                rightChildIndex = (i * 2) + 2;
            }
        }

        private void PercolateUpTheNode(int i)
        {
            while (i > 0)
            {
                var parentNodeValue = items[(i - 1) / 2];
                var newNodeValue = items[i];

                if (newNodeValue < parentNodeValue)
                {
                    //we need to percolate up this node. so swap it with parent.
                    items[(i - 1) / 2] = newNodeValue;
                    items[i] = parentNodeValue;
                    //update the position of newly inserted node after swapping
                    i = (i - 1) / 2;
                }
                else
                    break;
            }
        }
RBT
  • 24,161
  • 21
  • 159
  • 240
  • When you say "The heap binary tree looks something like this" you show a tree with a `-1` value but the line before you have a `1`. Also most of the tree has highest value on the left node, except for the bottom left. Have I missed something? – Enigmativity Jan 08 '18 at 05:10
  • @Enigmativity Ohh! not my fault. I just realized `-1` was converted to a bullet point by the editor. I made an edit to fix the array values. It is a min heap binary tree so minimum element is always at the root after every insertion. – RBT Jan 08 '18 at 05:37
  • @RBT, as I did not spot the exact error on your code, I was wondering if my answer helped you in someway. What about the optimization I told you about? – Raudel Ravelo Jan 10 '18 at 04:59
  • @RaudelRavelo Your post have been very helpful. I've certainly got the correct pointers from your algorithm steps. Actually initially, I was under an impression that since it is a min heap so every time I would require to percolate down the node which replaces the deleted node. But this would be true only in case of standard ADT of heap binary tree. In this case when we can delete any node randomly there is a chance of percolating up or percolating down the target node depending upon condition. I will respond back on your answer on your soon as soon I'm done fixing my code. I'm on it! – RBT Jan 10 '18 at 05:50
  • @RaudelRavelo first I want to fix the fundamental algorithmic error in my program while deleting the node and then I would try the hash table enhancement. Interestingly, while submitting the current solution only one of the test cases are failing and rest all are passing. My initial thought was that the current brute force approach of O(n) traversing that I'm doing to find the node to be deleted will certainly cause timeout on few test cases but it didn't happen so :P – RBT Jan 10 '18 at 05:56
  • 1
    Thanks for the feedback! I read a couple of comments from the problem discussion of people getting TLE because of the linear search. I have modified a heap like that before and that is why I was letting you know about the optimization. I remember we put a complementary problem for advanced students where the only way to get an nlogn solution was doing that or using a very complicated data structure they should not had seen ;) – Raudel Ravelo Jan 10 '18 at 06:07

1 Answers1

4

The problem is that when you remove at any position but the last one, all the elements to the right are shifted left by one position and this may end up in the heap to stop being a heap. From the 3 steps down below you are doing the first two but you are not doing the 3rd one properly.

1, Delete the value from the array but do not remove it (this creates a "hole" and the tree is no longer "complete") 

2. Replace the deleted value with the "fartest right node" on the lowest level of the heap.
//you can see the first two steps like a swap

3. Heapify (fix the heap)://but you have two possible cases now

     if ( newValue < its parent node )
        Do an UPHeap
     else
        Do a DownHeap

In the 3rd step, you compare with the parent and this tells you what to do, either go UP or DOWN. For this, I recommend you to create methods for UpHeap and DownHeap separately because you will be using them more than once and the code will become more clear.

I should also point out that you are finding the value with a loop and this makes each deletion O(n). As you can see in the problem statement they can ask you up to 1e5 question. That will probably give you Time Limit Exceeded (TLE) depending on the size of the array. As a thumb rule, for almost any online judge, the expected time to solve a problem is around 1 sec. So, for an array with size 1e5 you will have to wait longer than that which makes you think there should be something better, which it's true.

The thing is that you can keep track of the position inside the heap a value has. You can save it in a HashTable<int, int> (for example) so you can ask for a given value to get the position inside the heap. In this way you avoid the loop to get the position inside the heap, but you have to update it any time you move that value within the heap. In order to update it, you have to add a couple of lines inside both UpHeap and DownHeap methods, and every time you move a value UP/DOWN the heap you update the positions of the swapped elements in the HashTable.

UPDATE

I took your code and change a couple of things, then I went online and accepted the problem, now you can be sure this works. I think the error was in the DownHeap method, that's the only method I really changed.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ContestLibrary
{
    public class Heap
    {
        List<int> items;

        public int Root
        {
            get { return items[0]; }
        }

        public Heap()
        {
            items = new List<int>();
        }

        public int GetMin()
        {
            if(items.Count == 0)
                throw new Exception("Empty Heap");
            return items[0];
        }

        public void Insert(int item)
        {
            items.Add(item);
            PercolateUpTheNode(items.Count - 1);
        }

        public void DeleteSpecificValueFromHeap(int val)
        {
            for (var i = 0; i < items.Count; i++)
            {
                if (items[i] == val)
                {
                    items[i] = items[items.Count - 1];
                    items.RemoveAt(items.Count - 1);

                    if (i == items.Count)
                        return;//cause you deleted the right most node

                    var parentNodeValue = items[(i - 1) / 2];

                    if (items[i] < parentNodeValue)
                        PercolateUpTheNode(i);
                    else
                        PercolateDownTheNode(i);

                    return;
                }
            }
        }

        private void PercolateDownTheNode(int i)
        {
            while (i < items.Count / 2) {
                //get the min child first
                int minChildIndex = 2 * i + 1;
                if (minChildIndex < items.Count - 1 && items[minChildIndex] > items[minChildIndex + 1]) {
                    minChildIndex++;
                }

                if (items[i] <= items[minChildIndex])
                    return;//I'm smaller than the minimum of my children

                //swap
                int temp = items[i];
                items[i] = items[minChildIndex];
                items[minChildIndex] = temp;

                i = minChildIndex;
            }
        }

        private int ParentIndex(int i)
        {
            return (i - 1) / 2;
        }

        private void PercolateUpTheNode(int i)
        {
            while (i > 0)
            {
                var parentValue = items[ParentIndex(i)];
                var currentValue = items[i];

                if (currentValue < parentValue)//swap
                {
                    items[ParentIndex(i)] = currentValue;
                    items[i] = parentValue;
                    i = ParentIndex(i);
                }
                else
                    return;
            }
        }
    }

    public class Problem
    {

        static void Main(string[] args)
        {
            Heap heap = new Heap();
            int q = int.Parse(Console.ReadLine());
            while (q-->0)
            {
                var line = Console.ReadLine().Split();
                int type = int.Parse(line[0]);
                switch (type)
                {
                        case 1:
                            heap.Insert(int.Parse(line[1]));
                        break;
                        case 2:
                            heap.DeleteSpecificValueFromHeap(int.Parse(line[1]));
                        break;
                        default:
                            Console.WriteLine(heap.GetMin());
                        break;
                }
            }
        }
    }
}
Raudel Ravelo
  • 648
  • 2
  • 6
  • 24
  • I changed my code as per your recommendation but test case # 9 is still failing. I thought the test case I've mentioned in my post will be the one at the root cause of test case # 9 which will get fixed automatically the moment I fix it but my speculation turned out to be wrong. I'm missing something else due to which case # 9 is failing. Just an FYI it is *not* a case of TLE. – RBT Jan 11 '18 at 00:32
  • 1
    I'll take a look at your code again to see if I can spot the error. – Raudel Ravelo Jan 11 '18 at 01:11
  • @RBT I think finally I gave you the answer you were looking for ;) take a look at the UPDATE – Raudel Ravelo Jan 11 '18 at 01:58
  • wow. It worked. Although you helped me obtain only .85 additional points in there as 17 out of 18 test cases had already passed but the learnings that you've given me is simply invaluable. Thank you so much for making continuous follow-ups to ensure I reach my goal. #communityRocks – RBT Jan 11 '18 at 05:05
  • Btw, as I had mentioned earlier you must have seen now that even O(n) traversal to find the node to be deleted didn't cause the test cases to time out although that hash map based solution for an ammortized O(1) search of the node to be deleted is certainly a fabulous enhancement for this solution to make it even better. – RBT Jan 11 '18 at 05:07