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.