1. 程式人生 > 實用技巧 >LeetCode-Backtracking-Easy

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); //撤銷選擇
        }
    }
    
}

}