代码随想录打卡|Day21(复原ip地址、子集、子集2)
回溯算法 Part03
补充小知识(字符串的常用方法)
操作类型 | 方法/类 | 说明 | 示例(Java) |
---|---|---|---|
截取 | substring() | 提取字符串的指定部分 | "Hello".substring(1, 3) → "el" |
连接 | concat() / + | 拼接字符串 | "A".concat("B") 或 "A" + "B" → "AB" |
可变字符串 | StringBuilder | 高效修改字符串(非线程安全) | new StringBuilder("Hi").append("!") → "Hi!" |
查找 | indexOf() | 返回字符/子串的首次出现位置 | "apple".indexOf('p') → 1 |
替换 | replace() | 替换字符或子串 | "abc".replace("b", "x") → "axc" |
分割 | split() | 按正则表达式分割字符串 | "a,b,c".split(",") → ["a", "b", "c"] |
大小写转换 | toLowerCase() /toUpperCase() | 转换大小写 | "Hi".toUpperCase() → "HI" |
去除空格 | trim() | 移除首尾空白字符 | " a ".trim() → "a" |
格式化 | String.format() | 格式化字符串(类似printf ) | String.format("%s:%d", "ID", 10) → "ID:10" |
比较 | equals() /compareTo() | 比较内容或字典序 | "a".equals("A") → false ; "a".compareTo("b") → -1 |
正则匹配 | matches() | 检查字符串是否匹配正则表达式 | "123".matches("\\d+") → true |
字符提取 | charAt() | 获取指定位置的字符 | "cat".charAt(1) → 'a' |
长度 | length() | 返回字符串长度 | "hello".length() → 5 |
类型转换 | valueOf() | 将其他类型转为字符串 | String.valueOf(1.23) → "1.23" |
反转 | StringBuilder.reverse() | 反转字符串(需配合StringBuilder ) | new StringBuilder("123").reverse() → "321" |
空白检查 | isBlank() (Java 11+) | 检查字符串是否为空或仅含空白字符 | " ".isBlank() → true |
复原ip地址
力扣题目链接
代码随想录链接
视频链接
题目描述: 有效 IP 地址 正好由四个整数(每个整数位于 0 到 255 之间组成,且不能含有前导 0),整数之间用 ‘.’ 分隔。
例如:“0.1.2.201” 和 “192.168.1.1” 是 有效 IP 地址,但是 “0.011.255.245”、“192.168.1.312” 和 “192.168@1.1” 是 无效 IP 地址。
给定一个只包含数字的字符串 s ,用以表示一个 IP 地址,返回所有可能的有效 IP 地址,这些地址可以通过在 s 中插入 ‘.’ 来形成。你 不能 重新排序或删除 s 中的任何数字。你可以按 任何 顺序返回答案。
思路:本题的结束如下所示(代码随想录):
回溯法
法一(使用substring方法对字符串进行操作)
//方法二:比上面的方法时间复杂度低,更好地剪枝,优化时间复杂度
class Solution {// 定义全局变量结果List<String> result = new ArrayList<>();public List<String> restoreIpAddresses(String s) {backTracking(s,0,0);return result;}// 定义回溯函数private void backTracking(String s , int startIndex , int pointNum){// 定义结果的返回条件if(pointNum == 3){if(isValid(s,startIndex,s.length() - 1)){result.add(s);}return ;}for(int i = startIndex ; i < s.length() ; i ++){if(pointNum > 3)return ;if(isValid(s,startIndex,i)){s = s.substring(0,i+1)+"."+s.substring(i+1);pointNum += 1;// 此处为i+2的原因是,加了一个"."backTracking(s,i+2,pointNum);// 回溯pointNum -= 1;s = s.substring(0,i + 1) + s.substring(i+2);}else{break;}}}private boolean isValid(String s , int start , int end){if(start > end) return false;if(s.charAt(start) == '0' && start != end) return false;int num = 0 ;for(int i = start ; i <= end ; i++){if(s.charAt(i) > '9' || s.charAt(i) < '0')return false;num = num*10 +(s.charAt(i) - '0');if(num > 255)return false; }return true;}
}
回溯法(使用StringBuilder)
StringBuilder和subtring的优劣
- StringBuilder 向字符串之中插入字符的时候无需复制整个字符串,从而减少了操作的时间复杂度,也不用开辟内存空间用于存储substring,减少了空间复杂度
- StringBuider非线程安全
StringBuilder常用方法介绍:
方法名 | 作用描述 | 参数说明 | 返回值类型 | 示例(输入 → 输出) |
---|---|---|---|---|
**append(x) ** | 追加任意类型数据(自动转为字符串) | x : 基本类型/对象/String | StringBuilder | sb.append("Hi").append(1) → "Hi1" |
**insert(idx, x) ** | 在指定位置插入数据 | idx : 插入索引;x : 要插入的内容 | StringBuilder | sb.insert(1, "X") → "HXi1" |
**delete(start, end) ** | 删除子串(含头不含尾) | start : 起始索引;end : 结束索引 | StringBuilder | sb.delete(1, 2) → "Hi1" |
**reverse() ** | 反转字符串内容 | 无 | StringBuilder | sb.reverse() → "1iH" |
**replace(start, end, str) ** | 替换指定区间的字符为另一字符串 | start , end : 替换范围;str : 新字符串 | StringBuilder | sb.replace(0, 1, "A") → "AiH" |
**charAt(idx) ** | 获取指定位置的字符 | idx : 字符索引 | char | sb.charAt(0) → 'A' |
**setCharAt(idx, c) ** | 修改指定位置的字符 | idx : 索引;c : 新字符 | void | sb.setCharAt(0, 'B') → "BiH" |
**length() ** | 返回当前字符序列长度 | 无 | int | sb.length() → 3 |
**capacity() ** | 返回当前底层数组容量(≥length) | 无 | int | new StringBuilder(10).capacity() → 10 |
**ensureCapacity(min) ** | 确保容量至少为指定值(自动扩容时通常为 2*旧容量+2 ) | min : 最小容量 | void | sb.ensureCapacity(20) |
**toString() ** | 转为不可变String对象 | 无 | String | sb.toString() → "BiH" |
**substring(start[, end]) ** | 截取子串(返回String,不影响原内容) | start /end : 截取范围 | String | sb.substring(1) → "iH" |
**indexOf(str[, fromIdx]) ** | 查找子串首次出现的位置 | str : 目标字符串;fromIdx : 起始索引 | int | sb.indexOf("i") → 1 |
**lastIndexOf(str) ** | 查找子串最后一次出现的位置 | 同indexOf() | int | sb.append("i").lastIndexOf("i") → 2 |
**deleteCharAt(idx) ** | 删除指定位置的字符 | idx : 字符索引 | StringBuilder | sb.deleteCharAt(0) → "iH" |
**setLength(len) ** | 强制设置字符序列长度(截断或填充空字符\u0000 ) | len : 新长度 | void | sb.setLength(2) → "Bi" |
**trimToSize() ** | 释放多余容量(将底层数组缩至与length相同) | 无 | void | sb.trimToSize() |
代码如下:
//方法二:比上面的方法时间复杂度低,更好地剪枝,优化时间复杂度
class Solution {// 定义全局变量结果List<String> result = new ArrayList<>();public List<String> restoreIpAddresses(String s) {StringBuilder sb = new StringBuilder(s);backTracking(sb,0,0);return result;}// 定义回溯函数private void backTracking(StringBuilder s , int startIndex , int numOfDot){if(numOfDot == 3){if(isValid(s,startIndex,s.length() - 1)){result.add(s.toString());}return ;}for(int i = startIndex ; i < s.length() ; i++){if(isValid(s,startIndex , i)){s.insert(i+1,".");numOfDot += 1;backTracking(s,i+2,numOfDot);numOfDot -= 1;s.deleteCharAt(i+1);}}}private boolean isValid(StringBuilder s , int start , int end){if(start > end) return false;if(s.charAt(start) == '0' && start != end) return false;int num = 0 ;for(int i = start ; i <= end ; i++){if(s.charAt(i) > '9' || s.charAt(i) < '0') return false;num = num*10 + (s.charAt(i) - '0');if(num > 255) return false;}return true;}
}
在代码之中,还可以进一步剪枝
例如将判断字符串是否合法的部分直接写道回溯递归部分,从而直接免去不必要的操作
class Solution {List<String> result = new ArrayList<String>();StringBuilder stringBuilder = new StringBuilder();public List<String> restoreIpAddresses(String s) {restoreIpAddressesHandler(s, 0, 0);return result;}// number表示stringbuilder中ip段的数量public void restoreIpAddressesHandler(String s, int start, int number) {// 如果start等于s的长度并且ip段的数量是4,则加入结果集,并返回if (start == s.length() && number == 4) {result.add(stringBuilder.toString());return;}// 如果start等于s的长度但是ip段的数量不为4,或者ip段的数量为4但是start小于s的长度,则直接返回if (start == s.length() || number == 4) {return;}// 剪枝:ip段的长度最大是3,并且ip段处于[0,255]for (int i = start; i < s.length() && i - start < 3 && Integer.parseInt(s.substring(start, i + 1)) >= 0&& Integer.parseInt(s.substring(start, i + 1)) <= 255; i++) {if (i + 1 - start > 1 && s.charAt(start) - '0' == 0) {break;}stringBuilder.append(s.substring(start, i + 1));// 当stringBuilder里的网段数量小于3时,才会加点;如果等于3,说明已经有3段了,最后一段不需要再加点if (number < 3) {stringBuilder.append(".");}number++;restoreIpAddressesHandler(s, i + 1, number);number--;// 删除当前stringBuilder最后一个网段,注意考虑点的数量的问题stringBuilder.delete(start + number, i + number + 2);}}
}
子集
力扣题目链接
代码随想录链接
视频链接
题目描述: 给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
思路:这道题不算难题,只要将遍历过的所有情况都保存下来即可,本题的解树为:
本题的代码为:
class Solution {List<List<Integer>> result = new ArrayList<>();List<Integer> path = new ArrayList<>();public List<List<Integer>> subsets(int[] nums) {backTracking(nums,0);return result;}private void backTracking(int[] nums , int startIndex){// 将所有的结果均记录下来result.add(new ArrayList(path));if(startIndex > nums.length - 1 ){return ;}for(int i = startIndex ; i < nums.length ; i++){path.add(nums[i]);backTracking(nums,i + 1);path.remove(path.size() - 1);}}
}
子集2
力扣题目链接
代码随想录链接
视频链接
题目描述: 给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的 子集(幂集)。
解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。
思路:我们对于组合问题进行去重的时候,一般会初始化used数组用于标识数据是否被使用过。但是我们一定要分清楚什么时候使用树层去重,什么时候是树枝去重。
树层去重讲解链接
回溯法
代码如下:
class Solution {List<List<Integer>> result = new ArrayList<>();List<Integer> path = new ArrayList<>();boolean[] used;public List<List<Integer>> subsetsWithDup(int[] nums) {Arrays.sort(nums);used = new boolean[nums.length];Arrays.fill(used,false);backTracking(nums,0);return result;}// 定义回溯函数private void backTracking(int[] nums , int startIndex){result.add(new ArrayList(path));if(startIndex > nums.length -1)return ;for(int i = startIndex ; i <= nums.length -1 ; i++){// 树层去重if( i > 0 && nums[i] == nums[i - 1]&&!used[i - 1] )continue;used[i] = true;path.add(nums[i]);backTracking(nums,i+1);used[i] = false;path.removeLast();}}
}