LeetCode – 53 : Maximum Subarray

Given an integer array nums, find the contiguous subarray (containing at least one number) which has the largest sum and return its sum.

Example:

Input: [-2,1,-3,4,-1,2,1,-5,4], 
Output: 6
Explanation: [4,-1,2,1] has the largest sum = 6.

Follow up:

If you have figured out the O(n) solution, try coding another solution using the divide and conquer approach, which is more subtle.


class Solution {
    public int maxSubArray(int[] nums) {
        
    }
}

Problem Clarification

We need to consider sum of contiguous elements.

Idea - 1

Try each of O(n^2) subarray and keep track of the max sum. Time O(n^2), space O(1).

class Solution {
    public int maxSubArray(int[] nums) {
        int n = nums.length;
        int maxsum = Integer.MIN_VALUE;
        
        for(int i = 0; i < n; ++i)
        {
            int sum = 0;
            for(int j = i; j < n; ++j)
            {
                sum += nums[j];
                maxsum = Math.max(maxsum, sum);
            }
        }
        
        return maxsum;
    }
}


Runtime: 105 ms, faster than 5.03% of Java online submissions for Maximum Subarray.
Memory Usage: 49.3 MB, less than 5.05% of Java online submissions for Maximum Subarray.

Idea - 2

While considering if we should include a_i to the running sum, if the sum of a_0,\ a_1,\ \ldots\,\ a_{i-1} does not help a_i then we start a fresh run starting at a_i. The algorithm has time O(n) and space O(1).

class Solution {
    public int maxSubArray(int[] nums) {
        int maxsum = Integer.MIN_VALUE;
        int sum = 0;
        for(int i = 0; i < nums.length; ++i)
        {
            sum = Math.max(sum+nums[i], nums[i]);
            maxsum = Math.max(maxsum, sum);
        }
        
        return maxsum;
    }
}


Runtime: 1 ms, faster than 97.15% of Java online submissions for Maximum Subarray.
Memory Usage: 38.4 MB, less than 92.18% of Java online submissions for Maximum Subarray.

Idea - 3

If we break the array in middle and find solutions: one to the left and other to the right, then the global solution would be the maximum among these two solutions and the solution that cross boundary. Time O(n\lg{n}) and stack space O(\lg{n}).

class Solution {
    public int maxSubArray(int[] nums) {
        return maxSum(nums, 0, nums.length-1);
    }
    
    private int maxSum(int[] A, int lo, int hi)
    {
        if(lo > hi)
        {
            return 0;
        }
        
        if(lo == hi)
        {
            return A[lo];
        }
        
        int mid = lo+(hi-lo)/2;
        
        int leftSum = maxSum(A, lo, mid);
        int rightSum = maxSum(A, mid+1, hi);
        
        // last element to the left and first element
        // to the right must be in the crossing solution
        //
        int middleSum = mid+1 > hi ? 0 : A[mid+1];
        middleSum += A[mid];
        
        // can we increase going to the right
        //
        int sum = middleSum;
        for(int j = mid+2; j <= hi; ++j)
        {
            sum += A[j];
            middleSum = Math.max(middleSum, sum);
        }
        
        // can we increase going to the left
        //
        sum = middleSum;
        for(int j = mid-1; j >= lo; --j)
        {
            sum += A[j];
            middleSum = Math.max(middleSum, sum);
        }
        
        return Math.max( middleSum, Math.max(leftSum, rightSum) );
    }
}


Runtime: 3 ms, faster than 74.42% of Java online submissions for Maximum Subarray.
Memory Usage: 38.5 MB, less than 92.00% of Java online submissions for Maximum Subarray.

LeetCode – 4 : Valid Parentheses

Given a string containing just the characters '('')''{''}''[' and ']', determine if the input string is valid.

An input string is valid if:

  1. Open brackets must be closed by the same type of brackets.
  2. Open brackets must be closed in the correct order.

Note that an empty string is also considered valid.

Example 1:

Input: "()" 
Output: true

Example 2:

Input: "()[]{}" 
Output: true

Example 3:

Input: "(]" 
Output: false

Example 4:

Input: "([)]" 
Output: false

Example 5:

Input: "{[]}" 
Output: true

class Solution {
    public boolean isValid(String s) {
        
    }
}

Problem Clarification

Idea - 1

A close must always be preceded by an open of the right kind. If we use a stack, and on seeing a close if we pop the right kind of open from the stack, then in the end either there will be only opens left or the stack would be empty - later would be the valid case. The algorithm has time O(n) and space O(n).

class Solution {
    
    private static final String closes = ")}]";
    
    public boolean isValid(String s) {
        int n = s.length();
        Deque<Character> stack = new ArrayDeque<>();
        for(int i = 0; i < n; ++i)
        {
            char c = s.charAt(i);
            
            if(closes.indexOf(c) >= 0)
            {
                if(stack.isEmpty() || !match(c, stack.removeLast()))
                {
                    return false;
                }
            }
            else
            {
                stack.addLast(c);
            }
        }
        
        return stack.isEmpty();
    }
    
    private boolean match(char close, char open)
    {
        if(closes.indexOf(open) >= 0)
        {
            return false;
        }
        
        return (close==')' && open=='(')
            || (close=='}' && open=='{')
            || (close==']' && open=='[');
    }
}


Runtime: 1 ms, faster than 99.54% of Java online submissions for Valid Parentheses.
Memory Usage: 35.6 MB, less than 36.87% of Java online submissions for Valid Parentheses.

LeetCode – 4 : K Closest Points to Origin

We have a list of points on the plane.  Find the K closest points to the origin (0, 0).

(Here, the distance between two points on a plane is the Euclidean distance.)

You may return the answer in any order.  The answer is guaranteed to be unique (except for the order that it is in.)

Example 1:

Input: points = [[1,3],[-2,2]], K = 1
Output: [[-2,2]]
Explanation: 
The distance between (1, 3) and the origin is sqrt(10).
The distance between (-2, 2) and the origin is sqrt(8).
Since sqrt(8) < sqrt(10), (-2, 2) is closer to the origin.
We only want the closest K = 1 points from the origin, so the answer is just [[-2,2]].

Example 2:

Input: points = [[3,3],[5,-1],[-2,4]], K = 2
Output: [[3,3],[-2,4]]
(The answer [[-2,4],[3,3]] would also be accepted.)

Note:

  1. 1 <= K <= points.length <= 10000
  2. -10000 < points[i][0] < 10000
  3. -10000 < points[i][1] < 10000

class Solution {
    public int[][] kClosest(int[][] points, int K) {
        
    }
}

Idea - 1

Sort by distance from origin and take the first K points. Time O(n\lg{n}) and space O(K).

class Solution {
    public int[][] kClosest(int[][] points, int K) {
        int n = points.length;
        
        Arrays.sort( points, new Comparator<int[]>()
                    {
                        public int compare(int[] a, int[] b)
                        {
                            return dist(a)-dist(b);
                        }
                    });
        
        int[][] sol = new int[K][2];
        for(int i = 0; i < K; ++i)
        {
            sol[i] = points[i];
        }
        
        return sol;
    }
    
    private int dist(int[] p)
    {
        return p[0]*p[0] + p[1]*p[1];
    }
}


Runtime: 26 ms, faster than 69.27% of Java online submissions for K Closest Points to Origin.
Memory Usage: 54.7 MB, less than 92.47% of Java online submissions for K Closest Points to Origin.

Idea - 2

Let's take the first K points as a solution candidate. (K+1)-th point can be added to the solution if it improves the situation, therefore, if it is closer to origin than the worst in current solution set. So, keeping a max heap and whenever it has size > K removing the worst would give us the closest K in the end. Time O(K\lg{K}) and space O(K).

class Solution {
    public int[][] kClosest(int[][] points, int K) {
        int n = points.length;
        
        PriorityQueue<int[]> maxq = new PriorityQueue<int[]>(new Comparator<int[]>()
                                                             {
                                                                 public int compare(int[] a, int[] b)
                                                                 {
                                                                     return dist(b)-dist(a);
                                                                 }
                                                             });
        
        for(int[] p : points)
        {
            maxq.offer(p);
            if(maxq.size() > K)
            {
                maxq.poll();
            }
        }
        
        int[][] sol = new int[K][2];
        int i = 0;
        while(!maxq.isEmpty())
        {
            sol[i++] = maxq.poll();
        }
        
        return sol;
    }
    
    private int dist(int[] p)
    {
        return p[0]*p[0] + p[1]*p[1];
    }
}


Runtime: 35 ms, faster than 55.95% of Java online submissions for K Closest Points to Origin.
Memory Usage: 63 MB, less than 23.78% of Java online submissions for K Closest Points to Origin.

Idea - 3

We can sort the distances and collect K of the ones that are less or equal to K-th in the sorted distance. Time O(n\lg{n}) and space O(K).

class Solution {
    public int[][] kClosest(int[][] points, int K) {
        int n = points.length;
        int[] dist = new int[n];
        for(int i = 0; i < n; ++i)
        {
            dist[i] = dist(points[i]);
        }
        
        Arrays.sort(dist);
        int Kth = dist[K-1];
        int[][] sol = new int[K][2];
        int j = 0;
        for(int i = 0; i < n && j < K; ++i)
        {
            if(dist(points[i]) <= Kth)
            {
                sol[j++] = points[i];
            }
        }
        
        return sol;
    }
    
    private int dist(int[] p)
    {
        return p[0]*p[0] + p[1]*p[1];
    }
}


Runtime: 15 ms, faster than 80.58% of Java online submissions for K Closest Points to Origin.
Memory Usage: 67.3 MB, less than 5.04% of Java online submissions for K Closest Points to Origin.

Idea - 4

We can use partitioning idea from quick sort and as soon as the partition is the one we want (the pivot is the K-th in sorted order) we are done. Worst case time is O(n^2), however average case is O(n). Worst case stack space is O(n) and average case stack space is O(\lg{n}).

class Solution {
    
    private static final Random rng = new Random(System.currentTimeMillis()%Integer.MAX_VALUE);
    
    public int[][] kClosest(int[][] points, int K) {
        int n = points.length;
        int[] dist = new int[n];
        for(int i = 0; i < n; ++i)
        {
            dist[i] = dist(points[i]);
        }
        
        partition(dist, 0, n-1, K);
        
        int Kth = dist[K-1];
        int[][] sol = new int[K][2];
        int j = 0;
        for(int i = 0; i < n && j < K; ++i)
        {
            if(dist(points[i]) <= Kth)
            {
                sol[j++] = points[i];
            }
        }
        
        return sol;
    }
    
    private void partition(int[] A, int lo, int hi, int K)
    {
        if(lo > hi)
        {
            return;
        }
        
        int pi = lo+rng.nextInt(hi-lo+1);
        int pivot = A[pi];
        int i = lo-1, j = hi, k = lo;
        while(k <= j)
        {
            if(A[k] < pivot)
            {
                swap(A, ++i, k++);
            }
            else if(A[k] > pivot)
            {
                swap(A, k, j--);
            }
            else
            {
                ++k;
            }
        }
        
        // j-th is the K-th element in the array
        if(K==j+1)
        {
            return;
        }
        else if(K<j+1)
        {
            partition(A, lo, j-1, K);
        }
        else
        {
            partition(A, j+1, hi, K);    
        }
    }
    
    private void swap(int[] A, int i, int j)
    {
        int tmp = A[i];
        A[i] = A[j];
        A[j] = tmp;
    }
    
    private int dist(int[] p)
    {
        return p[0]*p[0] + p[1]*p[1];
    }
}


Runtime: 8 ms, faster than 92.25% of Java online submissions for K Closest Points to Origin.
Memory Usage: 60.9 MB, less than 47.54% of Java online submissions for K Closest Points to Origin.

LeetCode – 21 : Merge Two Sorted Lists

Merge two sorted linked lists and return it as a new list. The new list should be made by splicing together the nodes of the first two lists.

Example:

Input: 1->2->4, 1->3->4 
Output: 1->1->2->3->4->4

Problem Clarification

We cannot copy the nodes.

Idea – 1

Merge routine in mergesort, time O(m+n) and space O(1).

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        ListNode dummy = new ListNode(-1);
        ListNode t = dummy;
        
        while(l1 != null && l2 != null)
        {
            if(l1.val < l2.val)
            {
                t.next = l1;
                l1 = l1.next;
            }
            else
            {
                t.next = l2;
                l2 = l2.next;
            }
            
            t = t.next;
        }
        
        if(l1 != null)
        {
            t.next = l1;
        }
        else
        {
            t.next = l2;
        }
        
        return dummy.next;
    }
}


Runtime: 1 ms, faster than 88.84% of Java online submissions for Merge Two Sorted Lists.
Memory Usage: 37.1 MB, less than 97.90% of Java online submissions for Merge Two Sorted Lists.

LeetCode – 56 : Merge Intervals

Given a collection of intervals, merge all overlapping intervals.

Example 1:

Input: [[1,3],[2,6],[8,10],[15,18]] 
Output: [[1,6],[8,10],[15,18]]
Explanation: Since intervals [1,3] and [2,6] overlaps, merge them into [1,6].

Example 2:

Input: [[1,4],[4,5]] 
Output: [[1,5]]
Explanation: Intervals [1,4] and [4,5] are considered overlapping.

NOTE: input types have been changed on April 15, 2019. Please reset to default code definition to get new method signature.


class Solution {
    public int[][] merge(int[][] intervals) {
        
    }
}

Problem Clarification

[1, 4] and [4, 5] overlap, thus if even ends meet, we need to merge them.

Idea - 1

For two intervals [a1, b1] and [a2, b2] if we have a1 ≤ a2 then they overlap if b1 ≥ a2. One the other hand, if b1 < a2, then [a1, b1] does not overlap with [a2, b2] or with any interval [ai, bi] where b1 < ai. In that case we can output [a1, b1] and move on. Thus sorting helps. The resulting algorithm has time O(n\lg{n}) and space O(n).

class Solution {
    public int[][] merge(int[][] intervals) {
        int n = intervals.length;
        if(n < 2)
        {
            return intervals;
        }
        
        Arrays.sort( intervals, new Comparator<int[]>()
                    {
                        public int compare(int[] a, int[] b)
                        {
                            return a[0]-b[0];
                        }
                    });
        
        List<int[]> tmp = new ArrayList<>();
        int[] prev = intervals[0];
        for(int i = 1; i < n; ++i)
        {
            int[] curr = intervals[i];
            if(prev[1] >= curr[0]) // overlaps
            {
                prev[0] = Math.min(prev[0], curr[0]);
                prev[1] = Math.max(prev[1], curr[1]);
            }
            else
            {
                tmp.add(prev);
                prev = curr;
            }
        }
        
        tmp.add(prev);
        
        int[][] result = new int[tmp.size()][2];
        for(int i = 0; i < tmp.size(); ++i)
        {
            result[i] = tmp.get(i);
        }
        
        return result;
    }
}


Runtime: 6 ms, faster than 96.10% of Java online submissions for Merge Intervals.
Memory Usage: 43.9 MB, less than 61.07% of Java online submissions for Merge Intervals.

LeetCode – 15 : 3Sum

Given an array nums of n integers, are there elements abc in nums such that a + bc = 0? Find all unique triplets in the array which gives the sum of zero.

Note:

The solution set must not contain duplicate triplets.

Example:

Given array nums = [-1, 0, 1, 2, -1, -4],

A solution set is:
[
  [-1, 0, 1],
  [-1, -1, 2]
]
class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        
    }
}

Problem Clarification

Each pair of triplets in the solution must differ in at least one element.

Idea – 1

We first sort the array to facilitate comparing two triplets to see if they are unique. So, all triplets we would generate would be sorted as well, which is fine. Now, consider a triplet \langle a,\ b,\ c\rangle. If a+b+c\ =\ 0 then we have a+b\ =\ -c. Thus if two triplets has the same any two elements then the triplets are not unique. We need to ensure that two triplets either have different a’s or different b’s. If we ensure the following two conditions, we would collect all unique solutions: 1. We batch all triplets that have a as the first element but have different b’s. All elements in such a batch thus differ in b and would be considered as unique solutions. 2. After step 1, we now move onto a different a (never considering the a in an earlier batch) and repeat step 1. We would not miss a solution, because for each solution triplet, the first of its three elements (in sorted order) must be in one of the batches – since we consider each distinct element as the first element in some batch. For that batch, we have considered all distinct elements that come after a (in sorted order) as the second element. Given two elements the third element is uniquely determined. The algorithm has O(n\cdot\ lg{n})\ +\ O(n^2)\ =\ O(n^2) time complexity and O(1) space complexity.
class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        List<List<Integer>> sol = new ArrayList<>();
        int n = nums.length;
        if(n < 3)
        {
            return sol;
        }
        
        Arrays.sort(nums);
        
        for(int i = 0; i < n-2; ++i)
        {
            // make sure two batches differ in first element
            if(i > 0 && nums[i-1]==nums[i])
            {
                continue;
            }
            
            int j = i+1, k = n-1;
            while(j < k)
            {
                // make sure in a batch, two triplets differ in the second element
                if(j > i+1 && nums[j-1]==nums[j])
                {
                    ++j;
                }
                else
                {
                    int sum = nums[i]+nums[j]+nums[k];
                    if(sum == 0)
                    {
                        sol.add(Arrays.asList(nums[i], nums[j], nums[k]));
                        ++j;
                        --k;
                    }
                    else if(sum < 0) // increase sum
                    {
                        ++j;
                    }
                    else // decrease sum
                    {
                        --k;
                    }
                }
            }
        }
        
        return sol;
    }
}

Runtime: 38 ms, faster than 72.31% of Java online submissions for 3Sum.
Memory Usage: 49.6 MB, less than 36.09% of Java online submissions for 3Sum.

LeetCode -3 : Longest Substring Without Repeating Characters

Given a string, find the length of the longest substring without repeating characters.

Example 1:

Input: "abcabcbb"
Output: 3 
Explanation: The answer is "abc", with the length of 3. 

Example 2:

Input: "bbbbb"
Output: 1
Explanation: The answer is "b", with the length of 1.

Example 3:

Input: "pwwkew"
Output: 3
Explanation: The answer is "wke", with the length of 3. 
             Note that the answer must be a substring, "pwke" is a subsequence and not a substring.

Problem Clarification

We need to find the longest contiguous stretch that have all different characters. In other words, if such a contiguous stretch has length n, then there are n different characters.

Idea – 1

Try each of the O(n^2) substring and see if it has all different characters. Time O(n^3) and space O(n).

class Solution {
    public int lengthOfLongestSubstring(String s) {
        int n = s.length();
        if(n == 0)
        {
            return 0;
        }
        
        int max = 1;
        for(int len = n; len > 1; --len)
        {
            int i = 0;
            while(i+len <= n)
            {
                int j = i+len-1;
                if(allDistinct(s, i, j))
                {
                    return len;
                }
                
                ++i;
            }
        }
        
        return max;
    }
    
    private boolean allDistinct(String s, int begin, int end)
    {
        HashSet<Character> seen = new HashSet<>();
        for(int i = begin; i <= end; ++i)
        {
            seen.add(s.charAt(i));
            if(seen.size() < i-begin+1)
            {
                return false;
            }
        }
        
        return true;
    }
}


Time limit exceeded for an input of length 31,000.

Idea - 2

Say we have a window [i, j] of contiguous characters in the string where all characters are distinct. We now try to expand the window by adding (j+1)-th character of the string if it is not already in [i, j]. If we cannot, we have an optimal stretch ending at j-th index. To find a new solution we need to shrink the window and we shrink as little as possible. We need to keep track where the window started and the last index where we have seen a character. The resulting algorithm has time O(n) and space O(n).

class Solution {
    public int lengthOfLongestSubstring(String s) {
        int n = s.length();
        if(n == 0)
        {
            return 0;
        }
        
        HashMap<Character, Integer> map = new HashMap<>();
        int begin = 0;
        int max = 0;
        int i = 0;
        while(i < n)
        {
            char c = s.charAt(i);
            if(map.containsKey(c) && map.get(c) >= begin) // cannot expand
            {
                max = Math.max(max, i-begin);
                
                // shrink
                begin = map.get(c)+1;
            }
            
            map.put(c, i);
            
            ++i;
        }
        
        return Math.max(max, i-begin);
    }
}


Runtime: 8 ms, faster than 91.96% of Java online submissions for Longest Substring Without Repeating Characters.
Memory Usage: 38.8 MB, less than 28.87% of Java online submissions for Longest Substring Without Repeating Characters.

LeetCode – 904 : Fruit Into Baskets

In a row of trees, the i-th tree produces fruit with type tree[i].

You start at any tree of your choice, then repeatedly perform the following steps:

  1. Add one piece of fruit from this tree to your baskets.  If you cannot, stop.
  2. Move to the next tree to the right of the current tree.  If there is no tree to the right, stop.

Note that you do not have any choice after the initial choice of starting tree: you must perform step 1, then step 2, then back to step 1, then step 2, and so on until you stop.

You have two baskets, and each basket can carry any quantity of fruit, but you want each basket to only carry one type of fruit each.

What is the total amount of fruit you can collect with this procedure?

Example 1:

Input: [1,2,1] 
Output: 3
Explanation: We can collect [1,2,1].

Example 2:

Input: [0,1,2,2] 
Output: 3
Explanation: We can collect [1,2,2]. If we started at the first tree, we would only collect [0, 1].

Example 3:

Input: [1,2,3,2,2] 
Output: 4
Explanation: We can collect [2,3,2,2]. If we started at the first tree, we would only collect [1, 2].

Example 4:

Input: [3,3,3,1,2,1,1,2,3,3,4] 
Output: 5
Explanation: We can collect [1,2,1,1,2]. If we started at the first tree or the eighth tree, we would only collect 4 fruits.

Note:

  1. 1 <= tree.length <= 40000
  2. 0 <= tree[i] < tree.length

class Solution {
    public int totalFruit(int[] tree) {
        
    }
}

Problem Clarification

We need to find length of the longest subarray that contains at most 2 distinct numbers.

Idea - 1

We try each of the O(n^2) subarrays and check if the subarray contains only 2 distinct numbers. Time O(n^3) and space O(n).

class Solution {
    public int totalFruit(int[] tree) {
        int n = tree.length;
        int max = 1;
        for(int len = n; len > 1; --len)
        {
            int i = 0;
            while(i+len <= n)
            {
                int j = i+len-1;
                if( containsAtMostTwo(tree, i, j) )
                {
                    return len;
                }
                
                ++i;
            }
        }
        
        return max;
    }
    
    private boolean containsAtMostTwo(int[] tree, int begin, int end)
    {
        HashSet<Integer> set = new HashSet<>();
        for(int i = begin; i <= end; ++i)
        {
            set.add(tree[i]);
            
            if(set.size() > 2)
            {
                return false;
            }
        }
        
        return true;
    }
}


Time limit exceeded on an input of size 3000.

Idea - 2

Starting with a window of 1-length window containing begin and end index [0, 0], we can try to grow it rightwards as long as adding the new element would not cause the window to have more than 2 distinct elements. When we see an element that we cannot add to the window, we can no longer grow the current window. Now we shrink as little as possible so that the window has copies of just one element so that we can add the new element. Thus the states we need to keep track of are: which two integers are in the current window, the last indices of the two integers in the current window and the beginning of the window.
We have a O(n) time and O(1) space algorithm.

class Solution {
    public int totalFruit(int[] tree) {
        int n = tree.length;
        int startOfWindow = 0;
        int typeA = 0, typeB = 0;
        int maxLength = 0;
        int lastIndexOfTypeA = -1, lastIndexOfTypeB = -1;
        int i = 0;
        while(i < n)
        {
            if(lastIndexOfTypeA < 0)
            {
                lastIndexOfTypeA = i;
                typeA = tree[i];
            }
            else if(tree[i]==typeA)
            {
                lastIndexOfTypeA = i;
            }
            else if(lastIndexOfTypeB < 0)
            {
                lastIndexOfTypeB = i;
                typeB = tree[i];
            }
            else if(tree[i]==typeB)
            {
                lastIndexOfTypeB = i;
            }
            else // tree[i] is a 3rd type
            {
                maxLength = Math.max(maxLength, i-startOfWindow);
                if(lastIndexOfTypeA < lastIndexOfTypeB)
                {
                    startOfWindow = lastIndexOfTypeA+1;
                    typeA = tree[i];
                    lastIndexOfTypeA = i;
                }
                else
                {
                    startOfWindow = lastIndexOfTypeB+1;
                    typeB = tree[i];
                    lastIndexOfTypeB = i;
                }
            }
            
            ++i;
        }
        
        return Math.max( maxLength, i-startOfWindow );
    }
}


Runtime: 5 ms, faster than 99.82% of Java online submissions for Fruit Into Baskets.
Memory Usage: 48.4 MB, less than 84.46% of Java online submissions for Fruit Into Baskets.

LeetCode – 42 : Trapping Rain Water

Given n non-negative integers representing an elevation map where the width of each bar is 1, compute how much water it is able to trap after raining.


The above elevation map is represented by array [0,1,0,2,1,0,1,3,2,1,2,1]. In this case, 6 units of rain water (blue section) are being trapped. Thanks Marcos for contributing this image!

Example:

Input: [0,1,0,2,1,0,1,3,2,1,2,1]
Output: 6

Problem Clarification

The image clarifies it.

Idea – 1

We consider how much water we can trap considering a certain bar as the right end, how far left do we need to go to find the left end? Of course we need to subtract the area of the inbetween bars.
From the figure above, we see we need to go left until we find a bar that has same or higher height than the right end bar or in case all bars to the left are shorter in height we take the tallest among them. So, for each index j, if we precompute which index i to its left has the biggest height then in one pass from right to left we can compute the total trapped water. Time O(n) and space O(n).

class Solution {
    public int trap(int[] height) {
        int n = height.length;
        
        // need at least 3 indices to trap some water
        if(n < 3)
        {
            return 0;
        }
        
        int[] maxIndex = new int[n];
        maxIndex[0] = 0;
        for(int i = 1; i < n; ++i)
        {
            if( height[i] > height[maxIndex[i-1]] )
            {
                maxIndex[i] = i;
            }
            else
            {
                maxIndex[i] = maxIndex[i-1];
            }
        }
        
        int water = 0;
        
        // j is the index of right end bar
        // i would be the index of left end bar
        // min height of these two ends would define
        // height of the rectangle
        //
        int j = n-1;
        while(j > 1) // need at least three indices to trap some water
        {
            int i = j-1;
            int leftEnd = maxIndex[i];
            int inbetweenArea = 0;
            while(i > leftEnd)
            {
                if(height[i] >= height[j])
                {
                    break;
                }
                
                inbetweenArea += height[i];
                
                --i;
            }
            
            int w = j-i-1;
            int h = Math.min( height[i], height[j] );
            water += (w*h - inbetweenArea);
            
            // left end of previous rectangle
            // becomes right end of the new rectangle
            //
            j = i;
        }
        
        return water;
    }
}


Runtime: 1 ms, faster than 99.92% of Java online submissions for Trapping Rain Water.
Memory Usage: 38.9 MB, less than 81.28% of Java online submissions for Trapping Rain Water.

Idea - 2

We consider how much water a bar can hold on top of it, summing these up would give the result. For a given bar we are only interested in the highest bar to its left and the highest bar to its right, because the shorter among these two would determine how high the water can stay on the given bar.
To update leftmax (highest bar to the left) and rightmax (highest bar to the right) we keep switching directions as needed. This gives us a O(n) time and O(1) space algorithm.

class Solution {
    public int trap(int[] height) {
        int n = height.length;
        
        // need at least 3 indices to trap some water
        if(n < 3)
        {
            return 0;
        }
        
        int water = 0;
        int leftmax = 0, rightmax = 0;
        int i = 0, j = n-1;
        while(i < j)
        {
            if(height[i] < height[j]) // keep going left to right
            {
                leftmax = Math.max(leftmax, height[i]);
                water += (leftmax-height[i]);
                ++i;
            }
            else // switch direction, go right to left
            {
                rightmax = Math.max(rightmax, height[j]);
                water += (rightmax-height[j]);
                --j;
            }
        }
        
        return water;
    }
}


Runtime: 1 ms, faster than 99.92% of Java online submissions for Trapping Rain Water.
Memory Usage: 37.7 MB, less than 94.17% of Java online submissions for Trapping Rain Water.

LeetCode – 929 : Unique Email Addresses

Every email consists of a local name and a domain name, separated by the @ sign.

For example, in alice@leetcode.comalice is the local name, and leetcode.com is the domain name.

Besides lowercase letters, these emails may contain '.'s or '+'s.

If you add periods ('.') between some characters in the local name part of an email address, mail sent there will be forwarded to the same address without dots in the local name.  For example, "alice.z@leetcode.com" and "alicez@leetcode.com" forward to the same email address.  (Note that this rule does not apply for domain names.)

If you add a plus ('+') in the local name, everything after the first plus sign will be ignored. This allows certain emails to be filtered, for example m.y+name@email.com will be forwarded to my@email.com.  (Again, this rule does not apply for domain names.)

It is possible to use both of these rules at the same time.

Given a list of emails, we send one email to each address in the list.  How many different addresses actually receive mails? 

Problem Clarification

What is the boundary of local name and domain name? The last ‘@’ symbol. The rules apply to local name only.

Idea – 1

We break each email into local name and domain name. We then apply the rules on the local name portion, the result is a normalized local name. We then combine the normalized local name with the domain name to find a desired email and we keep track of unique such emails using a hash set. If there are n strings and the length of the longest string is m, the time and space both is O(m\cdot n).

class Solution {
    public int numUniqueEmails(String[] emails) {
        HashSet<String> emailSet = new HashSet<String>();
        for(String email : emails)
        {
            int indexOfLastAddressSymbol = email.lastIndexOf('@');
            String localName = email.substring(0, indexOfLastAddressSymbol);
            String domainName = email.substring(indexOfLastAddressSymbol);
            
            String normalizedEmail = normalize(localName)+domainName;
            emailSet.add(normalizedEmail);
        }
        
        return emailSet.size();
    }
    
    private String normalize(String localName)
    {
        StringBuilder sb = new StringBuilder();
        for(int i = 0; i < localName.length(); ++i)
        {
            char c = localName.charAt(i);
            if(c == '+')
            {
                break;
            }
            else if(c != '.')
            {
                sb.append(c);
            }
        }
        
        return sb.toString();
    }
}


Runtime: 6 ms, faster than 99.60% of Java online submissions for Unique Email Addresses.
Memory Usage: 38.6 MB, less than 95.07% of Java online submissions for Unique Email Addresses.