算法~回溯思想 (附带4道例题)

回溯

  • 回溯是一种通过穷举所有可能情况来找到所有解的算法。如果一个候选解最后被发现并不是可行解,回溯算法会舍弃它,并在前面的一些步骤做出一些修改,并重新尝试找到可行解。回溯算法一般会结合在搜索算法中。

电话号码的字母组合

给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。

给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
在这里插入图片描述

示例:

输入:“23”
输出:[“ad”, “ae”, “af”, “bd”, “be”, “bf”, “cd”, “ce”, “cf”].

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/letter-combinations-of-a-phone-number
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

  • 代码
class Solution {
    
    

    //需要一个map去初始化键盘数据
    private Map<Integer, String> info;

    public List<String> letterCombinations(String digits) {
    
    

        List<String> ret = new ArrayList<>();
        if (digits.length() == 0) {
    
    
            return ret;
        }
        //初始化键盘
        info = new HashMap<>();
        info.put(2, "abc");
        info.put(3, "def");
        info.put(4, "ghi");
        info.put(5, "jkl");
        info.put(6, "mno");
        info.put(7, "pqrs");
        info.put(8, "tuv");
        info.put(9, "wxyz");
        //使用深度优先加回溯思想完成
        DFS(digits, ret, "", 0);
        return ret;
    }

    private void DFS(String digits, List<String> ret, String curStr, int curLength) {
    
    
        //如果当前的字符串与输入数字的长度相同 说明一个组合完成
        if (curLength == digits.length()) {
    
    
            ret.add(curStr);
            return;
        }
        //获取当前的键盘数字输入
        int num = digits.charAt(curLength) - '0';
        String chars = info.get(num);
        for (int i = 0; i < chars.length(); i++) {
    
    
            curStr += chars.charAt(i);
            DFS(digits, ret, curStr, curLength + 1);
            //回溯
            curStr = curStr.substring(0, curStr.length() - 1);
        }
    }
}

组合总和

给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。

candidates 中的数字可以无限制重复被选取。

说明:

所有数字(包括 target)都是正整数。
解集不能包含重复的组合。
示例 1:

输入:candidates = [2,3,6,7], target = 7,
所求解集为:
[
[7],
[2,2,3]
]
示例 2:

输入:candidates = [2,3,5], target = 8,
所求解集为:
[
[2,2,2,2],
[2,3,3],
[3,5]
]

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/combination-sum
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

  • 代码
class Solution {
    
    

    private List<List<Integer>> ret;


    private void DFS(int[] candidates, List<Integer> list, int cur, int target, int prevPose) {
    
    
        if (cur >= target) {
    
    
            if (cur == target) {
    
    
                //需要将链表中的值拷贝一份 不然引用的是同一个变量
                List<Integer> curList = new ArrayList<>(list);
                ret.add(curList);
            }
            return;
        }
        //遍历累加数字可以重复取  而且避免重复组合 直接从上次位置开始累加
        for (int i = prevPose; i < candidates.length; i++) {
    
    
            //将当前数字加到链表中
            list.add(candidates[i]);
            //进行递归处理
            DFS(candidates, list, cur + candidates[i], target, i);
            //回溯
            list.remove(list.size() - 1);
        }
    }

    public List<List<Integer>> combinationSum(int[] candidates, int target) {
    
    
        //初始化返回结果
        ret = new ArrayList<>();
        //保存当前结果
        List<Integer> list = new ArrayList<>();
        DFS(candidates, list, 0, target, 0);
        return ret;
    }
}

活字印刷

你有一套活字字模 tiles,其中每个字模上都刻有一个字母 tiles[i]。返回你可以印出的非空字母序列的数目。

注意:本题中,每个活字字模只能使用一次。

示例 1:

输入:“AAB”
输出:8
解释:可能的序列为 “A”, “B”, “AA”, “AB”, “BA”, “AAB”, “ABA”, “BAA”。
示例 2:

输入:“AAABBC”
输出:188

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/letter-tile-possibilities
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

  • 代码
class Solution {
    
    

    private void DFS(String tiles, Set<String> ret, List<Integer> usedIndex, String cur) {
    
    
        //如果此时cur不为空就加入到set中
        if (cur.length() != 0) {
    
    
            ret.add(cur);
        }
        //遍历title是中的每一个字母
        for (int i = 0; i < tiles.length(); i++) {
    
    
            //每个字母不能重复使用 所以下标只要用过就不能再用
            if (usedIndex.contains(i)) {
    
    
                continue;
            }
            //使用这个下标
            usedIndex.add(i);
            DFS(tiles, ret, usedIndex, cur + tiles.charAt(i));
            //回溯 取消这个下标的使用
            usedIndex.remove(usedIndex.size() - 1);
        }

    }

    public int numTilePossibilities(String tiles) {
    
    
        //初始化一个set保存单词
        Set<String> ret = new HashSet<>();
        //使用一个链表保存使用过得下标
        List<Integer> usedIndex = new ArrayList<>();
        DFS(tiles, ret, usedIndex, "");
        return ret.size();

    }
}

N皇后

n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。
在这里插入图片描述

上图为 8 皇后问题的一种解法。

给定一个整数 n,返回所有不同的 n 皇后问题的解决方案。

每一种解法包含一个明确的 n 皇后问题的棋子放置方案,该方案中 ‘Q’ 和 ‘.’ 分别代表了皇后和空位。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/n-queens
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

  • 代码

/**
 * Created with IntelliJ IDEA.
 * Description: If you don't work hard, you will a loser.
 * User: Listen-Y.
 * Date: 2020-09-25
 * Time: 11:02
 */
class Solution {
    
    
    //保存x y节点的node
    static class Node {
    
    
        public int row;
        public int col;
        public Node(int row, int col) {
    
    
            this.row = row;
            this.col = col;
        }

        @Override
        public boolean equals(Object o) {
    
    
            if (o == null) {
    
    
                return false;
            }
            if (this == o) {
    
    
                return true;
            }
            if (!(o instanceof Node)) {
    
    
                return false;
            }
            Node cur = (Node) o;
            return cur.col == this.col && cur.row == this.row;
            

        }
    }

    //用于保存所有的解法
    private List<List<Node>> solutions;

    private void DFS(List<Node> queen, int curRow, int n) {
    
    
        //如果当前保存的queen的数量等于了n就说明是一种解决方案
        if (queen.size() == n) {
    
    
            //拷贝一份当前的queen
            List<Node> curQueen = new ArrayList<>();
            for (Node node : queen) {
    
    
                curQueen.add(node);
            }
            solutions.add(curQueen);
            return;
        }
        //遍历这一行的每一个位置
        for (int col = 0; col < n; col++) {
    
    
            //判断此时的位置是否可以放置皇后
            if (can(queen, curRow, col)) {
    
    
                queen.add(new Node(curRow, col));
                DFS(queen, curRow + 1, n);
                //回溯
                queen.remove(queen.size() - 1);
            }
        }
    }

    private boolean can(List<Node> queen, int row, int col) {
    
    
        //遍历此时保存的每一个皇后的node 看当前位置是否合法
        for (Node node : queen) {
    
    
            if (node.col == col || node.row + node.col == row + col || node.row - node.col == row - col) {
    
    
                return false;
            }
        }
        return true;
    }

    private List<List<String>> makeRet(int n) {
    
    
        List<List<String>> ret = new ArrayList<>();
        //遍历结果集
        for (List<Node> list : solutions) {
    
    
            //初始化一个保存结果的链表
            List<String> curRet = new ArrayList<>();
            for (int row = 0; row < n; row++) {
    
    
                StringBuilder str = new StringBuilder();
                for (int col = 0; col < n; col++) {
    
    
                    //如果当前位置是皇后就保存Q 不是就保存一个.
                    if (list.contains(new Node(row, col))) {
    
    
                        str.append("Q");
                    } else {
    
    
                        str.append(".");
                    }
                }
                curRet.add(str.toString());
            }
            ret.add(curRet);
        }
        return ret;
    }

    public List<List<String>> solveNQueens(int n) {
    
    
        //初始化
        solutions = new ArrayList<>();
        List<Node> queen = new ArrayList<>();
        DFS(queen, 0, n);
        //结果导出
        return makeRet(n);

    }
}

猜你喜欢

转载自blog.csdn.net/Shangxingya/article/details/108792872