一、分割回文串
题目:
给你一个字符串 s
,请你将 s
分割成一些子串,使每个子串都是 回文串。返回 s
所有可能的分割方案。
示例 1:
输入:s = "aab" 输出:[["a","a","b"],["aa","b"]]
示例 2:
输入:s = "a" 输出:[["a"]]
思路:
确定是否为回文串,定义一个起始索引startIndex从当前索引开始遍历,满足回文条件存入数组中,然后继续递归调用下一层,直到遍历完毕后存入结果数组中并输出
代码:
class Solution {
List<List<String>> result = new ArrayList<>(); // 存储所有分割方案的结果集合
LinkedList<String> path = new LinkedList<>(); // 当前路径的存储结构,用于回溯
// 主函数,返回所有可能的回文分割方案
public List<List<String>> partition(String s) {
backTracking(s, 0); // 开始回溯搜索
return result; // 返回最终的结果集合
}
// 回溯函数,用于搜索所有满足条件的回文分割方案
public void backTracking(String s, int startIndex) {
// 如果起始索引超过字符串长度,说明已经完成一种分割方案,将当前路径加入结果集合
if (startIndex >= s.length()) {
result.add(new ArrayList<>(path));
return;
}
// 从起始索引开始,尝试找到所有可能的回文子串
for (int i = startIndex; i < s.length(); i++) {
// 如果从 startIndex 到 i 的子串是回文串,继续搜索
if (isPalindrome(s, startIndex, i)) {
String str = s.substring(startIndex, i + 1); // 获取当前回文子串
path.add(str); // 将当前回文子串加入路径
} else {
continue; // 如果不是回文串,继续尝试下一个子串
}
// 递归搜索剩余部分
backTracking(s, i + 1);
// 回溯,移除最后一个加入的回文子串,尝试下一个可能的回文子串
path.removeLast();
}
}
// 判断从 startIndex 到 end 的子串是否是回文串
public boolean isPalindrome(String s, int startIndex, int end) {
// 使用双指针判断是否回文串
for (int i = startIndex, j = end; i < j; i++, j--) {
if (s.charAt(i) != s.charAt(j)) {
return false; // 如果存在字符不相等,则不是回文串
}
}
return true; // 如果整个区间都是回文串,则返回 true
}
}
-
partition 方法:
partition
方法是入口函数,调用backTracking
方法开始搜索所有可能的回文分割方案,并最终返回result
结果集合。
-
backTracking 方法:
backTracking
方法使用回溯算法,从给定的startIndex
开始,尝试找出所有从这里开始的回文子串。- 遍历字符串
s
,从startIndex
到末尾,检查每个子串是否是回文串。 - 如果是回文串,将该子串加入
path
中,然后递归调用backTracking
继续搜索剩余部分。 - 搜索完成后,需要回溯,即移除
path
中最后一个加入的子串,尝试下一个可能的回文子串。
-
isPalindrome 方法:
isPalindrome
方法用于判断从startIndex
到end
的子串是否是回文串。- 使用双指针分别从两端向中间移动,比较字符是否相等,直到相遇或交叉。
二、复原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
中的任何数字。你可以按 任何 顺序返回答案。
示例 1:
输入:s = "25525511135" 输出:["255.255.11.135","255.255.111.35"]
示例 2:
输入:s = "0000" 输出:["0.0.0.0"]
示例 3:
输入:s = "101023" 输出:["1.0.10.23","1.0.102.3","10.1.0.23","10.10.2.3","101.0.2.3"]
思路:
关键在于如何判断ip地址是否合法,必须是1到9的数字,每段中不得超过255,且首部不能为0,每遇到一个合法地址必须在其后面加上相应的 '.' 表示分割,由于每种地址最多有3个 '.' 因此可以用一个计数,来判断分割是否完成且合法
代码:
class Solution {
List<String> result = new ArrayList<>();
public List<String> restoreIpAddresses(String s) {
// 如果字符串长度超过12,则不可能形成有效的IP地址,直接返回空结果集
if (s.length() > 12)
return result;
// 开始回溯处理IP地址的恢复
backtracking(s, 0, 0);
return result;
}
// 回溯法尝试恢复所有可能的IP地址
public void backtracking(String s, int startIndex, int pointNum) {
// 如果已经插入了3个点,且此时的子串是有效的IP段,将其加入结果集
if (pointNum == 3) {
if (isValid(s, startIndex, s.length() - 1)) {
result.add(s);
}
return;
}
// 尝试从startIndex开始向后插入点
for (int i = startIndex; i <= s.length() - 1; i++) {
// 如果当前位置插入点后是有效的IP段
if (isValid(s, startIndex, i)) {
// 在当前位置插入点
s = s.substring(0, i + 1) + "." + s.substring(i + 1);
pointNum++; // 插入点数加一
// 递归处理下一个点的插入
backtracking(s, i + 2, pointNum);
pointNum--; // 回溯,恢复pointNum
// 恢复字符串,移除插入的点,进行下一次尝试
s = s.substring(0, i + 1) + s.substring(i + 2);
} else {
// 如果当前位置插入点后不是有效的IP段,跳出循环
break;
}
}
}
// 判断字符串s的子串[start, end]是否是有效的IP段
public boolean isValid(String s, int start, int end) {
// 起始索引大于结束索引,不合法
if (start > end)
return false;
// 如果以'0'开头且不是单独的'0',不合法
if (s.charAt(start) == '0' && start != end)
return false;
int num = 0;
// 将字符串转换为数字,判断是否在合法IP段的范围内
for (int i = start; i <= end; i++) {
if (s.charAt(i) > '9' || s.charAt(i) < '0')
return false;
//如果当前字符是合法的数字,将其转换成整数并添加到 num 中。这里通过 (s.charAt(i) - '0') 将字符转换成对应的整数值,然后将其加到 num 中。例如,字符 '1' 被转换为整数 1。
num = num * 10 + (s.charAt(i) - '0');
if (num > 255)
return false;
}
return true;
}
}
restoreIpAddresses
方法是入口函数,用于开始恢复 IP 地址。- 如果字符串
s
的长度超过 12,直接返回空的result
列表,因为有效的 IPv4 地址中每个部分最多是 3 位数字,共计 12 位。 - 否则,调用
backtracking
方法开始回溯搜索所有可能的 IP 地址。
backtracking
方法使用递归和回溯来生成所有可能的 IP 地址。startIndex
是当前正在处理的字符串的起始索引,pointNum
记录已经插入的点的个数(即已经处理了多少个 IP 地址的段)。- 如果
pointNum
等于 3,说明已经插入了三个点,此时需要验证最后一段字符串是否有效,如果有效则将其加入result
中。 - 否则,从
startIndex
开始向后遍历字符串,尝试在每一个有效的位置插入点(即将字符串分为一段 IP 地址的一部分),然后递归处理剩余的字符串。 - 在递归调用后需要进行回溯:移除最后插入的点,恢复字符串的原始形式,继续尝试下一个位置。
isValid
方法用于验证从start
到end
的子串是否是一个合法的 IP 地址段。- 首先检查起始索引是否超过结束索引,然后检查如果以 '0' 开头且不是单独的 '0',则不合法(比如 "01" 是不合法的)。
- 将子串转换为数字,检查是否在合法的 IP 地址范围(0-255)内。
在java中,substring方法用于从字符串中提取子串。它有两种重载形式:
-
substring(int beginIndex):
这个方法返回从 beginIndex
开始到字符串末尾的子串。例如
String str = "Hello, World!";
String sub1 = str.substring(7); // "World!"
- 在这个例子中,
substring(7)
返回从索引位置 7 开始到末尾的子串 "World!"
2. substring(int beginIndex, int endIndex):
这个方法返回从 beginIndex
开始到 endIndex
之前的子串。例如:
String str = "Hello, World!";
String sub2 = str.substring(7, 12); // "World"
- 在这个例子中,
substring(7, 12)
返回从索引位置 7 到 11 的子串 "World"。注意,endIndex
指定的索引位置是不包含在内的。
注意事项:
- 索引范围:对于
substring(int beginIndex, int endIndex)
方法,beginIndex
必须小于endIndex
,且两者必须在字符串的有效范围内。 - 索引边界:
beginIndex
的范围是从 0 到length() - 1
,endIndex
的范围是从 1 到length()
。 - 不变性:
substring
方法返回的是原字符串的一个新副本,原字符串本身不会被修改。
三、 子集
题目:
给你一个整数数组 nums
,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
示例 1:
输入:nums = [1,2,3] 输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]
示例 2:
输入:nums = [0] 输出:[[],[0]]
思路:
这道题重点在于每遍历一次都需要对结果进行一次收集,在遍历超出集合长度时再进行返回
代码:
List<List<Integer>> result = new ArrayList<>(); // 用于存储所有子集的列表
LinkedList<Integer> path = new LinkedList<>(); // 用于临时存储当前子集的路径
public List<List<Integer>> subsets(int[] nums) {
backtracking(nums, 0); // 调用回溯算法生成所有子集
return result; // 返回所有生成的子集列表
}
public void backtracking(int[] nums, int startIndex) {
result.add(new ArrayList<>(path)); // 将当前路径中的元素作为一个新子集加入到结果列表中
if (startIndex >= nums.length)
return; // 如果起始索引超出数组范围,则直接返回
for (int i = startIndex; i <= nums.length - 1; i++) {
path.add(nums[i]); // 将当前元素 nums[i] 加入到路径中,构成新的子集
backtracking(nums, i + 1); // 递归调用,从下一个索引位置开始继续生成子集
path.removeLast(); // 回溯,将刚刚添加的元素移除,尝试下一个元素
}
}
result
是一个List
,每个元素是一个List<Integer>
,表示一个子集。path
是一个LinkedList<Integer>
,用于暂存当前正在生成的子集。subsets
方法是公共方法,用于生成整数数组nums
的所有子集。它通过调用backtracking
方法来完成这个任务,并返回生成的所有子集。backtracking
方法是核心的递归回溯函数,用于生成所有可能的子集。- 首先,它将当前的
path
中的元素拷贝一份加入到result
中,表示找到了一个新的子集。 - 然后,通过一个
for
循环遍历从startIndex
到nums
数组末尾的所有元素:- 将当前的
nums[i]
添加到path
中,形成一个新的子集。 - 递归调用
backtracking(nums, i + 1)
,继续向下生成子集,确保不重复使用同一个元素。 - 当递归返回后,表示以
nums[i]
开头的所有子集都已经生成完毕,需要进行回溯操作。 - 回溯时,将刚刚添加的
nums[i]
元素从path
中移除,尝试下一个可能的元素。
- 将当前的
今天的学习就到这里