回溯法(探索與回溯法)是一種選優搜尋法,又稱為試探法,按選優條件向前搜尋,以達到目標。但當探索到某一步時,發現原先選擇並不優或達不到目標,就退回一步重新選擇,這種走不通就退回再走的技術為回溯法,而滿足回溯條件的某個狀態的點稱為“回溯點”。
百度百科說的有點晦澀,就是遞迴演算法,但是有要注意的點:
- 保留現場,在進行遞迴的時候,儲存上一次的狀態,遞迴回來之後仍然能回到上次的狀態
- 結束條件,滿足條件的時候儲存結果
題目
給定一個只包含數字的字串,復原它並返回所有可能的 IP 地址格式。
示例:
輸入: "25525511135"
輸出: ["255.255.11.135", "255.255.111.35"]
複製程式碼
解答
我做的演算法題不多,但是個人經驗,在做題的時候最好能手動的在紙上畫圖,模擬程式執行的圖。
- 一張大白紙
- 認真的畫圖
不過也可能再電腦上畫圖會更方便,我們先思考程式的執行大致應該是什麼路徑。如圖:
限定條件:
- 因為是IP地址,所以有一定的條件限制,數字不能大於255,不能小於0
- 在圖上可以看出來,得出是否是IP地址的在第3層。
- 先想到大概的變數。
- 層級
- 當前的字串
- 剩餘的字串
- 儲存結果的集合
- 寫出終止遞迴的條件
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;
}
....
}
複製程式碼
- 寫出校驗是否合法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;
}
複製程式碼
- 寫進行遞迴的條件
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;
}
}
}
複製程式碼
- 如果有不確定的地方,在其中可以輸出當前的狀態,判斷當前的結果
- 完整的方案
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;
}
}
複製程式碼
中間犯的錯誤
- 第一次忘了儲存現場,就會出現”資料打架“,資料很奇怪
- 出現過"."開頭的地址情況
都是想問題不夠全面導致的,程式執行的快,人算的慢,但是程式執行的規則是人定義的。只有自己能想清楚規則,才能指導計算機做正確的事情,否則當自己都迷糊的時候,程式怎麼可能執行的正確。
最後
畫圖是的個好東西,幫你理清思路,慢慢來,不要著急