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.

LeetCode – 5 : Longest Palindromic Substring

Given a string s, find the longest palindromic substring in s. You may assume that the maximum length of s is 1000.

Example 1:

Input: "babad"
Output: "bab"
Note: "aba" is also a valid answer.

Example 2:

Input: "cbbd"
Output: "bb"

class Solution {
    public String longestPalindrome(String s) {
        
    }
}

Problem Clarification

The longest palindromic substring must have characters that are adjacent in the original string.

Idea - 1

We can check each of the O(n^2) substrings and see if it is palindromic in O(n) time and keep track of the longest among them. Total time is O(n^3) and space is O(1).

class Solution {
    public String longestPalindrome(String s) {
        
        int n = s.length();
        if(n == 0)
        {
            return "";
        }
        
        int maxLength = 1;
        int longestStart = 0, longestEnd = 0;
        for(int i = 0; i < n-1; ++i)
        {
            for(int j = i+1; j < n; ++j)
            {
                if(palindromic(s, i, j) && j-i+1 > maxLength)
                {
                    maxLength = j-i+1;
                    longestStart = i;
                    longestEnd = j;
                }
            }
        }
        
        return s.substring(longestStart, longestEnd+1);
    }
    
    private boolean palindromic(String s, int begin, int end)
    {
        while(begin < end)
        {
            if(s.charAt(begin) != s.charAt(end))
            {
                return false;
            }
            
            ++begin;
            --end;
        }
        
        return true;
    }
}


Runtime: 1541 ms, faster than 5.00% of Java online submissions for Longest Palindromic Substring.
Memory Usage: 37.3 MB, less than 94.27% of Java online submissions for Longest Palindromic Substring.

Idea - 2

If c_0c_1\ \ldots\ c_{n-2}c_{n-1} is a palindrome, then it follows that c_0\ =\ c_{n-1} and c_1c_2\ldots c_{n-2} is a palindrome. So, if we find out which 2-length substrings are palindrome then finding if a 4-length substring is a palindrome take O(1) time. So, overall we need to find which of the O(n^2) substrings are palindrome and each can be checked in O(1) time making overall time complexity O(n^2) and space complexity O(n^2) as well.

class Solution {
    public String longestPalindrome(String s) {
        
        int n = s.length();
        if(n == 0)
        {
            return "";
        }
        
        boolean[][] dp = new boolean[n][n];
        
        // all 1-length substrings are palindromes
        for(int i = 0; i < n; ++i)
        {
            dp[i][i] = true;
        }
        
        int maxLength = 1;
        int longestBegin = 0, longestEnd = 0;
        for(int length = 2; length <= n; ++length)
        {
            int i = 0;
            while(i+length <= n)
            {
                int j = i+length-1;
                // i-th and j-th char match and these are the only chars or
                // the in between chars form palindrome
                if(s.charAt(i)==s.charAt(j) && ( i+1 > j-1 || dp[i+1][j-1] ))
                {
                    dp[i][j] = true;
                    
                    if(length > maxLength)
                    {
                        maxLength = length;
                        longestBegin = i;
                        longestEnd = j;
                    }
                }
                
                ++i;
            }
        }
        
        return s.substring(longestBegin, longestEnd+1);
    }
}


Runtime: 46 ms, faster than 40.03% of Java online submissions for Longest Palindromic Substring.
Memory Usage: 39.4 MB, less than 19.86% of Java online submissions for Longest Palindromic Substring.

Idea - 3

A palindrome can be checked starting at two ends and going inwards or starting at a center going outwards. The latter approach has the benefit that there are only O(n) centers, so we end up with an algorithm with time O(n^2) and space O(1). Centers are of two types: falls on a character, falls between two characters.
class Solution {
    
    class IndexPair
    {
        int i, j;
        public IndexPair(int i, int j)
        {
            this.i = i;
            this.j = j;
        }
        
        public int length()
        {
            return j-i+1;
        }
    }
    
    public String longestPalindrome(String s) {
        
        int n = s.length();
        if(n == 0)
        {
            return "";
        }
        
        int maxLength = 1;
        IndexPair sol = new IndexPair(0, 0);
        for(int center = 0; center < n; ++center)
        {
            IndexPair candidate = longestFromCenter(s, center);
            if(candidate.length() > sol.length())
            {
                sol = candidate;
            }
        }
        
        return s.substring(sol.i, sol.j+1);
    }
    
    private IndexPair longestFromCenter(String s, int center)
    {
        int n = s.length();
        
        // falls on a character
        int left1 = center-1;
        int right1 = center+1;
        while(left1 >= 0 && right1 < n && s.charAt(left1)==s.charAt(right1))
        {
            --left1;
            ++right1;
        }
        
        int length1 = right1-left1-1;
        
        // falls between two characters
        int left2 = center-1;
        int right2 = center;
        while(left2 >= 0 && right2 < n && s.charAt(left2)==s.charAt(right2))
        {
            --left2;
            ++right2;
        }
        
        int length2 = right2-left2-1;
        
        if(length1 > length2)
        {
            return new IndexPair(left1+1, right1-1);
        }
        else
        {
            return new IndexPair(left2+1, right2-1);
        }
    }
}

Runtime: 14 ms, faster than 64.48% of Java online submissions for Longest Palindromic Substring.
Memory Usage: 37.1 MB, less than 95.43% of Java online submissions for Longest Palindromic Substring.

LeetCode – 4 : Median of Two Sorted Arrays

There are two sorted arrays nums1 and nums2 of size m and n respectively.

Find the median of the two sorted arrays. The overall run time complexity should be O(log (m+n)).

You may assume nums1 and nums2 cannot be both empty.

Example 1:

nums1 = [1, 3]
nums2 = [2]

The median is 2.0

Example 2:

nums1 = [1, 2]
nums2 = [3, 4]

The median is (2 + 3)/2 = 2.5

class Solution {
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
        
    }
}

Problem Clarification

Median of a single sorted array divides the array into two halves of equal length. If the number of elements in the array is even, the median is not an element of the original array, but falls between two consecutive elements.

Idea - 1

We can find median of two sorted array in O(m+n) time using the merge routine of mergesort. Suppose we have num1 = \langle 2,\ 5,\ 12 \rangle and num2 = \langle 1,\ 7\rangle. If we sorted the two arrays into one, we would have \langle 1,\ 2,\ 5,\ 7,\ 11 \rangle. Here, total number of elements is 5 and the middle element aka \frac{5}{2}+1\ =\ 3 or the 3rd element (5) in the sorted order is the median. On the other hand, if we had num1 = \langle 2,\ 5 \rangle and num2 = \langle 1,\ 7\rangle, then in the sorted array we would have \langle  1,\ 2,\ 5,\ 7 \rangle and the median would be the average of the middle element or \frac{4}{2}+1 = 3 rd element and the previous element. So, the median is \frac{2+5}{2}\ =\ 3.5. Clearly, if we take \frac{m+n}{2}+1 steps in the merge process and keep track of the currently selected element and the previous as well, we would have all information to compute median.

class Solution {
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
        int m = nums1.length;
        int n = nums2.length;
        
        int total = m+n;
        int nsteps = total/2+1;
        int prev = 0, curr = 0;
        int i = 0, j = 0;
        while(nsteps > 0)
        {
            prev = curr;
            if(i >= m)
            {
                curr = nums2[j++];    
            }
            else if(j >= n)
            {
                curr = nums1[i++];
            }
            else if(nums1[i] < nums2[j])
            {
                curr = nums1[i++];
            }
            else
            {
                curr = nums2[j++];
            }
            
            --nsteps;
        }
        
        if(total%2==1)
        {
            return curr;
        }
        else
        {
            return (prev+curr)/2.0;
        }
    }
}


Runtime: 2 ms, faster than 100.00% of Java online submissions for Median of Two Sorted Arrays.
Memory Usage: 47.2 MB, less than 91.63% of Java online submissions for Median of Two Sorted Arrays.

Idea - 2

Like the single array case, the median should divide the elements in the two sorted arrays into equal halves.
So, if we pick two indices i and j in the two sorted arrays such that i+j = \frac{m+n+1}{2} then we at least have the correct divide in terms of number of elements because i is the number of elements left of i-th index of the first array and j is the number of elements left of j-th index of second array, thus the left half of the divide has \frac{m+n+1}{2} elements. To make sure this is the median divide, we need to make sure \max(\textrm{num1}[i-1],\ \textrm{nums2}[j-1])\ \le \min(\textrm{nums1}[i],\ \textrm{nums2}[j]), therefore the left half contains smaller elements and the right half contains bigger elements. For example, below all three division are valid in terms of size of the two halves, but only 3rd is the median divide.
In general, if we have a divide like below (A[m] and B[n] are fictitious to allow divides that may not have right half):
We want max{A[i-1], B[j-1]} ≤ min{A[i], B[j]}. Note, if i==m, we only have A[i-1] no A[i]. Likewise for i==0 we only have A[i] no A[i-1], for j==0 we only have B[j] no B[j-1] and for j==n we only have B[j-1] no B[j]. If we have i > imin and A[i-1] > B[j] then it follows that max{A[i-1], B[j-1]}=A[j-1] > min{A[i], B[j]}=B[j]. So in this case i is too big, we can throw away the part [i, imax] and go left during binary search. On the other hand, if we have i < imax and A[i] < B[j-1] then it follows that max{A[i-1], B[j-1]} = B[i-1] > min{A[i], B[j]} = A[i]. So in this case i is too small, we can throw away [imin, i] and go right during binary search. Note, there is a reciprocal relationship between i and j, if i is big j gets smaller and if i is small j gets bigger. As a result, i < imax implies j > 0 which we need in the check A[i] < B[j-1]. Similarly i > imin implies j < n which we need in the check A[i-1] > B[j]. We can show more rigorously using the facts: (i) m ≤ n (ii) i is in [0, m] (iii) j = \frac{m+n+1}{2}-i. We can actually iterate on [imin, imax] (intially imin=0 and imax=m) in binary search fashion and check if the divide is the median divide. Choosing nums1 as the shorter array gives us the desired time complexity of O(\log(\min(m, n))).

class Solution {
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
        int m = nums1.length;
        int n = nums2.length;
        
        // We make sure nums1 is the shorter array
        // This ensures:
        // (1) i > imin => j < n
        // (2) i < imax => j > 0
        if(m > n)
        {
            int[] tmp = nums1;
            nums1 = nums2;
            nums2 = tmp;
            
            int bin = m;
            m = n;
            n = bin;
        }
        
        int imin = 0, imax = m;
        int halflen = (m+n+1)/2;
        
        // this is regular binary search on [imin, imax]
        while(imin <= imax)
        {
            int i = imin+(imax-imin)/2;
            int j = halflen-i;
            
            if(i > imin && nums1[i-1] > nums2[j]) // go left
            {
                imax = i-1;
            }
            else if(i < imax && nums1[i] < nums2[j-1]) // go right
            {
                imin = i+1;
            }
            else
            {
                int leftmax = 0;
                if(i==0)
                {
                    leftmax = nums2[j-1];
                }
                else if(j == 0)
                {
                    leftmax = nums1[i-1];
                }
                else
                {
                    leftmax = Math.max( nums1[i-1], nums2[j-1] );
                }
                
                if((m+n)%2==1)
                {
                    return leftmax;
                }
                
                int rightmin = 0;
                if(i==m)
                {
                    rightmin = nums2[j];
                }
                else if(j==n)
                {
                    rightmin = nums1[i];
                }
                else
                {
                    rightmin = Math.min(nums1[i], nums2[j]);
                }
                
                return (leftmax+rightmin)/2.0;
            }
        }
        
        return Double.NEGATIVE_INFINITY;
    }
}


Runtime: 3 ms, faster than 86.92% of Java online submissions for Median of Two Sorted Arrays.
Memory Usage: 48.1 MB, less than 90.12% of Java online submissions for Median of Two Sorted Arrays.