LeetCode – 200 : Number of Islands

Given a 2d grid map of '1's (land) and '0's (water), count the number of islands. An island is surrounded by water and is formed by connecting adjacent lands horizontally or vertically. You may assume all four edges of the grid are all surrounded by water.

Example 1:

Input: 
11110
11010
11000
00000
Output: 1

Example 2:

Input: 
11000
11000
00100
00011
Output: 3

Understand Problem

We need to traverse through adjacent 1’s which would form one island.

Idea – 1

The grid can be thought of as an undirected graph and the islands are connected components. A dfs from a vertex in a connected component stays within the component and traverses all nodes in the component. So, we would be doing dfs’s from each unvisited island node (grid cell with ‘1’) and keep count. We would be traversing each edge at most once and there are 4\ \cdot\ m\ \cdot\ n edges, so time complexity is O(m\cdot n). We use recursion, so worst case stack space is O(m\cdot n) as well (which could happen say if there is a single island).

class Solution {
    public int numIslands(char[][] grid) {
        int m = grid.length;
        if(m==0)
        {
            return 0;
        }
        
        int n = grid[0].length;
        
        boolean[][] visited = new boolean[m][n];
        int islandCount = 0;
        for(int row = 0; row < m; ++row)
        {
            for(int col = 0; col < n; ++col)
            {
                if(grid[row][col]=='1' && !visited[row][col])
                {
                    ++islandCount;
                    dfs(grid, row, col, visited);
                }
            }
        }
        
        return islandCount;
    }
    
    private void dfs(char[][] grid, int row, int col, boolean[][] visited)
    {
        int m = grid.length;
        int n = grid[0].length;
        
        if(row < 0 || row >= m || col < 0 || col >= n || grid[row][col] == '0' || visited[row][col])
        {
            return;
        }
        
        visited[row][col] = true;
        
        // visit upwards
        dfs(grid, row-1, col, visited);
        
        // visit rightwards
        dfs(grid, row, col+1, visited);
        
        // visit downwards
        dfs(grid, row+1, col, visited);
        
        // visit leftwards
        dfs(grid, row, col-1, visited);
    }
}

Runtime: 1 ms, faster than 100.00% of Java online submissions for Number of Islands.

Memory Usage: 40.8 MB, less than 40.82% of Java online submissions forNumber of Islands.

Idea - 2

If we are allowed to modify the grid, we could used it to keep track of visited and save some space. The worst case time and space are still O(m\cdot n).

class Solution {
    public int numIslands(char[][] grid) {
        int m = grid.length;
        if(m==0)
        {
            return 0;
        }
        
        int n = grid[0].length;
        
        int islandCount = 0;
        for(int row = 0; row < m; ++row)
        {
            for(int col = 0; col < n; ++col)
            {
                if(grid[row][col]=='1')
                {
                    ++islandCount;
                    dfs(grid, row, col);
                }
            }
        }
        
        return islandCount;
    }
    
    private void dfs(char[][] grid, int row, int col)
    {
        int m = grid.length;
        int n = grid[0].length;
        
        if(row < 0 || row >= m || col < 0 || col >= n || grid[row][col] != '1')
        {
            return;
        }
        
        grid[row][col] = 'X';
        
        // visit upwards
        dfs(grid, row-1, col);
        
        // visit rightwards
        dfs(grid, row, col+1);
        
        // visit downwards
        dfs(grid, row+1, col);
        
        // visit leftwards
        dfs(grid, row, col-1);
    }
}

Runtime: 1 ms, faster than 100.00% of Java online submissions for Number of Islands.

Memory Usage: 42.3 MB, less than 5.02% of Java online submissions forNumber of Islands.

Interestingly the memory usage has gone up, probably because I used 'X' to mark visited nodes. Using '2' to mark visited node the result is better:

Runtime: 1 ms, faster than 100.00% of Java online submissions for Number of Islands.

Memory Usage: 41 MB, less than 28.87% of Java online submissions forNumber of Islands.

Idea - 3

If stack space is limited, we can avoid recursion and use a stack object. The time and space complexity do not change.

class Solution {
    public int numIslands(char[][] grid) {
        int m = grid.length;
        if(m==0)
        {
            return 0;
        }
        
        int n = grid[0].length;
        
        boolean[][] visited = new boolean[m][n];
        int islandCount = 0;
        for(int row = 0; row < m; ++row)
        {
            for(int col = 0; col < n; ++col)
            {
                if(grid[row][col]=='1' && !visited[row][col])
                {
                    ++islandCount;
                    
                    Deque<int[]> stack = new ArrayDeque<>();
                    int[] root = {row, col};
                    stack.addLast(root);
                    while(!stack.isEmpty())
                    {
                        int[] node = stack.removeLast();
                        int x = node[0];
                        int y = node[1];
                        if(x >= 0 && x < m && y >= 0 && y < n && grid[x][y]=='1' && !visited[x][y])
                        {
                            visited[x][y] = true;
                            
                            int[] up = {x-1, y};
                            stack.addLast(up);
                            
                            int[] right = {x, y+1};
                            stack.addLast(right);
                            
                            int[] down = {x+1, y};
                            stack.addLast(down);
                            
                            int[] left = {x, y-1};
                            stack.addLast(left);
                        }
                    }
                }
            }
        }
        
        return islandCount;
    }
}

Runtime: 8 ms, faster than 9.89% of Java online submissions for Number of Islands.

Memory Usage: 42 MB, less than 5.02% of Java online submissions for Number of Islands.

Idea - 4

Using BFS we could reduce worst case space to O\left( \min\{m, n\} \right)

class Solution {
    public int numIslands(char[][] grid) {
        int m = grid.length;
        if(m==0)
        {
            return 0;
        }
        
        int n = grid[0].length;
        
        int islandCount = 0;
        for(int row = 0; row < m; ++row)
        {
            for(int col = 0; col < n; ++col)
            {
                if(grid[row][col]=='1')
                {
                    ++islandCount;
                    
                    Deque<int[]> queue = new ArrayDeque<>();
                    int[] root = {row, col};
                    queue.addLast(root);
                    
                    while(!queue.isEmpty())
                    {
                        int[] node = queue.removeFirst();
                        int x = node[0];
                        int y = node[1];
                        
                        if(x >=0 && x < m && y >= 0 && y < n && grid[x][y] == '1')
                        {
                            grid[x][y] = '2';
                            
                            int[] up = {x-1, y};
                            queue.addLast(up);
                            
                            int[] right = {x, y+1};
                            queue.addLast(right);
                            
                            int[] down = {x+1, y};
                            queue.addLast(down);
                            
                            int[] left = {x, y-1};
                            queue.addLast(left);
                        }
                    }
                }
            }
        }
        
        return islandCount;
    }
}

RuRuntime: 7 ms, faster than 13.47% of Java online submissions for Number of Islands.

Memory Usage: 41.9 MB, less than 5.02% of Java online submissions forNumber of Islands.

Idea - 5

Union-Find can be leveraged to find the number of connected components aka islands, however the graph is given as a whole in the beginning, so Union-Find is not better but may be a little worse than DFS/BFS. We would prefer Union-Find over DFS/BFS if the graph was not given as a whole, instead we get a stream of edges aka online.

LeetCode – 2 : Add Two Numbers

You are given two non-empty linked lists representing two non-negative integers. The digits are stored in reverse order and each of their nodes contain a single digit. Add the two numbers and return it as a linked list.

You may assume the two numbers do not contain any leading zero, except the number 0 itself.

Example:

Input: (2 -> 4 -> 3) + (5 -> 6 -> 4)
Output: 7 -> 0 -> 8
Explanation: 342 + 465 = 807.

Idea – 1

We add the lists digit by digit in O(n) time.
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        ListNode dummy = new ListNode(-1);
        ListNode t = dummy;
        int carry = 0;
        while(l1 ≠ null || l2 ≠ null || carry > 0)
        {
            int x = l1==null ? 0 : l1.val;
            int y = l2==null ? 0 : l2.val;
            int sum = x+y+carry;
            ListNode node = new ListNode(sum%10);
            t.next = node;
            carry = sum/10;
            
            t = t.next;
            if(l1 ≠ null)
            {
                l1 = l1.next;
            }
            
            if(l2 ≠ null)
            {
                l2 = l2.next;
            }
        }
        
        return dummy.next;
    }
}
Runtime: 2 ms, faster than 97.82% of Java online submissions for Add Two Numbers. Memory Usage: 40.7 MB, less than 73.62% of Java online submissions for Add Two Numbers.

LeetCode-1: Two Sum

Given an array of integers, return indices of the two numbers such that they add up to a specific target.

You may assume that each input would have exactly one solution, and you may not use the same element twice.

Example:

Given nums = [2, 7, 11, 15], target = 9,

Because nums[0] + nums[1] = 2 + 7 = 9,
return [0, 1].

Idea – 1

Try each of the O(n^2) pairs and see if the sum equals target.
class Solution {
    public int[] twoSum(int[] nums, int target) {
        int[] sol = new int[2];
        for(int i = 0; i < nums.length-1; ++i)
        {
            for(int j = i+1; j < nums.length; ++j)
            {
                int sum = nums[i]+nums[j];
                if(sum==target)
                {
                    sol[0] = i;
                    sol[1] = j;
                    
                    return sol;
                }
            }
        }
        
        return sol;
    }
}
Runtime: 22 ms, faster than 29.76% of Java online submissions for Two Sum. Memory Usage: 38.4 MB, less than 52.20% of Java online submissions for Two Sum.

Idea – 2

For i-th element x, find target-x and see if we have already seen it in [0, i-1]. For efficient lookup we use a HashMap that stores the last index where an element was seen. Time and space O(n). Overflow/underflow does not seem to create an issue in Java probably because the addition and subtraction behaves like being on a circle.
		int target = Integer.MIN_VALUE;
		int x = 1000;
		int y = target-x;
		int z = x+y;
		
		System.out.println( String.format( "x=%d, y=%d, z=%d", x, y, z ) );
		
		target = Integer.MAX_VALUE;
		x = -1000;
		y = target-x;
		z = x+y;
		
		System.out.println( String.format( "x=%d, y=%d, z=%d", x, y, z ) );

x=1000, y=2147482648, z=-2147483648

x=-1000, y=-2147482649, z=2147483647

class Solution {
    public int[] twoSum(int[] nums, int target) {
        int[] sol = new int[2];
        HashMap<Integer, Integer> map = new HashMap();
        for(int i = 0; i < nums.length; ++i)
        {
            int delta = target-nums[i];
            if(map.containsKey(delta))
            {
                sol[0] = map.get(delta);
                sol[1] = i;
                break;
            }
            
            map.put(nums[i], i);
        }
        
        return sol;
    }
}
Runtime: 2 ms, faster than 99.73% of Java online submissions for Two Sum. Memory Usage: 38 MB, less than 84.34% of Java online submissions for Two Sum.