当前位置: 首页 > news >正文

代码随想录算法训练营第二十四天

LeetCode.93 复原IP地址

题目链接 复原IP地址

题解

class Solution {List<String> resList = new ArrayList<String>();List<String> res = new ArrayList<String>();public List<String> restoreIpAddresses(String s) {if(s.length() == 0) return resList;dfs(s,0);return resList;}public void dfs(String s,int index){if(res.size() == 4){if(index == s.length()){String tmp = String.join(".",res);resList.add(tmp);}return;}for(int i = index;i<s.length();i++){String tmp = s.substring(index,i + 1);if(check(tmp)){res.add(tmp);dfs(s,i + 1); res.remove(res.size() - 1);}if(s.charAt(index) == '0') break;}}public boolean check(String s){if(s.length() < 1 || s.length() > 3) return false;int num = Integer.parseInt(s);return false; }
}

解题思路

这段代码解决的是 "恢复 IP 地址" 的问题,即把一个字符串转换为所有可能的有效 IP 地址格式。下面是具体的解题思路:

问题分析

IP 地址由 4 个整数组成,每个整数的范围是 0-255,整数之间用点号分隔,且每个整数不能有前导零(除非这个数本身就是 0)。例如 "25525511135" 可以恢复为 "255.255.11.135" 或 "255.255.111.35"。

核心思路:回溯法(深度优先搜索)

  1. 递归分割:将字符串分成 4 个部分,每部分对应 IP 地址的一段
  2. 验证有效性:每分割出一段就检查其是否符合 IP 地址的规范
  3. 回溯探索:如果当前分割有效则继续递归,否则回溯尝试其他分割方式

代码解析

  1. 成员变量

    • resList:存储所有有效的 IP 地址结果
    • res:存储当前正在构建的 IP 地址的各个部分
  2. 主函数restoreIpAddresses

    • 边界检查:如果输入字符串为空则直接返回空列表
    • 调用深度优先搜索函数dfs开始递归分割
    • 返回所有有效的 IP 地址
  3. 深度优先搜索函数dfs

    • 终止条件:当已分割出 4 个部分时
      • 如果此时刚好遍历完整个字符串,则将当前构建的 IP 地址加入结果集
      • 否则直接返回(这种分割无效)
    • 递归过程:
      • 从当前索引开始尝试分割 1-3 个字符(因为 IP 段最长 3 位)
      • 检查分割出的子串是否有效
      • 如果有效则加入res,继续递归下一段
      • 递归返回后移除最后添加的部分(回溯)
      • 特殊处理:如果当前字符是 '0',则只能作为单独一段,不能和后面的字符组合
  4. 验证函数check

    • 检查子串长度是否在 1-3 之间
    • 检查转换值是否在 0-255 之间

这种方法通过回溯遍历所有可能的分割方式,同时进行有效性检查,确保只保留符合 IP 地址规范的结果,时间复杂度为 O (3^4),因为每个 IP 段最多有 3 种可能的分割方式,总共 4 个段。

LeetCode.78 子集

题目链接 子集

题解

class Solution {List<List<Integer>> resList = new ArrayList<List<Integer>>();List<Integer> res = new ArrayList<>();public List<List<Integer>> subsets(int[] nums) {if(nums == null || nums.length == 0) return resList;dfs(nums,0);resList.stream().distinct().collect(Collectors.toList());return resList;}public void dfs(int[] nums,int index){resList.add(new ArrayList<>(res));for(int i = index;i<nums.length;i++){res.add(nums[i]);dfs(nums,i+1);res.remove(res.size()-1);}}
}

解题思路

这段代码解决的是 "子集" 问题,即找出一个数组的所有可能子集(幂集)。下面是具体的解题思路:

问题分析

子集问题要求找出一个数组的所有可能子集,包括空集和数组本身。每个元素只能出现一次,且子集之间的顺序不考虑(即 [1,2] 和 [2,1] 视为同一个子集)。

核心思路:回溯法(深度优先搜索)

  1. 递归构建:通过递归逐步构建所有可能的子集
  2. 选择与不选择:对于每个元素,都有两种选择(加入当前子集或不加入)
  3. 避免重复:通过控制遍历起始索引来避免生成重复子集

代码解析

  1. 成员变量

    • resList:存储所有找到的子集结果
    • res:存储当前正在构建的子集
  2. 主函数subsets

    • 边界检查:如果输入数组为空则直接返回空列表
    • 调用深度优先搜索函数dfs开始构建子集
    • 注:代码中的distinct()调用实际不会生效,因为算法本身已保证不会产生重复子集
    • 返回所有子集的集合
  3. 深度优先搜索函数dfs

    • 先将当前构建的子集加入结果集(初始时为包含空集)
    • 遍历过程:
      • 从当前索引index开始遍历数组(保证元素顺序,避免重复子集)
      • 将元素加入当前子集res
      • 递归调用dfs,索引 + 1(确保每个元素只使用一次)
      • 递归返回后移除最后添加的元素(回溯操作)

这种方法的时间复杂度是 O (n×2ⁿ),其中 n 是数组长度。因为每个元素有选和不选两种可能,总共有 2ⁿ个子集,而每个子集的构建需要 O (n) 的时间。空间复杂度是 O (n),主要用于递归栈和存储当前子集。

LeetCode.90 子集II

题解链接 子集II

题解

class Solution {List<List<Integer>> resList = new ArrayList<List<Integer>>();List<Integer> res = new ArrayList<>();boolean[] used;public List<List<Integer>> subsetsWithDup(int[] nums) {if(nums == null || nums.length == 0) return resList;Arrays.sort(nums);used = new boolean[nums.length];dfs(nums,0);return resList;}public void dfs(int[] nums,int index){resList.add(new ArrayList<>(res));for(int i = index;i<nums.length;i++){if (i > 0 && nums[i] == nums[i - 1] &&!used[i - 1]) {continue;}res.add(nums[i]);used[i] = true;dfs(nums,i+1);used[i] =false;res.remove(res.size()-1);}}
}

解题思路

这段代码解决的是 "子集 II" 问题,即在包含重复元素的数组中找出所有不重复的子集。下面是具体的解题思路:

问题分析

与基础子集问题不同,本题的输入数组可能包含重复元素,但要求输出的子集不能有重复。例如,对于数组 [1,2,2],其所有不重复子集为:[], [1], [2], [1,2], [2,2], [1,2,2]。

核心思路:回溯法 + 去重处理

  1. 排序预处理:先对数组排序,使相同元素相邻,为去重做准备
  2. 递归构建子集:通过回溯算法构建所有可能的子集
  3. 去重逻辑:对于重复元素,只有当前面的相同元素已被使用时,才允许使用当前元素,避免生成重复子集

代码解析

  1. 成员变量

    • resList:存储所有不重复的子集结果
    • res:存储当前正在构建的子集
    • used:布尔数组,标记元素是否已被使用,用于去重判断
  2. 主函数subsetsWithDup

    • 边界检查:处理空数组情况
    • 排序数组:将相同元素放在一起,为去重打基础
    • 初始化used数组
    • 调用dfs开始递归构建子集
    • 返回结果集
  3. 深度优先搜索函数dfs

    • 先将当前子集加入结果集(初始为空集)
    • 遍历过程:
      • 从当前索引index开始遍历(保证元素顺序,避免重复)
      • 去重关键判断i > 0 && nums[i] == nums[i - 1] && !used[i - 1]
        • 含义:如果当前元素与前一个元素相同,且前一个元素未被使用,则跳过当前元素
        • 作用:确保相同元素只按顺序被选择,避免重复子集
      • 选择元素:将当前元素加入子集,并标记为已使用
      • 递归调用:索引 + 1,继续构建子集
      • 回溯操作:移除元素,标记为未使用

这种算法的时间复杂度是 O (n×2ⁿ),其中 n 是数组长度,排序需要 O (n log n) 时间。空间复杂度是 O (n),用于递归栈、resused数组。

去重逻辑是本题的关键,通过排序和used数组的配合,高效地避免了重复子集的生成,无需事后去重,提升了算法效率。

http://www.lryc.cn/news/592751.html

相关文章:

  • Linux学习之认识Linux的基本指令
  • Linux 环境下 NTP 时间同步与 SSH 免密登录实战
  • 函数返回值问题,以及返回值的使用问题(c/c++)
  • RWA是什么意思?
  • 李天意考研数学精讲课学习笔记(课堂版)
  • elementui-admin构建
  • MBIST - Memory BIST会对memory进行清零吗?
  • PHP 8.0 升级到 PHP 8.1
  • 机器学习17-Mamba
  • 2025年UDP应用抗洪指南:从T级清洗到AI免疫,实战防御UDP洪水攻击
  • 从0开始学习R语言--Day50--ROC曲线
  • C语言—如何生成随机数+原理详细分析
  • 系统IO对于目录的操作
  • 服务器内存满了怎么清理缓存?
  • 多线程-4-线程池
  • 从零构建监控系统:先“完美设计”还是先“敏捷迭代”?
  • 内存数据库的持久化与恢复策略:数据安全性与重启速度的平衡点
  • 数据结构-3(双向链表、循环链表、栈、队列)
  • SGLang 推理框架核心组件解析:请求、内存与缓存的协同工作
  • 【PTA数据结构 | C语言版】左堆的合并操作
  • LS-DYNA分析任务耗时长,如何避免资源浪费与排队?
  • Machine Learning HW2 report:语音辨识(Hongyi Lee)
  • Glary Utilities(系统优化工具) v6.20.0.24 专业便携版
  • 【Python】一些PEP提案(三):with 语句、yield from、虚拟环境
  • [FDBUS4.2] watcher的使用
  • 利用五边形几何关系计算cos36°及推导黄金比例
  • 50天50个小项目 (Vue3 + Tailwindcss V4) ✨ | NotesApp(便签笔记组件)
  • 深入理解 Spring:事务管理与事件机制全解析
  • 如何将本地Git仓库推送到远程仓库的一个文件中并保留Commit记录
  • 借助AI学习开源代码git0.7之三git-init-db