这期刷leetcode上所有的回溯专题
这题先不考虑一些剪枝类问题,只是单纯考虑AC,我们设置的两个参数,一个index表示索引到哪个字符串下标了,另一个strs双端队列放入表示合法的字符串(0-255)
class Solution {
int[] segment = new int[4];
List<String> res;
public List<String> restoreIpAddresses(String s) {
res = new ArrayList<>();
char[] ch = s.toCharArray();
dfs(ch, 0, 0);
return res;
}
public void dfs(char[] ch, int segId, int index){
if(segId == 4){
if(index == ch.length){
StringBuilder sb = new StringBuilder();
for(int i = 0; i<4; i++){
sb.append(segment[i]);
if(i < 3) sb.append(".");
}
res.add(new String(sb));
}
return;
}
if(index == ch.length) return;
if(ch[index] == '0'){
segment[segId] = 0;
dfs(ch, segId+1, index+1);
}
int sum = 0;
for(int i = index; i<ch.length; i++){
sum = sum * 10 + ch[i]-'0';
if(sum > 0 && sum < 256){
segment[segId] = sum;
dfs(ch, segId+1, i+1);
}else break;
}
}
}
这里的思路和上题差不多,但是坑比较多,我太菜的原因,先看代码:
class Solution {
public List<List<String>> partition(String s) {
List<List<String>> res = new ArrayList<>();
if(s.length() == 0) return res;
helper(s,0,new ArrayList<>(),res);
return res;
}
public boolean isPalindrome(String str){
int start = 0;
int end = str.length()-1;
while(end > start){
if(str.charAt(start) != str.charAt(end)) return false;
end--;
start++;
}
return true;
}
public void helper(String s, int index, List<String> r, List<List<String>> res){
if(index == s.length()) {
res.add(new ArrayList<>(r)); // 这里不能直接res.add(r),否则r改变后,res里面随之改变
return;
}
for(int i = 1; i+index<=s.length(); i++){
String choose = s.substring(index,index+i);
if(isPalindrome(choose)){
r.add(choose);
helper(s,index+i,r,res);
r.remove(r.size()-1);
}
else continue; // 坑二
}
return;
}
坑一: List列表的删除会影响到已经加到res里面的列表
坑二:该位置之前写的return,直接影响到数组后面元素的添加,比如说 aba,我添加到ab时显示的是非回文字符,return之后就没有继续下去考虑aba的情况,所以此处用continue
排列问题,这类问题在面试中会常问到
class Solution {
List<List<Integer>> res = new ArrayList<>();
int[] visited;
public List<List<Integer>> permute(int[] nums) {
if(nums.length == 0) return res;
visited = new int[nums.length];
helper(nums, new LinkedList<>(),0);
return res;
}
public void helper(int []num, Deque<Integer> t, int index){
if(index == num.length){
res.add(new ArrayList<>(t));
return ;
}
if(index > num.length) return;
for(int i = 0; i < num.length; i++){
if(visited[i] == 1) continue;
visited[i] = 1;
t.offerLast(num[i]);
helper(num,t,index+1);
visited[i] = 0;
t.pollLast();
}
return;
}
}
class Solution {
List<List<Integer>> res = new ArrayList<>();
public List<List<Integer>> combine(int n, int k) {
if(k > n || n == 0 || k == 0) return res;
helper(n,k,0,new ArrayList<>());
return res;
}
public void helper(int n, int k, int index, List<Integer>list){
if(list.size() == k){
res.add(new ArrayList<>(list));
return;
}
for(int i = index; i<n; i++){
list.add(i+1);
helper(n,k,i+1,list); // i+1 表示用过的元素,后面不用了,这里不能用index+1
list.remove(list.size()-1);
}
return;
}
}
全套组合总和问题
class Solution {
List<List<Integer>> res = new ArrayList<>();
public List<List<Integer>> combinationSum(int[] candidates, int target) {
if(candidates.length == 0) return res;
helper(candidates,target,new ArrayList<>(),0,0);
return res;
}
public void helper(int [] candidates, int target, List<Integer> list, int sum, int index){
if(sum == target){
res.add(new ArrayList<>(list));
return;
}
if(sum > target) return;
for(int i = index; i < candidates.length; i++){
list.add(candidates[i]);
sum += candidates[i];
helper(candidates,target,list,sum, i);
sum -= candidates[i];
list.remove(list.size()-1);
}
return;
}
}
组合总和这个,这个必须消化,这是一个模板了:组合总和 II
class Solution {
List<List<Integer>> res = new ArrayList<>();
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
Arrays.sort(candidates);
if(candidates.length == 0) return res;
helper(candidates,target,new ArrayList<>(),0,0);
return res;
}
public void helper(int [] candidates, int target, List<Integer> list, int sum, int index){
if(sum == target){
res.add(new ArrayList<>(list));
return;
}
if(sum > target) return;
for(int i = index; i < candidates.length; i++){
if(i > index && candidates[i] == candidates[i-1]) continue;
list.add(candidates[i]);
sum += candidates[i];
helper(candidates,target,list,sum, i+1);
sum -= candidates[i];
list.remove(list.size()-1);
}
return;
}
}
继续延伸到 组合总和 III,将组合问题贯彻到底
class Solution {
public List<List<Integer>> res = new ArrayList<>();
public List<List<Integer>> combinationSum3(int k, int n) {
if(k == 0 || n < 1) return res;
helper(k,n,new ArrayList<>(),0,1);
return res;
}
public void helper(int k, int n, List<Integer> list, int sum, int index){
if(k == list.size() && sum == n){
res.add(new ArrayList<>(list));
return;
}
if(sum > n) return;
if(index > 9) return;
for(int i = index; i<=9; i++){
sum += i;
list.add(i);
helper(k,n,list,sum,i+1);
sum -= i;
list.remove(list.size()-1);
}
return;
}
}
组合问题 IV(超时了,需要剪枝)
class Solution {
int res = 0;
public int combinationSum4(int[] nums, int target) {
if(nums.length == 0) return res;
helper(nums, target, 0);
return res;
}
public void helper(int []nums, int target, int sum){
if(sum == target){
res += 1;
return;
}
if(sum > target) return;
for(int i = 0; i<nums.length; i++){
sum += nums[i];
if(sum > target) return;
helper(nums,target,sum);
sum -= nums[i];
}
return;
}
}
记忆化搜索AC:
class Solution {
int[] memo;
public int combinationSum4(int[] nums, int target) {
int len = nums.length;
if(len == 0) return 0;
memo = new int[target+1];
Arrays.fill(memo, -1);
return helper(nums, target);
}
public int helper(int[] nums, int target){
if(target < 0) return 0;
if(target == 0) return 1;
if(memo[target] != -1) return memo[target];
int sum = 0;
for(int i = 0; i<nums.length; i++) sum += helper(nums, target-nums[i]);
memo[target]= sum;
return sum;
}
}
这里再补充一个 用背包解决的,这里显然是一个完全背包问题:
这道题目,我看题解大部分都是用动态规划做的,但是由于是回溯专题,强行用回溯做了 ,结果可想而知:
class Solution {
int res = 0;
public int countNumbersWithUniqueDigits(int n) {
if(n == 0) return 1;
if(n == 1) return 10;
helper(n, new ArrayList<>());
return res;
}
public void helper(int n, List<Integer> list){
if(list.size() > 1 && list.get(0) == 0) return;
if(list.size() > n) return;
if(list.size() > 0){
res += 1; // 这里有一个坑,习惯性在后面写个return; 结果直接不考虑后面的情况,只输出一位数的情况
}
for(int i = 0; i < 10; i++){
if(list.contains(i)) continue;
list.add(i);
helper(n, list);
list.remove(list.size()-1);
}
return;
}
}
class Solution {
public List<List<Integer>> res = new ArrayList<>();
public List<List<Integer>> subsetsWithDup(int[] nums) {
if(nums.length == 0) return res;
helper(nums, 0, new ArrayList<>(),new HashMap<>());
return res;
}
public void helper(int[] nums, int index, List<Integer> list, HashMap<List<Integer>,Integer> map){
if(index > nums.length) return;
if(list.size() > nums.length) return;
List<Integer> temp = new ArrayList<>(list);
Collections.sort(temp);
if(map.get(temp) == null){
res.add(new ArrayList<>(temp));
map.put(temp,0);
}
for(int i = index; i<nums.length; i++){
list.add(nums[i]);
helper(nums, i+1, list, map);
list.remove(list.size()-1);
}
return;
}
}
二维平面中的回溯算法
class Solution {
public boolean exist(char[][] board, String word) {
if(word.length() == 0) return false;
for(int i = 0; i<board.length; i++){
for(int j = 0; j<board[0].length; j++){
if(helper(board,word,i,j,0)) return true;
}
}
return false;
}
public boolean helper(char[][] board, String word, int x, int y, int index){
if(index == word.length()){ // 没有考虑边界问题,看下面代码
return true;
}
if(board[x][y] != word.charAt(index)) return false;
board[x][y] = '1'; // 这个相当于以前常用的 visited[] = true,表明这个点已经被访问过了
if(x+1<board.length && helper(board, word, x+1, y, index+1)) return true;
if(x-1>=0 && helper(board, word, x-1, y, index+1)) return true;
if(y-1>=0 && helper(board, word, x, y-1, index+1)) return true;
if(y+1<board[0].length && helper(board, word, x, y+1, index+1)) return true;
board[x][y] = word.charAt(index); // 还原
return false;
}
}
上面的代码没有考虑到边界问题,碰到一个元素的情况,就gg了,改成下面这种:
class Solution {
public boolean exist(char[][] board, String word) {
if(word.length() == 0) return false;
for(int i = 0; i<board.length; i++){
for(int j = 0; j<board[0].length; j++){
if(helper(board,word,i,j,0)) return true;
}
}
return false;
}
public boolean helper(char[][] board, String word, int x, int y, int index){
if(index == word.length()-1){ // 这里是重点
return board[x][y] == word.charAt(index);
}
if(board[x][y] != word.charAt(index)) return false;
board[x][y] = '1';
if(x+1<board.length && helper(board, word, x+1, y, index+1)) return true;
if(x-1>=0 && helper(board, word, x-1, y, index+1)) return true;
if(y-1>=0 && helper(board, word, x, y-1, index+1)) return true;
if(y+1<board[0].length && helper(board, word, x, y+1, index+1)) return true;
board[x][y] = word.charAt(index);
return false;
}
}
接下来是floodfill类型的题目,也是二维平面的回溯:
class Solution {
int res = 0;
int [][]direct = {{0,1},{0,-1},{1,0},{-1,0}};
public int numIslands(char[][] grid) {
if(grid.length == 0) return 0;
for(int i = 0; i<grid.length; i++){
for(int j = 0; j<grid[0].length; j++){
if(grid[i][j] == '1'){
res ++;
helper(grid,i,j);
}
}
}
return res;
}
public void helper(char[][] grid, int x, int y){
grid[x][y] = '2'; // 不用 visited数组,节省空间
for(int i = 0; i<4; i++){
int new_x = x + direct[i][0];
int new_y = y + direct[i][1];
if(inArea(new_x,new_y,grid) && grid[new_x][new_y] == '1'){
helper(grid,new_x,new_y);
}
}
}
public boolean inArea(int x, int y, char[][] grid){
if(x<grid.length && x>=0 && y<grid[0].length && y>=0) return true;
return false;
}
}
class Solution {
int [][] direct = {{1,0},{-1,0},{0,1},{0,-1}};
int [][]visited;
public void solve(char[][] board) {
if(board.length == 0 || board[0].length == 0) return;
visited = new int[board.length][board[0].length];
for(int i = 0; i<board.length; i++){
if(board[i][0] == 'O' && visited[i][0] == 0)
helper(board,i,0);
if(board[i][board[0].length-1] == 'O' && visited[i][board[0].length-1] == 0)
helper(board,i,board[0].length-1);
}
for(int i = 0; i<board[0].length; i++){
if(board[0][i] == 'O' && visited[0][i] == 0)
helper(board,0,i);
if(board[board.length-1][i] == 'O' && visited[board.length-1][i] == 0)
helper(board,board.length-1,i);
}
for(int i = 0; i < board.length; i++)
for(int j = 0; j<board[0].length; j++){
if(board[i][j] == 'O') board[i][j] = 'X';
if(board[i][j] == 'B') board[i][j] = 'O';
}
}
public void helper(char[][] board, int x, int y){
visited[x][y] = 1;
board[x][y] = 'B';
for(int i = 0; i<4; i++){
int new_x = x + direct[i][0];
int new_y = y + direct[i][1];
if(inArea(new_x,new_y, board) && board[new_x][new_y] == 'O' && visited[new_x][new_y] == 0)
helper(board,new_x,new_y);
}
}
public boolean inArea(int x, int y, char[][] board){
if(x >= 0 && x < board.length && y >= 0 && y < board[0].length) return true;
else return false;
}
}
思想: 首先对所有的边界为 “O” 的坐标进行遍历,对O位置以及与O位置相连的坐标全都标注为“B”,对于剩下的坐标为“O”
的,肯定是被包围的,全都设为“X”,再把之前标注为B的,全都还原为O
class Solution {
public List<List<Integer>> pacificAtlantic(int[][] matrix) {
List<List<Integer>> res = new ArrayList<>();
if(matrix == null || matrix.length == 0){
return res;
}
for(int i=0; i<matrix.length; i++){
for(int j=0; j<matrix[0].length; j++){
if(dfsPacific(matrix,i,j,Long.MAX_VALUE)
&& dfsAtlantic(matrix,i,j,Long.MAX_VALUE)){
res.add(Arrays.asList(i,j));
}
}
}
return res;
}
//“太平洋”处于大陆的左边界和上边界
private boolean dfsPacific(int[][] matrix, int i, int j, long prev){
if(i > matrix.length-1 || j > matrix[0].length-1 || matrix[i][j] == -1 || matrix[i][j] > prev){
return false;
}
//到了太平洋的边界,说明可以流入,返回true
if(i <= 0 || j <= 0){
return true;
}
//temp临时记录当前matrix[i][j]的值,返回重复遍历
int temp = matrix[i][j];
matrix[i][j] = -1;
boolean result = dfsPacific(matrix,i-1,j,temp)
|| dfsPacific(matrix,i+1,j,temp)
|| dfsPacific(matrix,i,j-1,temp)
|| dfsPacific(matrix,i,j+1,temp);
matrix[i][j] = temp;
return result;
}
//“大西洋”处于大陆的右边界和下边界
private boolean dfsAtlantic(int[][] matrix, int i, int j, long prev){
if(i < 0 || j < 0 || matrix[i][j] == -1 || matrix[i][j] > prev){
return false;
}
//到了大西洋的边界,说明可以流入,返回true
if(i >= matrix.length-1 || j >= matrix[0].length-1){
return true;
}
//temp临时记录当前matrix[i][j]的值,返回重复遍历
int temp = matrix[i][j];
matrix[i][j] = -1;
boolean result = dfsAtlantic(matrix,i-1,j,temp)
|| dfsAtlantic(matrix,i+1,j,temp)
|| dfsAtlantic(matrix,i,j-1,temp)
|| dfsAtlantic(matrix,i,j+1,temp);
matrix[i][j] = temp;
return result;
}
}
作者:YanShaoJiangHu
链接:https://leetcode-cn.com/problems/pacific-atlantic-water-flow/solution/shen-du-you-xian-sou-suo-jian-dan-yi-dong-by-yansh/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
这个问题我的几次答案都是stack溢出,题解里面好多题主提到了上面的floodfill方法,需要反复试验一下
这道题wr一直没找到原因,直到在lc评论区看到,原来少了点什么
class Solution {
List<List<Integer>> res = new ArrayList<>();
public List<List<Integer>> pathSum(TreeNode root, int sum) {
if(root == null) return res;
helper(root, sum, new ArrayList<>());
return res;
}
public void helper(TreeNode root, int sum, List<Integer> list){
if(root == null) return;
if(root.left == null && root.right == null && root.val == sum){
list.add(root.val);
res.add(new ArrayList<>(list));
list.remove(list.size()-1); // 这里一定要注意,一定要在return之前要删除之前加进的元素
return;
}
list.add(root.val);
helper(root.left, sum-root.val, list);
helper(root.right, sum-root.val, list);
list.remove(list.size()-1); // 这里也要
return;
}
}