Showing posts with label Divide and Conquer. Show all posts
Showing posts with label Divide and Conquer. Show all posts

Monday, November 21, 2022

[Uber][LeetCode] Reverse Pairs

Problem: Given an integer array nums, return the number of reverse pairs in the array.

A reverse pair is a pair (i, j) where:

  • 0 <= i < j < nums.length and
  • nums[i] > 2 * nums[j].

Example:

Input: nums = [1,3,2,3,1]
Output: 2
Explanation: The reverse pairs are:
(1, 4) --> nums[1] = 3, nums[4] = 1, 3 > 2 * 1
(3, 4) --> nums[3] = 3, nums[4] = 1, 3 > 2 * 1
Input: nums = [2,4,3,5,1]
Output: 3
Explanation: The reverse pairs are:
(1, 4) --> nums[1] = 4, nums[4] = 1, 4 > 2 * 1
(2, 4) --> nums[2] = 3, nums[4] = 1, 3 > 2 * 1
(3, 4) --> nums[3] = 5, nums[4] = 1, 5 > 2 * 1


Approach: We can use Merge sort approach here. The only change which we need to make is while merging the two (left and right) sorted array we can check how many reverse pair are there.

 

Implementation in C#:

    public int ReversePairs(int[] nums)
    {
        int length = nums?.Length ?? 0;
        if (length <= 1)
        {
            return 0;
        }
        return this.CountReversePairs(nums, 0, length - 1);
    }

    private int CountReversePairs(int[] nums, int left, int right)
    {
        if (left >= right)
        {
            return 0;
        }
        int mid = left + (right - left) / 2;
        int leftRevCount = this.CountReversePairs(nums, left, mid);
        int rightRevCount = this.CountReversePairs(nums, mid + 1, right);
        int mergeRevCount = this.Merge(nums, left, mid, right);
        return leftRevCount + rightRevCount + mergeRevCount;
    }

    private int Merge(int[] nums, int left, int mid, int right)
    {
        int i = left;
        int j = mid + 1;
        var temp = new List<int>();
        int revPairsCount = this.GetReversPairs(nums, left, mid, right);
        while (i <= mid && j <= right)
        {
            if (nums[i] > nums[j])
            {
                temp.Add(nums[j++]);
            }
            else
            {
                temp.Add(nums[i++]);
            }
        }
        while (i <= mid)
        {
            temp.Add(nums[i++]);
        }
        while (j <= right)
        {
            temp.Add(nums[j++]);
        }
        i = left;
        for (j = 0; j < temp.Count; ++j)
        {
            nums[i++] = temp[j];
        }
        return revPairsCount;
    }

    private int GetReversPairs(int[] nums, int left, int mid, int right)
    {
        int j = mid + 1;
        int revPairsCount = 0;
        for (int i = left; i <= mid; ++i)
        {
            while (j <= right && nums[i] > (long)(2 * (long)nums[j]))
            {
                ++j;
            }
            revPairsCount += (j - (mid + 1));
        }
        return revPairsCount;
    }


Complexity: O(nlogn)

Wednesday, July 28, 2021

[LeetCode] Beautiful Array

Problem: For some fixed n, an array nums is beautiful if it is a permutation of the integers 1, 2, ..., n, such that:

For every i < j, there is no k with i < k < j such that nums[k] * 2 = nums[i] + nums[j].

Given n, return any beautiful array nums.  (It is guaranteed that one exists.)

Example:

Input: n = 4
Output: [2,1,4,3]
Input: n = 5
Output: [3,1,2,5,4]


Approach: Let's try to understand the condition:

i < k < j such that nums[k] * 2 = nums[i] + nums[j]

what it is saying that for every 1 <= k <= n - 2, there are no element in left of k and right of k such that the sum of those elements is equal to 2 * k. How we can ensure it? if we see closely:

2 * nums[k] is even for any nums[k].

so if we keep odd numbers to the left of k and even numbers to the right of k. We will be able to achieve it as sum of an even number and an odd number can't be even. We can achieve it by using divide and conquer approach; basically we first divide the array in two halves and then create the beautiful array of each half recursively and then concatenate theses two arrays(halves).

Let's see how we can do it? The shortest beautiful array is [1,3,2] or [3,1,2]. Right, how to get this array:

Here n is 3 so what we can do we can divid this into two halves:

  • Number of odd numbers = (n + 1) / 2 = (3 + 1) / 2 = 2 so the first half is [1,2] for now.
  • Number of even numbers = n / 2 = 3 / 2 = 1 so the second half is [1] for now.

There are some problems with these halves right, as both halves have 1 in it. Odd half has 2 and even half has 1. Let's see how to actually generate right halves:

  • The odd numbers will be 2 * oddHalf[i] - 1 so the first half will become [2 * 1 - 1, 2 * 2 - 1] = [1,3]
  • The even numbers will be 2 * evenHalf[i] so the second half will become [2 * 1] = [2]

Now we have generated these halves we can just concatenate these halves and it will become [1,3,2] which is our answer. Now that the approach is clear, here is how our algorithm looks like:

  • DEF Solve(n):
    • IF n == 1:
      • RETURN [1]
    • oddHalf = Solve((n + 1) / 2)
    • evenHalf = Solve(n/2)
    • result = []
    • int writeIndex = 0
    • FOR i = 0 TO length of oddHalf
      • result[writeIndex] = 2 * oddHalf[i] - 1
      • writeIndex = writeIndex + 1
    • FOR i = 0 TO length of evenHalf
      • result[writeIndex] = 2 * evenHalf[i]
      • writeIndex = writeIndex + 1
  • RETURN result

In the implementation I have just taken a hashmap and store the intermediate results in it so that we don't have to recalculate the result for same intermediate n and in that way we can save some time.


Implementation in C#:

    public int[] BeautifulArray(int n) 

    {

        Dictionary<int, int[]> resultDict = new Dictionary<int, int[]>();

        return this.SolveBeautifulArray(n, resultDict);

    }

    

    private int[] SolveBeautifulArray(int n, Dictionary<int, int[]> resultDict)

    {

        if (resultDict.ContainsKey(n))

        {

            return resultDict[n];

        }        

        int[] result = new int[n];

        if (n == 1)

        {

            result[0] = 1;

            resultDict.Add(1, result);

            return result;

        }

        int i = 0;

        // odd nums

        foreach (int x in this.SolveBeautifulArray((n + 1) / 2, resultDict))

        {

            result[i++] = 2 * x - 1;

        }

        // even nums

        foreach (int x in this.SolveBeautifulArray(n / 2, resultDict))

        {

            result[i++] = 2 * x;

        }

        resultDict.Add(n, result);        

        return result;

    }


Complexity: O(nlogn)

Friday, February 12, 2021

Implement Pow(x, n)

Problem: Implement Pow(x, n).

Example:

Input: x = 3.00000, n = 3
Output: 27.00000
Input: x = 2.00000, n = -1
Output: 0.5000
Explanation: 2-1 = 1/2 = 0.5


Approach: We can solve it in linear time easily but we need to improve it. We can use divide and conquer here. 

If n is even:

 x ^ n = x ^ (n/2) * x ^ (n/2) 

else:

 x ^ n = x ^ (n/2) * x ^ (n/2) * x

Right. So if you see, we just need to calculate x ^ (n/2) to calculate x ^ n. If we go in this way we can solve it in logarithmic time.

 

Implementation in C#:

    public double MyPow(double x, int n)
    {
        if (n == 0 || x == 1)
        {
            return 1;
        }
        long pow = n;
        if (pow < 0)
        {
            return this.Pow(1/x, -pow);
        }
        return this.Pow(x, pow);
    }

    public double Pow(double x, long n)
    {
        if (n == 1)
        {
            return x;
        }
        double halfPow = this.Pow(x, n/2);
        if (n % 2 == 0)
        {
            return halfPow * halfPow;
        }
        return halfPow * halfPow * x;
    }


Complexity: O(logn)

Monday, February 8, 2021

[Google Question] Second Maximum number in an array

Problem: Get the second maximum number in an array of unique integers. You need to minimize the number of comparisons.

Example:

Input: arr = [1,2,3,4]
Output: 3


Approach: We can use two variables approach to solve this problem easily but it will take 2*n comparisons to identify the second maximum. Let's see how we can reduce the number of comparisons. Let's use divide and conquer approach:

We can get the maximum of every pair of elements (arr[i], arr[i + 1]) in the array and store it in temp array. If we find that number of elements in the temp array is one that means we get the maximum number otherwise we will assign temp array to original array and repeat the process.

Using the above approach, we can get the maximum number but how to get the second maximum. If you see we really care about the numbers which were being compared to maximum number, right! As one of them should be the second maximum so what we will do while comparing arr[i] and arr[i + 1], we will also store the Min(arr[i], arr[i + 1]) in an hash where key is Max(arr[i], arr[i + 1)) and value is Min(arr[i], arr[i + 1]). Here are hash will have key as number and value is a list of numbers that were less than the key number which we found while comparing. If you see we are creating a tree here. For example say our input array is [1, 2, 3, 4, 5, 6, 7, 8]

                8

           /         \

       4                8

   /       \          /     \

  2        4       6        8

 /  \    /   \      /  \      /  \

1   2  3    4  5    6  7    8

If you see in the above tree, we just care about the children of 8 (max number) to get the second maximum. Instead of maintaining a tree, we are storing the children of max number in a hash table. Hopefully the approach is clear now.


Implementation in C#:

        public int FindSecondMaxUsingMinimumComparisons(int[] arr)

        {

            if (arr?.Length <= 1)

            {

                throw new ArgumentException();

            }

            List<int> nums = new List<int>(arr);

            Dictionary<int, List<int>> lesserNumbers = new Dictionary<int, List<int>>();

            while (nums.Count != 1)

            {

                List<int> tempStorage = new List<int>();

                for (int i = 0; i < nums.Count; i += 2)

                {

                    if (i + 1 == nums.Count)

                    {

                        tempStorage.Add(nums[i]);

                    }

                    else

                    {

                        if (nums[i] > nums[i + 1])

                        {

                            tempStorage.Add(nums[i]);

                            if (!lesserNumbers.ContainsKey(nums[i]))

                            {

                                lesserNumbers.Add(nums[i], new List<int>());

                            }

                            lesserNumbers[nums[i]].Add(nums[i + 1]);

                        }

                        else

                        {

                            tempStorage.Add(nums[i + 1]);

                            if (!lesserNumbers.ContainsKey(nums[i + 1]))

                            {

                                lesserNumbers.Add(nums[i + 1], new List<int>());

                            }

                            lesserNumbers[nums[i + 1]].Add(nums[i]);

                        }

                    }

                }

                nums = tempStorage;

            }

            return Enumerable.Max(lesserNumbers[nums[0]]);

        }


Complexity: Total comparisons: n + logn

Thursday, December 31, 2020

[Uber] Top K Frequent Elements

Problem: Given a non-empty array of integers, return the k most frequent elements.

Example:

Input: nums = [1,1,1,1,3,2,2,3,3], k = 2
Output: [1,3]


Approach: We can use the quick select approach which we used in our previous problem of getting the kth smallest number in an unsorted array. Here are the few points which we need to take care:

  1. Partial sorting will be done based on frequency of the number so to efficiently get the frequency of a number, we can maintain a hash with key as element of input array and value as the frequency of this number.
  2. Here we need to get rid of duplicates as we need to return the top k frequent elements, right. That means we need to apply quick select on an array containing unique numbers of input array.
  3. We need to get the top k frequent elements that means we just need to find (n - k)th less frequent element. Once we get it, our answer will be unique[n-k...n] where unique is the array of unique numbers of input array.
That's all!

Implementation in C#:

    public int[] TopKFrequent(int[] nums, int k) 

    {

        Dictionary<int, int> frequencies = new Dictionary<int, int>();

        foreach(int num in nums)

        {

            if (!frequencies.ContainsKey(num))

            {

                frequencies.Add(num, 0);

            }

            ++frequencies[num];

        }

        int[] uniqueNums = frequencies.Keys.ToArray();

        return this.FindKMostFrequentElements(uniqueNums, uniqueNums.Length - k + 1, 0, uniqueNums.Length - 1, frequencies, k);

    }

    

    private int[] FindKMostFrequentElements(int[] uniqueNums, int k, int start, int end, Dictionary<int, int> frequencies, int length)

    {

        if (k > 0 && k <= end - start + 1)

        {

            int pos = this.RandomPartition(uniqueNums, start, end, frequencies);

            if (pos - start + 1 == k)

            {

                return this.SubArray(uniqueNums, pos, length);

            }

            else if (pos - start + 1 > k)

            {

                return this.FindKMostFrequentElements(uniqueNums, k, start, pos - 1, frequencies, length);

            }

            return this.FindKMostFrequentElements(uniqueNums, k - (pos - start + 1), pos + 1, end, frequencies, length);

        }

        return new int[] { };

    }

    

    private int RandomPartition(int[] uniqueNums, int start, int end, Dictionary<int, int> frequencies)

    {

        int pivot = new Random().Next(start, end);

        this.SwapElements(uniqueNums, pivot, end);

        return this.Partition(uniqueNums, start, end, frequencies);

    }

    

    private int Partition(int[] uniqueNums, int start, int end, Dictionary<int, int> frequencies)

    {

        int x = uniqueNums[end];

        int i = start;

        for (int j = start; j < end; ++j)

        {

            if (frequencies[uniqueNums[j]] < frequencies[x])

            {

                this.SwapElements(uniqueNums, i, j);

                ++i;

            }

        }

        this.SwapElements(uniqueNums, i, end); 

        return i;

    }

    

    public void SwapElements(int[] arr, int i, int j)

    {

        int temp = arr[i];

        arr[i] = arr[j];

        arr[j] = temp;

    }

    

    public int[] SubArray(int[] array, int startIndex, int length)

    {

        int[] subArray = new int[length];

        Array.Copy(array, startIndex, subArray, 0, length);

        return subArray;

    }


Complexity: O(n)

Wednesday, December 23, 2020

Count of Smaller Numbers After Self in an Array

Problem: You are given an integer array nums and you have to return a new counts array. The counts array has the property where counts[i] is the number of smaller elements to the right of nums[i].

Example:

Input: nums = [5,3,1,2]
Output: [3,2,0,0]
Explanation:
To the right of 5 there are 3 smaller elements (3, 1 and 2).
To the right of 3 there are 2 smaller element (1 and 2).
To the right of 1 there are no smaller element.
To the right of 1 there is 0 smaller element.


Approach: This is a clearly a problem of finding the number of inversions. Now here instead of finding total number of inversions, we need to find inversions per element/index in the array. We will use the same merge sort approach here but here for simplicity I am sorting indices instead of actual array as we need to fill result array according to how many numbers are less than on the right of the index. 

You can use original merge sort too. You can also use any balanced binary tree to solve this problem.  


Implementation in C#:

        public static IList<int> CountSmaller(int[] nums) 

        {

            int[] result = new int[nums.Length];

            int[] indexes = Enumerable.Range(0, nums.Length).ToArray();

            CountSmallerNumberUsingMergeSort(ref indexes, nums, result);

            return result.ToList();

        }


        private static void CountSmallerNumberUsingMergeSort(ref int[] indexes, int[] nums, int[] result)

        {

            if (indexes.Length <= 1)

            {

                return;

            }

            int[] leftArray = indexes.SubArray(0, indexes.Length / 2);

            int[] rightArray = indexes.SubArray(indexes.Length / 2, indexes.Length - indexes.Length / 2);

            CountSmallerNumberUsingMergeSort(ref leftArray, nums, result);

            CountSmallerNumberUsingMergeSort(ref rightArray, nums, result);

            indexes = CountSmallerWhileMerge(leftArray, rightArray, nums, result);

        }


        private static int[] CountSmallerWhileMerge(int[] leftArray, int[] rightArray, int[] nums, int[] result)

        {

            int lIndex = 0, rIndex = 0;

            int countOfSmallerNumber = 0;

            List<int> newIndexes = new List<int>();

            while (lIndex < leftArray.Length && rIndex < rightArray.Length)

            {

                if (nums[leftArray[lIndex]] > nums[rightArray[rIndex]])

                {

                    ++countOfSmallerNumber;

                    newIndexes.Add(rightArray[rIndex++]);

                }

                else

                {

                    result[leftArray[lIndex]] += countOfSmallerNumber;

                    newIndexes.Add(leftArray[lIndex++]);

                }

            }

            while(lIndex < leftArray.Length)

            {

                result[leftArray[lIndex]] += countOfSmallerNumber;

                newIndexes.Add(leftArray[lIndex++]);

            }

            while(rIndex < rightArray.Length)

            {

                newIndexes.Add(rightArray[rIndex++]);

            }

            return newIndexes.ToArray();

        }

        

        // Put this extension method in a static class if you want to use it as is

        public static T[] SubArray<T>(this T[] array, int startIndex, int length)

        {

            T[] subArray = new T[length];

            Array.Copy(array, startIndex, subArray, 0, length);

            return subArray;

        }


Complexity: O(nlogn)

Create Maximum Number From Two Array

Problem: Given two arrays which only contains digits 0 - 9 of lengths say m and n represents two numbers. Create the maximum number of length k <= m + n from digits of the input two arrays. The relative order of the digits from the same array must be preserved. Return an array of the k digits.

Example: 

Input:
nums1 = [6, 0, 1, 2]
nums2 = [6, 7, 3]
k = 5
Output:
[7, 6, 3, 1, 2]


Approach: Let's breakdown this problem into sub problems:

1. Given an array of length n, create the maximum number of length k. Basically instead of looking at 2 arrays, just look at one array:

This is straight forward, we can do it using the stack. Here is the algorithm:

  • stack = empty stack
  • FOREACH i from 0 to n:
    • WHILE stack is not empty AND top of stack < array[i] AND n - i + number of elements in stack > k
      • Pop from stack
    • IF number of elements in stack < k
      • Push array[i] to stack
  • Add all the elements of the stack in an array in reverse order and return this array
Let me explain what exactly "WHILE stack is not empty AND top of stack < array[i] AND n - i + number of elements in stack > k" condition is checking; basically its saying pop the top of stack if it is smaller than array[i] until either stack is empty or the number of digits left in the array is not enough to fill the stack to size k.

2. Given two array say array1 and array2 of length m and n, create maximum number of length k = m + n: This problem is lot closer to the original problem with the exception that we will use all the digits we have. 

Let's say result is the array of size k which we want to return. We can use the approach similar to merge method of merge sort where we just fill the greater digit in result by comparing current element of array1 and current element of array2 and increment the index of the array from where we find the larger digit. This looks good and works until we find current digits of both the arrays are same. Which one we should choose here? In case of merge sort, it does not matter which one we choose but here it does matter. Let's see it by example. Say we have following 2 arrays:
  1. [6, 0]
  2. [6, 7]
Now in this case when current digit of both array is 6, we just can't take any 6, we need to take 6 from array 2 as then we will get the maximum number which is [6, 7, 6, 0] otherwise if we took the 6 from array 1 the number which we have gotten is [6, 6, 7, 0] which is clearly not the maximum number. That means in case of same digits we need to look further into both strings until we get different digits and choose the array where we get the greater digit. So you see our merge algorithm will be almost same as merge method of merge sort except here we will use a method say IsGreater (which will look further in both strings until we get the different digits) instead of usual greater than '>' operator. Here is the algorithm for merge method:
  • result = []
  • i = 0, j = 0
  • FOR resultIndex = 0 To k
    • IF IsGreater(array1, i, array2, j) 
      • result[resultIndex] = array1[i]
      • i = i + 1
    • ELSE
      • result[resultIndex] = array2[j]
      • j = j + 1
  • return result 
Now here is the algorithm for IsGreater method:
  • WHILE array1[i] == array2[j] // Looking further till we get same digits
    • i = i + 1
    • j = j + 1
  • return array1[i] > array2[i]
Of course we need to handle some boundary conditions which you can see in the implementation in the above algorithm. I did not put it here just to make core logic simple to understand.

Now given we have solved above subproblems we can go back to our original problem which will be fairly easy to solve using the solution of above subproblems:
  1. First, we divide the k digits required into two parts; i and k-i. 
  2. Find the maximum number of length i in one array and the maximum number of length k-i in the other array using the algorithm of subproblem 1. 
  3. Merge the two results of step 2 in to one array using the algorithm of subproblem 2. 
  4. Compare the current result with final result and assign the current result to final result if current result is greater than final result. We can use IsGreater method of subproblem 2 to identify which one is greater.
That's all!


Implementation in C#:

        public static int[] MaxNumberOfTwoArray(int[] nums1, int[] nums2, int k)

        {

            if ( k > nums1?.Length + nums2?.Length)

            {

                return null;

            }

            int[] maxNumber = new int[k];

            // Dividing k into i and k -i

            for (int i = Math.Max(0, k - nums2.Length); i <= k && i <= nums1.Length; ++i)

            {

                int[] currNumber = MergeMaxNumbers(MaxNumberOfOneArray(nums1, i), MaxNumberOfOneArray(nums2, k - i), k);

                if (IsGreater(currNumber, 0, maxNumber, 0))

                {

                    maxNumber = currNumber;

                }

            }

            return maxNumber;

        }


        private static int[] MaxNumberOfOneArray(int[] nums, int k)

        {

            Stack<int> stack = new Stack<int>();

            for (int i = 0; i < nums.Length; ++i)

            {

                while (stack.Count > 0 && nums.Length - i + stack.Count > k && stack.Peek() < nums[i])

                {

                    stack.Pop();

                }

                if (stack.Count < k)

                {

                    stack.Push(nums[i]);

                }

            }

            int[] result = new int[k];

            for (int i = k - 1; i >= 0; --i)

            {

                result[i] = stack.Pop();

            }

            return result;

        }


        private static int[] MergeMaxNumbers(int[] nums1, int[] nums2, int k)

        {

            int[] result = new int[k];

            for (int nums1I = 0, nums2I = 0, resultI = 0; resultI < k; ++resultI)

            {

                result[resultI] = IsGreater(nums1, nums1I, nums2, nums2I) ? nums1[nums1I++] : nums2[nums2I++];

            }

            return result;

        }


        private static bool IsGreater(int[] nums1, int nums1I, int[] nums2, int nums2I)

        {

            while (nums1I < nums1.Length && nums2I < nums2.Length && nums1[nums1I] == nums2[nums2I])

            {

                ++nums1I;

                ++nums2I;

            }

            return nums2I == nums2.Length || (nums1I < nums1.Length && nums1[nums1I] > nums2[nums2I]);

        }


Complexity: O(k * (m + n)) where m is the size of nums1 and n is the size of nums2

Friday, October 30, 2020

Different Ways to Add Parentheses

Problem: Given a string of numbers and operators, return all possible results from computing all the different possible ways to group numbers and operators. The valid operators are +, - and *.

Example(Taken from leetcode):

Input: "2-1-1"
Output: [0, 2]
Explanation: 
((2-1)-1) = 0 
(2-(1-1)) = 2


Approach: We can solve this problem by parenthesizing all possible valid substring of the expression and then evaluating them, but as you can see it involves solving lots of subproblems which are already solved before or you can say solving lots of repeating subproblem which clearly tells us that we can use DP here. 

We can have a HashMap whose key is substring and value is all possible results for that substring. Basically we will memorize the results of a substring and whenever we try to solve the same substring again, we just get the result from the HashMap. 

For each character in the input string, we check if the current character is operator then we divide the input into two parts; one before operator and second after operator. Recursively solve each part and then combine the result in all possible ways.


Implementation in C#:

        public static IList<int> DiffWaysToCompute(string input)

        {

            if (string.IsNullOrWhiteSpace(input))

            {

                return new List<int>();

            }

            Dictionary<string, List<int>> memory = new Dictionary<string, List<int>>();

            return DiffWaysToCompute(input, ref memory);

        }


        private static List<int> DiffWaysToCompute(string input, ref Dictionary<string, List<int>> memory)

        {

            // Checking if the current string is already solved

            if (memory.ContainsKey(input))

            {

                return memory[input];

            }

            List<int> result = new List<int>();

            for (int i = 0; i < input.Length; ++i)

            {

                if (IsOperator(input[i]))

                {

                    List<int> resultsBeforeOperator = DiffWaysToCompute(input.Substring(0, i), ref memory);

                    List<int> resultAfterOperator = DiffWaysToCompute(input.Substring(i + 1), ref memory);

                    foreach(int preNum in resultsBeforeOperator)

                    {

                        foreach(int postNum in resultAfterOperator)

                        {

                            switch(input[i])

                            {

                                case '+': result.Add(preNum + postNum);

                                    break;

                                case '-': result.Add(preNum - postNum);

                                    break;

                                case '*': result.Add(preNum * postNum);

                                    break;

                            }

                        }

                    }                  

                }

            }

            // The whole string was a number

            if (result.Count == 0)

            {

                result.Add(int.Parse(input));

            }

            memory.Add(input, result);

            return result;

        }


        private static bool IsOperator(char ch)

        {

            return ch == '+' || ch == '-' || ch == '*';

        }


Complexity: O(2^n)

Monday, October 19, 2020

The Skyline Problem

Problem: (Taken from leetcode) A city's skyline is the outer contour of the silhouette formed by all the buildings in that city when viewed from a distance. Now suppose you are given the locations and height of all the buildings as shown on a cityscape photo (Figure A), write a program to output the skyline formed by these buildings collectively (Figure B).


The geometric information of each building is represented by a triplet of integers [Li, Ri, Hi], where Li and Ri are the x coordinates of the left and right edge of the ith building, respectively, and Hi is its height. It is guaranteed that 0 ≤ Li, Ri ≤ INT_MAX, 0 < Hi ≤ INT_MAX, and Ri - Li > 0. You may assume all buildings are perfect rectangles grounded on an absolutely flat surface at height 0.

For instance, the dimensions of all buildings in Figure A are recorded as: [ [2 9 10], [3 7 15], [5 12 12], [15 20 10], [19 24 8] ] .

The output is a list of "key points" (red dots in Figure B) in the format of [ [x1,y1], [x2, y2], [x3, y3], ... ] that uniquely defines a skyline. A key point is the left endpoint of a horizontal line segment. Note that the last key point, where the rightmost building ends, is merely used to mark the termination of the skyline, and always has zero height. Also, the ground in between any two adjacent buildings should be considered part of the skyline contour.

For instance, the skyline in Figure B should be represented as:[ [2 10], [3 15], [7 12], [12 0], [15 10], [20 8], [24, 0] ].

Notes:

  1. The number of buildings in any input list is guaranteed to be in the range [0, 10000].
  2. The input list is already sorted in ascending order by the left x position Li.
  3. The output list must be sorted by the x position.
  4. There must be no consecutive horizontal lines of equal height in the output skyline. For instance, [...[2 3], [4 5], [7 5], [11 5], [12 7]...] is not acceptable; the three lines of height 5 should be merged into one in the final output as such: [...[2 3], [4 5], [12 7], ...]


Approach: Using Merge sort approach (Divide and conquer); divide the given buildings in two subsets. Recursively construct skyline for two halves and finally merge the two skylines. Lets go to part of merging but before going there, let's understand what will happen if there is only one building with say {x, y, h} dimensions. It is easy to understand that the output skyline will be [{x, h}, {y, 0}]. 
Now let's see how to merge:
  • Say we have 2 subsets left_skyLines and right_skyLines. 
  • leftIndex = 0, rightIndex = 0, leftHeight = 0, rightHeight = 0
  • While leftIndex < left_skyLines.Size AND rightIndex < right_skyLines.Size
    • leftX = left_skyLines[leftIndex][0], rightX = right_skyLines[rightIndex][0]
    • curr_x = Min (leftX, rightY)
    • If leftX < rightX
      • leftHeight = left_skyLines[leftIndex][1]
      • leftIndex = leftIndex + 1
    • ELSE IF leftX > rightX
      • rightHeight = right_skyLines[rightIndex][1]
      • rightIndex = rightIndex + 1
    • ELSE
      • leftHeight = left_skyLines[leftIndex][1]
      • rightHeight = right_skyLines[rightIndex][1]
      • leftIndex = leftIndex + 1
      • rightIndex = rightIndex + 1
    • curr_height = Max(leftHeight, rightHeight)
    • Add {curr_x, curr_height} to result only of height of last element in result is not equal to curr_height to satisfy Note #4 in the problem statement.
  • Add the remaining skylines to result.
That's all! If you see it's just a merge sort and we just have to set right comparators.

Implementation in C#:

        public static IList<IList<int>> GetSkyline(int[][] buildings)

        {

            return GetSkylineRecur(buildings, 0, buildings.Length - 1);

        }


        private static List<IList<int>> GetSkylineRecur(int[][] buildings, int low, int high)

        {

            if (low > high)

            {

                return new List<IList<int>>();

            }

            if (low == high)

            {

                List<IList<int>> list = new List<IList<int>>();

                list.Add(new List<int> { buildings[low][0], buildings[low][2] });

                list.Add(new List<int> { buildings[low][1], 0 });

                return list;

            }

            int mid = (low + high) / 2;

            List<IList<int>> leftSkylines = GetSkylineRecur(buildings, low, mid);

            List<IList<int>> rightSkylines = GetSkylineRecur(buildings, mid + 1, high);

            return MergeSkylines(leftSkylines, rightSkylines);

        }


        private static List<IList<int>> MergeSkylines(List<IList<int>> leftSkylines, List<IList<int>> rightSkylines)

        {

            List<IList<int>> result = new List<IList<int>>();

            int hLeft = 0;

            int hRight = 0;

            int left = 0;

            int right = 0;

            while (left < leftSkylines.Count && right < rightSkylines.Count)

            {

                int currX;

                int currH;

                if (leftSkylines[left][0] < rightSkylines[right][0]) // comparing x 

                {

                    hLeft = leftSkylines[left][1];

                    currX = leftSkylines[left][0];

                    currH = Math.Max(hLeft, hRight);

                    ++left;

                }

                else if (leftSkylines[left][0] > rightSkylines[right][0])

                {

                    hRight = rightSkylines[right][1];

                    currX = rightSkylines[right][0];

                    currH = Math.Max(hLeft, hRight);

                    ++right;

                }

                else

                {

                    hLeft = leftSkylines[left][1];

                    hRight = rightSkylines[right][1];

                    currX = leftSkylines[left][0];

                    currH = Math.Max(hLeft, hRight);

                    ++left;

                    ++right;

                }

                if (result.Count <= 0 || result.Last()[1] != currH)

                {

                    result.Add(new List<int> { currX, currH });

                }

            }

            while(left < leftSkylines.Count)

            {

                result.Add(leftSkylines[left]);

                ++left;

            }

            while (right < rightSkylines.Count)

            {

                result.Add(rightSkylines[right]);

                ++right;

            }

            return result;

        }


Complexity: O(nLogn)