Remove minimum elements from either side such that 2*min becomes more than max

Author: Narendra Jha | Published on: March 15, 2020


Prerequisite: SegmentTree

Problem statement:

Given an unsorted array, trim the array such that twice of minimum is greater than the maximum in the trimmed array. Elements should be removed from either end of the array. The number of removals should be minimum.

Example:

	Input: arr[] = {4, 5, 100, 9, 10, 11, 12, 15, 200}
	Output: 4
	We need to remove 4 elements (4, 5, 100, 200)
	so that 2*min becomes more than max.
	
	Input: arr[] = {4, 7, 5, 6}
	Output: 0
	We don’t need to remove any element as 4*2 > 7
	
	Input: arr[] = {20, 7, 5, 6}
	Output: 1
	   

Solution:

An O(n2) Solution: We run two nested loops, the outer loop chooses a starting point and the inner loop chooses ending point for the current starting point. We keep track of longest subarray with the given property. Doing this, We find the maximum sized subarray such that 2*min > max.

However, we can do better than this.

An O(nlgn) Solution: We can solve this problem in O(n * logn) time using Sliding Window and Segment Tree concepts. Here is the approach:

  1. Construct Segment Tree for RangeMinimumQuery and RangeMaximumQuery for the given input array.
  2. Take two pointers start and end, and initialize both to 0.
  3. While end is less than the length of the input array, do the following:
    1. Find min and max in the current window using Segment Trees constructed in step 1.
    2. Check if 2 * min ≤ max, if so then increment start pointer else update max valid length so far, if required
    3. Increment end
  4. Return length(arr[]) – maxValidLength as the required answer.

Time Complexity:
In worst case, we will have to iterate through the entire array and in each iteration we find min and max in segment tree which takes O(logn). So that contributes O(n * logn). But before that, we also have to contruct segment trees which takes O(n) time.

So overall time complexity is T(n) = O(nlgn) + O(n) = O(nlgn)

Implementation:
Below is Java implementation of above algorithms:

	  
/**
 * Removes minimum number of elements from given array to
 *
 * @author Narendra Jha, njha.sde@gmail.com
 *
 */
public class RemoveMinElementsFromEitherSide {

    // T(n): O(n^2)
    public int removeMinElements(int[] a) {
        int n = a.length;
        int maxValidLen = 0;
        
        for (int start = 0; start < n; start++) {
            int min = Integer.MAX_VALUE; // min in current window
            int max = Integer.MIN_VALUE; // max in current window
            
            for (int end = start; end < n; end++) {
                min = Math.min(min, a[end]);
                max = Math.max(max, a[end]);
                if (2*min <= max)
                    break;
                
                int len = end-start+1;
                if (maxValidLen < len)
                    maxValidLen = len;
            }
        }
        return n - maxValidLen;
    }
    
    // T(n): O(nlgn) + O(n) = O(nlgn)
    class SolutionUsingRMQ {
        /*
         * Algorithm:
         * 1. Construct Segment Tree for RangeMinimumQuery and RangeMaximumQuery
         *    for given input array
         * 2. Take two pointers 'start' and 'end', and initialize both to 0
         * 3. While 'end' is less than length of input array, do following
         *    3.1 find min and max in current window using Segment Trees 
         *        constructed in step 1
         *    3.2 check if 2*min <= max, if so then increment 'start' pointer
         *        else update max valid length so far, if required 
         *    3.3 increment 'end'
         * 4. (inputArrayLength - maxValidLen) is your answer
         */
        public int removeMinElements(int[] a) {
            int n = a.length;
            
            RangeMinimumQuery rMinQ = new RangeMinimumQuery();
            int[] minTree = rMinQ.createSegmentTree(a);
            
            RangeMaximumQuery rMaxQ = new RangeMaximumQuery();
            int[] maxTree = rMaxQ.createSegmentTree(a);
            
            int start = 0, end = 0;
            int min, max; // min, max in current window
            int maxValidLen = 0;
            
            while (end < n) {
                min = rMinQ.rangeMinimumQuery(minTree, start, end, n);
                max = rMaxQ.rangeMaximumQuery(maxTree, start, end, n);
                if (2*min <= max)
                    start++;
                else
                    maxValidLen = Math.max(maxValidLen, end-start+1);
                end++;
            }
            return n - maxValidLen;
        }
    }
    
    public static void main(String[] args) {
        int[] a = {4, 5, 100, 9, 10, 11, 12, 15, 200};
        RemoveMinElementsFromEitherSide o = new RemoveMinElementsFromEitherSide();
        
        System.out.println(o.removeMinElements(a)); // 4
        
        System.out.println(o.new SolutionUsingRMQ().removeMinElements(a)); // 4
    }
    
    /**
     * Segment Tree - Range Minimum Query
     *
     * @author Narendra Jha, njha.sde@gmail.com
     */
    class RangeMinimumQuery {
        
        /**
         * Creates a new segment tree from given input array.
         */
        public int[] createSegmentTree(int[] input) {
            int n = input.length;
            int segTreeSize = 2 * getNextPowerOfTwo(n) - 1;
            int[] segmentTree = new int[segTreeSize];
            
            createSegmentTreeUtil(segmentTree, input, 0, n-1, 0);
            return segmentTree;
        }

        private void createSegmentTreeUtil(int[] segmentTree, int[] input, 
                int low, int high, int pos) {
            if (low == high) {
                // its a leaf node
                segmentTree[pos] = input[low];
                return;
            }
            
            // construct left and right subtrees and then update value for current node
            int mid = (low + high) / 2;
            createSegmentTreeUtil(segmentTree, input, low, mid, (2*pos + 1));
            createSegmentTreeUtil(segmentTree, input, mid+1, high, (2*pos + 2));
            segmentTree[pos] = Math.min(segmentTree[2*pos + 1], segmentTree[2*pos + 2]); 
        }
        
        /**
         * Queries given range for minimum value.
         */
        public int rangeMinimumQuery(int[] segmentTree, int from, int to, int inputSize){
            return rangeMinimumQueryUtil(segmentTree, 0, inputSize-1, from, to, 0);
        }

        private int rangeMinimumQueryUtil(int[] segmentTree, int low, int high, 
                int from, int to, int pos) {
            // total overlap
            if (from <= low && to >= high) {
                return segmentTree[pos];
            }
            
            // no overlap
            if (from > high || to < low) {
                return Integer.MAX_VALUE;
            }
            
            // partial overlap
            int mid = (low + high) / 2;
            int left = rangeMinimumQueryUtil(segmentTree, low, mid, from, to, (2*pos + 1));
            int right = rangeMinimumQueryUtil(segmentTree, mid+1, high, from, to, (2*pos + 2));
            return Math.min(left, right);
        }
    }

    /**
     * Segment Tree - Range Maximum Query
     *
     * @author Narendra Jha, njha.sde@gmail.com
     */
    class RangeMaximumQuery {
        
        /**
         * Creates a new segment tree from given input array.
         */
        public int[] createSegmentTree(int[] input) {
            int n = input.length;
            int segTreeSize = 2 * getNextPowerOfTwo(n) - 1;
            int[] segmentTree = new int[segTreeSize];
            
            createSegmentTreeUtil(segmentTree, input, 0, n-1, 0);
            return segmentTree;
        }

        private void createSegmentTreeUtil(int[] segmentTree, int[] input, 
                int low, int high, int pos) {
            if (low == high) {
                // its a leaf node
                segmentTree[pos] = input[low];
                return;
            }
            
            // construct left and right subtrees and then update value for current node
            int mid = (low + high) / 2;
            createSegmentTreeUtil(segmentTree, input, low, mid, (2*pos + 1));
            createSegmentTreeUtil(segmentTree, input, mid+1, high, (2*pos + 2));
            segmentTree[pos] = Math.max(segmentTree[2*pos + 1], segmentTree[2*pos + 2]); 
        }
        
        /**
         * Queries given range for maximum value.
         */
        public int rangeMaximumQuery(int[] segmentTree, int from, int to, int inputSize){
            return rangeMaximumQueryUtil(segmentTree, 0, inputSize-1, from, to, 0);
        }

        private int rangeMaximumQueryUtil(int[] segmentTree, int low, int high, 
                int from, int to, int pos) {
            // total overlap
            if (from <= low && to >= high) {
                return segmentTree[pos];
            }
            
            // no overlap
            if (from > high || to < low) {
                return Integer.MIN_VALUE;
            }
            
            // partial overlap
            int mid = (low + high) / 2;
            int left = rangeMaximumQueryUtil(segmentTree, low, mid, from, to, (2*pos + 1));
            int right = rangeMaximumQueryUtil(segmentTree, mid+1, high, from, to, (2*pos + 2));
            return Math.max(left, right);
        }
    }
    
    
    private int getNextPowerOfTwo(int n) {
        int logPart = (int) Math.ceil(Math.log(n) / Math.log(2));
        return (int) Math.pow(2, logPart);
    }
}
	  
	

See this solution on GitHub

Feel free to shoot your questions at njha.sde@gmail.com

© 2019 Narendra Jha. All rights reserved