LeetCode – 295 : Find Median from Data Stream

Median is the middle value in an ordered integer list. If the size of the list is even, there is no middle value. So the median is the mean of the two middle value.For example,

[2,3,4], the median is 3

[2,3], the median is (2 + 3) / 2 = 2.5

Design a data structure that supports the following two operations:

  • void addNum(int num) – Add a integer number from the data stream to the data structure.
  • double findMedian() – Return the median of all elements so far.

Example:

addNum(1)
addNum(2)
findMedian() -> 1.5
addNum(3) 
findMedian() -> 2

Follow up:

  1. If all integer numbers from the stream are between 0 and 100, how would you optimize it?
  2. If 99% of all integer numbers from the stream are between 0 and 100, how would you optimize it?
[code lang="java"]
class MedianFinder {

    /** initialize your data structure here. */
    public MedianFinder() {
        
    }
    
    public void addNum(int num) {
        
    }
    
    public double findMedian() {
        
    }
}

/**
 * Your MedianFinder object will be instantiated and called as such:
 * MedianFinder obj = new MedianFinder();
 * obj.addNum(num);
 * double param_2 = obj.findMedian();
 */
[code]

Idea – 1

We could use a list, addNum() takes time O(n). To find the median, we could sort the list, thus findMedian() takes time O(n\lg{n}). Space complexity is O(n).
[code lang="java"]
class MedianFinder {

    List<Integer> data;
    
    /** initialize your data structure here. */
    public MedianFinder() {
        data = new ArrayList<>();
    }
    
    public void addNum(int num) {
        data.add(num);
    }
    
    public double findMedian() {
        int n = data.size();
        
        Collections.sort(data);
        
        // assume n > 0
        if(n%2 == 1)
        {
            return data.get(n/2);
        }
        else
        {
            // assume no overflow or underflow
            return (data.get(n/2-1)+data.get(n/2))*0.5;
        }
    }
}
[code]

Runtime: 483 ms, faster than 7.04% of Java online submissions for Find Median from Data Stream.Memory Usage: 65.4 MB, less than 66.73% of Java online submissions for Find Median from Data Stream.

Idea – 2

In findMedian() we could use the quicksort partitioning idea to find an element of rank x, which should make the average runtime O(n) — worst case quadratic. Space is linear.
[code lang="java"]
class MedianFinder {

    List<Integer> data;
    
    static final Random rng = new Random( System.currentTimeMillis()%Integer.MAX_VALUE );
    
    /** initialize your data structure here. */
    public MedianFinder() {
        data = new ArrayList<>();
    }
    
    public void addNum(int num) {
        data.add(num);
    }
    
    public double findMedian() {
        int n = data.size();
        
        int rightRank = n/2;
        double rightElement = select(rightRank, 0, n-1);
        
        if(n%2==1)
        {
            return rightElement;
        }
        else
        {
            double leftElement = select(rightRank-1, 0, rightRank);
            
            return (leftElement+rightElement)*0.5;
        }
    }
    
    private int select(int rank, int lo, int hi)
    {
        if(lo==hi)
        {
            return data.get(lo);
        }
        
        int pi = lo + rng.nextInt(hi-lo+1);
        int pivot = data.get(pi);
        
        int i = lo-1, k = lo;
        int j = hi;
        
        while(k <= j)
        {
            if(data.get(k) > pivot)
            {
                Collections.swap(data, k, j--);
            }
            else if(data.get(k) < pivot)
            {
                Collections.swap(data, ++i, k++);
            }
            else
            {
                ++k;
            }
        }
        
        int pivotRank = i+1;
        if(pivotRank==rank)
        {
            return pivot;
        }
        else if(pivotRank < rank)
        {
            return select(rank, i+2, hi);
        }
        else
        {
            return select(rank, lo, i);
        }
    }
}
[code]

Unfortunately that one had time limit.

Idea – 3

We could use a binary search tree to get both addNum and findMedian run in time proportional to height or for random input on average O(\lg{n}). Space is linear.
[code lang="java"]
class BST
{
    class Node
    {
        int key;
        int size;
        Node left, right;
        
        public Node(int key)
        {
            this.key = key;
            this.size = 1;
        }
    }
    
    Node root;
    
    public int size()
    {
        return size(root);
    }
    
    private int size(Node x)
    {
        return x==null ? 0 : x.size;
    }
    
    public int select(int rank)
    {
        return select(root, rank);
    }
    
    private int select(Node x, int rank)
    {
        int leftSize = size(x.left);
        if(leftSize==rank)
        {
            return x.key;
        }
        else if(rank < leftSize)
        {
            return select(x.left, rank);
        }
        else
        {
            return select(x.right, rank-leftSize-1);
        }
    }
    
    public void put(int key)
    {
        root = put(root, key);
    }
    
    private Node put(Node x, int key)
    {
        if(x==null)
        {
            return new Node(key);
        }
        
        if(x.key <= key)
        {
            x.left = put(x.left, key);
        }
        else if(x.key > key)
        {
            x.right = put(x.right, key);
        }
        
        x.size = size(x.left)+size(x.right)+1;
        
        return x;
    }
}


class MedianFinder {

    BST data;
    
    /** initialize your data structure here. */
    public MedianFinder() {
        data = new BST();
    }
    
    public void addNum(int num) {
        data.put(num);
    }
    
    public double findMedian() {
        int n = data.size();
        
        double rightElement = data.select(n/2);
        if(n%2==1)
        {
            return rightElement;
        }
        
        double leftElement = data.select(n/2-1);
        
        return (leftElement+rightElement)*0.5;
    }
}
[code]

Runtime: 112 ms, faster than 77.72% of Java online submissions for Find Median from Data Stream.Memory Usage: 57.8 MB, less than 87.98% of Java online submissions for Find Median from Data Stream.

Idea – 4

If we can keep two groups: smaller \frac{n+1}{2} in one group and the rest in the other group, we can find the median in O(1) time – if total number of element is odd, we take the max from left half, otherwise we take the average of leftmax and rightmin. To maintain the two groups we use a maxheap for the left half and minheap for the right half. Add takes O(\lg{n}) time and findMedian() takes O(1) time.
[code lang="java"]
class MedianFinder {
    
    PriorityQueue<Integer> leftq;
    PriorityQueue<Integer> rightq;

    /** initialize your data structure here. */
    public MedianFinder() {
        leftq = new PriorityQueue<Integer>(new Comparator<Integer>()
                                           {
                                               public int compare(Integer a, Integer b)
                                               {
                                                   return b-a;
                                               }
                                           });
        
        rightq = new PriorityQueue<>();
    }
    
    public void addNum(int num) {
        if(leftq.size() == rightq.size())
        {
            leftq.offer(num);
        }
        else
        {
            rightq.offer(num);
        }
        
        if(!rightq.isEmpty())
        {
            leftq.offer(rightq.poll());
            rightq.offer(leftq.poll());
        }
    }
    
    public double findMedian() {
        int n = leftq.size()+rightq.size();
        if(n%2==1)
        {
            return leftq.peek();
        }
        else
        {
            return (leftq.peek()+rightq.peek())*0.5;
        }
    }
}
[code]

Runtime: 124 ms, faster than 39.99% of Java online submissions for Find Median from Data Stream.Memory Usage: 65.6 MB, less than 66.73% of Java online submissions for Find Median from Data Stream.

Follow up

If all integers are between 0 and 100, using counting sort idea and an array of size 100 we can keep the numbers sorted, add takes O(1) time. Binary search can be used to find the median in O(\lg{n}) time. If 99% of the numbers are in [0, 100], we can use the two heap idea for < 0 and > 100 and for the rest of the numbers we use the array idea.

LeetCode – 31 : Next Permutation

Implement next permutation, which rearranges numbers into the lexicographically next greater permutation of numbers.

If such arrangement is not possible, it must rearrange it as the lowest possible order (ie, sorted in ascending order).

The replacement must be in-place and use only constant extra memory.

Here are some examples. Inputs are in the left-hand column and its corresponding outputs are in the right-hand column.

1,2,3 → 1,3,2
3,2,1 → 1,2,3
1,1,5 → 1,5,1

[code lang="java"]
class Solution {
    public void nextPermutation(int[] nums) {
        
    }
}
[code]

Idea – 1

Idea is to increase the sequence as little as possible. We index from right to left (lsb to msb). Smallest increase is achieved by swapping two elements a_i and a_j such that i > j and a_i < a_j and a_j is the smallest such number in [i-1..0]. After swapping we decrease [i-1..0] as much as possible. For example, in [1, 2, 4, 3], from right to left since [4, 3] is non-decreasing, we cannot find a_i and a_j that meet the requirements. However, 2 is the best candidate for a_i since anything left of 2 will make the increase bigger. The smallest number bigger than 2 and right of 2 is 3, so we swap 2 and 3 and get [1, 3, 4, 2]. Now we decrease right of 3 as much as possible by just reversing it, since right of 3 is guaranteed to be nondecreasing (right to left). Finally we have the next higher permutation [1, 3, 2, 4]. Time complexity is O(n) and space complexity is O(1).
[code lang="java"]
class Solution {
    public void nextPermutation(int[] nums) {
        int n = nums.length;
        int i = n-2;
        while(i >= 0)
        {
            if(nums[i] < nums[i+1])
            {
                break;
            }
            
            --i;
        }
        
        if(i < 0) // highest permutation, cannot increase
        {
            reverse(nums, 0, n-1);
            return;
        }
        
        int j = n-1;
        while(j > i)
        {
            if(nums[j] > nums[i])
            {
                break;
            }
            
            --j;
        }
        
        swap(nums, i, j);
        reverse(nums, i+1, n-1);
    }
    
    private void reverse(int[] A, int begin, int end)
    {
        while(begin < end)
        {
            swap(A, begin++, end--);
        }
    }
    
    private void swap(int[] A, int i, int j)
    {
        int tmp = A[i];
        A[i] = A[j];
        A[j] = tmp;
    }
}
[code]

Runtime: 1 ms, faster than 99.58% of Java online submissions for Next Permutation.Memory Usage: 37.2 MB, less than 94.57% of Java online submissions for Next Permutation.

LeetCode – 22 : Generate Parentheses

Given n pairs of parentheses, write a function to generate all combinations of well-formed parentheses.

For example, given n = 3, a solution set is:

[
  "((()))",
  "(()())",
  "(())()",
  "()(())",
  "()()()"
]
[code lang="java"]
class Solution {
    public List<String> generateParenthesis(int n) {

    }
}
[code]

Idea – 1

If there is an open parenthesis left to take, we take it. If there is a close parenthesis left to take and taking it would not make close count bigger than open count, we take it. Time and space complexity both are proportional to Catalan number (exponential).
[code lang="java"]
class Solution {
    public List<String> generateParenthesis(int n) {
        List<String> ans = new ArrayList<>();
        collect(n, 0, 0, "", ans);
        
        return ans;
    }
    
    private void collect(int n, int openused, int closeused, String current, List<String> result)
    {
        if(openused==n && closeused==n)
        {
            result.add(current);
            return;
        }
        
        if(openused < n)
        {
            collect(n, openused+1, closeused, current+"(", result);
        }
        
        if(closeused < openused)
        {
            collect(n, openused, closeused+1, current+")", result);
        }
    }
}
[code]

Runtime: 1 ms, faster than 96.17% of Java online submissions for Generate Parentheses.Memory Usage: 35.2 MB, less than 100.00% of Java online submissions for Generate Parentheses.

LeetCode – 811 : Subdomain Visit Count

A website domain like “discuss.leetcode.com” consists of various subdomains. At the top level, we have “com”, at the next level, we have “leetcode.com”, and at the lowest level, “discuss.leetcode.com”. When we visit a domain like “discuss.leetcode.com”, we will also visit the parent domains “leetcode.com” and “com” implicitly.

Now, call a “count-paired domain” to be a count (representing the number of visits this domain received), followed by a space, followed by the address. An example of a count-paired domain might be “9001 discuss.leetcode.com”.

We are given a list cpdomains of count-paired domains. We would like a list of count-paired domains, (in the same format as the input, and in any order), that explicitly counts the number of visits to each subdomain.

Example 1: 
Input: ["9001 discuss.leetcode.com"]
Output: ["9001 discuss.leetcode.com", "9001 leetcode.com", "9001 com"]
Explanation: We only have one website domain: "discuss.leetcode.com". As discussed above, the subdomain "leetcode.com" and "com" will also be visited. So they will all be visited 9001 times.
Example 2: 
Input: ["900 google.mail.com", "50 yahoo.com", "1 intel.mail.com", "5 wiki.org"]
Output: ["901 mail.com","50 yahoo.com","900 google.mail.com","5 wiki.org","5 org","1 intel.mail.com","951 com"]
Explanation: We will visit "google.mail.com" 900 times, "yahoo.com" 50 times, "intel.mail.com" once and "wiki.org" 5 times. For the subdomains, we will visit "mail.com" 900 + 1 = 901 times, "com" 900 + 50 + 1 = 951 times, and "org" 5 times.

Notes:

  • The length of cpdomains will not exceed 100
  • The length of each domain name will not exceed 100.
  • Each address will have either 1 or 2 “.” characters.
  • The input count in any count-paired domain will not exceed 10000.
  • The answer output can be returned in any order.

Idea – 1

We break each cpdomain into count and domain. Then in the domain part we process from right to left since all higher level domain has the same count. Since dot count and lengths are limited, this has time and space O(n).
[code lang="java"]
class Solution {
    public List<String> subdomainVisits(String[] cpdomains) {
        List<String> ans = new ArrayList<>();
        if(cpdomains==null || cpdomains.length==0)
        {
            return ans;
        }
        
        int n = cpdomains.length;
        
        HashMap<String, Integer> map = new HashMap<>();
        for(String cp : cpdomains)
        {
            String[] pair = cp.split(" ");
            int count =  Integer.parseInt(pair[0]);
            
            String[] parts = pair[1].split("\\.");
            
            String prev = "";
            for(int i = parts.length-1; i >= 0; --i)
            {
                String key = parts[i];
                if(!prev.isEmpty())
                {
                    key = key+"."+prev;
                }
                
                int oldCount = map.getOrDefault(key, 0);
                map.put(key, oldCount + count);
                
                prev = key;
            }
        }
        
        for(String k : map.keySet())
        {   
            int count =  map.get(k);
            ans.add( String.format("%d %s", count, k) );
        }
        
        return ans;
    }
}
[code]

Runtime: 36 ms, faster than 12.47% of Java online submissions for Subdomain Visit Count.Memory Usage: 39.6 MB, less than 48.68% of Java online submissions for Subdomain Visit Count.

LeetCode – 771 : Jewels and Stones

You’re given strings J representing the types of stones that are jewels, and S representing the stones you have.  Each character in S is a type of stone you have.  You want to know how many of the stones you have are also jewels.

The letters in J are guaranteed distinct, and all characters in J and S are letters. Letters are case sensitive, so"a" is considered a different type of stone from "A".

Example 1:

Input: J = "aA", S = "aAAbbbb" 
Output: 3

Example 2:

Input: J = "z", S = "ZZ" 
Output: 0

Note:

  • S and J will consist of letters and have length at most 50.
  • The characters in J are distinct.
[code lang="java"]
class Solution {
    public int numJewelsInStones(String J, String S) {
        
    }
}
[code]

Idea – 1

While processing letter c in S, we need to answer “Is c a jewel?” To answer efficiently we use hash set (array based) for all the jewels. Time complexity is O( max(|J|, |S|) ) and space complexity is O(1).
[code lang="java"]
class Solution {
    public int numJewelsInStones(String J, String S) {
        boolean[] set = new boolean[128];
        for(int i = 0; i < J.length(); ++i)
        {
            int j = J.charAt(i);
            set[j] = true;
        }
        
        int jewelCount = 0;
        for(int i = 0; i < S.length(); ++i)
        {
            int j = S.charAt(i);
            if(set[j])
            {
                ++jewelCount;
            }
        }
        
        return jewelCount;
    }
}
[code]

Runtime: 1 ms, faster than 98.37% of Java online submissions for Jewels and Stones.Memory Usage: 36.8 MB, less than 87.95% of Java online submissions for Jewels and Stones.

LeetCode – 289 : Game of Life

According to the Wikipedia’s article: “The Game of Life, also known simply as Life, is a cellular automaton devised by the British mathematician John Horton Conway in 1970.”

Given a board with m by n cells, each cell has an initial state live (1) or dead (0). Each cell interacts with its eight neighbors (horizontal, vertical, diagonal) using the following four rules (taken from the above Wikipedia article):

  1. Any live cell with fewer than two live neighbors dies, as if caused by under-population.
  2. Any live cell with two or three live neighbors lives on to the next generation.
  3. Any live cell with more than three live neighbors dies, as if by over-population..
  4. Any dead cell with exactly three live neighbors becomes a live cell, as if by reproduction.

Write a function to compute the next state (after one update) of the board given its current state. The next state is created by applying the above rules simultaneously to every cell in the current state, where births and deaths occur simultaneously.

Example:

Input:  
[  
[0,1,0],  
[0,0,1],  
[1,1,1],  
[0,0,0]
]
Output:
[  
[0,0,0],  
[1,0,1],  
[0,1,1],  
[0,1,0]
]

Follow up:

  1. Could you solve it in-place? Remember that the board needs to be updated at the same time: You cannot update some cells first and then use their updated values to update other cells.
  2. In this question, we represent the board using a 2D array. In principle, the board is infinite, which would cause problems when the active area encroaches the border of the array. How would you address these problems?
[code lang="java"]
class Solution {
    public void gameOfLife(int[][] board) {
        
    }
}
[code]

Idea – 1

We can use another m\times n board to update. Time and space complexity O(m\cdot n).
[code lang="java"]
class Solution {
    public void gameOfLife(int[][] board) {
        if(board==null || board.length==0)
        {
            return;
        }

        int m = board.length;
        int n = board[0].length;
        
        int[][] tmp = new int[m][n];
        for(int row = 0; row < m; ++row)
        {
            for(int col = 0; col < n; ++col)
            {
                int aliveNeighbors = countLiveNeighbors(board, row, col);
                if(board[row][col]==0)
                {
                    tmp[row][col] = (aliveNeighbors==3) ? 1 : 0;
                }
                else
                {
                    tmp[row][col] = (aliveNeighbors==2 || aliveNeighbors==3) ? 1 : 0;
                }
            }
        }
        
        for(int row = 0; row < m; ++row)
        {
            for(int col = 0; col < n; ++col)
            {
                board[row][col] = tmp[row][col];
            }
        }
    }
    
    private int countLiveNeighbors(int[][] board, int row, int col)
    {
        int m = board.length;
        int n = board[0].length;
        
        int live = 0;
        int[] offsets = {-1, 0, 1};
        for(int dr : offsets)
        {
            for(int dc : offsets)
            {
                if(dr==0 && dc==0)
                {
                    continue;
                }
                
                int neighborRow = row+dr;
                int neighborCol = col+dc;
                
                if(neighborRow >= 0 
                   && neighborRow < m 
                   && neighborCol >= 0 
                   && neighborCol < n 
                   && board[neighborRow][neighborCol]==1)
                {
                    ++live;
                }
            }
        }
        
        return live;
    }
}
[code]

Runtime: 0 ms, faster than 100.00% of Java online submissions for Game of Life. Memory Usage: 37.1 MB, less than 84.24% of Java online submissions for Game of Life.

Idea – 2

A cell has two possible current states (alive and dead) and two possible next states. We can represent these four cases with four different numbers other than 0 and 1, and update the input table in place. Later we will do another pass to convert those numbers into 0 and 1.
Current State Next State Code
0 0 -2
0 1 2
1 0 -3
1 1 3
Time complexity is O(m\cdot n) and space is O(1).
[code lang="java"]
class Solution {
    public void gameOfLife(int[][] board) {
        if(board==null || board.length==0)
        {
            return;
        }

        int m = board.length;
        int n = board[0].length;
        
        // (0, 0) : -2
        // (0, 1) :  2
        // (1, 0) : -3
        // (1, 1) :  3
        
        for(int row = 0; row < m; ++row)
        {
            for(int col = 0; col < n; ++col)
            {
                int aliveNeighbors = countLiveNeighbors(board, row, col);
                if(Math.abs(board[row][col])%2==0) // current state == dead
                {
                    board[row][col] = (aliveNeighbors==3) ? 2 : -2;
                }
                else
                {
                    board[row][col] = (aliveNeighbors==2 || aliveNeighbors==3) ? 3 : -3;
                }
            }
        }
        
        for(int row = 0; row < m; ++row)
        {
            for(int col = 0; col < n; ++col)
            {
                board[row][col] = board[row][col]<0 ? 0 : 1; 
            }
        }
    }
    
    private int countLiveNeighbors(int[][] board, int row, int col)
    {
        int m = board.length;
        int n = board[0].length;
        
        int live = 0;
        int[] offsets = {-1, 0, 1};
        for(int dr : offsets)
        {
            for(int dc : offsets)
            {
                if(dr==0 && dc==0)
                {
                    continue;
                }
                
                int neighborRow = row+dr;
                int neighborCol = col+dc;
                
                if(neighborRow >= 0 
                   && neighborRow < m 
                   && neighborCol >= 0 
                   && neighborCol < n 
                   && Math.abs(board[neighborRow][neighborCol])%2==1)
                {
                    ++live;
                }
            }
        }
        
        return live;
    }
}
[code]

Runtime: 0 ms, faster than 100.00% of Java online submissions for Game of Life.Memory Usage: 37.1 MB, less than 91.99% of Java online submissions for Game of Life.

To represent an infinite board, we could store coordinates of the cells that are alive in a HashSet. If a cell dies in the next update that gets removed from the hash set, if a cell becomes alive then it gets added to the hash set. Integer values are bounded by [Integer.MIN_VALUE, Integer.MAX_VALUE], which causes problem for an infinite board. We may think the hash set implicitly represents a m x n board where m is the difference between the smallest y coordinate and the largest y coordinate. and n is the difference between the smallest x coordinate and the largest x coordinate.

LeetCode – 322: Coin Change

You are given coins of different denominations and a total amount of money amount. Write a function to compute the fewest number of coins that you need to make up that amount. If that amount of money cannot be made up by any combination of the coins, return -1.

Example 1:

Input: coins = [1, 2, 5], amount = 11 
Output: 3
Explanation: 11 = 5 + 5 + 1

Example 2:

Input: coins = [2], amount = 3 
Output: -1

Note:
You may assume that you have an infinite number of each kind of coin.

[code lang="java"]
class Solution {
    public int coinChange(int[] coins, int amount) {
        
    }
}
[code]

Idea – 1

Starting with the first coin we try taking it once, twice etc., for each such choice we try taking second coin once, twice, etc. If the smallest coin goes into amount m number of times and there are n coins, time complexity is O(n^m) and space complexity due to recursion stack is O(n).
[code lang="java"]
class Solution {
    public int coinChange(int[] coins, int amount) {
        int n = dfs(coins, 0, amount);
        
        return n == Integer.MAX_VALUE ? -1 : n;
    }
    
    private int dfs(int[] coins, int i, int amount)
    {
        if(i >= coins.length || amount < 0)
        {
            return Integer.MAX_VALUE;
        }
        
        if(amount == 0) // found a change
        {
            return 0;
        }
        
        int takeit = dfs(coins, i, amount-coins[i]);
        if(takeit < Integer.MAX_VALUE)
        {
            // take into account the current coin
            ++takeit;
        }
        
        int leaveit = dfs(coins, i+1, amount);
        
        return Math.min(takeit, leaveit);
    }
}
[code]

Time limit exceeded for the following input:

[122,112,383,404,25,368]
6977

Idea – 2

Looking at dfs(), it has two params of interest i and amount, so there are only n*amount number of distinct calls. We shall cache. Time complexity is O(n\cdot \textrm{amount}) and space complexity is also O(n\cdot \textrm{amount}).
[code lang="java"]
class Solution {
    public int coinChange(int[] coins, int amount) {
        
        if(coins.length == 0)
        {
            return -1;
        }
        
        Integer[][] cache = new Integer[coins.length][amount+1];
        
        int n = dfs(coins, 0, amount, cache);
        
        return n == Integer.MAX_VALUE ? -1 : n;
    }
    
    private int dfs(int[] coins, int i, int amount, Integer[][] cache)
    {
        if(i >= coins.length || amount < 0)
        {
            return Integer.MAX_VALUE;
        }
        
        if(amount == 0) // found a change
        {
            return 0;
        }
        
        if(cache[i][amount] != null)
        {
            return cache[i][amount];
        }
        
        int takeit = dfs(coins, i, amount-coins[i], cache);
        if(takeit < Integer.MAX_VALUE)
        {
            // take into account the current coin
            ++takeit;
        }
        
        int leaveit = dfs(coins, i+1, amount, cache);
        
        cache[i][amount] = Math.min(takeit, leaveit);
        
        return cache[i][amount];
    }
}
[code]

Runtime: 41 ms, faster than 12.06% of Java online submissions for Coin Change.
Memory Usage: 38.3 MB, less than 10.49% of Java online submissions for Coin Change.

Idea – 3

Bottom up dynamic programming, time and space O(n\cdot \textrm{amount}).
[code lang="java"]
class Solution {
    public int coinChange(int[] coins, int amount) {
        
        if(coins.length == 0)
        {
            return -1;
        }
        
        int n = coins.length;
        
        int[][] dp = new int[n][amount+1];
        for(int a = 1; a <= amount; ++a)
        {
            dp[0][a] = (a%coins[0]==0) ? a/coins[0] : Integer.MAX_VALUE;
        }
        
        for(int i = 0; i < n; ++i)
        {
            dp[i][0] = 0;
        }
        
        for(int i = 1; i < n; ++i)
        {
            for(int a = 1; a <= amount; ++a)
            {
                // if taking current coin makes sense
                int takeit = (coins[i] <= a && dp[i][a-coins[i]] != Integer.MAX_VALUE) ? 1+dp[i][a-coins[i]] : Integer.MAX_VALUE;
                int leaveit = dp[i-1][a];
                
                dp[i][a] = Math.min(takeit, leaveit);
            }
        }
        
        return dp[n-1][amount] == Integer.MAX_VALUE ? -1 : dp[n-1][amount];
    }
}
[code]

Runtime: 13 ms, faster than 78.26% of Java online submissions for Coin Change.
Memory Usage: 37.5 MB, less than 90.66% of Java online submissions for Coin Change.

Idea – 4

To update dp[i][a], we need either dp[i-1][a] or dp[i][a-coins[i]], so keeping a single row is enough. Thus time remains O(n\cdot \textrm{amount}), but space becomes O(\textrm{amount}).
[code lang="java"]
class Solution {
    public int coinChange(int[] coins, int amount) {
        
        if(coins.length == 0)
        {
            return -1;
        }
        
        int n = coins.length;
        
        int[] dp = new int[amount+1];
        
        // 0-th coin
        for(int a = 1; a <= amount; ++a)
        {
            dp[a] = (a%coins[0]==0) ? a/coins[0] : Integer.MAX_VALUE;
        }
        
        for(int i = 1; i < n; ++i)
        {
            for(int a = 1; a <= amount; ++a)
            {
                // if taking current coin makes sense
                int takeit = (coins[i] <= a && dp[a-coins[i]] != Integer.MAX_VALUE) ? 1+dp[a-coins[i]] : Integer.MAX_VALUE;
                int leaveit = dp[a];
                
                dp[a] = Math.min(takeit, leaveit);
            }
        }
        
        return dp[amount] == Integer.MAX_VALUE ? -1 : dp[amount];
    }
}
[code]

Runtime: 11 ms, faster than 84.75% of Java online submissions for Coin Change.
Memory Usage: 37 MB, less than 93.91% of Java online submissions for Coin Change.

LeetCode – 33 : Search in Rotated Sorted Array

Suppose an array sorted in ascending order is rotated at some pivot unknown to you beforehand.

(i.e., [0,1,2,4,5,6,7] might become [4,5,6,7,0,1,2]).

You are given a target value to search. If found in the array return its index, otherwise return -1.

You may assume no duplicate exists in the array.

Your algorithm’s runtime complexity must be in the order of O(log n).

Example 1:

Input: nums = [4,5,6,7,0,1,2], target = 0 
Output: 4

Example 2:

Input: nums = [4,5,6,7,0,1,2], target = 3 
Output: -1
[code lang="java"]
class Solution {
    public int search(int[] nums, int target) {
        
    }
}
[code]

Idea – 1

There are no duplicates. So, after rotation the single sorted array breaks into two sorted arrays separated by a break. We do modified binary search, deciding to go left or right but we need to consider cases when mid is to the left or to the right of the break. Time complexity is O(\lg{n}) and space is O(1).
[code lang="java"]
class Solution {
    public int search(int[] nums, int target) {
        int lo = 0, hi = nums.length-1;
        while(lo <= hi)
        {
            int mid = lo + (hi-lo)/2;
            
            if(target == nums[mid])
            {
                return mid;
            }
            else if(nums[mid] > nums[hi]) // mid is to the left of break
            {
                if(target >= nums[lo] && target < nums[mid]) // target is between lo and mid
                {
                    // go left
                    hi = mid-1;
                }
                else
                {
                    // go right
                    lo = mid+1;
                }
            }
            else // mid is to the right of break;
            {
                if(target > nums[mid] && target <= nums[hi]) // target is between mid and hi
                {
                    // go right
                    lo = mid+1;
                }
                else
                {
                    // go left
                    hi = mid-1;
                }
            }
        }
        
        return -1;
    }
}
[code]

Runtime: 0 ms, faster than 100.00% of Java online submissions for Search in Rotated Sorted Array.
Memory Usage: 38.9 MB, less than 75.56% of Java online submissions for Search in Rotated Sorted Array.

LeetCode – 11: Container With Most Water

Given n non-negative integers a1a2, …, a, where each represents a point at coordinate (iai). n vertical lines are drawn such that the two endpoints of line i is at (iai) and (i, 0). Find two lines, which together with x-axis forms a container, such that the container contains the most water.

Note: You may not slant the container and n is at least 2.

The above vertical lines are represented by array [1,8,6,2,5,4,8,3,7]. In this case, the max area of water (blue section) the container can contain is 49.

Example:

Input: [1,8,6,2,5,4,8,3,7] 
Output: 49

class Solution {
    public int maxArea(int[] height) {
        
    }
}

Idea - 1

We try all of the possible O(n^2) pairs of bars for the container. Time complexity O(n^2) and space complexity O(1).

class Solution {
    public int maxArea(int[] height) {
        int max = 0;
        
        for(int i = 0; i < height.length-1; ++i)
        {
            for(int j = i+1; j < height.length; ++j)
            {
                int curr = Math.min(height[i], height[j])*(j-i);
                max = Math.max(curr, max);
            }
        }
        
        return max;
    }
}


Runtime: 206 ms, faster than 20.38% of Java online submissions for Container With Most Water.
Memory Usage: 41.3 MB, less than 5.05% of Java online submissions for Container With Most Water.

Idea - 2

We start at two ends because that maximize width. The minimum of the two bars would define the height of the container - this area is best possible for the shorter container. So we move past the shorter. Time O(n) and space O(1).

class Solution {
    public int maxArea(int[] height) {
        int i = 0, j = height.length-1;
        int max = 0;
        
        while(i < j)
        {
            int w = j-i;
            int h = Math.min(height[i], height[j]);
            
            max = Math.max(max, w*h);
            
            if(height[i] == h)
            {
                ++i;
            }
            else
            {
                --j;
            }
        }
        
        return max;
    }
}


Runtime: 2 ms, faster than 97.80% of Java online submissions for Container With Most Water.
Memory Usage: 40.6 MB, less than 22.88% of Java online submissions for Container With Most Water.

LeetCode – 297 : Serialize and Deserialize Binary Tree

Serialization is the process of converting a data structure or object into a sequence of bits so that it can be stored in a file or memory buffer, or transmitted across a network connection link to be reconstructed later in the same or another computer environment.

Design an algorithm to serialize and deserialize a binary tree. There is no restriction on how your serialization/deserialization algorithm should work. You just need to ensure that a binary tree can be serialized to a string and this string can be deserialized to the original tree structure.

Example: 

You may serialize the following tree:     
1
/ \
2 3
/ \
4 5
as "[1,2,3,null,null,4,5]"

Clarification: The above format is the same as how LeetCode serializes a binary tree. You do not necessarily need to follow this format, so please be creative and come up with different approaches yourself.

Note: Do not use class member/global/static variables to store states. Your serialize and deserialize algorithms should be stateless.


/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
public class Codec {

    // Encodes a tree to a single string.
    public String serialize(TreeNode root) {
        
    }

    // Decodes your encoded data to tree.
    public TreeNode deserialize(String data) {
        
    }
}

// Your Codec object will be instantiated and called as such:
// Codec codec = new Codec();
// codec.deserialize(codec.serialize(root));

Idea - 1

In the level order traversal of a full binary tree, each non-null node in a level would have exactly 2 children (can be null). Thus if we keep track of the null children in the serialization, we would be able to recreate the tree during deserialization. For an n-node tree, time and space complexity is O(n).

public class Codec {

    // Encodes a tree to a single string.
    public String serialize(TreeNode root) {
        
        if(root==null)
        {
            return "null";
        }
        
        StringBuilder sb = new StringBuilder();
        Deque<TreeNode> q = new ArrayDeque<>();
        q.addLast(root);
        
        sb.append(String.format("%s,", root.val));
        
        while(!q.isEmpty())
        {
            TreeNode front = q.removeFirst();
            String leftString = front.left==null ? "null" : String.valueOf(front.left.val);
            sb.append(String.format("%s,", leftString));
            
            if(front.left != null)
            {
                q.addLast(front.left);
            }
            
            String rightString = front.right==null ? "null" : String.valueOf(front.right.val);
            sb.append(String.format("%s,", rightString));
            
            if(front.right != null)
            {
                q.addLast(front.right);
            }
        }
        
        String result = sb.toString();
        
        // skip trailing comma
        return result.substring(0, result.length()-1);
    }

    // Decodes your encoded data to tree.
    public TreeNode deserialize(String data) {
        if(data.equals("null"))
        {
            return null;
        }
        
        String[] nodes = data.split(",");
        int i = 0;
        TreeNode root = new TreeNode(Integer.parseInt(nodes[i++]));
        List<TreeNode> previousLevel = new ArrayList<>();
        previousLevel.add(root);
        
        while(!previousLevel.isEmpty())
        {
            List<TreeNode> currentLevel = new ArrayList<>();
            for(TreeNode p : previousLevel)
            {
                String leftString = nodes[i++];
                if(!leftString.equals("null"))
                {
                    TreeNode leftChild = new TreeNode(Integer.parseInt(leftString));
                    p.left = leftChild;
                    
                    currentLevel.add(leftChild);
                }
                
                String rightString = nodes[i++];
                if(!rightString.equals("null"))
                {
                    TreeNode rightChild = new TreeNode(Integer.parseInt(rightString));
                    p.right = rightChild;
                    
                    currentLevel.add(rightChild);
                }
            }
            
            previousLevel = currentLevel;
        }
        
        return root;
    }
}


Runtime: 53 ms, faster than 17.30% of Java online submissions for Serialize and Deserialize Binary Tree.
Memory Usage: 46.8 MB, less than 5.05% of Java online submissions for Serialize and Deserialize Binary Tree.

Idea - 2

We could use preorder traversal as well, complexity is similar.

    public String serialize(TreeNode root) {
        
        if(root==null)
        {
            return "null";
        }
        
        StringBuilder sb = new StringBuilder();
        
        traversePreorder(root, sb);
        
        String result = sb.toString();
        
        return result.substring(0, result.length()-1);
    }
    
    private void traversePreorder(TreeNode x, StringBuilder sb)
    {
        if(x==null)
        {
            sb.append(String.format("null,"));
            return;
        }
        
        sb.append(String.format("%s,", x.val));
        traversePreorder(x.left, sb);
        traversePreorder(x.right, sb);
    }

    // Decodes your encoded data to tree.
    public TreeNode deserialize(String data) {
        if(data.equals("null"))
        {
            return null;
        }
        
        String[] nodeStrings = data.split(",");
        int[] I = {0};
        return buildFromPreorder(nodeStrings, I);
    }
    
    private TreeNode buildFromPreorder(String[] nodeStrings, int[] I)
    {
        int i = I[0];
        if(i >= nodeStrings.length || nodeStrings[i].equals("null"))
        {
            return null;
        }
        
        TreeNode x = new TreeNode( Integer.parseInt(nodeStrings[i]) );
        ++I[0];
        x.left = buildFromPreorder(nodeStrings, I);
        ++I[0];
        x.right = buildFromPreorder(nodeStrings, I);
        
        return x;
    }
}


Runtime: 44 ms, faster than 18.75% of Java online submissions for Serialize and Deserialize Binary Tree.
Memory Usage: 40.7 MB, less than 41.81% of Java online submissions for Serialize and Deserialize Binary Tree.