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

常见经典递归过程解析

常见经典递归过程解析

带路径的递归 vs 不带路径的递归(大部分dp,状态压缩dp认为是路径简化了结构,dp专题后续讲述)

任何递归都是dfs且非常灵活。回溯这个术语并不重要。

题目1 : 返回字符串全部子序列,子序列要求去重。时间复杂度O(2^n * n)

注: 子序列本身是可以有重复的,只是这个题目要求去重

时间复杂度:

子序列个数O(2^n) * 子序列的平均长度O(n)

  • 测试链接 : https://www.nowcoder.com/practice/92e6247998294f2c933906fdedbc6e6a

  • 思路1

    • 将需要操作的字符串转化为字符数组, 在字符数组中逐个处理
      • 若字符数组已经处理完毕, 说明整个字符串的一个子串已经找到, 加入set(收集每一个子串 并 去重)中即可
      • 收集当前字符
      • 递归继续处理下一个字符
      • 不收集当前字符, 将当前字符从路径中删除
      • 递归处理下一个字符
    • 将收集好的set中的数据整理返回
  • 代码1

    • 	public static String[] generatePermutation1(String str) {char[] s = str.toCharArray();// 字符串转换为数组 进行操作HashSet<String> set = new HashSet<>();// 用于去重f1(s, 0, new StringBuilder(), set);// stringbuilder set 会一直往下传递// 将set中的数据 复制一份 放在String[]中按要求返回int m = set.size();String[] ans = new String[m];int i = 0;for (String cur : set) {// 从set中取出ans[i++] = cur;}return ans;}// s[i]: 当前来到的字符串里的字符,path: 之前决定的路径,用于形成新的字符串,set: 收集结果时去重public static void f1(char[] s, int i, StringBuilder path, HashSet<String> set) {if (i == s.length) {// set到达了终止位置set.add(path.toString());// 路径里的信息收集到set中去} else {path.append(s[i]); // 要这个字符f1(s, i + 1, path, set);// 走递归 判断 后面位置的字符path.deleteCharAt(path.length() - 1); // 该位置 加进path的字符 从路径里 删掉f1(s, i + 1, path, set);// 字符第s[i+1]走另外一条不要i这个字符的路}}
      
  • 思路2

    • 与思路1一致, 但将path改为字符串数组, 从而用覆盖操作来实现删除操作, 使删除操作更简单
  • 代码2

    • 	public static String[] generatePermutation2(String str) {char[] s = str.toCharArray();HashSet<String> set = new HashSet<>();f2(s, 0, new char[s.length], 0, set);int m = set.size();String[] ans = new String[m];int i = 0;for (String cur : set) {ans[i++] = cur;}return ans;}// s[i]: 当前来到的字符串里的字符  // 当前路径数组path填进来了size个字符(size就是下一个字符要放的位置)public static void f2(char[] s, int i, char[] path, int size, HashSet<String> set) {if (i == s.length) {set.add(String.valueOf(path, 0, size));} else {path[size] = s[i];// 当前字符填进来f2(s, i + 1, path, size + 1, set);// 包含这个走下一个f2(s, i + 1, path, size, set);// 不包含这个走下一个}}
      

题目2 : 返回数组的所有组合,可以无视元素顺序。时间复杂度O(2^n * n)

时间复杂度: 最差即所有数字都不重复, 无法进行剪枝, 每一个数都分为 加入 和 不加入, 共O(2^n) 每个path长度平均为O(n), 共 O(2^n * n)

  • 测试链接 : https://leetcode.cn/problems/subsets-ii/

  • 思路: 剪枝

    • 先将数组排序, 按照数字进行分组, 根据每一组加入 : 0个 1个 2个 … n(该数字的个数)个, 分别往后递归加入下一组的数字
  • 代码

    • 	public static List<List<Integer>> subsetsWithDup(int[] nums) {List<List<Integer>> ans = new ArrayList<>();// 用于放置答案Arrays.sort(nums);// 先排个序f(nums, 0, new int[nums.length], 0, ans);return ans;}// 当前数组来到i位置	  来到的路径用size管理		public static void f(int[] nums, int i, int[] path, int size, List<List<Integer>> ans) {if (i == nums.length) { // 到达终止位置, 收集答案ArrayList<Integer> cur = new ArrayList<>();for (int j = 0; j < size; j++) {// 使用for(需要用size进行限制)收集path中size个cur.add(path[j]);// 将路径加入ans}ans.add(cur);// 放入ans} else {// 没有遇到终止位置// 找下一组的开始位置int j = i + 1;while (j < nums.length && nums[i] == nums[j]) {// j 没有越界        j位置的数 等于 i位置的数j++;// j继续找}// 当前数x,要0个(size没变, path没增加)f(nums, j, path, size, ans);// 当前数x,要1个、要2个、要3个...都尝试for (; i < j; i++) {path[size++] = nums[i];// 当前的, size不断增加, path不断增加, 进行后续的递归f(nums, j, path, size, ans);// 后续的}}}
      

题目3 : 返回没有重复值数组的全部排列。时间复杂度O(n! * n)

  • 测试链接 : https://leetcode.cn/problems/permutations/

  • 思路

    • 在原数组中进行递归, 实现全排列
    • 当处理到数组i位置, 分别将i位置数与i + 1,i + 2 … nums.length - 1交换
    • 继续处理第i + 1位置
    • 每次处理结束后恢复数组为交换前的排列
    • 直到数组处理完毕, 复制一份加到ans中
  • 代码

    • 时间复杂度: 全排列 O(n!) * 每次排列需要处理的位数, 即数组的长度O(n)

    • 	public static List<List<Integer>> permute(int[] nums) {List<List<Integer>> ans = new ArrayList<>();f(nums, 0, ans);return ans;}// 						在原数组中不断修改, 生成答案public static void f(int[] nums, int i, List<List<Integer>> ans) {if (i == nums.length) {// 终止位置List<Integer> cur = new ArrayList<>();for (int num : nums) {cur.add(num);}ans.add(cur);} else {// 尚未到达终止位置for (int j = i; j < nums.length; j++) {// j从i到结尾swap(nums, i, j);// 确认i位置: 将i位置的数不断修改为后续的数f(nums, i + 1, ans);// 确认!!!i+1位置, 不是j的位置swap(nums, i, j); // 复原: 将i位置的数从后续的数修改为原来的数}}// end else}public static void swap(int[] nums, int i, int j) {int tmp = nums[i];nums[i] = nums[j];nums[j] = tmp;}
      

题目4 : 返回可能有重复值数组的全部排列,排列要求去重。时间复杂度O(n! * n)

  • 测试链接 : https://leetcode.cn/problems/permutations-ii/

  • 思路

    • 在原数组中进行递归, 实现全排列
    • 当处理到数组i位置, 分别将i位置数与i + 1,i + 2 … nums.length - 1交换
      • 若当前位置的值已经与i位置交换过, 则不再进行处理, 直接进入下一次循环
    • 继续处理第i + 1位置
    • 每次处理结束后恢复数组为交换前的排列
    • 当数组处理完毕, 复制一份加到ans中
  • 代码

    • 时间复杂度: 最差即为数组中没有重复值, 此时时间复杂度与题目3相同, 为 O(n! * n)

    • 	public static List<List<Integer>> permuteUnique(int[] nums) {List<List<Integer>> ans = new ArrayList<>();f(nums, 0, ans);return ans;}// 						在原数组中不断修改, 生成答案public static void f(int[] nums, int i, List<List<Integer>> ans) {if (i == nums.length) {// 终止位置List<Integer> cur = new ArrayList<>();for (int num : nums) {cur.add(num);}ans.add(cur);} else {// 尚未到达终止位置HashSet<Integer> set = new HashSet<>();// 用于去重for (int j = i; j < nums.length; j++) {// nums[j]没有来到过i位置,才会去尝试if (!set.contains(nums[j])) {// 不能用j判断, 要用nums[j], j的值一定不同, 但是nums[j]可能重复set.add(nums[j]);swap(nums, i, j);f(nums, i + 1, ans);swap(nums, i, j);}}}// end else}public static void swap(int[] nums, int i, int j) {int tmp = nums[i];nums[i] = nums[j];nums[j] = tmp;}
      

题目5 : 用递归逆序一个栈。时间复杂度O(n^2)

  • 思路

    • 每层递归获取并在该层保存此时栈的最后一个元素, 继续往下层递归
    • 直到栈空, 递归结束
    • 根据递归返回顺序 : 深层递归 -> 浅层递归 , 即栈底 -> 栈顶的顺序 , 保存每层栈临时保存的变量, 从而实现反转
  • 代码

    • 时间复杂度

      • bottomOut: n + n - 1 + n - 2 + n - 3 + ... + 0 -> O(n^2)
    • 	// 该方法让对应递归层次保存倒序后栈中的值, 并保存在栈中, 从而实现逆序的过程public static void reverse(Stack<Integer> stack) {if (stack.isEmpty()) {// 递归出口return;}int num = bottomOut(stack);// 获取此时栈底的值reverse(stack);// 递归stack.push(num);// 将该层元素压入栈中}// 栈底元素移除掉,上面的元素盖下来// 返回移除掉的栈底元素public static int bottomOut(Stack<Integer> stack) {int ans = stack.pop();if (stack.isEmpty()) {// 当来到栈底, 将栈底元素返回return ans;} else {int last = bottomOut(stack);// 上一层返回的元素, 即递归最后一次返回的元素, 即栈底元素stack.push(ans);// 放入该层保存的元素return last;// 继续向上传递 深层递归传递过来的值(栈底的值)}}// 测试public static void main(String[] args) {Stack<Integer> stack = new Stack<Integer>();stack.push(1);stack.push(2);stack.push(3);stack.push(4);stack.push(5);reverse(stack);while (!stack.isEmpty()) {System.out.println(stack.pop());}}
      

题目6 : 用递归排序一个栈。时间复杂度O(n^2)

  • 思路

    • 计算栈的深度
    • 返回这个深度下栈的最大值
    • 返回这个深度下 这个最大值的出现次数
    • 将栈中这么多个最大值下沉
    • 递归重新计算栈的深度, 处理除掉那些最大值的数
  • 代码

    • public static void sort(Stack<Integer> stack) {int deep = deep(stack);while (deep != 0) {// todo deep > 0 ?int max = max(stack,deep);int count = count(stack,deep,max);down(stack,deep,max,count);deep -= count;}}private static void down(Stack<Integer> stack, int deep, int max, int count) {if (deep == 0) {for (int i = 0; i < count; i++) {stack.push(max);}} else {int num = stack.pop();down(stack,deep - 1,max,count);if (num != max) {stack.push(num);}}}private static int count(Stack<Integer> stack, int deep, int max) {if (deep == 0) {return 0;}int num = stack.pop();int restC = count(stack, deep - 1,max);int count = restC + (num == max ? 1 : 0);stack.push(num);return count;}private static int max(Stack<Integer> stack, int deep) {if (deep == 0) {return Integer.MIN_VALUE;}int num = stack.pop();int restMax = max(stack,deep - 1);int max = Math.max(num,restMax);stack.push(num);return max;}private static int deep(Stack<Integer> stack) {if (stack.isEmpty()) {return 0;}int num = stack.pop();int deep = deep(stack) + 1;stack.push(num);return deep;}
      

题目7 : 打印n层汉诺塔问题的最优移动轨迹。时间复杂度O(2^n)

  • 思路

    • 设总共有n层塔, 现将上面 n-1 层移动到中间层, 然后将第n层移动到目标层, 最后将上面 n-1 层移动到目标层即可
  • 代码

    • f = f(n - 1) + 1 + f(n - 1) = 2f(n - 1) + 1 | f1 = 1 -> O(2^n)

    • 	public static void hanoi(int n) {if (n > 0) {f(n, "左", "右", "中");}}// 			将n层汉诺塔从from移动到topublic static void f(int i, String from, String to, String other) {if (i == 1) { // basecaseSystem.out.println("移动圆盘 1 从 " + from + " 到 " + to);} else {f(i - 1, from, other, to);// 移动上面i - 1个 到 other(中转)System.out.println("移动圆盘 " + i + " 从 " + from + " 到 " + to);// 将第i个移动到to(目的地)f(i - 1, other, to, from);// 移动other上i - 1个到to}}
      

http://www.mrgr.cn/news/28574.html

相关文章:

  • 基于Springboot+微信小程序的线上水果店系统 (含源码数据库)
  • Move开发语言在区块链的开发与应用
  • qt中ctrl+鼠标左键无法进入
  • [OpenGL]使用OpenGL实现硬阴影效果
  • 摄像机视频分析软件下载LiteAIServer视频智能分析软件抖动检测的技术实现
  • 数字IC实践项目(10)—基于System Verilog的DDR4 Model/Tb 及基础Verification IP的设计与验证(付费项目)
  • 嵌入式系统中的u-boot、kernel、rootfs的区别与关系
  • 【20.5 python中的FastAPI】
  • bootstrapping in the main distro: listing WSL distros: running WSL xxxx
  • Python酷库之旅-第三方库Pandas(120)
  • Java基础-反射
  • MATLAB系列06:复数数据、字符数据和附加画图类
  • Linux: fs:支持最大的文件大小 limit file;truncate
  • 操作数组不越界的妙法C++
  • Nginx:高性能Web服务器与反向代理的深度剖析
  • rk3568 Android12 增加 USB HOST 模式开关(二)
  • Java 技巧 如何在IDEA2024 中快速打出System.out.println();
  • ICMP
  • 数据与结构算法平衡二叉树详解叉树--基本概念
  • 【架构设计】多级缓存:应用案例与问题解决策略
  • 南大通用等保测评
  • 【C++】STL数据结构最全函数详解2-向量vector
  • 【智路】智路OS 应用开发
  • 嵌套类问题的递归解题套路
  • Java中Redis大Key的优化拆分方案与示例
  • 【C++算法】位运算