Generally, every constraint satisfaction problem which has clear and well-defined constraints on any objective solution, that incrementally builds candidate to the solution and abandons a candidate (“backtracks”) as soon as it determines that the candidate cannot possibly be completed to a valid solution, can be solved by Backtracking. However, most of the problems that are discussed, can be solved using other known algorithms like Dynamic Programming or Greedy Algorithms in logarithmic, linear, linear-logarithmic time complexity in order of input size, and therefore, outshine the backtracking algorithm in every respect (since backtracking algorithms are generally exponential in both time and space). However, a few problems still remain, that only have backtracking algorithms to solve them until now.
ArrayList provides constant time for search operation, so it is better to use ArrayList if searching is more frequent operation than add and remove operation.
The LinkedList provides constant time for add and remove operations. So it is better to use LinkedList for manipulation.
LinkedList:
Adding Elements:
addFirst(element): Adds an element to the beginning of the list.
addLast(element): Adds an element to the end of the list.
add(index, element): Adds an element at the specified index.
Removing Elements:
removeFirst(): Removes and returns the first element.
removeLast(): Removes and returns the last element.
remove(index): Removes the element at the specified index.
Accessing Elements:
get(index): Retrieves the element at the specified index.
linkedList.set(i, newValue);
Iterating:
Iterating through elements using an iterator or enhanced for loop.
Size:
size(): Returns the number of elements in the list.
ArrayList:
Adding Elements:
add(element): Adds an element to the end of the list.
add(index, element): Adds an element at the specified index.
Removing Elements:
remove(index): Removes the element at the specified index.
removeRange(fromIndex, toIndex): Removes elements within the specified range.
Accessing Elements:
get(index): Retrieves the element at the specified index.
set(index, element): Replaces the element at the specified index.
Iterating:
Iterating through elements using an iterator or enhanced for loop.
Size:
size(): Returns the number of elements in the list.
ArrayList Specific:
Dynamic Sizing:
The underlying array dynamically grows as elements are added, reducing the need for manual resizing.
Random Access:
Provides fast O(1) random access to elements by index.
Insertion/Deletion Trade-Off:
Insertions and deletions at the beginning/middle of the list are slower (O(n)) due to shifting elements.
LinkedList Specific:
Insertion/Deletion:
Offers fast O(1) insertion and deletion at the beginning and end of the list.
Mid-list insertions/deletions are slower (O(n)) due to traversal.
Memory Overhead:
Uses more memory due to storing both elements and references/pointers.
No Random Access:
Accessing elements by index is slower (O(n)) since traversal is required.
Both LinkedList and ArrayList provide similar APIs for common operations like adding, removing, accessing, and iterating through elements. The choice between them depends on your specific use case and the operations you’ll perform most frequently.
Given two integers n and k, return all possible combinations ofknumbers chosen from the range[1, n].
You may return the answer in any order.
Example :
1 2 3 4
Input: n = 4, k = 2 Output: [[1,2],[1,3],[1,4],[2,3],[2,4],[3,4]] Explanation: There are 4 choose 2 = 6 total combinations. Note that combinations are unordered, i.e., [1,2] and [2,1] are considered to be the same combination.
class Solution { public List<List<Integer>> res = new ArrayList<>(); public LinkedList<Integer> path = new LinkedList<>(); public List<List<Integer>> combine(int n, int k) { seek(n,k,1); return res; }
public void seek(int n, int k, int start) { if(path.size() == k ){ res.add(new ArrayList<>(path)); return; } for(int i = start; i <= n; i++){ path.add(i); //like put in the element 1 seek(n,k, i+1); // recursive like 从1以后的元素遍历,加进去 path.removeLast(); // backtrack 比如说加了1,2 进去然后把2退出来,再加3进去成为1,3 } } }
why res.add(new ArrayList<>(path)); instead of using res.add(path).
你的 path 是一个 LinkedList,用于存储当前正在构建的组合。当你找到一个符合条件的组合时,你想把这个组合加入到 res 中,然后继续寻找其他的组合。
如果你直接将 path 加入到 res 中,实际上你只是将 path 对象的引用加入到了 res 中,而不是 path 的一个拷贝。这意味着,如果之后你继续修改 path,那么 res 中保存的组合也会随之改变,这样会导致 res 中的结果不正确。
所以,为了避免这个问题,你需要在将 path 加入到 res 中之前,先创建一个 path 的拷贝,然后再将拷贝加入到 res 中。这样,即使之后 path 发生变化,res 中保存的组合仍然保持不变。
when you add path directly to res, you’re not adding a copy of path, you’re adding a reference to the same LinkedList object. During backtracking, as you remove elements from path, those changes will also reflect in the lists already added to res. This leads to incorrect results because the contents of path are changing during the exploration of different combinations.
private void combineHelper(int n, int k, int startIndex){ //终止条件 if (path.size() == k){ result.add(new ArrayList<>(path)); return; } for (int i = startIndex; i <= n - (k - path.size()) + 1; i++){ //!!!!!!! path.add(i); combineHelper(n, k, i + 1); path.removeLast(); } } //my version
class Solution { List<List<Integer>> res = new ArrayList<>(); public List<List<Integer>> combine(int n, int k) { if(n == 0 || k == 0) return res; List<Integer> temp = new LinkedList<>(); backtrack(n, k, 1, temp); return res; } void backtrack(int n, int k, int start, List<Integer> temp ){ if(temp.size() == k){ res.add(new ArrayList<>(temp)); return; } for(int i = start; i <= n ; i++){ if(k - temp.size() <= n - i + 1 ){ temp.add(i); backtrack(n, k, i + 1, temp); temp.removeLast( ); } } } }
Find all valid combinations of k numbers that sum up to n such that the following conditions are true:
Only numbers 1 through 9 are used.
Each number is used at most once.
Return a list of all possible valid combinations. The list must not contain the same combination twice, and the combinations may be returned in any order.
Example :
1 2 3 4 5
Input: k = 3, n = 7 Output: [[1,2,4]] Explanation: 1 + 2 + 4 = 7 There are no other valid combinations.
class Solution { public List<List<Integer>>res = new ArrayList<>(); public LinkedList<Integer> path = new LinkedList<Integer>(); public List<List<Integer>> combinationSum3(int k, int n) { seek(k, n , 1); return res; } public void seek(int k, int n, int start){ if(path.size() == k){ int sum = 0; for(int element: path){ sum += element; } if(sum == n){ res.add(new ArrayList<>(path)); } return; } for(int i = start; i <= 9; i++){ path.add(i); seek(k,n, i+1); // notice it's i+1 here not start +1 path.removeLast(); } } }
Given a string containing digits from 2-9 inclusive, return all possible letter combinations that the number could represent. Return the answer in any order.
A mapping of digits to letters (just like on the telephone buttons) is given below. Note that 1 does not map to any letters.
class Solution { public List<String> res = new ArrayList<String>(); public StringBuilder temp = new StringBuilder(); public String[] dict = {"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"}; //!!!!!!!!!!
public List<String> letterCombinations(String digits) { if( digits.isEmpty()) return res; /* notice: not return null not digits == null */ seek(digits, 0); return res; } public void seek(String digits, int num){ // num stands for which digit we are processing 遍历到哪一个数字了 int len = digits.length(); if(len == num){ // you can also say-> if(len == temp.length()){ res.add(temp.toString()); return; } int digit = digits.charAt(num) - '0'; String str = dict[digit]; // current digit's letters for(int i = 0; i < str.length(); i++){ temp.append(str.charAt(i)); seek(digits, num+1); // append next num's letter temp.deleteCharAt(temp.length() - 1); }
} }
//second try:(chaotic) use string array could make code look better class Solution { List<String> res = new LinkedList<>(); StringBuilder temp = new StringBuilder(); public List<String> letterCombinations(String digits) { if(digits.equals("")){ return res; } int[] digit = new int[digits.length()]; for(int i = 0 ; i < digits.length(); i++){ digit[i] = digits.charAt(i) - '0'; } com(digit, 0); return res; } void com(int[] digit, int start){ if(temp.length() == digit.length){ res.add(temp.toString()); return; }
for(int i = start; i < digit.length; i++){ int order = 3; if(digit[i] == 7 || digit[i] == 9) order = 4;
int base; if(digit[i] < 8){ base = (digit[i] - 2)*3; }else if( digit[i] == 8){ base = 19; }else{ base = 22; } for(int j = 0 ; j < order; j++){ temp.append((char)('a' + j + base )); com(digit, i + 1); temp.deleteCharAt(temp.length() - 1); } }
Given an array of distinct integers candidates and a target integer target, return a list of all unique combinations ofcandidateswhere the chosen numbers sum totarget. 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.
The test cases are generated such that the number of unique combinations that sum up to target is less than 150 combinations for the given input.
Example 1:
1 2 3 4 5 6
Input: candidates = [2,3,6,7], target = 7 Output: [[2,2,3],[7]] Explanation: 2 and 3 are candidates, and 2 + 2 + 3 = 7. Note that 2 can be used multiple times. 7 is a candidate, and 7 = 7. These are the only two combinations.
class Solution { public List<List<Integer>> res = new ArrayList<>(); public LinkedList<Integer> temp = new LinkedList<>(); public List<List<Integer>> combinationSum(int[] candidates, int target) { seek(candidates, target, 0, 0); return res; } public void seek(int[] candidates, int target, int index, int sum){ if(sum > target) return; if(sum == target){ res.add(new ArrayList<>(temp)); return; //!!!! 我用break 也通过了,但是这里我觉得用return更好想 //这里不可以用continue,如果是continue的话层序遍历还会继续 //这里用return 不用 continue,终止的是这个深度上的检索 } for(int i = index; i < candidates.length; i++){ temp.add(candidates[i]); //notice here is i not index sum += candidates[i]; seek(candidates, target, i, sum); temp.remove(temp.size() - 1); sum -= candidates[i]; } } }
pruning: // 剪枝优化 class Solution { public List<List<Integer>> combinationSum(int[] candidates, int target) { List<List<Integer>> res = new ArrayList<>(); Arrays.sort(candidates); // 先进行排序 backtracking(res, new ArrayList<>(), candidates, target, 0, 0); return res; }
public void backtracking(List<List<Integer>> res, List<Integer> path, int[] candidates, int target, int sum, int idx) { // 找到了数字和为 target 的组合 if (sum == target) { res.add(new ArrayList<>(path)); return; }
for (int i = idx; i < candidates.length; i++) { // 如果 sum + candidates[i] > target 就终止遍历 if (sum + candidates[i] > target) break; path.add(candidates[i]); backtracking(res, path, candidates, target, sum + candidates[i], i); path.remove(path.size() - 1); // 回溯,移除路径 path 最后一个元素 } } }
//second try: class Solution { List<List<Integer>> res = new LinkedList<>(); List<Integer> temp = new LinkedList<>(); public List<List<Integer>> combinationSum(int[] candidates, int target) { cal(candidates, target, 0,0); return res; } public void cal(int[] candidates, int target, int index, int sum){ if(index > candidates.length || sum > target){ return; } for(int i = index ; i < candidates.length; i++){ if(sum == target){ res.add(new LinkedList<>(temp)); break; } temp.add(candidates[i]); sum += candidates[i]; cal(candidates, target, i, sum); sum-= candidates[i]; temp.removeLast(); } } }
Given a collection 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.
这道题强调了candidates里面有重复的元素,我们需要去重
Note: The solution set must not contain duplicate combinations.
class Solution { public List<List<Integer>> res = new ArrayList<>(); public List<Integer> temp = new LinkedList<>(); public List<List<Integer>> combinationSum2(int[] candidates, int target) { Arrays.sort(candidates); seek(candidates, target, 0, 0); return res; } public void seek(int[] candidates, int target, int index, int sum){ if(sum > target) return; if(sum == target){ res.add(new ArrayList<>(temp)); return; } for(int i = index; i < candidates.length; i++){ if(i > index && candidates[i-1] == candidates[i]) !!!!!!!!!!!! //树层逻辑, 横向检查 continue; temp.add(candidates[i]); sum += candidates[i]; seek(candidates, target, i + 1, sum); temp.remove(temp.size() - 1); sum -= candidates[i]; } } }
//second try: class Solution { List<List<Integer>> res = new LinkedList<>(); List<Integer> temp = new LinkedList<>(); public List<List<Integer>> combinationSum2(int[] candidates, int target) { Arrays.sort(candidates); cal(candidates, target, 0, 0); return res; } public void cal(int[] candidates, int target, int index, int sum){ if( sum > target) return; if(sum == target){ res.add(new LinkedList<>(temp)); return; } for(int i = index; i < candidates.length; i++){ if ( i > index && candidates[i] == candidates[i - 1] ) { continue; } or: while(i > index && candidates[i] == candidates[i - 1]) i++; wrong: while(i + 1 < candidates.length && candidates[i] == candidates[i+1] ) i++; //要和前面比不能和后面比。 temp.add(candidates[i]); sum += candidates[i]; cal(candidates, target, i + 1, sum); temp.removeLast(); sum -= candidates[i]; //while(i + 1 < candidates.length && candidates[i] == candidates[i+1] ) i++; } } }
树层(横向)上不能重复,
树枝(纵向)上可以重复。
if(i > index && candidates[i-1] == candidates[i])
这里如果想不明白的话,其实这句话等同于
if( i != index && candidates[i-1] == candidates[i]) continue; 从第二个开始去重
class Solution { public List<List<String>> res = new ArrayList<>(); public LinkedList<String> path = new LinkedList<>(); public List<List<String>> partition(String s) { seek(s,0); return res; } public boolean pal(String s, int left, int right){ for(int i = left; i < right; i ++){ if(s.charAt(i) == s.charAt(right)){ right --; }else{ return false; } } return true; } public void seek(String s, int index){ if(index >= s.length()){ res.add(new ArrayList<>(path)); return; } for(int i = index; i < s.length(); i++){ //i++是树的横向,左边划分的杠在动,划的线数量不变,因为++之前都removeLast了 if(pal(s,index, i)){ String str = s.substring(index, i+1); // for substring, left is closed but the right is open path.add(str); }else{ continue; //注意这里是continue不是return;!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! //如果这里是return, 相当于层序遍历直接就终止了。 } seek(s, i+1); //树的纵向, i+1是右边的划杠在动,划的线增多 path.removeLast(); } } }
//对比39: class Solution { public List<List<Integer>> res = new ArrayList<>(); public LinkedList<Integer> temp = new LinkedList<>(); public List<List<Integer>> combinationSum(int[] candidates, int target) { seek(candidates, target, 0, 0); return res; } public void seek(int[] candidates, int target, int index, int sum){ if(sum > target) return; if(sum == target){ res.add(new ArrayList<>(temp)); return; //!!!! 我用break 也通过了,但是这里我觉得用return更好想 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! //这里不可以用continue,如果是continue的话层序遍历还会继续 //这里用return 不用 continue,终止的是这个深度上的检索 } for(int i = index; i < candidates.length; i++){ temp.add(candidates[i]); //notice here is i not index sum += candidates[i]; seek(candidates, target, i, sum); temp.remove(temp.size() - 1); sum -= candidates[i]; } } }
A valid IP address consists of exactly four integers separated by single dots. Each integer is between 0 and 255 (inclusive) and cannot have leading zeros.
For example, "0.1.2.201" and "192.168.1.1" are valid IP addresses, but "0.011.255.245", "192.168.1.312" and "192.168@1.1" are invalid IP addresses.
Given a string s containing only digits, return all possible valid IP addresses that can be formed by inserting dots intos. You are not allowed to reorder or remove any digits in s. You may return the valid IP addresses in any order.
Example 1:
1 2
Input: s = "25525511135" Output: ["255.255.11.135","255.255.111.35"]
class Solution { public List<String> res = new ArrayList<>(); public List<String> restoreIpAddresses(String s) { backtrack(s,0,0); return res; } public void backtrack(String s, int index, int point){ // index is slash if(point == 3){ //check the last part, the fourth part if(isValid(s, index, s.length() - 1)){//left close right close res.add(s); } return; } for(int i = index; i < s.length(); i++){// i++是树的横向,左边划分的杠在动,划的线数量不变,因为++之前都removeLast了 if(isValid(s, index, i)){ s = s.substring(0, i+1)+"."+s.substring(i+1); point++; backtrack(s, i + 2, point); //vertical point--; s = s.substring(0, i+1) + s.substring(i+2); }else{ break; } } }
private Boolean isValid(String s, int start, int end) { if (start > end) { return false; } if (s.charAt(start) == '0' && start != end) { // 0开头的数字不合法 return false; } int num = 0; for (int i = start; i <= end; i++) { if (s.charAt(i) > '9' || s.charAt(i) < '0') { // 遇到⾮数字字符不合法 return false; } num = num * 10 + (s.charAt(i) - '0'); if (num > 255) { // 如果⼤于255了不合法 return false; } } return true; }
}
//second try class Solution { List<String> res = new LinkedList<>(); //StringBuilder temp = new StringBuilder(); int len; public List<String> restoreIpAddresses(String s) { len= s.length(); sort(s, 0); return res; } public void sort(String s, int index){ if(s.length() == len + 3){ if(check(s, index, s.length() - 1)) res.add(s); return; }
for(int i = index; i < s.length(); i++){ if( check(s, index, i)){ s = s.substring(0, i+1) + "." + s.substring(i+1); sort(s, i + 2); s = s.substring(0, i+1) + s.substring(i + 2);
}else{ break; } } } // public boolean check(String s , int start, int end){ // if (start > end) { // return false; // } // if(s.charAt(start) == '0' && start != end) return false; // //if(s!="" && Integer.parseInt(s.substring(start,end)) > 255) return false;
// String substring = s.substring(start, end); // if (!substring.isEmpty()) { // int intValue = Integer.parseInt(substring); // if (intValue > 255) return false; // }
private Boolean check(String s, int start, int end) { if (start > end) { return false; } if (s.charAt(start) == '0' && start != end) { // 0开头的数字不合法 return false; } int num = 0; for (int i = start; i <= end; i++) { if (s.charAt(i) > '9' || s.charAt(i) < '0') { // 遇到⾮数字字符不合法 return false; } num = num * 10 + (s.charAt(i) - '0'); if (num > 255) { // 如果⼤于255了不合法 return false; } } return true; }
}
s.substring(i + 1) expression suggests that you’re extracting a substring from the string s, starting from the index i + 1 and continuing until the end of the string.
class Solution { public List<List<Integer>> res = new ArrayList<>(); public LinkedList<Integer> temp = new LinkedList<>(); public List<List<Integer>> subsets(int[] nums) { res.add(new ArrayList<>(temp)); // 加上空子集 backtrack(nums, 0); return res; } public void backtrack(int[] nums, int index){ if(index >= nums.length) return; for(int i = index; i < nums.length; i++){ //res.add(new ArrayList<>(temp)); temp.add(nums[i]); res.add(new ArrayList<>(temp)); backtrack(nums, i + 1); temp.removeLast(); }
Given an integer array nums, return all the different possible non-decreasing subsequences of the given array with at least two elements. You may return the answer in any order.
public List<List<Integer>> res = new ArrayList<>(); public LinkedList<Integer> temp = new LinkedList<>(); public List<List<Integer>> findSubsequences(int[] nums) { backtrack(nums, 0); return res; } public void backtrack(int[] nums, int index){ // if(nums.length == index){ // return; // } if you add this, the result is wrong, should put this after if(temp.size() > 1) statement if(temp.size() > 1){ res.add(new ArrayList<>(temp)); } HashMap<Integer, Integer> map = new HashMap<>(); for(int i = index; i < nums.length; i++){ if(!temp.isEmpty() && nums[i] < temp.getLast()){ continue; } if(map.getOrDefault(nums[i], 0) == 1){ continue; } map.put(nums[i], 1); temp.add(nums[i]); backtrack(nums, i+1); temp.removeLast(); } } }
class Solution { public List<List<Integer>> res = new ArrayList<>(); public LinkedList<Integer> temp = new LinkedList<>(); public boolean[] used; public List<List<Integer>> permuteUnique(int[] nums){ Arrays.sort(nums); used = new boolean[nums.length]; backtrack(nums); return res; } public void backtrack(int[] nums){ if(nums.length == temp.size()){ res.add(new ArrayList(temp)); return; } for(int i = 0; i < nums.length; i++){ if(i > 0 && nums[i] == nums[i-1] && used[i - 1] == false) continue; //!!!!!notice used[i - 1] == false !!! if(used[i] == true) continue; used[i] = true; temp.add(nums[i]); backtrack(nums); used[i] = false; temp.removeLast(); } } }
// use linkedlist to see if it contains duplicate elements class Solution { List<List<Integer>> res = new LinkedList<>(); List<Integer> temp = new LinkedList<>(); HashSet<Integer> set = new HashSet<>(); public List<List<Integer>> permuteUnique(int[] nums) { int[] used = new int[nums.length]; find(nums, used); return res; } public void find(int[] nums, int[] used){ if(temp.size() == nums.length){ if(!res.contains(temp)) res.add(new LinkedList<>(temp)); return; } for(int i = 0 ; i < nums.length; i++){ if( used[i] == 1) continue; temp.add(nums[i]); used[i] = 1; find(nums, used); temp.remove(temp.size() - 1); used[i] = 0; } } }
The n-queens puzzle is the problem of placing n queens on an n x n chessboard such that no two queens attack each other.
Given an integer n, return all distinct solutions to the n-queens puzzle. You may return the answer in any order.
Each solution contains a distinct board configuration of the n-queens’ placement, where 'Q' and '.' both indicate a queen and an empty space, respectively.
Example 1:
1 2 3
Input: n = 4 Output: [[".Q..","...Q","Q...","..Q."],["..Q.","Q...","...Q",".Q.."]] Explanation: There exist two distinct solutions to the 4-queens puzzle as shown above
class Solution { public List<List<String>> res = new ArrayList<>(); public List<List<String>> solveNQueens(int n) { char[][] board = new char[n][n]; for (char[] c : board) { Arrays.fill(c, '.'); !!!!!!! } backtrack(board, n , 0); return res; } public void backtrack(char[][] board,int n, int row){ if(row == n){ //走到叶子节点,那一定是合法的 res.add(Array2List(board)); return; } for(int i = 0; i<n; i++){ if(check(board,n,row,i)){ board[row][i] = 'Q'; backtrack(board, n, row + 1); board[row][i] = '.'; } } }
public List Array2List(char[][] chessboard) {!!!!! List<String> list = new ArrayList<>();
for (char[] c : chessboard) { list.add(String.copyValueOf(c)); !!!!! } return list; }
public boolean check(char[][] chessboard, int n, int row, int col) { // 检查列 for (int i=0; i<row; ++i) { // 相当于剪枝 if (chessboard[i][col] == 'Q') { return false; } }
javaCopy codefor (char[] c : board) { Arrays.fill(c, '.'); }
In this approach, you are using the Arrays.fill() method to fill each row of the 2D array board with the character '.'. This method directly modifies the array elements in place, replacing the characters within each subarray with '.'.
Using nested loops:
1 2 3 4 5
javaCopy codefor (char[] c : board) { for (char cc : c) { cc = '.'; } }
In this approach, you are using nested loops to iterate through each row (char[] c) and then iterating through each character (char cc) within that row. However, the assignment cc = '.'; only updates the value of the loop variable cc, which does not have any effect on the actual characters stored in the board array. This means that the characters within the board array remain unchanged.
The key difference is that the first approach (Arrays.fill()) directly modifies the array elements, while the second approach (nested loops) only modifies the loop variables without affecting the array contents.
To achieve the same result as the Arrays.fill() approach, you would need to use the index to update the characters within the board array:
1 2 3
javaCopy codefor (int i = 0; i < board.length; i++) { Arrays.fill(board[i], '.'); }
This way, you are directly modifying the characters in the board array using the indices of the rows and columns.