剑指 Offer II 087. 复原 IP(93. 复原 IP 地址)
题目:
思路:
【1】回溯的方式处理
代码展示:
进行优化:
//时间0 ms击败100% //内存40.2 MB击败90.50% //首先做了范围判断免去无用的递归,其次,再遇到前导零的时候进行剪枝,但这种其实提高不了多少 //其次能变为0ms的原因主要是将StringBuilder 替换了 StringBuffer,两者区别一个前者无锁,后者则是加了锁 //在多线程下其实应该选后者,但是在做题中应该先前者 class Solution { static final int SEG_COUNT = 4; List<String> ans = new ArrayList<String>(); int[] segments = new int[SEG_COUNT]; public List<String> restoreIpAddresses(String s) { if(s.length()<4||s.length()>12){ return ans; } segments = new int[SEG_COUNT]; dfs(s, 0, 0); return ans; } public void dfs(String s, int segId, int segStart) { // 如果找到了 4 段 IP 地址并且遍历完了字符串,那么就是一种答案 if (segId == SEG_COUNT) { if (segStart == s.length()) { StringBuilder ipAddr = new StringBuilder(); for (int i = 0; i < SEG_COUNT; ++i) { ipAddr.append(segments[i]); if (i != SEG_COUNT - 1) { ipAddr.append('.'); } } ans.add(ipAddr.toString()); } return; } // 如果还没有找到 4 段 IP 地址就已经遍历完了字符串,那么提前回溯 if (segStart == s.length()) return; // 由于不能有前导零,如果当前数字为 0,那么这一段 IP 地址只能为 0 if (s.charAt(segStart) == '0') { segments[segId] = 0; dfs(s, segId + 1, segStart + 1); return; } // 一般情况,枚举每一种可能性并递归 // 罗列当前段的可能性,只要字符串凑起来符合0~255都可能是当前段的值 // 至于为什么上面的0要特殊的抽离出来,因为0000,转成数字的话也是0,实际应该是4段的 int addr = 0; for (int segEnd = segStart; segEnd < s.length(); ++segEnd) { addr = addr * 10 + (s.charAt(segEnd) - '0'); if (addr > 0 && addr <= 0xFF) { segments[segId] = addr; dfs(s, segId + 1, segEnd + 1); } else { break; } } } }
回溯的方式:
//时间1 ms击败93.44% //内存40.1 MB击败93.23% //时间复杂度:O(3^SEG_COUNT * ∣s∣)。由于 IP 地址的每一段的位数不会超过 3,因此在递归的每一层,我们最多只会深入到下一层的 3 种情况。 //由于 SEG_COUNT=4,对应着递归的最大层数,所以递归本身的时间复杂度为 O(3^SEG_COUNT)。 //如果我们复原出了一种满足题目要求的 IP 地址,那么需要 O(∣s∣) 的时间将其加入答案数组中,因此总时间复杂度为 O(3^SEG_COUNT * ∣s∣)。 //空间复杂度:O(SEG_COUNT),这里只计入除了用来存储答案数组以外的额外空间复杂度。 //递归使用的空间与递归的最大深度 SEG_COUNT 成正比。 //并且在上面的代码中,我们只额外使用了长度为 SEG_COUNT 的数组 segments 存储已经搜索过的 IP 地址,因此空间复杂度为 O(SEG_COUNT)。 class Solution { static final int SEG_COUNT = 4; List<String> ans = new ArrayList<String>(); int[] segments = new int[SEG_COUNT]; public List<String> restoreIpAddresses(String s) { segments = new int[SEG_COUNT]; dfs(s, 0, 0); return ans; } public void dfs(String s, int segId, int segStart) { // 如果找到了 4 段 IP 地址并且遍历完了字符串,那么就是一种答案 if (segId == SEG_COUNT) { if (segStart == s.length()) { StringBuffer ipAddr = new StringBuffer(); for (int i = 0; i < SEG_COUNT; ++i) { ipAddr.append(segments[i]); if (i != SEG_COUNT - 1) { ipAddr.append('.'); } } ans.add(ipAddr.toString()); } return; } // 如果还没有找到 4 段 IP 地址就已经遍历完了字符串,那么提前回溯 if (segStart == s.length()) return; // 由于不能有前导零,如果当前数字为 0,那么这一段 IP 地址只能为 0 if (s.charAt(segStart) == '0') { segments[segId] = 0; dfs(s, segId + 1, segStart + 1); } // 一般情况,枚举每一种可能性并递归 // 罗列当前段的可能性,只要字符串凑起来符合0~255都可能是当前段的值 // 至于为什么上面的0要特殊的抽离出来,因为0000,转成数字的话也是0,实际应该是4段的 int addr = 0; for (int segEnd = segStart; segEnd < s.length(); ++segEnd) { addr = addr * 10 + (s.charAt(segEnd) - '0'); if (addr > 0 && addr <= 0xFF) { segments[segId] = addr; dfs(s, segId + 1, segEnd + 1); } else { break; } } } }