回溯演算法之復原IP地址

Real_man發表於2019-03-19

回溯法(探索與回溯法)是一種選優搜尋法,又稱為試探法,按選優條件向前搜尋,以達到目標。但當探索到某一步時,發現原先選擇並不優或達不到目標,就退回一步重新選擇,這種走不通就退回再走的技術為回溯法,而滿足回溯條件的某個狀態的點稱為“回溯點”。

百度百科說的有點晦澀,就是遞迴演算法,但是有要注意的點:

  • 保留現場,在進行遞迴的時候,儲存上一次的狀態,遞迴回來之後仍然能回到上次的狀態
  • 結束條件,滿足條件的時候儲存結果

題目

給定一個只包含數字的字串,復原它並返回所有可能的 IP 地址格式。

示例:

輸入: "25525511135"
輸出: ["255.255.11.135", "255.255.111.35"]
複製程式碼

解答

我做的演算法題不多,但是個人經驗,在做題的時候最好能手動的在紙上畫圖,模擬程式執行的圖。

  • 一張大白紙
  • 認真的畫圖

不過也可能再電腦上畫圖會更方便,我們先思考程式的執行大致應該是什麼路徑。如圖:

image-20190319081953592

限定條件:

  • 因為是IP地址,所以有一定的條件限制,數字不能大於255,不能小於0
  • 在圖上可以看出來,得出是否是IP地址的在第3層。
  1. 先想到大概的變數。
  • 層級
  • 當前的字串
  • 剩餘的字串
  • 儲存結果的集合
  1. 寫出終止遞迴的條件
private void restoreAddressNew(String source, String currentStr, Integer currentLevel, Integer offset, List<String> res) {
	if (currentLevel == 3) {
            if (isValidNew(source.substring(offset))
                    ) {
                res.add(currentStr + "." + source.substring(offset));
            }
            return;
     }
     ....
}        
複製程式碼
  1. 寫出校驗是否合法IP中段位的函式
    private boolean isValidNew(String str) {
        if (str.length() == 0 || str.length() > 3 || Integer.parseInt(str) < 0 || Integer.parseInt(str) > 255
                || (str.startsWith("0") && str.length() > 1)
                ) {
            return false;
        }
        return true;
    }
複製程式碼
  1. 寫進行遞迴的條件
private void restoreAddressNew(String source, String currentStr, Integer currentLevel, Integer offset, List<String> res) {
        if (currentLevel == 3) {

            if (isValidNew(source.substring(offset))
                    ) {
                res.add(currentStr + "." + source.substring(offset));
            }
            return;
        }

    	// 遞迴段
        for (int i = 1; i <= 3; i++) {
            if (offset > source.length() || (offset + i) > source.length()) {
                return;
            }
            String seg = source.substring(offset, offset + i);
            // 用於儲存原先的狀態
            String oldStr = currentStr;
            
            // 防止出現以 "."開頭的地址
            if (currentStr.length() == 0) {
                currentStr = seg;
            } else {
                currentStr = currentStr + "." + seg;
            }
            if (isValidNew(seg)) {
                restoreAddressNew(source, currentStr, currentLevel + 1, offset + i, res);
                // 處理完之後恢復狀態
                currentStr = oldStr;
            }
        }
    }
複製程式碼
  1. 如果有不確定的地方,在其中可以輸出當前的狀態,判斷當前的結果
  2. 完整的方案
public class RestoreIPAddress {

    public static void main(String[] args) {
        List<String> strings = new RestoreIPAddress().restoreIpAddressesNew("25525511135");
    }

    private List<String> restoreIpAddressesNew(String s) {
        List<String> result = new ArrayList<>();
        restoreAddressNew(s, "", 0, 0, result);
        return result;
    }

    
    private void restoreAddressNew(String source, String currentStr, Integer currentLevel, Integer offset, List<String> res) {
        if (currentLevel == 3) {

            if (isValidNew(source.substring(offset))
                    ) {
                res.add(currentStr + "." + source.substring(offset));

            }
            return;
        }

        for (int i = 1; i <= 3; i++) {
            if (offset > source.length() || (offset + i) > source.length()) {
                return;
            }
            String seg = source.substring(offset, offset + i);
            String oldStr = currentStr;
            if (currentStr.length() == 0) {
                currentStr = seg;
            } else {
                currentStr = currentStr + "." + seg;
            }
            if (isValidNew(seg)) {
                restoreAddressNew(source, currentStr, currentLevel + 1, offset + i, res);
                currentStr = oldStr;
            }
        }
    }

    private boolean isValidNew(String str) {
        if (str.length() == 0 || str.length() > 3 || Integer.parseInt(str) < 0 || Integer.parseInt(str) > 255
                || (str.startsWith("0") && str.length() > 1)
                ) {
            return false;
        }
        return true;
    }
    
}

複製程式碼

中間犯的錯誤

  • 第一次忘了儲存現場,就會出現”資料打架“,資料很奇怪
  • 出現過"."開頭的地址情況

都是想問題不夠全面導致的,程式執行的快,人算的慢,但是程式執行的規則是人定義的。只有自己能想清楚規則,才能指導計算機做正確的事情,否則當自己都迷糊的時候,程式怎麼可能執行的正確。

最後

畫圖是的個好東西,幫你理清思路,慢慢來,不要著急

參考

相關文章