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
  • 16. N-Queens (51)
  • 17. Generate Parentheses (22)
  • 18. Permutations (46)
  • 19. Combination Sum (39)
  • 20. Palindrome Partitioning (131)
  1. Leetcode
  2. DFS

Backtracking

PreviousTree TraversalNextTopological Sorting

Last updated 1 year ago

16. N-Queens (51)

  1. Problem Description: The N-Queens problem is a classic problem in computer science where you need to place N queens on an N x N chessboard in such a way that no two queens attack each other. A queen can attack horizontally, vertically, and diagonally. Given an integer N, find all distinct solutions to the N-Queens problem.

Here is the link to the problem on LeetCode:

  1. Optimized Java Solution:

import java.util.ArrayList;
import java.util.List;

public class NQueens {
    public List<List<String>> solveNQueens(int n) {
        List<List<String>> result = new ArrayList<>();
        char[][] board = new char[n][n];

        // Initialize the chessboard
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                board[i][j] = '.';
            }
        }

        solve(result, board, 0, n);

        return result;
    }

    private void solve(List<List<String>> result, char[][] board, int row, int n) {
        if (row == n) {
            // Found a valid solution, convert the board to a list of strings
            List<String> solution = new ArrayList<>();
            for (int i = 0; i < n; i++) {
                solution.add(new String(board[i]));
            }
            result.add(solution);
            return;
        }

        // Try placing a queen in each column of the current row
        for (int col = 0; col < n; col++) {
            if (isValid(board, row, col, n)) {
                board[row][col] = 'Q';
                solve(result, board, row + 1, n);
                board[row][col] = '.';
            }
        }
    }

    private boolean isValid(char[][] board, int row, int col, int n) {
        // Check if there is a queen in the same column
        for (int i = 0; i < row; i++) {
            if (board[i][col] == 'Q') {
                return false;
            }
        }

        // Check if there is a queen in the diagonal from top-left to bottom-right
        for (int i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) {
            if (board[i][j] == 'Q') {
                return false;
            }
        }

        // Check if there is a queen in the diagonal from top-right to bottom-left
        for (int i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++) {
            if (board[i][j] == 'Q') {
                return false;
            }
        }

        return true;
    }
}
  1. Algorithm Description: To solve the N-Queens problem, we use a backtracking algorithm. The idea is to place a queen in each row of the chessboard, ensuring that no two queens are attacking each other.

We start with an empty chessboard and iterate through each row. For each row, we iterate through each column and try placing a queen. Before placing a queen, we check if it is valid to place the queen in that position. We check for validity by making sure that no other queen exists in the same column, same diagonal, or same anti-diagonal.

If a queen can be placed in a position, we mark it as 'Q' on the board and move on to the next row. We then recursively solve the problem for the next row. If we reach the last row and successfully place all the queens, we have found a valid solution. We convert the board to a list of strings and add it to the result list.

After finding all the solutions, we return the result list.

During an interview, it is important to understand and explain the backtracking algorithm, as well as the reasoning behind the validity checks. You can also discuss the time complexity of the solution, which is typically O(N!) due to the large number of possible configurations.

17. Generate Parentheses (22)

  1. Problem Description and Link: The problem "Generate Parentheses" is to generate all valid combinations of n pairs of parentheses. A valid combination must satisfy the following rules:

  • Each pair of parentheses must be closed in the correct order.

  • The number of open and closed parentheses must be equal.

Link to the problem on LeetCode: https://leetcode.com/problems/generate-parentheses/

  1. Optimized Java Code with Detailed Comments:

class Solution {
    public List<String> generateParenthesis(int n) {
        List<String> result = new ArrayList<>();
        backtrack(result, "", 0, 0, n);
        return result;
    }
    
    // Backtracking function to generate all valid combinations
    private void backtrack(List<String> result, String current, int open, int close, int max) {
        // Base case: if we have generated a valid combination of parentheses
        if (current.length() == max * 2) {
            result.add(current);
            return;
        }
        
        // Recursive case: add an opening parenthesis if we have remaining open parentheses
        if (open < max) {
            backtrack(result, current + "(", open + 1, close, max);
        }
        // Recursive case: add a closing parenthesis if it does not violate the validity condition
        if (close < open) {
            backtrack(result, current + ")", open, close + 1, max);
        }
    }
}
  1. Detailed Description of Algorithm and Interview Thought Process:

The problem can be solved efficiently using a backtracking algorithm. The backtracking algorithm is a brute force search algorithm that builds the solution incrementally by trying all possible choices at each step. During an interview, it's important to discuss the algorithm and walk through it step by step.

The backtrack function is responsible for generating all valid combinations of parentheses. It takes five parameters:

  • result: The list in which we store the valid combinations of parentheses.

  • current: The current combination of parentheses being built.

  • open: The number of opening parentheses already added to the current combination.

  • close: The number of closing parentheses already added to the current combination.

  • max: The maximum number of pairs of parentheses allowed.

The algorithm progresses in the following way:

  1. Check the base case: If the length of the current combination is equal to max * 2, it means we have generated a valid combination. Add it to the result list and return.

  2. If the number of opening parentheses (open) is less than max, it means we can still add an opening parenthesis. Recursively call the backtrack function with an updated current combination, one more opening parenthesis open + 1, the same number of closing parentheses close, and the maximum limit max.

  3. If the number of closing parentheses (close) is less than the number of opening parentheses (open), it means we can still add a closing parenthesis without violating the validity condition. Recursively call the backtrack function with an updated current combination, the same number of opening parentheses open, one more closing parenthesis close + 1, and the maximum limit max.

By following this backtracking approach, we can generate all valid combinations of parentheses efficiently.

During an interview, it's important to discuss the time and space complexity of the algorithm. The time complexity of this algorithm is O(4^n / (n^1/2)), where n is the given number of pairs of parentheses. The space complexity is O(n), as we only store valid combinations and not the intermediate combinations.

18. Permutations (46)

  1. The problem "Permutations (46)" on LeetCode asks us to generate all possible permutations of a given array of distinct integers.

Problem link: https://leetcode.com/problems/permutations/

  1. Optimized Java code for the problem "Permutations (46)" with detailed comments:

import java.util.ArrayList;
import java.util.List;

class Solution {
    public List<List<Integer>> permute(int[] nums) {
        List<List<Integer>> result = new ArrayList<>();
        backtrack(result, new ArrayList<>(), nums);
        return result;
    }
    
    private void backtrack(List<List<Integer>> result, List<Integer> tempList, int[] nums) {
        // Base case: tempList contains all elements from nums
        if (tempList.size() == nums.length) {
            result.add(new ArrayList<>(tempList));
        }
        else {
            // Iterate through each element in nums
            for (int i = 0; i < nums.length; i++) {
                // If the current element is already in tempList, skip it
                if (tempList.contains(nums[i])) {
                    continue;
                }
                // Add the current element to tempList
                tempList.add(nums[i]);
                // Recursively call backtrack with the updated tempList
                backtrack(result, tempList, nums);
                // Remove the current element from tempList to backtrack
                tempList.remove(tempList.size() - 1);
            }
        }
    }
}
  1. Detailed description of the algorithm and how to think through it during the interview process:

To solve this problem, we can use a backtracking algorithm. The basic idea is to recursively generate all possible permutations by making choices at each step.

Here's the step-by-step thought process when approaching this problem during an interview:

  1. Start by understanding the problem requirements and constraints. In this case, we need to generate all possible permutations of a given array of distinct integers.

  2. Think about the possible approach. Since we need to generate all permutations, a backtracking algorithm is a good choice.

  3. Initialize an empty list to store the permutations. We'll use it as a parameter in the permute function.

  4. Create a helper function called backtrack, which will take the resultList, tempList, and nums array as parameters.

  5. In the backtrack function, check if tempList contains all elements from nums. If so, add a copy of tempList to the result list.

  6. If tempList doesn't contain all elements, iterate through each element in nums.

  7. If the current element is already in tempList, skip it.

  8. Otherwise, add the current element to tempList and recursively call backtrack with the updated tempList.

  9. After the recursive call, remove the current element from tempList to backtrack and explore other possibilities.

  10. Finally, return the result list.

  11. Test your solution with different input arrays, and verify that it generates all possible permutations correctly.

During an interview, it's important to explain your thought process to the interviewer and be able to code the solution efficiently. Additionally, remember to test your code thoroughly and handle any edge cases.

19. Combination Sum (39)

  1. Problem Description: The problem "Combination Sum" is described as follows: Given a set of candidate numbers (candidates) and a target number (target), find all unique combinations in candidates where the candidate numbers sum to target. Each number in candidates may only be used once in the combination.

Link to the problem on LeetCode: https://leetcode.com/problems/combination-sum/

  1. Optimized Java Code with Detailed Comments:

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

class Solution {
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        List<List<Integer>> combinations = new ArrayList<>();
        
        // Sort the input array to handle duplicates and for optimization
        Arrays.sort(candidates);
        
        findCombinations(combinations, new ArrayList<>(), candidates, target, 0);
        
        return combinations;
    }
    
    private void findCombinations(List<List<Integer>> combinations, List<Integer> currCombination, int[] candidates, int target, int startIdx) {
        if (target == 0) {
            // Add the current combination to the list of combinations if target is reached
            combinations.add(new ArrayList<>(currCombination));
            return;
        }
        
        if (target < 0) {
            // If target becomes negative, it means the combination sum exceeds the target
            return;
        }
        
        for (int i = startIdx; i < candidates.length; i++) {
            // Skip duplicates to avoid duplicate combinations
            if (i > startIdx && candidates[i] == candidates[i - 1]) {
                continue;
            }
            
            currCombination.add(candidates[i]);
            
            // Recursively find combinations for the remaining target
            findCombinations(combinations, currCombination, candidates, target - candidates[i], i + 1);
            
            // Backtrack: Remove the last added number to explore other combinations
            currCombination.remove(currCombination.size() - 1);
        }
    }
}
  1. Algorithm and Thought Process:

  • This problem can be easily solved using the backtracking technique.

  • The general approach is to select a number from the candidates array and recursively find combinations for the remaining target by subtracting the selected number from the target.

  • The base case is when the target becomes zero, indicating that a valid combination has been found. In this case, the current combination is added to the list of combinations.

  • To handle duplicates, we need to sort the candidates array before finding combinations and skip duplicate numbers while making combinations.

  • During the recursive process, we maintain a current combination list to keep track of the selected numbers. In each iteration, we add a number to the current combination and explore other valid combinations. If a combination is not valid (target becomes negative), we backtrack by removing the last added number and try other numbers.

  • The algorithm uses a start index to avoid selecting the same number multiple times in a single combination. This ensures that a number is used only once in a combination.

  • Lastly, when all possible combinations are found, the algorithm returns the list of combinations.

During the interview process, it is important to analyze the problem and come up with an optimal algorithm before starting the implementation. Discuss the approach with the interviewer, handle edge cases, and explain the code using clear comments.

20. Palindrome Partitioning (131)

  1. The problem "Palindrome Partitioning" on Leetcode can be found here: https://leetcode.com/problems/palindrome-partitioning/

Given a string s, partition s such that every substring of the partition is a palindrome. Return all possible palindrome partitioning of s.

Example: Input: "aab" Output: [ ["aa","b"], ["a","a","b"] ]

  1. Optimized Java code for the problem "Palindrome Partitioning (131)":

import java.util.ArrayList;
import java.util.List;

public class PalindromePartitioning {
    public List<List<String>> partition(String s) {
        List<List<String>> result = new ArrayList<>();
        backtrack(result, new ArrayList<>(), s, 0);
        return result;
    }

    private void backtrack(List<List<String>> result, List<String> tempList, String s, int start) {
        if (start == s.length()) {
            result.add(new ArrayList<>(tempList));
        } else {
            for (int i = start; i < s.length(); i++) {
                if (isPalindrome(s, start, i)) {
                    tempList.add(s.substring(start, i + 1));
                    backtrack(result, tempList, s, i + 1);
                    tempList.remove(tempList.size() - 1);
                }
            }
        }
    }

    private boolean isPalindrome(String s, int low, int high) {
        while (low < high) {
            if (s.charAt(low++) != s.charAt(high--)) {
                return false;
            }
        }
        return true;
    }
    
    public static void main(String[] args) {
        PalindromePartitioning solution = new PalindromePartitioning();
        String input = "aab";
        List<List<String>> result = solution.partition(input);
        System.out.println(result);
    }
}
  1. Algorithm and thought process for the problem "Palindrome Partitioning":

To solve this problem, we can use a backtracking algorithm.

The idea is to recursively generate all possible palindromic partitions of the input string. We start by treating each character as a potential starting point for a new substring (i.e., a potential partition).

For each starting point, we check if the substring from the current starting point to the current index is a palindrome. If it is, we add it to our current list of partitions and recursively continue to partition the remaining portion of the string.

If we reach the end of the string, it means we've found a valid partitioning, so we add a copy of the current list of partitions to our final result list.

During the backtracking process, if we find a palindrome, we add it to the current list of partitions and make a recursive call to partition the remaining portion. After the recursive call returns, we remove the last added palindrome from the current list of partitions and continue the loop to check for other palindromes.

This approach ensures that we generate all possible palindromic partitions of the input string.

During the interview process, it is important to break down the problem and understand the requirements. This problem requires thinking about recursive backtracking and identifying the base case to terminate the recursion. It also involves checking for palindromic substrings using two pointers. By understanding the problem statement and breaking it down into smaller steps, we can efficiently solve this problem and discuss the solution approach during the interview.

N-Queens