The problem:
Given a string containing only digits, restore it by returning all possible valid IP address combinations.
For example:
Given "25525511135"
,
return ["255.255.11.135", "255.255.111.35"]
. (Order does not matter)
My analysis:
his problem is not complex, but it includes many important skills in implementing a recursion algorithm.
Key: this problem is a finite recursion problem. (we could clearly say the recursion only and must have four layers)
seg4. seg3. seg2. seg1
What's more, we could use extra required properity of a valid IP address to limit searching branches.
1. each segement could only hold at most 3 characters and at least 1 character.
int len; if (s.length() < start + 3) //in case of the last segment may not be able to have 3 characters left to search. len = s.length(); else len = start + 3; for (int i = start; i < len; i++) {...}
2. a segement must only hold the number in the range [0, 255]: 0<= segment_number <=255.
if (cur_temp <= 255 && cur_str.length() <= 3) { ans += cur_str; ret.add(ans); }
3. if a segement's initial character is '0', and the segement's length is larger than 1 (e. "00", "011"). this case is illegal, we should stop searching along this branch.
if (cur_str.length() > 1 && cur_str.charAt(0) == '0') return;
4. if the left characters's length is smaller than the segment's number. (e. "11", seg3. seg2. seg1)
in this case, it's impossible to assgin enough character for each segment.
5. if the left characters's length is larger than three times of the segment's number. (e. "1111101", seg2, seg1)
if (digit_index * 3 < s.length() - 1 - start + 1) return; if (digit_index > s.length() - 1 - start + 1) return;
Important skills:
1. How to preserve the searching answers ?
1.1 we usually use an ArrayList and pass it around all recursion.
1.2 we add the answer at the final recursion level(in this problem is segment1).if the searching branch can reach segement4,
it means the branch is valid in segement 1, segement2 and segment3(otherwise, we have already stop the searching along this branch).
if (digit_index == 1) { //only the last section left, we should check it. cur_str = s.substring(start, s.length()); cur_temp = Integer.parseInt(cur_str); if (cur_str.length() > 1 && cur_str.charAt(0) == '0') return; if (cur_temp <= 255 && cur_str.length() <= 3) { ans += cur_str; ret.add(ans); } return; }
2. How to stop the searching on the invalid branches? (very imprtant)
Note: In a recursion level, we may extend to several sub searching branches.
for (int i = start; i < len; i++) {...}
2.1 if the invalid condition would invalid all sub searching branches, we use "return" directly.
if (digit_index * 3 < s.length() - 1 - start + 1) //if the left elements' number exceeds the left digits return;
2.2 (note) if the invalid condition would only invalid one sub search branches, we should use "continue" to skip this search branch.(since it can never reach the next level recursion)
for (int i = start; i < len; i++) { //only three numbers possible for this section cur_str = s.substring(start, i + 1); cur_temp = Integer.parseInt(cur_str); String ans_temp = new String(ans); //this is very very important!!!, must be a sperate new string! if (cur_str.length() > 1 && cur_str.charAt(0) == '0') continue;
3. Use the new object properly. (very important).
3.1 Since the Java pass all things by value, the mainpulating string would be different at different rucursion level.
private void helper(String s, int start, int digit_index, String ans, ArrayList<String> ret) { .... helper(s, i + 1, digit_index - 1, ans, ret); .... }
Even with the same name "ret", the manipulating object at present recursion level and next recursion level would be different Strings. Therefore, the manipulation would not interfere with each other.
3.2 However, we should be very careful when there are several sub-searching branches in the same recursion level. In this case, it's very likely to use the same object repeatedly, which would in a very strange and wrong output.
Wrong way:
for (int i = start; i < len; i++) { //only three numbers possible for this section cur_str = s.substring(start, i + 1); cur_temp = Integer.parseInt(cur_str); if (cur_str.length() > 1 && cur_str.charAt(0) == '0') continue; if (cur_temp > 255) //the three digits is not valid return; ans += cur_str; helper(s, i + 1, digit_index - 1, ans, ret); }
Note: the same ans was reused wen i = start to len.
Improved Solution: we new a string for each sub-search branches.
for (int i = start; i < len; i++) { cur_str = s.substring(start, i + 1); cur_temp = Integer.parseInt(cur_str); String ans_temp = new String(ans); //this is very very important!!!, must be a sperate new string! if (cur_str.length() > 1 && cur_str.charAt(0) == '0') continue; if (cur_temp > 255) //the three digits is not valid return; ans_temp += cur_str; helper(s, i + 1, digit_index - 1, ans_temp, ret); }
My first ugly solution:
public class Solution { public List<String> restoreIpAddresses(String s) { ArrayList<String> ret = new ArrayList<String> (); if (s == null||s.length() < 4||s.length() > 12) return ret; helper(s, 0, 4, "", ret); return ret; } private void helper(String s, int start, int digit_index, String ans, ArrayList<String> ret) { int cur_temp; String cur_str; if (digit_index * 3 < s.length() - 1 - start + 1) //if the left elements' number exceeds the left digits return; if (digit_index > s.length() - 1 - start + 1) return; if (digit_index != 4) //if not the first section, we appen a '.' first! ans +="."; if (digit_index == 1) { //only the last section left, we should check it. cur_str = s.substring(start, s.length()); cur_temp = Integer.parseInt(cur_str); if (cur_str.length() > 1 && cur_str.charAt(0) == '0') return; if (cur_temp <= 255 && cur_str.length() <= 3) { ans += cur_str; ret.add(ans); } return; } int len; if (s.length() < start + 3) len = s.length(); else len = start + 3; for (int i = start; i < len; i++) { //only three numbers possible for this section cur_str = s.substring(start, i + 1); cur_temp = Integer.parseInt(cur_str); String ans_temp = new String(ans); //this is very very important!!!, must be a sperate new string! if (cur_str.length() > 1 && cur_str.charAt(0) == '0') continue; if (cur_temp > 255) //the three digits is not valid return; ans_temp += cur_str; helper(s, i + 1, digit_index - 1, ans_temp, ret); } } }
A more elegant solution:
public class Solution { public List<String> restoreIpAddresses(String s) { ArrayList<String> ret = new ArrayList<String> (); if (s == null||s.length() < 4||s.length() > 12) return ret; helper(s, 0, 4, "", ret); return ret; } private void helper(String s, int start, int digit_index, String ans, ArrayList<String> ret) { String seg_str; if (s == null || s.length() == 0) return; if (s.length() - start > 3 * digit_index) //the number of characters left = s.length() - 1 - start + 1 return; if (s.length() - start < digit_index) return; if (digit_index == 1) { seg_str = s.substring(start); if (isvalid(seg_str) == true) { //record or stop ret.add(ans + "." + seg_str); } return; } int len; if (s.length() >= start + 3) //not exceed the left string's length len = start + 3; else len = s.length(); for (int i = start + 1; i <= len; i++) { seg_str = s.substring(start, i);//i is the exclusive boundary. if (isvalid(seg_str) == false) continue; /*pass the validation*/ if (digit_index == 4) { helper(s, i, digit_index - 1, ans + seg_str, ret); //make no changes to ans at all!!! } else { helper(s, i, digit_index - 1, ans + "." + seg_str, ret); } } } private boolean isvalid (String s) { //to devise a proper function!!! if (s.length() == 0 || s.length() > 3) return false; int temp_int = Integer.parseInt(s); if (temp_int < 0 || temp_int > 255) return false; if (s.length() > 1 && s.charAt(0) == '0') return false; return true; } }