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); //撤销选择
        }
    }
    
}

}

原文地址:https://www.cnblogs.com/miaomiaowu/p/13292151.html