算法日记day 25(回溯之分割回文串|复原IP地址|子集)

一、分割回文串

题目:

给你一个字符串 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方法用于从字符串中提取子串。它有两种重载形式:

  1. 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() - 1endIndex 的范围是从 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 中移除,尝试下一个可能的元素。

 

今天的学习就到这里

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值