zoukankan      html  css  js  c++  java
  • LeetCode-Backtracking-Easy 回溯算法

    1. 二进制手表(leetcode-401)

    二进制手表顶部有 4 个 LED 代表 小时(0-11),底部的 6 个 LED 代表 分钟(0-59)。
    每个 LED 代表一个 0 或 1,最低位在右侧。
    给定一个非负整数 n 代表当前 LED 亮着的数量,返回所有可能的时间。
    
    示例:
    输入: n = 1
    返回: ["1:00", "2:00", "4:00", "8:00", "0:01", "0:02", "0:04", "0:08", "0:16", "0:32"]
    
    提示:
        输出的顺序没有要求。
        小时不会以零开头,比如 “01:00” 是不允许的,应为 “1:00”。
        分钟必须由两位数组成,可能会以零开头,比如 “10:2” 是无效的,应为 “10:02”。
        超过表示范围(小时 0-11,分钟 0-59)的数据将会被舍弃,也就是说不会出现 "13:00", "0:61" 等时间。
    

    1)暴力穷举(推荐)

    思路:

    1. 由题目要求:小时(0-11)、分钟(0-59)可知,表示小时的灯最多亮3个,表示分钟的灯最多亮5个。
    2. 穷举表示小时的灯不亮、亮一盏、亮两盏、亮三盏的情况;穷举表示分钟的灯亮0~5盏的情况。
    3. 根据指定的num,列出所有情况。
    暴力穷举
    
    class Solution {
    
    public List<String> readBinaryWatch(int num) {
            List<String> res = new ArrayList<>();
            //if(num<0 || num>8)
               // return res;
    
            String[][] hstrs = {{"0"}, {"1","2","4","8"}, {"3","5","6","9","10"}, {"7","11"}};  // 没亮灯、亮一个灯、亮两个灯、亮三个灯。
            String[][] mstrs = {{"00"}, {"01","02","04","08","16","32"}, 
            {"03","05","06","09","10","12","17","18","20","24","33","34","36","40","48"}, 
            {"07","11","13","14","19","21","22","25","26","28","35","37","38","41","42","44","49","50","52","56"}, 
            {"15","23","27","29","30","39","43","45","46","51","53","54","57","58"}, 
            {"31","47","55","59"}};  // 亮0~5个灯的各情况
    
            for(int i=0; i<=Math.min(3,num); i++){ // i:表示小时的灯的数量
                if(num-i > 5) continue;
                String[] hstr = hstrs[i];
                String[] mstr = mstrs[num - i];
                for(int j=0; j<hstr.length; j++){
                    for(int k=0; k<mstr.length; k++){
                        res.add(hstr[j]+":"+mstr[k]);
                    }
                }
            }
    
            return res;
    }
    

    }

    自动生成枚举
    
    public List readBinaryWatch(int num) {
    
        int[] nums = new int[]{8,4,2,1};
        List<List<Integer>> h = new ArrayList<>();
        help(nums, 0, 0, 1, 12, h);
        h.get(0).add(0);
    
        nums = new int[]{32, 16, 8, 4, 2, 1};
        List<List<Integer>> m = new ArrayList<>();
        help(nums, 0, 0, 1, 60, m);
        m.get(0).add(0);
    
        List<String> rs = new ArrayList<>();
    
        for(int i = 0; i <= 3 && i <= num; i++){
            if(num - i > 5) continue;
            for (int j : h.get(i)) {
                for (int k : m.get(num - i)) {
                    if (k >= 10) rs.add(j + ":" + k);
                    else rs.add(j + ":0" + k);
                }
            }
        }
        return rs;
    }
    
    public void help(int[] nums, int v, int start, int level, int max, List<List<Integer>> rs){
        for(int i = start; i < nums.length; i++){
            int value = v + nums[i];
            if(value < max) {
                while(rs.size() <= level) rs.add(new ArrayList<>());
                rs.get(level).add(value);
                help(nums, value, i + 1, level + 1, max, rs);
            }
        }
    }
    

    2)Integer.bitCount()法

    bitCount实现的功能是计算一个(byte,short,char,int统一按照int方法计算)int,long类型的数值在二进制下“1”的数量。

    Integer.bitCount()法
    
    class Solution {
    
    public List<String> readBinaryWatch(int num) {
        List<String> res = new ArrayList<>();
        for(int h=0; h<12; h++){
            for(int m=0; m<60; m++){
                if(Integer.bitCount(h) + Integer.bitCount(m) == num){
                    res.add(String.format("%d:%02d",h,m));
                }
            }
        } 
        return res;
    }
    

    }

    源码

    public static int bitCount(int i) {
            // HD, Figure 5-2
            i = i - ((i >>> 1) & 0x55555555);
            i = (i & 0x33333333) + ((i >>> 2) & 0x33333333);
            i = (i + (i >>> 4)) & 0x0f0f0f0f;
            i = i + (i >>> 8);
            i = i + (i >>> 16);
            return i & 0x3f;
    }
    

    3)回溯方法

    DFS法
    
    class Solution {
    
    public List<String> readBinaryWatch(int num) {
        // 可选择的内容数组
        int[] times = new int[]{8,4,2,1,32,16,8,4,2,1};
        List<String> res = new ArrayList<>();
        int hours = 0;
        int minutes = 0;
        dfs(times,num,0,hours,minutes,res);
        return res;
    }
    public  void dfs(int[] times, int num,int start,int hours,int minutes, List<String> res){
        if (0 == num){
            if (hours < 12 && minutes < 60){ // 合理的值
                StringBuilder sb = new StringBuilder();
                sb.append(hours).append(':').append(minutes < 10 ? "0" + minutes: minutes);
                res.add(sb.toString());
                // String result = hours + ":" + (minutes < 10 ? "0" + minutes: minutes);
                // String result = hours + ":" + (minutes < 10 ? "0" + minutes: minutes);
                // res.add(result);
            }
        } else {
            for (int i = start; i < times.length; i++) {
               if (i < 4){ // hours
                   hours += times[i];
                   dfs(times,num-1,i+1,hours,minutes,res);
                   hours -= times[i];
               } else {
                   minutes += times[i];
                   dfs(times,num-1,i+1,hours,minutes,res);
                   minutes -= times[i];
               }
            }
        }
    } 
    

    }

    2. 字母大小写全排列(leetcode-784)

    给定一个字符串S,通过将字符串S中的每个字母转变大小写,我们可以获得一个新的字符串。返回所有可能得到的字符串集合。
    
    示例:
    输入: S = "a1b2"
    输出: ["a1b2", "a1B2", "A1b2", "A1B2"]
    
    输入: S = "3z4"
    输出: ["3z4", "3Z4"]
    
    输入: S = "12345"
    输出: ["12345"]
    
    注意:
        S 的长度不超过12。
        S 仅由数字和字母组成。
    

    1)队列

    从左往右依次遍历字符,过程中保持 ans 为已遍历过字符的字母大小全排列。

    如果下一个字符 c 是字母,将当前已遍历过的字符串全排列复制两份,在第一份的每个字符串末尾添加 lowercase(c),在第二份的每个字符串末尾添加 uppercase(c)。

    如果下一个字符 c 是数字,将 c 直接添加到每个字符串的末尾。

    队列
    
    class Solution {
    
    public List<String> letterCasePermutation(String S) {
        List<StringBuffer> ans = new ArrayList<>();
        ans.add(new StringBuffer());
    
        for(char ch : S.toCharArray()){
            int n = ans.size();
    
            if(Character.isLetter(ch)){
                for(int i=0; i<n;i++){
                    ans.add(new StringBuffer(ans.get(i)));   //ans.add(ans.get(i));   算法结果不对!
                    ans.get(i).append(Character.toLowerCase(ch));
                    ans.get(n+i).append(Character.toUpperCase(ch));
                }
            }else{
                for(int i=0; i<n;i++){
                    ans.get(i).append(ch);
                }
            }
        }
    
        List<String> res = new ArrayList<>();
        for(StringBuffer sb: ans){
            res.add(sb.toString());
        }
        return res;
    }
    

    }

    复杂度分析
    时间复杂度:O(2^N * N),其中 N 是 S 的长度。
    空间复杂度:O(2^N * N)。

    2)二分掩码

    假设字符串 S 有 B 个字母,那么全排列就有 2^B 个字符串,且可以用位掩码 bits 唯一地表示。

    例如,可以用 00 表示 a7b, 01 表示 a7B, 10 表示 A7b, 11 表示 A7B。注意数字不是掩码的一部分。

    根据位掩码,构造正确的全排列结果。如果下一个字符是字母,则根据位掩码添加小写或大写字母。 否则添加对应的数字。

    二分掩码
    
    class Solution {
    
    public List<String> letterCasePermutation(String S) {
        int B = 0;  // 统计字符的个数
        for(char ch: S.toCharArray()){
            if(Character.isLetter(ch))
                B++;
        }
    
        List<String> ans = new ArrayList();
    
        for(int bits = 0; bits< (1<<B); bits++){  // 2^b种情况
            int b = 0;
            StringBuilder word = new StringBuilder();
            for(char ch: S.toCharArray()){
                if (Character.isLetter(ch)){
                    if(((bits>> b++) & 1) ==1){ //遍历bits的每一位(比特位)
                        word.append(Character.toLowerCase(ch));
                    }else{
                        word.append(Character.toUpperCase(ch));
                    }
                }else{
                    word.append(ch);
                }
            }
            ans.add(word.toString());
        }
        return ans;
    }
    

    }

    时间和空间复杂度:O(2^N∗N),与方法一分析相同。

    3)深度优先遍历

    小技巧
    
    大小写的转换:直接异或32。
    
    大小写之间差了32,是2的5次方,异或是不进位的加法。大写的二进制码第5位是0,小写的二进制码是1,所以异或就实现了大小写的转换。
    
    深度优先遍历实现一
    
    class Solution {
    
    public List<String> letterCasePermutation(String S) {
        List<String> ans = new ArrayList<String >();
        dfs(S.toCharArray(), ans, 0);
        return ans;
    }
    public void dfs(char[] arr, List<String > e, int index){
        if(index == arr.length) {
        	e.add(String.valueOf(arr));
        	return;
        }
        dfs(arr, e, index + 1);  //不处理数字与字母
        if(Character.isLetter(arr[index])) { 
        	arr[index] ^= 32;   //转换字母大小写
        	dfs(arr, e, index + 1);
        }
    }
    

    }

    深度优先遍历实现二
    
    class Solution {
    
    public List<String> letterCasePermutation(String S) {
        List<String> res = new ArrayList<>();
        dfs( S, res, 0, new StringBuffer(), S.length());
        return res;
    }
    
    public void dfs(String S, List<String> res, int start, StringBuffer tmp,int len){
        if(tmp.length() == len){
            res.add(tmp.toString());
            return;
        }
        if(start<len){
            char ch = S.charAt(start);
        
            if(Character.isDigit(ch)){  
                tmp.append(ch);
                dfs( S, res, start+1, tmp, len);
                tmp.deleteCharAt(tmp.length()-1); //撤销选择
            }else{
                tmp.append(Character.toUpperCase(ch));
                dfs( S, res, start+1, tmp, len);
                tmp.deleteCharAt(tmp.length()-1); //撤销选择
    
                tmp.append(Character.toLowerCase(ch));
                dfs( S, res, start+1, tmp, len);
                tmp.deleteCharAt(tmp.length()-1); //撤销选择
            }
        }
        
    }
    

    }

  • 相关阅读:
    Java 反射
    类中静态/普通/构造初始化顺序
    计算机世界中的0和1
    Java并发练习
    HashMap底层
    HashMap 与 Hashtable 的区别
    为什么重写了equals() 就要重写hashcode()
    干货型up主
    JSP页面元素
    重定向与请求转发的区别
  • 原文地址:https://www.cnblogs.com/miaomiaowu/p/13292151.html
Copyright © 2011-2022 走看看