回溯算法解法 针对数组或字符串

 

目录

NC20 数字字符串转化成IP地址

回溯模板

NC27 集合的所有子集(一) 

BM55 没有重复项数字的全排列 

BM56 有重复项数字的全排列

最后


NC20 数字字符串转化成IP地址

描述

        现在有一个只包含数字的字符串,将该字符串转化成IP地址的形式,返回所有可能的情况。

例如:给出的字符串为"25525522135",返回["255.255.22.135", "255.255.221.35"]. (顺序没有关系)

数据范围:字符串长度 0≤𝑛≤120≤n≤12,注意:ip地址是由四段数字组成的数字序列,格式如 "x.x.x.x",其中 x 的范围应当是 [0,255]。

题目链接: https://www.nowcoder.com/practice/ce73540d47374dbe85b3125f57727e1e

输入:"25525522135"   ,返回值:["255.255.22.135","255.255.221.35"] 

回溯模板

        1,确定初始回溯索引位置,回溯过程中的中间状态。本题中 dfs(s,0,"")  s 是不变的,我们要搜索它得到整个解,0是索引初始位置,"" 是初始中间状态。

        2,确定 dfs() 的结束状态,比如回溯起始位置到达问题 arr 数组或字符串的末尾,或是中间状态符合期望的结果。本题中是找到一个合法的 ip 字符串,如果发现中间状态 tmp 不是一个合法的ip,就终止本次回溯。

        3,确定回溯中起始位置,本题中可以从 2.xxx,25.xxx,255.xxx,2552.xxx ... 开始回溯。回溯函数里一般都会有一层for 循序,每次回溯都会用 for 遍历选择某个元素或不选择某个元素并继续递归地回溯。每次回溯的起始位置可以是问题 arr数组或字符串索引 idx 中的任一位置,可以是0,也可以是上次回溯起始位置加 1 ,0 =< idx < s.length-1 。

        4,处理回溯逻辑,确定下一次回溯的条件。比如要不要选择某个元素,要不要在某个位置添加一个"."点号,该位置要回溯几次等等......比如获取所有子集获取全排列,根据我们的需求来确定怎么进行下次回溯。

                本题中是找到一个从回溯起始位置截断的子串,如果中间状态字符串 tmp 不为空就向后添加 "." 并添加这个子串,并把回溯起始位置后置1 。

public static class Solution4 {
        static ArrayList<String> res = new ArrayList<String>();

        public static List<String> restoreIpAddresses(String s) {
            // s 长度不能超过 3*4 
            if (s.length() > 12) {
                return res;
            }
            // 回溯算法,和深度优先差不多
            // 0 是开始回溯索引位置,"" 是字符串
            dfs(s, 0, "");
            return res;
        }
        // 回溯函数,每次回溯都会进入一层 for 循环确定要不要在某元素后添加一个点号
        private static void dfs(String s, int idx, String tmp) {
            // 剪枝
            if (tmp.split("\\.").length > 4) {
                return;
            }
            // 得到一个合法的结果
            if (idx >= s.length()) {
                res.add(tmp);
                return;
            }
            // 确定起始位置,比如 25525522135 从 2.xxx开始,
            // 也可以是从 25.xxx 或 255.xxx ... 开始去回溯
            for (int i = idx; i < s.length(); i++) {
                String str = s.substring(idx, i + 1);
                if (isLegal(str)) {
                    // 只要 tmp 不为空就添加 “.” 号作为截断
                    // 该做法不论之后是否得到的 tmp 是不是合法 ip 都加快了回溯速度
                    if (!tmp.isEmpty()) {
                        dfs(s, i + 1, tmp + "." + str);
                    } else {
                        // 开头只进来一次
                        // 如 2.xxx,25.xxx,255.xxx 都会进入一次
                        dfs(s, i + 1, str);
                    }
                }
            }
        }
        // 确定 str 是否在 0 到 255 内且不以 0 开始
        private static boolean isLegal(String str) {
            if (str.length() > 3) {
                return false;
            }
            if (str.length() == 3 && str.charAt(0) == '0') {
                return false;
            }
            if (str.length() == 2 && str.charAt(0) == '0') {
                return false;
            }
            int num = Integer.parseInt(str);
            if (num <= 255 && num >= 0) {
                return true;
            }
            return false;
        }
}

     

NC27 集合的所有子集(一) 

          一个和它类似的题目,现在有一个没有重复元素的整数集合S,求S的所有子集 

集合的所有子集(一)_牛客题霸_牛客网

public static class Solution5 { 

        public static void main(String[] args) {
            int[] ints = {1, 2, 3};
            System.out.println(subsets(ints));
        }

        public static ArrayList<ArrayList<Integer>> res = new ArrayList<>();

        public static ArrayList<ArrayList<Integer>> subsets(int[] arr) {
            // 0 是回溯起始位置,tmp 是中间结果
            dfs(arr, 0, new ArrayList<Integer>()); 

            // 排序
            Collections.sort(res, new Comparator<ArrayList<Integer>>() {
            @Override
            public int compare(ArrayList<Integer> o1, ArrayList<Integer> o2) {
                int o1Size = o1.size();
                int o2Size = o2.size();
                if (o1Size != o2Size) return Integer.compare(o1Size, o2Size);
                else {
                    for (int i = 0; i < o1.size(); i++) {
                        int comp = Integer.compare(o1.get(i), o2.get(i));
                        if (comp != 0) return comp;
                    }
                }
                return 0;
            }
        });
            return res;
        }
        // 回溯函数,每次进入回溯时都要进入 for 循环遍历 arr 数组选择是否选取某元素进行下次遍历
        public static void dfs(int[] arr, int idx, ArrayList<Integer> tmp) {
            // 任意结果都可接受
            Collections.sort(tmp);
            if (!res.contains(tmp)) {
                res.add(new ArrayList<>(tmp));
            }
            // 回溯终止条件
            if (idx >= arr.length) {
                return;
            }
            // 每次回溯过程中回溯起始元素位置之后的元素都有被选择的可能,可以选,也可以不选
            for (int i = idx; i < arr.length; i++) {
                // 每个回溯起始位置的元素都不要,即不选
                dfs(arr, i + 1, new ArrayList<>(tmp));
                // 每个回溯起始位置的元素都要
                tmp.add(arr[i]);
                // 本次元素arr[idx]回溯结束后还要进入for循环,对下一个起始元素进行回溯
                dfs(arr, i + 1, new ArrayList<>(tmp));
            }
        }

没有从 0 开始回溯的例子:

BM55 没有重复项数字的全排列 

没有重复项数字的全排列_牛客题霸_牛客网

        假如 还是套用上面的模板,从索引 0 处开始回溯,就得不到正确结果。因为每个元素都要用到 1 次,但顺序不变。

 public static class Solution8 {
        public static void main(String[] args) {
            System.out.println(permute(new int[]{1, 2, 3}));
        }

        public static ArrayList<ArrayList<Integer>> res = new ArrayList<>();

        public static ArrayList<ArrayList<Integer>> permute(int[] num) {
            dfs(num, new ArrayList<Integer>());
            return res;
        }

        private static void dfs(int[] num,   ArrayList<Integer> tmp) {
            // 回溯结束条件
            if (tmp.size() == num.length) {
                res.add(new ArrayList<>(tmp));
                return;
            }
            // 每次进入递归函数时每个元素都有被选择的可能,但已经选过的就跳过
            for (int i = 0; i < num.length; i++) {
                if (!tmp.contains(num[i])) {
                    // 选择本元素后进入递归,递归完后再进入for循环
                    tmp.add(num[i]);
                    dfs(num, tmp);
                    // 相当于此次进入递归函数没选择本元素,下次进入递归函数时可以再选,
                    // 执行完这条语句后进入for循环遍历其他未选中的元素
                    tmp.remove(tmp.size() - 1);
                }
            }
        }
    }

BM56 有重复项数字的全排列

        有重复项数字的全排列_牛客题霸_牛客网

    public class Solution9 {
        ArrayList<ArrayList<Integer>> res = new ArrayList<>();

        public ArrayList<ArrayList<Integer>> permuteUnique(int[] num) {
            Arrays.sort(num);
            // 因为每个元素都必须使用一次
            boolean[] visited = new boolean[num.length];
            dfs(num, new ArrayList<>(), visited);
            // 排序
            res.sort(new Comparator<ArrayList<Integer>>() {
                @Override
                public int compare(ArrayList<Integer> o1, ArrayList<Integer> o2) {
                    for (int i = 0; i < o1.size(); i++) {
                        if (o1.get(i) < o2.get(i)) {
                            return 1;
                        } else if (o1.get(i) > o2.get(i)) {
                            return -1;
                        }
                    }
                    return 0;
                }
            });
            return res;
        }
        // 回溯函数里继续递归回溯,每次回溯都会进入一层for循环,即每次回溯都只是确定该某一个位置的元素是否选择
        private void dfs(int[] num, ArrayList<Integer> tmp, boolean[] visited) {
            
            if (tmp.size() == num.length) {
                res.add(new ArrayList<>(tmp));
            }
            // 每次回溯每个元素都有被选中的可能
            for (int i = 0; i < num.length; i++) {
                // 剪枝,如果前一个相同的元素A选择不访问,那我们也不访问,因为A后面必定会被
                // 访问
                if((i>0)&&(num[i]==num[i-1])&&(!visited[i-1])){
                    continue;
                }
                // 每次回溯只选择未被选择的元素
                if (!visited[i]) { 
                    // 本次回溯选择了本元素
                    tmp.add(num[i]);
                    visited[i] = true;
                    // 本次选择已结束,进入下次回溯,下次回溯又会进入for循环,每个元素都有被选择的可能
                    dfs(num, tmp, visited);
                      
                    // 恢复原状,相当于本次for遍历 没有选择本元素,后面应该要遍历下一个元素
                    tmp.remove(tmp.size() - 1);
                    visited[i] = false; 
                }
            } 
        }
    }

最后

        回溯算法也可以用在一些 n*m 的矩阵里面,这时候就不是在一个回溯函数里套一个 for 循环这样了,但回溯的思路是不变的,因为矩阵不是只有一个 0 到 n 的单一搜索方向,它每次都有东南西北四个方向要分别进行深度优先搜索,每个矩阵元素 matrix[i][j] 都可以作为深度搜索的起点并进行四个方向的搜索遍历

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值