Bea.AI coding blog
  • Tree
    • Tree Traverse
    • Iteration based traverse
    • Typical problems and solutions
      • Max width of binary tree
      • Binary Tree & BST Serialize & Deserialize
      • Lowest common ancestor (LCA) for Binary Tree and BST
      • Subproblem-based Approach for Resolving Max Value on Trees
      • Path sum III
  • Graph
  • Data structure
    • Java data structure
  • Algorithm general templates
    • Tree
    • Trie
    • Graph
  • Java Algorithm Tips and Tricks
    • Java concurrent list
  • Coding patterns & Strategies
  • Coding patterns & template
    • 1. Binary Search
    • 2. Two Pointer
    • 3. Sliding Window
    • 4. Backtracking
    • 5. DFS
    • 6. BFS
    • 7. Stack
    • 8. Heap
    • 9. Prefix Sum
    • 10. Linked List
    • 11. BST
    • 12. Line sweep
    • 14. Tree
    • 15. Graph
    • 16. Bit manipulation
    • 17. Matrix
    • 18. Monotonic Stack
    • 19. Sorting
    • 20. Union Find
    • 21. Trie
    • 22. Dynamic programming
    • 23. Customized Data Structure
  • Code interview steps
  • Leetcode
    • Tree
      • Traversal
      • Construction and Conversion
      • Search and Validation
      • Manipulation and Modification
      • Special Trees
      • Miscellaneous
    • Dynamic programing
      • Classic DP Problems
      • Sequence/Array DP
      • Matrix DP
      • Knapsack Problems
      • String DP
      • Tree/Graph DP
      • Optimization Problems
      • Combinatorial DP
    • DFS
      • Graph Traversal
      • Path Finding
      • Tree Traversal
      • Backtracking
      • Topological Sorting
      • Traversal Order
      • Dynamic Programming with DFS
      • Connected Components
    • BFS
      • Graph
      • Tree
      • Matrix
      • String
      • Others
    • Two points
      • Array
      • String
      • Linked List
      • Sorting
      • Others
    • LinkedList
      • Basic Operations
      • Cycle Detection and Handling
      • Intersection and Union
      • Partitioning and Merging
      • Palindrome
      • Miscellaneous
    • Backtracking
    • Binary Search
    • Union Find
    • Trie
    • Sort
    • Heap
    • Randomness
    • Topological sort
    • Stack
    • HashTable
    • Sliding Window
  • Blind 75
    • Array
    • Binary
    • Dynamic Programming
    • Graph
    • Interval
    • LinkedList
    • Matrix
    • String
    • Tree
    • Heap
  • Companies
    • Apple
Powered by GitBook
On this page
  • 1. Number of Islands
  • 2. Word Ladder
  • 3. Clone Graph
  • 4. Pacific Atlantic Water Flow
  • 5. Shortest Bridge
  1. Leetcode
  2. BFS

Graph

1. Number of Islands

  1. Problem Description:

The problem "Number of Islands" on LeetCode (Problem #200) can be found at the following link: https://leetcode.com/problems/number-of-islands/

The problem asks us to determine the number of islands in a given grid. The grid consists of '1's (land) and '0's (water), where an island is defined as a group of adjacent '1's (vertically or horizontally) that are surrounded by '0's.

  1. Optimized Java Code for "Number of Islands" (with detailed comments):

class Solution {
    // Define the directions to explore adjacent cells
    int[][] dirs = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};
    
    public int numIslands(char[][] grid) {
        if (grid == null || grid.length == 0) {
            return 0;
        }
        
        int numIslands = 0;
        int rows = grid.length;
        int cols = grid[0].length;
        
        // Iterate over each cell in the grid
        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < cols; j++) {
                // If the cell contains land (1), increment the count of islands and explore all connected land cells
                if (grid[i][j] == '1') {
                    numIslands++;
                    explore(grid, i, j);
                }
            }
        }
        
        return numIslands;
    }
    
    private void explore(char[][] grid, int row, int col) {
        int rows = grid.length;
        int cols = grid[0].length;
        
        // Mark the current cell as visited by changing its value to '0'
        grid[row][col] = '0';
        
        // Explore all four adjacent cells
        for (int[] dir : dirs) {
            int newRow = row + dir[0];
            int newCol = col + dir[1];
            
            // Check if the adjacent cell is within the grid bounds and contains land (1), then explore it
            if (newRow >= 0 && newRow < rows && newCol >= 0 && newCol < cols && grid[newRow][newCol] == '1') {
                explore(grid, newRow, newCol);
            }
        }
    }
}
  1. Algorithm and Approach:

To solve the "Number of Islands" problem, we can use a depth-first search (DFS) approach. The idea is to iterate over each cell in the grid and, whenever we encounter land (represented by '1'), we increment our count of islands and explore all connected land cells.

During the interview process, we can follow these steps to arrive at the solution:

  • Start by understanding the problem requirements and clarifying any doubts.

  • Iterate over each cell in the grid and, whenever we encounter land (1), increment our count of islands and explore all connected land cells using a helper function (explore()).

  • The explore() function performs a depth-first search (DFS) from a given cell by exploring its adjacent cells (up, down, left, right). To keep track of the adjacent cells, we define the directions using a 2D array (dirs) containing the vertical and horizontal changes required to move in each direction.

  • The explore() function marks the current cell as visited by changing its value to '0', indicating that it has been processed and is no longer part of an island.

  • The explore() function recursively explores the adjacent cells that contain land (1), as long as they are within the grid bounds.

  • Finally, we return the count of islands as our result.

This approach has a time complexity of O(rows * cols) and a space complexity of O(rows * cols) due to the recursive calls on the call stack. However, in the worst case, when all cells are land (1), the space complexity can be O(rows * cols) if the recursion goes through the entire grid.

By understanding the problem requirements, applying a depth-first search (DFS) approach, and implementing the optimized Java code provided above, you will be well-prepared to solve the "Number of Islands" problem on LeetCode.

2. Word Ladder

  1. Problem Description: The Word Ladder problem on LeetCode asks us to find the shortest transformation sequence from the starting word to the end word, such that each intermediate word in the sequence must differ by exactly one character, and each transformed word must be in the given word list. We need to return the length of the shortest transformation sequence, or 0 if no such transformation sequence exists.

Problem Link: https://leetcode.com/problems/word-ladder/

  1. Optimized Java Code: Here's the optimized Java code for the Word Ladder problem with detailed comments:

import java.util.HashSet;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Set;

public class WordLadder {
    public int ladderLength(String beginWord, String endWord, List<String> wordList) {
        // Convert the wordList into a set for faster lookup
        Set<String> dict = new HashSet<>(wordList);
        
        // If the endWord is not present in the wordList, return 0
        if (!dict.contains(endWord)) {
            return 0;
        }
        
        // Create a queue to perform BFS
        Queue<String> queue = new LinkedList<>();
        queue.offer(beginWord);
        
        // Create a set to keep track of visited words
        Set<String> visited = new HashSet<>();
        visited.add(beginWord);
        
        int level = 1; // Initialize level as 1
        
        while (!queue.isEmpty()) {
            int size = queue.size();
            
            // Process nodes at the current level
            for (int i = 0; i < size; i++) {
                String currWord = queue.poll();
                
                // Check if we have reached the endWord
                if (currWord.equals(endWord)) {
                    return level;
                }
                
                char[] wordChars = currWord.toCharArray();
                
                // Try replacing each character of the current word with all possible characters
                for (int j = 0; j < wordChars.length; j++) {
                    char originalChar = wordChars[j];
                    
                    for (char c = 'a'; c <= 'z'; c++) {
                        wordChars[j] = c;
                        String newWord = String.valueOf(wordChars);
                        
                        // Check if the new word is present in the dictionary and not visited before
                        if (dict.contains(newWord) && !visited.contains(newWord)) {
                            queue.offer(newWord);
                            visited.add(newWord);
                        }
                    }
                    
                    // Revert back the character
                    wordChars[j] = originalChar;
                }
            }
            
            level++; // Increment the level as we move to the next level
        }
        
        return 0; // If no transformation sequence exists
    }
}
  1. Detailed Description: In this problem, we can use a Breadth First Search (BFS) approach to find the shortest transformation sequence. The idea is to treat each word as a node in a graph and perform a level-by-level traversal from the starting word to the end word.

First, we convert the wordList into a set for faster lookup. Then, we create a queue to perform BFS and a set to keep track of the visited words.

We start with the beginWord and add it to the queue and visited set. We also initialize the level as 1. While the queue is not empty, we process nodes at the current level.

For each word in the current level, we try replacing each character of the word with all possible characters from 'a' to 'z'. If the resulting new word is present in the dictionary and has not been visited before, we add it to the queue and visited set.

We continue this process until we reach the endWord or the queue becomes empty. If we reach the endWord, we return the current level, which represents the shortest transformation sequence. If the queue becomes empty without reaching the endWord, it means that no transformation sequence exists, so we return 0.

This algorithm has a time complexity of O(M^2 * N), where M is the length of each word and N is the total number of words in the wordList. This is because for each word in the wordList, we try replacing each character with all possible characters.

During an interview, it's important to explain the problem, ask clarifying questions, identify the key requirements, and design an efficient algorithm. You can also discuss the time and space complexity of your solution and test it with different test cases to demonstrate its correctness.

3. Clone Graph

  1. The problem "Clone Graph" on LeetCode can be found at the following link:

    • https://leetcode.com/problems/clone-graph/

  2. Here's the optimized Java code for the problem "Clone Graph" with detailed comments:

/**
 * Definition for a Node.
 * class Node {
 *     public int val;
 *     public List<Node> neighbors;
 *     
 *     public Node() {
 *         val = 0;
 *         neighbors = new ArrayList<Node>();
 *     }
 *     
 *     public Node(int _val) {
 *         val = _val;
 *         neighbors = new ArrayList<Node>();
 *     }
 *     
 *     public Node(int _val, ArrayList<Node> _neighbors) {
 *         val = _val;
 *         neighbors = _neighbors;
 *     }
 * }
 */

class Solution {
    Map<Node, Node> visited = new HashMap<>();
    
    public Node cloneGraph(Node node) {
        if (node == null) {
            return null;
        }
        
        if (visited.containsKey(node)) {
            return visited.get(node);
        }
        
        Node newNode = new Node(node.val, new ArrayList<>());
        visited.put(node, newNode); // Store the mapping of old node to new node
        
        for (Node neighbor : node.neighbors) {
            newNode.neighbors.add(cloneGraph(neighbor)); // Recursively clone the neighbors
        }
        
        return newNode;
    }
}
  1. Explanation of the algorithm and how to think through it during the interview process:

To solve the problem, we can use depth-first search (DFS) to traverse the given graph and clone the nodes. We can use a hashmap to keep track of the mapping between the original nodes and their clones.

Here are the steps to solve the problem:

  • Create a helper function cloneGraph that takes a node as input and returns its clone.

  • In the cloneGraph function, handle the base case where the input node is null. In this case, return null.

  • If the visited hashmap already contains the input node, it means that its clone has already been created. So, return the clone of the input node from the hashmap.

  • Create a new node with the same value as the input node and an empty list of neighbors.

  • Add the mapping of input node to its clone in the visited hashmap.

  • Iterate through all the neighbors of the input node, and recursively clone them by calling the cloneGraph function. Add the clones to the neighbors list of the newly created node.

  • Finally, return the cloned node.

During the interview, it's important to explain the approach step by step and justify the use of data structures like the hashmap to avoid redundant cloning of nodes. Focus on the recursive nature of the algorithm and how the hashmap helps in storing the mapping between original nodes and clones. Also, discuss the time and space complexity analysis of the solution.

4. Pacific Atlantic Water Flow

  1. Problem: Pacific Atlantic Water Flow Problem Link: https://leetcode.com/problems/pacific-atlantic-water-flow/

    The problem is about a matrix representing the heights of a continent. Water flows from the Pacific Ocean to the Atlantic Ocean vertically or horizontally. We need to find the list of coordinates in the matrix where the water can flow to both the Pacific and Atlantic Oceans.

  2. Optimized Java Code:

import java.util.*;

class Solution {
    public List<List<Integer>> pacificAtlantic(int[][] matrix) {
        List<List<Integer>> result = new ArrayList<>();
        
        if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {
            return result;
        }
        
        int[][] directions = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};
        int rows = matrix.length;
        int columns = matrix[0].length;
        boolean[][] pacific = new boolean[rows][columns];
        boolean[][] atlantic = new boolean[rows][columns];
        
        for (int i = 0; i < rows; i++) {
            dfs(matrix, i, 0, pacific, Integer.MIN_VALUE);
            dfs(matrix, i, columns - 1, atlantic, Integer.MIN_VALUE);
        }
        
        for (int i = 0; i < columns; i++) {
            dfs(matrix, 0, i, pacific, Integer.MIN_VALUE);
            dfs(matrix, rows - 1, i, atlantic, Integer.MIN_VALUE);
        }
        
        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < columns; j++) {
                if (pacific[i][j] && atlantic[i][j]) {
                    result.add(Arrays.asList(i, j));
                }
            }
        }
        
        return result;
    }
    
    private void dfs(int[][] matrix, int row, int col, boolean[][] ocean, int prevHeight) {
        int rows = matrix.length;
        int columns = matrix[0].length;
        
        if (row < 0 || row >= rows || col < 0 || col >= columns || ocean[row][col] || matrix[row][col] < prevHeight) {
            return;
        }
        
        ocean[row][col] = true;
        
        for (int[] dir : directions) {
            int newRow = row + dir[0];
            int newCol = col + dir[1];
            dfs(matrix, newRow, newCol, ocean, matrix[row][col]);
        }
    }
}
  1. Detailed Description:

The problem can be solved using a Depth First Search (DFS) approach.

First, we need to handle some edge cases. If the input matrix is null or has no rows or columns, we return an empty list as there are no coordinates to process.

We create a 2D boolean array pacific to keep track of the cells that water can reach from the Pacific Ocean, and another boolean array atlantic to keep track of the cells that water can reach from the Atlantic Ocean.

We initialize the directions array with the four possible directions: right, left, down, and up.

We start by iterating over each row and performing a DFS from the leftmost column to mark cells that can be reached from the Pacific Ocean. We also perform a DFS from the rightmost column to mark cells that can be reached from the Atlantic Ocean. We repeat this process for each column, from top to bottom. This ensures that all cells reachable from the Pacific Ocean are marked in the pacific array, as well as all cells reachable from the Atlantic Ocean in the atlantic array.

Finally, we iterate over all cells in the matrix and check if they are marked as reachable from both oceans. If so, we add their coordinates to the result list.

During an interview, it's important to explain the overall approach before diving into the code. It's good to mention that we are using DFS to explore the cells and mark them as reachable from the oceans. It's also good to discuss time and space complexity, which for this approach is O(rows * columns), as we are iterating over all cells in the matrix and using auxiliary arrays of the same size.

5. Shortest Bridge

  1. The problem "Shortest Bridge" on LeetCode is described as follows:

    In a given 2D binary matrix grid, there are two islands. An island is a 4-directionally connected group of 1s not connected to any other 1s.

    Now, we may change 0s to 1s so as to connect the two islands together to form 1 island.

    Return the smallest number of 0s that must be flipped. It is guaranteed that the answer is at least 1.

  2. Optimized Java code for the "Shortest Bridge" problem:

import java.util.ArrayDeque;
import java.util.Queue;

class Solution {
    public int shortestBridge(int[][] grid) {
        int m = grid.length;
        int n = grid[0].length;
        int[][] dirs = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};
        
        boolean[][] visited = new boolean[m][n];
        Queue<int[]> queue = new ArrayDeque<>();
        
        // Step 1: Use DFS to find the first island and mark all its cells as visited
        boolean found = false;
        for (int i = 0; i < m; i++) {
            if (found) {
                break;
            }
            
            for (int j = 0; j < n; j++) {
                if (grid[i][j] == 1) {
                    dfs(grid, i, j, visited, queue);
                    found = true;
                    break;
                }
            }
        }
        
        // Step 2: Use BFS to find the shortest path to the second island
        int steps = 0;
        while (!queue.isEmpty()) {
            int size = queue.size();
            
            for (int k = 0; k < size; k++) {
                int[] curr = queue.poll();
                
                for (int[] dir : dirs) {
                    int x = curr[0] + dir[0];
                    int y = curr[1] + dir[1];
                    
                    if (x < 0 || y < 0 || x >= m || y >= n || visited[x][y]) {
                        continue;
                    }
                    
                    if (grid[x][y] == 1) {
                        return steps;
                    }
                    
                    visited[x][y] = true;
                    queue.offer(new int[] {x, y});
                }
            }
            
            steps++;
        }
        
        return -1; // If no path is found
    }
    
    private void dfs(int[][] grid, int row, int col, boolean[][] visited, Queue<int[]> queue) {
        int m = grid.length;
        int n = grid[0].length;
        
        if (row < 0 || col < 0 || row >= m || col >= n || visited[row][col] || grid[row][col] == 0) {
            return;
        }
        
        visited[row][col] = true;
        queue.offer(new int[] {row, col});
        
        dfs(grid, row + 1, col, visited, queue);
        dfs(grid, row - 1, col, visited, queue);
        dfs(grid, row, col + 1, visited, queue);
        dfs(grid, row, col - 1, visited, queue);
    }
}
  1. Detailed description of the algorithm and how to think through it during an interview process:

    • The problem can be solved using a combination of Depth-First Search (DFS) and Breadth-First Search (BFS).

    • The main idea is to use DFS to find the first island and mark all its cells as visited.

    • After finding the first island, we can use BFS to explore the neighboring cells until we find the second island.

    • To implement the solution, we can follow these steps:

      1. For Step 1 (DFS):

        • Iterate through the grid to find the first cell with value 1.

        • Once a cell with value 1 is found, call the DFS function to mark all its neighboring cells as visited and store them in a queue.

        • This DFS step helps to find and mark all cells of the first island.

      2. For Step 2 (BFS):

        • Use a while loop to explore cells in the queue until the queue becomes empty.

        • Inside the while loop, we first obtain the size of the queue to explore the cells at the current level of BFS.

        • For each cell in the queue, we check its neighboring cells in all four directions (up, down, left, and right).

        • If a neighboring cell is out of bounds or has been visited, we skip it. If it is a cell of the first island, we return the number of steps taken.

        • Otherwise, we mark the current cell as visited and enqueue it for further exploration.

        • After processing all cells in the queue for the current level, we increment the steps count.

      3. Finally, if no path is found, we return -1.

    • During an interview, it's important to communicate the steps and thought process to the interviewer. You can start by clarifying the problem requirements and asking any questions to ensure you fully understand the problem.

    • Next, you can mention that this problem can be solved using a combination of DFS and BFS. Explain the main idea and the steps involved in the solution.

    • Provide a high-level overview of the algorithm and how DFS helps in finding the first island, while BFS explores the neighboring cells to find the shortest path to the second island.

    • Walk through the code implementation with the interviewer, explaining the purpose of each step and how it contributes to the overall solution.

    • Discuss the time and space complexity of the solution (which are both O(N)), and mention any potential optimizations or edge cases to consider.

    • Finally, run through test cases and example inputs to demonstrate the correctness and efficiency of the code.

PreviousBFSNextTree

Last updated 1 year ago

You can find the problem on LeetCode with the following link:

Shortest Bridge problem