数据结构题型--回溯法

    组合问题

1.组合 力扣77

用for循环来确定第一层,也就是第一个数的位置,用回溯来确定第二个数,终止条件就是大小相等时,这样就可以遍历完每一种可能的情况,剪。枝就把for循环中每次循环不需要的地方缩小范围就可以了。换句话说,如果for循环以后元素的个数已经不足我们要的个数了,就没必要搜索了。

class Solution {

        List<List<Integer>> ans = new ArrayList<>();

        LinkedList<Integer> path = new LinkedList<>();

        public List<List<Integer>> combine(int n, int k) {

                backtrack(n,k,1);

                return ans;

        }

        public void backtrack(int n,int k,int start) {

                //终止条件

                if (path.size() == k) {

                        ans.add(new ArrayList<>(path));

                         return;

                }

                for (int i = start;i <= n - (k - path.size()) + 1;i++) {

                        path.add(i);

                        backtrack(n,k,i + 1);

                        path.removeLast();

                }

        }

}

2.电话号码的字母组合 力扣17

先用哈希表存储起来,然后用stringbuilder记录临时字符串,设置终止条件为要求长度,之后利用回溯法遍历字符串即可。

class Solution {

        List<String> ans = new ArrayList<>();

        public List<String> letterCombinations(String digits) {

                if (digits.length() == 0) return ans;

                Map<Character,String> res = new HashMap<>() {{

                        put('2',"abc");

                        put('3',"def");

                        put('4',"ghi");

                        put('5',"jkl");

                        put('6',"mno");

                        put('7',"pqrs");

                        put('8',"tuv");

                        put('9',"wxyz");

                }};

                backtrack(digits,0,new StringBuilder(),ans,res);

                return ans;

        }

        public void backtrack(String digits,int start,StringBuilder com,List<String> ans,Map<Character,String> res) {

                if (start == digits.length()) {

                        ans.add(com.toString());

                        return;

                }

                char ch = digits.charAt(start);//数

                String str = res.get(ch);//数对应字符串

                for (int i = 0;i < str.length();i++) {

                        com.append(str.charAt(i));

                        backtrack(digits,start + 1,com,ans,res);

                        com.deleteCharAt(start);

                }

        }

}

3.组合总和 力扣39

此题因为是可以重复的,回溯的指针设置本身即可,并且在for循环内也要设置终止条件,当sum大于给定数时,终止循环。因为已经是排好序的了,后面的都要更大,所以立即break

class Solution {

        List<List<Integer>> res = new ArrayList<>();

        List<Integer> ans = new ArrayList<>();

        public List<List<Integer>> combinationSum(int[] candidates, int target) {

                Arrays.sort(candidates);

                backtrack(candidates,target,0,0);

                return res;

        }

        public void backtrack(int[] candidates, int target,int index,int sum) {

                if (sum == target) {

                        res.add(new ArrayList<>(ans));

                        return;

                }

                for (int i = index;i < candidates.length;i++) {

                        if (sum + candidates[i] > target) {

                                break;

                        }

                        ans.add(candidates[i]);

                        backtrack(candidates,target,i,sum + candidates[i]);

                        ans.remove(ans.size() - 1);

                }

        }

}

4.组合总和2 40

和组合总和1一样,只不过此题不能用自身。另外要求一个数组内不准重复,我们就利用pre来记录已经遍历过的数,然后加上判断条件,看看是否重复。

class Solution {

        List<List<Integer>> res = new ArrayList<>();

        List<Integer> ans = new ArrayList<>();

        public List<List<Integer>> combinationSum2(int[] candidates, int target) {

                Arrays.sort(candidates);

                backtrack(candidates,target,0,0);

                return res;

        }

        public void backtrack(int[] candidates, int target,int index,int sum) {

                if (sum == target) {

                        res.add(new ArrayList<>(ans));

                        return;

                }

                //单个数组不能出现重复

                List<Integer> pre = new ArrayList<>();

                for (int i = index;i < candidates.length;i++) {

                        if (sum + candidates[i] > target) break;

                        if (pre.contains(candidates[i])) continue;

                        ans.add(candidates[i]);

                        pre.add(candidates[i]);

                        backtrack(candidates,target,i + 1,sum + candidates[i]);

                        ans.remove(ans.size() - 1);

                }

        }

}

5.组合总和3 216

此题与上两道类似,但是条件变多了。即需要满足k,又需要满足n。所以判断条件多了两项,分别来判断总和与长度。

class Solution {

        List<List<Integer>> result = new ArrayList<>();

        List<Integer> path = new ArrayList<>();

        public List<List<Integer>> combinationSum3(int k, int n) {

                backtrack(k,n,1,0);

                return result;

        }

        public void backtrack(int k,int n,int index,int sum) {

                if (path.size() > k) {

                        return;

                  }

                if (sum > n) {

                        return;

                }

                if (path.size() == k) {

                        if (sum == n) {

                                result.add(new ArrayList<>(path));

                        }

                }

                for (int i = index;i <= 9;i++) {

                        sum = sum + i;

                        path.add(i);

                        backtrack(k,n,i + 1,sum);

                        sum = sum - i;

                        path.remove(path.size() - 1);

                }

        }

}

        分割问题

1.分割回文串 131

 首先,我们要建立一个布尔来判断是否为回文,用while循环双指针判断即可。然后就是要设置回溯函数,回溯的方法跟组合就一样了,只需要一个起始的索引,for循环遍历即可。

class Solution {

        List<List<String>> res = new ArrayList<>();

        List<String> ans = new ArrayList<>();

        public List<List<String>> partition(String s) {

                backtrack(s,0);

                return res;

        }

        public void backtrack(String s,int startindex) {

                if (startindex == s.length()) {

                        res.add(new ArrayList<>(ans));

                        return;

                }

                for (int i = startindex;i < s.length();i++) {

                        if (ishui(s,startindex,i)) {

                                ans.add(s.substring(startindex,i + 1));

                        } else {

                                continue;

                        }

                        backtrack(s,i + 1);

                        ans.remove(ans.size() - 1);

                }

        }

        public boolean ishui(String s,int left,int right) {

                while (left < right) {

                        if (s.charAt(left) != s.charAt(right)) {

                                return false;

                        }

                        left++;

                        right--;

                }

                return true;

        }

}

2.复原ip地址。力扣93

首先我们要编写一个布尔型来判断截取的是否合法,开头是否不为0,不超过255,已经没有非法字符这三方面。然后这道题的终止条件为分成四份,那么就是三个句号。当三个句号终止。最后就是回溯,还是从一个个的分割开始,然后到最后再回溯。另外也需要时刻判断所分的区间是否合法,不合法马上break跳出循环。

class Solution {

        List<String> res = new ArrayList<>();

        public List<String> restoreIpAddresses(String s) {

                backtrack(s,0,0);

                return res;

        }

        public void backtrack(String s,int pointsum,int startindex) {

                if (pointsum == 3) {

                        if (isvaild(s,startindex,s.length() - 1)) {

                                res.add(s);

                        }

                        return;

                }

                for (int i = startindex;i < s.length();i++) {

                        if (isvaild(s,startindex,i)) {

                                s = s.substring(0,i + 1) + '.' + s.substring(i + 1);

                                pointsum++;

                                //因为添加了标点,所以是i + 2

                                backtrack(s,pointsum,i + 2);

                                pointsum--;

                                s = s.substring(0,i + 1) + s.substring(i + 2);

                        }else {

                                break;

                        }

                }

        }

        //判断区段数是否合法

        public boolean isvaild(String s,int left,int right) {

                if (left > right) return false;

                //不能有0开头

                if (s.charAt(left) == '0' && left != right) {

                        return false;

                }

                //不能有非数字

                int sum = 0;

                for (int i = left;i <= right;i++) {

                        if (s.charAt(i) > '9' || s.charAt(i) < '0') {

                                return false;

                        }

                        sum = sum * 10 + (s.charAt(i) - '0');

                        if (sum > 255) return false;

                 }

                return true;

        }

}

             子集问题

1.子集 力扣78

子集就是把所有符合条件的都写进去,所以把add条件放在开始,只要符合就加进去,终止条件就是索引超出范围时,就停止。剩下的就是for循环回溯的过程。

class Solution {

        List<Integer> path = new ArrayList<>();

        List<List<Integer>> res = new ArrayList<>();

        public List<List<Integer>> subsets(int[] nums) {

                backtrack(nums,0);

                return res;

        }

        public void backtrack(int[] nums,int startindex) {

                res.add(new ArrayList<>(path));

                if (startindex == nums.length) {

                        return;

                }

                for (int i = startindex;i < nums.length;i++) {

                        path.add(nums[i]);

                        backtrack(nums,i + 1);

                        path.remove(path.size() - 1);

                }

        }

}

2.子集二。 力扣90

第一种方法,设置新列表来记录重复。

class Solution {

        List<Integer> path = new ArrayList<>();

        List<List<Integer>> res = new ArrayList<>();

        public List<List<Integer>> subsetsWithDup(int[] nums) {

                Arrays.sort(nums);

                if (nums.length == 0) {

                        return res;

                }

                backtrack(nums,0);

                        return res;

                }

        public void backtrack(int[] nums,int startindex) {

                res.add(new ArrayList<>(path));

                if (startindex == nums.length) {

                        return;

                }

                List<Integer> pre = new ArrayList<>();

                for (int i = startindex;i < nums.length;i++) {

                        if (pre.contains(nums[i])) {

                                continue;

                        }

                        path.add(nums[i]);

                        pre.add(nums[i]);

                        backtrack(nums,i + 1);

                        path.remove(path.size() - 1);

                }

        }

}

时间复杂度过低,要采用设置布尔数组的方法。

布尔数组中used[i -1] = false,证明同一层用过,used[i - 1] = true 证明同一树枝用过,而此题我们就是要排重同一数层。

class Solution {

        List<Integer> path = new ArrayList<>();

        List<List<Integer>> res = new ArrayList<>();

        boolean[] used;

        public List<List<Integer>> subsetsWithDup(int[] nums) {

                Arrays.sort(nums);

                if (nums.length == 0) {

                        return res;

                }

                used = new boolean[nums.length];

                backtrack(nums,0);

                return res;

        }

        public void backtrack(int[] nums,int startindex) {

                res.add(new ArrayList<>(path));

                if (startindex == nums.length) {

                        return;

                }

                for (int i = startindex;i < nums.length;i++) {

                        if (i > 0 && !used[i - 1] && nums[i] == nums[i - 1]) {

                                continue;

                        }

                path.add(nums[i]);

                used[i] = true;

                backtrack(nums,i + 1);

                path.remove(path.size() - 1);

                used[i] = false;

                }

        }

}

                排列问题   

首先,排列问题中不需要所谓的startindex了,因为1,2 与2,1属于不同的排列,1用到了两次,不需要所谓的startindex来跳出,所以排列问题要用到user布尔数组,来标记用到的数。

1. 全排列 力扣46

我们的for循环,每次都是要遍历每个nums,从头开始,我们发现1,加入,回溯,再发现1,1已经标记为true,所以跳过依次加入了2,3。然后回溯,删除3,2,后添加了3,继续回溯 跳过了1,3加入了2。以此类推,最后回溯了所有情况的全排列输出即可。

class Solution {

        List<List<Integer>> res = new ArrayList<>();

        List<Integer> ans = new ArrayList<>();

        boolean[] used;

        public List<List<Integer>> permute(int[] nums) {

                if (nums.length == 0) return res;

                used = new boolean[nums.length];

                backtrack(nums);

                return res;

        }

        public void backtrack(int[] nums) {

                if (ans.size() == nums.length) {

                        res.add(new ArrayList<>(ans));

                        return;

                }

                for (int i = 0;i < nums.length;i++) {

                        if (used[i] == true) {

                                continue;

                        }

                        used[i] = true;

                        ans.add(nums[i]);

                        backtrack(nums);

                        ans.remove(ans.size() - 1);

                        used[i] = false;

                }

        }

}

2.全排列2 力扣47

跟上一题一样,只不过加入了排重, 所有的全排列题,都是树层排重更效率高一些,树层排重就是used[i - 1] = false,剩下的都是全排列1的做法即可

class Solution {

        List<Integer> res = new ArrayList<>();

        List<List<Integer>> ans = new ArrayList<>();

        boolean[] used;

        public List<List<Integer>> permuteUnique(int[] nums) {

                if (nums.length == 0) return ans;

                used = new boolean[nums.length];

                Arrays.sort(nums);

                Arrays.fill(used,false);

                backtrack(nums);

                return ans;

        }

        public void backtrack(int[] nums) {

                if (res.size() == nums.length) {

                        ans.add(new ArrayList<>(res));

                        return;

                }

                for (int i = 0;i < nums.length;i++) {

                        if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) {

                                continue;

                        }

                        if (used[i] == false) {

                                used[i] = true;

                                res.add(nums[i]);

                                backtrack(nums);

                                res.remove(res.size() - 1);

                                used[i] = false;

                        }

                }

        }

}

                        棋盘问题

1.n皇后,力扣51

首先n皇后三个条件,就是行,列,斜线不能重合,此道题我们是回溯行,所以行一定不会重复,所以只需要检查三个条件,那就是列,45度,135度

首先建立布尔检查,用for循环来检查列,45度,135度是否已经有了q,如果有了q,就直接进入下一次for循环,也就是下一列。

需要建立一个返回列表的arraylist,用于以行的形式存储棋盘里的字符,然后存储到最终返回的res中。

然后就是建立回溯,首先此题终止条件,应该是行与n相等,此时返回。以回溯为行,for循环为列进行backtrack即可。

class Solution {

        List<List<String>> res = new ArrayList<>();

        char[][] checkboard;

        public List<List<String>> solveNQueens(int n) {

                checkboard = new char[n][n];

                for (char[] c : checkboard) {

                        Arrays.fill(c,'.');

                }

                backtrack(n,0,checkboard);

                return res;

        }

        public void backtrack (int n,int row,char[][] checkboard) {

                if (row == n) {

                        res.add(Array2List(checkboard));

                        return;

                }

                for (int col = 0;col < n;col++) {

                        if (check(n,row,col,checkboard)) {

                                checkboard[row][col] = 'Q';

                                backtrack(n,row + 1,checkboard);

                                checkboard[row][col] = '.';

                        }

                }

        }

        //把棋盘转换为字符串

        public List Array2List (char[][] checkboard) {

                List<String> ans = new ArrayList<>();

                for (char[] c : checkboard) {

                        ans.add(String.copyValueOf(c));

                }

                return ans;

        }

        public boolean check(int n,int row,int col,char[][] checkboard) {

                //检查列

                for (int i = 0;i < row;i++) {

                        if (checkboard[i][col] == 'Q') return false;

                }

                //检查45度

                for (int i = row - 1,j = col - 1;i >= 0 && j >= 0;i--,j--) {

                        if (checkboard[i][j] == 'Q') return false;

                }

                 //检查135度

                for (int i = row - 1,j = col + 1;i >= 0 && j <= n - 1;i--,j++) {

                        if (checkboard[i][j] == 'Q') return false;

                }

                return true;

        }

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值