Backtracking
1. Combination Sum (LeetCode #39)
Problem Description:
The Combination Sum problem is defined as follows:
Given a list of distinct integers candidates and a target integer target, return a list of all unique combinations of candidates where the chosen numbers sum to target. You may return the combinations in any order.
The same number may be chosen from candidates an unlimited number of times. Two combinations are unique if the frequency of at least one of the chosen numbers is different.
Example: Input: candidates = [2,3,6,7], target = 7 Output: [[2,2,3],[7]]
Problem link: Combination Sum (LeetCode #39)
Java Code:
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
class Solution {
public List<List<Integer>> combinationSum(int[] candidates, int target) {
// Sort the candidates array to handle duplicate combinations easily
Arrays.sort(candidates);
List<List<Integer>> result = new ArrayList<>();
List<Integer> path = new ArrayList<>();
// Start the combination sum computation from the first index
backtrack(candidates, target, 0, path, result);
return result;
}
private void backtrack(int[] candidates, int target, int start, List<Integer> path, List<List<Integer>> result) {
if (target == 0) {
// Add the current combination to the result list
result.add(new ArrayList<>(path));
return;
}
for (int i = start; i < candidates.length; i++) {
if (candidates[i] > target) {
break;
}
// Add the current candidate to the combination
path.add(candidates[i]);
// Recursively compute combinations using the remaining target value
backtrack(candidates, target - candidates[i], i, path, result);
// Remove the current candidate from the combination to try different candidates
path.remove(path.size() - 1);
}
}
}
Algorithm and Explanation:
The problem can be solved using backtracking, a technique often used to solve combination and permutation problems. Here's a step-by-step explanation of the algorithm:
Sort the candidates array in ascending order to handle duplicate combinations easily.
Create a result list to store all the unique combinations that sum up to the target value.
Create a path list to store the current combination being generated.
Start the backtracking process by calling the
backtrack()
function with the initial parameters: candidates array, target value, starting index, path list, and result list.Inside the
backtrack()
function, if the target value becomes 0, it means we have found a valid combination that sums up to the target. So, we add the current combination (stored in the path list) to the result list.Iterate through the candidates array from the current start index. If the current candidate is greater than the target value, break out of the loop because any subsequent candidates will also be greater.
Add the current candidate to the path list to form a new combination.
Recursively call the
backtrack()
function with the updated target value (subtracting the current candidate) and the same start index as we can reuse the same candidate.After the recursive call, remove the last added candidate from the path list to try different candidates for the same target value.
Repeat the above steps for all candidates from the current start index to generate all possible combinations.
Finally, return the result list containing all unique combinations.
During an interview, it is important to communicate the approach and thought process clearly. Start by understanding the problem statement and define the input/output requirements. Then explain the chosen algorithm (backtracking in this case) and its efficiency. Break down the problem into smaller logical steps and discuss the implementation details.
Mention that the algorithm sorts the candidates array to handle duplicates more efficiently. Also highlight the importance of the start index in the backtrack()
function to prevent duplicate combinations and unnecessary computations.
During the implementation, use clear variable names and add comments where required to improve code readability. Test the code with different test cases to ensure correctness.
Remember to discuss time and space complexity. In this case, the time complexity is O(N^target) and space complexity is O(target), where N is the length of the candidates array and target is the given target value.
2. Permutations (LeetCode #46)
Problem Description: Given a collection of distinct integers, return all possible permutations.
Example: Input: [1,2,3] Output: [ [1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], [3,2,1] ]
Problem Link: Permutations
Optimized Java Solution with Comments:
import java.util.ArrayList;
import java.util.List;
class Solution {
public List<List<Integer>> permute(int[] nums) {
List<List<Integer>> result = new ArrayList<>();
List<Integer> currPermutation = new ArrayList<>();
boolean[] used = new boolean[nums.length];
backtrack(nums, used, currPermutation, result);
return result;
}
private void backtrack(int[] nums, boolean[] used, List<Integer> currPermutation, List<List<Integer>> result) {
if (currPermutation.size() == nums.length) { // Base case: reached end of permutation
result.add(new ArrayList<>(currPermutation));
return;
}
for (int i = 0; i < nums.length; i++) {
if (used[i]) { // Skip if the number is already used in the current permutation
continue;
}
used[i] = true; // Mark the number as used
currPermutation.add(nums[i]); // Add the number to the current permutation
backtrack(nums, used, currPermutation, result);
used[i] = false; // Mark the number as unused for the next iteration
currPermutation.remove(currPermutation.size() - 1); // Remove the number from the current permutation
}
}
}
Detailed Description and Algorithm:
To find all possible permutations of a given collection of distinct integers, we can use backtracking.
The idea behind backtracking is to try every possible option for the next element in the permutation and backtrack if it doesn't lead to a valid solution.
We start with an empty current permutation and an array of flags to track which numbers have been used in the permutation so far.
In the
permute
method, we initialize the result list, current permutation list, and the array of used flags.Then, we call the helper
backtrack
method to perform the backtracking process.In the
backtrack
method:The base case is when the size of the current permutation is equal to the total number of distinct integers (i.e., we have reached the end of the permutation). In this case, we add the current permutation to the result list and return.
For each position in the current permutation, we iterate through all the possible numbers that haven't been used yet.
If a number is already used, we skip it and move on to the next iteration. Otherwise, we mark it as used, add it to the current permutation, and recursively call the
backtrack
method.After the recursive call, we need to backtrack by marking the number as unused and removing it from the current permutation. This allows us to explore other possible permutations by trying different numbers in the current position.
Finally, we return the result list containing all the possible permutations.
During an interview, it is essential to explain the approach, mention the key steps, and highlight the time and space complexity of the solution. Additionally, it is recommended to discuss edge cases and potential optimizations, if any.
3. Sudoku Solver (LeetCode #37)
Problem Description: The problem "Sudoku Solver" asks you to write a program that can solve a partially filled Sudoku puzzle according to the rules. The Sudoku puzzle consists of a 9x9 grid, where some cells are filled with digits from 1 to 9, while others are empty. The goal is to fill in the empty cells with numbers from 1 to 9, such that each column, each row, and each of the nine 3x3 sub-grids contain all of the digits from 1 to 9 without repetition. The input to the program is a 2D array containing the initial state of the Sudoku grid, with 0 representing an empty cell.
LeetCode Link: https://leetcode.com/problems/sudoku-solver/
Optimized Java Code for Sudoku Solver (with detailed comments):
class Solution {
public void solveSudoku(char[][] board) {
if (board == null || board.length == 0) {
return;
}
solve(board);
}
private boolean solve(char[][] board) {
for (int row = 0; row < 9; row++) {
for (int col = 0; col < 9; col++) {
if (board[row][col] == '.') { // If the cell is empty
for (char num = '1'; num <= '9'; num++) {
if (isValid(board, row, col, num)) { // If the current number (num) is valid in that cell
board[row][col] = num; // Set the cell to the current number (num)
if (solve(board)) { // Recursively solve the Sudoku puzzle
return true;
} else {
board[row][col] = '.'; // If the current number (num) doesn't lead to a valid solution, backtrack and reset the cell to empty
}
}
}
return false; // If no valid number can be placed in the current cell, the puzzle cannot be solved
}
}
}
return true; // If all cells have been filled with valid numbers, the puzzle is solved
}
private boolean isValid(char[][] board, int row, int col, char num) {
// Check the current row for conflicts
for (int i = 0; i < 9; i++) {
if (board[row][i] == num) {
return false;
}
}
// Check the current column for conflicts
for (int i = 0; i < 9; i++) {
if (board[i][col] == num) {
return false;
}
}
// Check the current 3x3 sub-grid for conflicts
int subGridRow = (row / 3) * 3;
int subGridCol = (col / 3) * 3;
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
if (board[subGridRow + i][subGridCol + j] == num) {
return false;
}
}
}
return true; // If no conflict is found, the current number (num) can be placed in the current cell
}
}
Algorithm Explanation:
The algorithm uses a backtracking approach to solve the Sudoku puzzle. We start with the first cell in the grid and try to fill it with numbers from 1 to 9. For each number, we check if it is valid to be placed in that cell by checking the row, column, and 3x3 sub-grid for conflicts. If the number is valid, we move on to the next empty cell and repeat the process. If we reach a point where no valid number can be placed in a cell, we backtrack to the previous cell and try a different number.
During an interview, it is important to explain the backtracking approach and how it helps in solving Sudoku efficiently. Backtracking is a technique that works by trying all possible solutions and "backtracking" when a solution is found to be invalid. In the context of Sudoku, the backtracking approach saves time by avoiding unnecessary computation when an invalid number is placed in a cell.
In this specific implementation, we use a recursive function solve()
to solve the puzzle. The function iterates over each cell in the grid and checks if it is empty. If a cell is empty, we try placing numbers from 1 to 9 and check if the current number is valid. If the number is valid, we update the cell with the number and proceed to the next empty cell. If all cells are filled with valid numbers, we have found a solution. Otherwise, if no valid number can be placed in a cell, we backtrack and reset the cell to empty, allowing us to try a different number in the previous cell.
The isValid()
function is used to check if a number can be placed in a specific cell without violating the rules of Sudoku. It checks the row, column, and 3x3 sub-grid of the cell for conflicts. If no conflicts are found, the number is valid.
Overall, the time complexity of this algorithm is O(9^(n*n)), where n is the size of the Sudoku grid (9 in this case). However, in practice, the backtracking algorithm prunes a significant portion of the search space, leading to efficient solutions for most Sudoku puzzles.
4. N-Queens (LeetCode #51)
Problem Description:
The problem is called "N-Queens" and can be found on LeetCode at the following link: https://leetcode.com/problems/n-queens/
The problem statement is as follows:
The N-Queens problem is a classic problem in which you need to place N queens on an NxN chessboard such that no two queens threaten each other. In other words, no two queens should share the same row, column, or diagonal.
You are required to write a program that returns all the distinct solutions to the problem for a given value of N.
Optimized Java code for "N-Queens" problem:
import java.util.*;
class Solution {
List<List<String>> result;
int[] cols;
int[] diagonals1;
int[] diagonals2;
public List<List<String>> solveNQueens(int n) {
result = new ArrayList<>();
cols = new int[n];
diagonals1 = new int[2 * n - 1];
diagonals2 = new int[2 * n - 1];
backtrack(0, n, new ArrayList<>());
return result;
}
private void backtrack(int row, int n, List<String> board) {
if (row == n) {
result.add(new ArrayList<>(board));
return;
}
for (int col = 0; col < n; col++) {
if (isValid(row, col, n)) {
updateBoard(row, col, n, true);
board.add(generateRow(col, n));
backtrack(row + 1, n, board);
updateBoard(row, col, n, false);
board.remove(board.size() - 1);
}
}
}
private boolean isValid(int row, int col, int n) {
int diagonal1 = row + col;
int diagonal2 = row - col + n - 1;
return cols[col] + diagonals1[diagonal1] + diagonals2[diagonal2] == 0;
}
private void updateBoard(int row, int col, int n, boolean isPlaced) {
cols[col] = isPlaced ? 1 : 0;
diagonals1[row + col] = isPlaced ? 1 : 0;
diagonals2[row - col + n - 1] = isPlaced ? 1 : 0;
}
private String generateRow(int col, int n) {
char[] row = new char[n];
Arrays.fill(row, '.');
row[col] = 'Q';
return new String(row);
}
}
Algorithm Description:
The N-Queens problem can be solved using backtracking. The idea is to place queens on the chessboard row by row, and backtrack when a placement is not valid.
In the given code, we maintain three arrays: cols
, diagonals1
, and diagonals2
. These arrays keep track of whether a particular column, diagonal1 (row + col), or diagonal2 (row - col + n - 1) has been used by a queen or not.
The solveNQueens
method initializes the required variables and starts the backtracking process by calling the backtrack
method.
The backtrack
method takes two parameters: the current row number (row
) and the size of the board (n
). It uses a for
loop to iterate over all possible columns for the current row.
Inside the loop, it checks if the current placement is valid by calling the isValid
method. If the placement is valid, it updates the board, adds the current row to the board, and recursively calls the backtrack
method for the next row. After the recursive call, it backtracks by undoing the changes made to the board and the arrays.
The isValid
method checks if the current placement is valid by looking at the values in the cols
, diagonals1
, and diagonals2
arrays for the corresponding column and diagonals.
The updateBoard
method updates the cols
, diagonals1
, and diagonals2
arrays based on whether a queen is placed or removed at a particular position.
The generateRow
method generates a string representation of a row on the chessboard, with the 'Q' character representing the queen's position.
Overall, the backtracking algorithm explores all possible positions for the queens on the chessboard and finds all distinct solutions to the N-Queens problem.
5. Word Search (LeetCode #79)
Problem Description: The problem "Word Search" is described as follows: Given a 2D board and a word, determine if the word exists in the grid. The word can be constructed from letters of sequentially adjacent cells, where "adjacent" cells are horizontally or vertically neighboring. The same letter cell may not be used more than once.
LeetCode Problem Link: https://leetcode.com/problems/word-search/
Optimized Java Code with Comments:
class Solution {
public boolean exist(char[][] board, String word) {
// Convert the word to a character array for easy access
char[] target = word.toCharArray();
// Iterate through every cell in the board
for(int i = 0; i < board.length; i++) {
for(int j = 0; j < board[0].length; j++) {
// If the current cell is the start of the word, start the backtracking algorithm
if(board[i][j] == target[0] && backtrack(board, i, j, target, 0)) {
return true;
}
}
}
return false;
}
// Helper function to perform backtracking
private boolean backtrack(char[][] board, int i, int j, char[] target, int index) {
// If index is equal to the target length, we have successfully found the word
if(index == target.length) {
return true;
}
// If the cell is out of bounds or does not match the current target character, return false
if(i < 0 || i >= board.length || j < 0 || j >= board[0].length || board[i][j] != target[index]) {
return false;
}
// Temporarily mark the cell as visited (using a special character like '*')
char temp = board[i][j];
board[i][j] = '*';
// Recursively check the neighbors for the next characters in the target word
boolean found = backtrack(board, i+1, j, target, index+1) ||
backtrack(board, i-1, j, target, index+1) ||
backtrack(board, i, j+1, target, index+1) ||
backtrack(board, i, j-1, target, index+1);
// Restore the original value of the cell
board[i][j] = temp;
// Return whether the word was found or not
return found;
}
}
Description of the Algorithm and Steps to Think Through:
The problem can be solved using a backtracking algorithm, where we start from each cell in the board and try to find the target word.
We convert the target word to a character array for ease of access.
We iterate through each cell in the board and check if it matches the first character of the target word.
If the cell matches, we start the backtracking algorithm from that cell, passing the current board, position, target word, and current index of the target word.
In the backtracking function, we check if we have reached the end of the target word (index == target.length). If so, we have found the word and return true.
Otherwise, we check if the current cell is out of bounds or does not match the current character of the target word. If any of these conditions are met, we return false.
If the current cell matches, we temporarily mark it as visited (using a special character like '*') to avoid revisiting it.
We then recursively check the neighboring cells (up, down, left, and right) for the next character in the target word and continue the backtracking process.
After exploring all possible paths from the current cell, we restore the original value of the cell and return whether the word was found or not.
During an interview, it is important to discuss the approach, explain the backtracking algorithm, and potential optimizations like early stopping or pruning. It is also a good idea to discuss possible edge cases and test the code against different test cases to ensure correctness.
6. Letter Combinations of a Phone Number (LeetCode #17)
Problem description and link: The problem "Letter Combinations of a Phone Number" is a LeetCode problem #17. You can find the problem statement and test cases on the LeetCode website: https://leetcode.com/problems/letter-combinations-of-a-phone-number/
Optimized Java code with detailed comments for "Letter Combinations of a Phone Number":
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
class Solution {
// Map to store the mapping of digits to corresponding letters
private Map<Character, String> digitToLetters = new HashMap<>();
public List<String> letterCombinations(String digits) {
// Initialize the digit to letter mapping
initializeDigitToLetterMapping();
List<String> result = new ArrayList<>();
// Check for an empty input
if (digits.length() == 0) {
return result;
}
backtrack(digits, 0, new StringBuilder(), result);
return result;
}
private void initializeDigitToLetterMapping() {
digitToLetters.put('2', "abc");
digitToLetters.put('3', "def");
digitToLetters.put('4', "ghi");
digitToLetters.put('5', "jkl");
digitToLetters.put('6', "mno");
digitToLetters.put('7', "pqrs");
digitToLetters.put('8', "tuv");
digitToLetters.put('9', "wxyz");
}
private void backtrack(String digits, int index, StringBuilder currentCombination, List<String> result) {
// Base case: if the currentCombination has the same length as digits, we have found a valid combination
if (index == digits.length()) {
result.add(currentCombination.toString());
return;
}
// Get the digit at the current index
char digit = digits.charAt(index);
// Get the corresponding letters for the digit
String letters = digitToLetters.get(digit);
// Iterate through all the possible letters for the current digit
for (int i = 0; i < letters.length(); i++) {
// Append the current letter to the currentCombination
currentCombination.append(letters.charAt(i));
// Recursive call with the next digit
backtrack(digits, index + 1, currentCombination, result);
// Remove the last letter from the currentCombination to backtrack
currentCombination.deleteCharAt(currentCombination.length() - 1);
}
}
}
Detailed description and algorithm explanation:
The problem asks for finding all possible letter combinations that can be formed by the digits of a given phone number.
We can solve this problem by applying the concept of backtracking, where we explore all possible combinations.
We can create a map to store the mapping of each digit to its corresponding letters.
The backtracking algorithm recursively generates combinations by iterating over the digits of the input string.
The
backtrack
function takes the following parameters:digits
: The input string of digits.index
: The current index being processed.currentCombination
: The combination built so far.result
: The list to store the valid combinations.
The base case is when the currentCombination has the same length as the digits string, indicating that we have formed a valid combination.
In each recursion, we get the digit at the current index and retrieve the corresponding letters from the map.
We iterate through each letter and append it to the currentCombination.
Then, we make a recursive call with the next digit (index + 1) to explore other possible combinations.
After the recursive call, we remove the last letter from the currentCombination to backtrack and try other letters.
Finally, we return the result list containing all the valid combinations.
This approach has a time complexity of O(3^N * 4^M), where N is the count of digits that have 3 corresponding letters (e.g., 2, 3, 4, 5, 6, 8) and M is the count of digits with 4 corresponding letters (e.g., 7, 9).
7. Generate Parentheses (LeetCode #22)
Problem Description: The problem "Generate Parentheses" is a classic backtracking problem on LeetCode. Given an integer n, the task is to generate all combinations of well-formed parentheses pairs.
Link: Generate Parentheses - LeetCode #22
Optimized Java Code:
import java.util.ArrayList;
import java.util.List;
class Solution {
public List<String> generateParenthesis(int n) {
List<String> result = new ArrayList<>();
backtrack(result, "", 0, 0, n);
return result;
}
private void backtrack(List<String> result, String current, int open, int close, int max) {
if (current.length() == max * 2) { // Base case: if the length of the current string is equal to 2n
result.add(current); // then we have found a valid combination; add it to the result list
return;
}
if (open < max) { // If the number of opening parentheses is less than n, add an opening parenthesis
backtrack(result, current + "(", open + 1, close, max);
}
if (close < open) { // If the number of closing parentheses is less than the number of opening parentheses, add a closing parenthesis
backtrack(result, current + ")", open, close + 1, max);
}
}
}
Algorithm Explanation: The algorithm uses a recursive backtracking approach to generate all valid combinations of parentheses pairs.
The generateParenthesis
function initializes an empty list called result
to store the valid combinations. It then calls the backtrack
function to generate the combinations.
The backtrack
function takes five parameters:
result
: The list to store the valid combinations.current
: The current combination being generated.open
: The number of opening parentheses in the current combination.close
: The number of closing parentheses in the current combination.max
: The target number of parentheses pairs.
The base case of the backtrack
function is when the length of the current string is equal to max * 2
. This means we have formed a valid combination of parentheses pairs, so we add it to the result
list.
Inside the backtrack
function, we have two conditions for adding parentheses:
If the number of opening parentheses (
open
) is less thanmax
, we can add an opening parenthesis to the current combination and call thebacktrack
function recursively withopen + 1
andclose
unchanged.If the number of closing parentheses (
close
) is less than the number of opening parentheses (open
), we can add a closing parenthesis to the current combination and call thebacktrack
function recursively withopen
unchanged andclose + 1
.
By exploring all the possible combinations using backtracking, we can generate all valid combinations of parentheses pairs.
During the interview process, it is important to understand the problem statement and the constraints. It is also useful to consider different approaches to solve the problem, such as using backtracking in this case. Explaining the algorithm and thought process clearly can demonstrate problem-solving skills and attention to detail.
8. Word Search II (LeetCode #212)
Description:
The problem "Word Search II" asks us to find all the words from a given list in a 2D board of characters. The constraint is that we can only look for adjacent characters (up, down, left, and right) to form the words.
Here is the link to the problem on LeetCode: https://leetcode.com/problems/word-search-ii/
Optimized Java Code:
class TrieNode {
TrieNode[] children;
String word;
public TrieNode() {
children = new TrieNode[26];
word = null;
}
}
class Solution {
public List<String> findWords(char[][] board, String[] words) {
List<String> result = new ArrayList<>();
if (board == null || board.length == 0 || board[0].length == 0 || words == null || words.length == 0)
return result;
int rows = board.length;
int cols = board[0].length;
TrieNode root = buildTrie(words);
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
dfs(board, i, j, root, result);
}
}
return result;
}
private TrieNode buildTrie(String[] words) {
TrieNode root = new TrieNode();
for (String word : words) {
TrieNode current = root;
for (char ch : word.toCharArray()) {
int index = ch - 'a';
if (current.children[index] == null)
current.children[index] = new TrieNode();
current = current.children[index];
}
current.word = word;
}
return root;
}
private void dfs(char[][] board, int row, int col, TrieNode current, List<String> result) {
char ch = board[row][col];
if (ch == '#' || current.children[ch - 'a'] == null)
return;
current = current.children[ch - 'a'];
if (current.word != null) {
result.add(current.word);
current.word = null;
}
board[row][col] = '#'; // Mark visited
if (row > 0)
dfs(board, row - 1, col, current, result); // up
if (row < board.length - 1)
dfs(board, row + 1, col, current, result); // down
if (col > 0)
dfs(board, row, col - 1, current, result); // left
if (col < board[0].length - 1)
dfs(board, row, col + 1, current, result); // right
board[row][col] = ch; // Reset visited
}
}
Detailed Description:
The algorithm uses a Trie data structure to efficiently search for words in the 2D board. We first build the Trie using the given list of words. Each TrieNode represents a character in a word, and the children
array stores references to the next characters in the word.
During the DFS traversal of the board, we check if the current character exists as a child in the Trie. If it does, we follow the path and update the current node accordingly. If the current node has a non-null word
field, we add it to the result list and set it as null to avoid adding the same word multiple times.
To avoid revisiting the same cell in the board during DFS, we temporarily mark it as '#' and reset it after completing the traversal from that cell.
During an interview, it is important to understand and explain the overall approach of the algorithm, including the use of Trie data structure and backtracking (DFS) on the 2D board. It's also important to mention the time and space complexity of the solution, which is O(n * m * l) in the worst case, where n and m are the dimensions of the board and l is the average length of the words.
9. Subsets (LeetCode #78)
The problem "Subsets" on LeetCode (#78) asks us to generate all possible subsets of a given array of integers. The problem link on LeetCode is: https://leetcode.com/problems/subsets/
Here is the optimized Java code for the "Subsets" problem with detailed comments:
class Solution {
public List<List<Integer>> subsets(int[] nums) {
List<List<Integer>> result = new ArrayList<>();
backtrack(result, new ArrayList<>(), nums, 0);
return result;
}
private void backtrack(List<List<Integer>> result, List<Integer> temp, int[] nums, int start) {
result.add(new ArrayList<>(temp)); // Add the current subset to the result
for (int i = start; i < nums.length; i++) {
temp.add(nums[i]); // Include the current element in the subset
backtrack(result, temp, nums, i + 1); // Recursively generate subsets by considering the next elements
temp.remove(temp.size() - 1); // Backtrack and remove the included element to explore other possibilities
}
}
}
The algorithm approach for solving this problem is called backtracking. Backtracking is a technique used to solve problems that require generating all possible combinations, such as subsets, permutations, combinations, etc. It follows a recursive approach where we systematically explore different possibilities and backtrack to previous states to explore other possibilities.
During an interview, you can explain the algorithm step by step as follows:
We define a
backtrack
function that takes four parameters:result
: List of all subsetstemp
: Current subset being constructednums
: Given array of integersstart
: Index to start from while generating the subsets
In the
subsets
function, we initialize an empty result list and call thebacktrack
function with an emptytemp
list andstart
index as 0.Inside the
backtrack
function:Firstly, we add the current
temp
list (subset) to theresult
list.Then, we iterate from
start
to the end of thenums
array:Include the current element in the
temp
list.Recursively call the
backtrack
function with the updatedtemp
list and the next index (i + 1
).After the recursive call, we backtrack by removing the last element from the
temp
list. This is important to explore other possibilities.This process continues for all possible combinations and generates subsets.
Finally, we return the
result
list which contains all the subsets.
This approach has a time complexity of O(2^N), where N is the length of the input array.
10. Palindrome Partitioning (LeetCode #131)
Problem Description: The problem "Palindrome Partitioning" states that given a string s, partition s such that every substring of the partition is a palindrome. Return all possible palindrome partitioning of the string.
Link to the problem: https://leetcode.com/problems/palindrome-partitioning/
Optimized Java Code with detailed comments:
class Solution {
public List<List<String>> partition(String s) {
List<List<String>> result = new ArrayList<>(); // To store the final result
List<String> currentPartition = new ArrayList<>(); // Current partition being built
partitionHelper(s, 0, currentPartition, result);
return result;
}
private void partitionHelper(String s, int start, List<String> currentPartition, List<List<String>> result) {
if (start == s.length()) { // Base case: Reached the end of the string, add currentPartition to the result
result.add(new ArrayList<>(currentPartition));
return;
}
for (int i = start; i < s.length(); i++) { // Iterate through all possible substrings starting from 'start'
if (isPalindrome(s, start, i)) { // If substring s[start:i] is a palindrome, proceed
currentPartition.add(s.substring(start, i + 1)); // Add the substring to the current partition
partitionHelper(s, i + 1, currentPartition, result); // Recursively call the function for the remaining string
currentPartition.remove(currentPartition.size() - 1); // Backtrack by removing the last partitioned string
}
}
}
private boolean isPalindrome(String s, int start, int end) {
while (start <= end) {
if (s.charAt(start++) != s.charAt(end--)) {
return false;
}
}
return true;
}
}
Description and Approach: The problem can be solved using a backtracking approach. We start with an initial empty partition and iterate through all possible substrings of the given string. If a substring is found to be a palindrome, we add it to the current partition and recursively partition the remaining string. Once we reach the end of the string, we add the current partition to the result. At each step, we backtrack by removing the last partitioned string to explore other possible partitions.
During an interview, it is important to break down the problem into smaller steps and focus on the correct order of operations. The backtracking approach allows us to explore all possible combinations by building a partition incrementally and removing the last partitioned string before exploring other possibilities. It is also important to check for palindromes efficiently by comparing characters from both ends of the substring.
Last updated