37.【必备】常见经典递归过程解析

本文的网课内容学习自B站左程云老师的算法详解课程,旨在对其中的知识进行整理和分享~

网课链接:算法讲解038【必备】常见经典递归过程解析_哔哩哔哩_bilibili

一.字符串的全部子序列

题目:字符串的全部子序列

算法原理

①generatePermutation1方法相关

  • 总体思路
    • 对于给定的字符串,通过递归地考虑每个字符是否加入到子序列中来生成所有可能的子序列。由于要求去重,使用HashSet来存储中间结果以避免重复。
  • 具体步骤
    • generatePermutation1方法
      • 首先将输入字符串转换为字符数组s
      • 创建一个HashSet<String>用于去重存储子序列。
      • 调用f1方法开始生成子序列,传入字符数组s、起始索引0、一个空的StringBuilder用于构建子序列路径、以及用于去重的HashSet
      • 根据HashSet中去重后的子序列数量创建结果数组ans
      • HashSet中的子序列依次放入结果数组ans中并返回。
    • f1方法(递归生成子序列)
      • 递归终止条件(if (i == s.length)
        • 当索引i等于字符数组s的长度时,表示已经处理完字符串的所有字符。此时将当前构建的子序列路径(path.toString())添加到HashSet中进行去重存储。
      • 递归生成子序列(else
        • 首先将当前字符s[i]添加到path中(path.append(s[i])),然后递归调用f1方法,将索引i加1,表示继续处理下一个字符,同时传入更新后的pathHashSet。这一步相当于选择将当前字符加入子序列。
        • 接着,将刚刚添加到path中的字符移除(path.deleteCharAt(path.length() - 1)),再次递归调用f1方法,同样将索引i加1,但此时传入的path是移除了当前字符后的情况。这一步相当于不选择将当前字符加入子序列。

generatePermutation2方法相关

  • 总体思路
    • 同样是通过递归地考虑每个字符是否加入到子序列中来生成所有可能的子序列,不过这里使用一个固定大小的字符数组path来构建子序列,并且也使用HashSet去重。
  • 具体步骤
    • generatePermutation2方法
      • 将输入字符串转换为字符数组s
      • 创建一个HashSet<String>用于去重存储子序列。
      • 调用f2方法开始生成子序列,传入字符数组s、起始索引0、一个与输入字符串长度相同的字符数组path、初始大小为0(表示当前path中有效字符的数量)、以及用于去重的HashSet
      • 根据HashSet中去重后的子序列数量创建结果数组ans
      • HashSet中的子序列依次放入结果数组ans中并返回。
    • f2方法(递归生成子序列)
      • 递归终止条件(if (i == s.length)
        • 当索引i等于字符数组s的长度时,表示已经处理完字符串的所有字符。此时将当前构建的子序列(String.valueOf(path, 0, size),即根据有效字符数量sizepath中获取子序列)添加到HashSet中进行去重存储。
      • 递归生成子序列(else
        • 首先将当前字符s[i]放入path数组的size位置(path[size] = s[i]),然后递归调用f2方法,将索引i加1,表示继续处理下一个字符,同时传入更新后的pathsize + 1(因为加入了一个字符,有效字符数量加1)和HashSet。这一步相当于选择将当前字符加入子序列。
        • 接着,再次递归调用f2方法,将索引i加1,但传入的size不变(即不选择将当前字符加入子序列),同时传入更新后的pathHashSet

代码实现 

import java.util.HashSet;

// 字符串的全部子序列
// 子序列本身是可以有重复的,只是这个题目要求去重
// 测试链接 : https://www.nowcoder.com/practice/92e6247998294f2c933906fdedbc6e6a
public class Code01_Subsequences {

    public static String[] generatePermutation1(String str) {
        char[] s = str.toCharArray();
        HashSet<String> set = new HashSet<>();
        f1(s, 0, new StringBuilder(), 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,set收集结果时去重
    public static void f1(char[] s, int i, StringBuilder path, HashSet<String> set) {
        if (i == s.length) {
            set.add(path.toString());
        } else {
            path.append(s[i]); // 加到路径中去
            f1(s, i + 1, path, set);
            path.deleteCharAt(path.length() - 1); // 从路径中移除
            f1(s, i + 1, path, set);
        }
    }

    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;
    }

    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);
        }
    }

}

二.子集

题目:子集 II​​​​​​

算法原理

  • 总体思路
    • 先对输入数组进行排序,这样相同的元素会相邻。然后通过递归的方式构建所有可能的组合,在构建过程中避免生成重复的组合。
  • 具体步骤
    • subsetsWithDup方法
      • 创建一个空的结果列表ans,用于存储所有不重复的组合。
      • 对输入的整数数组nums进行排序(Arrays.sort(nums)),这是为了方便后续处理重复元素。
      • 调用f方法开始构建组合,传入nums数组、起始索引0、一个与nums长度相同的临时数组path(用于构建组合路径)、初始大小为0(表示path中有效元素的数量)以及结果列表ans
      • 最后返回结果列表ans
    • f方法(递归构建组合)
      • 递归终止条件(if (i == nums.length)
        • 当索引i等于nums数组的长度时,表示已经处理完数组中的所有元素。此时根据path数组中有效元素的数量(size)构建一个ArrayList,并将其添加到结果列表ans中。
      • 递归构建组合(else
        • 首先确定下一组的第一个数的位置(int j = i + 1; while (j < nums.length && nums[i] == nums[j]) {j++;}),这里通过循环跳过与nums[i]相同的连续元素,找到下一个不同元素的位置j
        • 考虑当前数nums[i]0个的情况,即不包含当前数的组合,直接递归调用f方法,传入j作为新的起始索引,pathsize不变(因为不包含当前数)以及ans
        • 然后考虑当前数nums[i]1个、要2个、要3个等情况(for (; i < j; i++)),在这个循环中,将当前数nums[i]放入path数组(path[size++] = nums[i]),并递归调用f方法,传入j作为新的起始索引,更新后的path(包含了当前数)、更新后的size(因为加入了当前数)以及ans。通过这种方式,构建包含不同数量当前数的组合,同时避免了重复组合的生成。例如,对于[1,2,2],当处理第一个2时,会构建包含不同数量2的组合,但由于已经跳过了后续相同的2,不会产生重复的组合。

代码实现

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

// 给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的组合
// 答案 不能 包含重复的组合。返回的答案中,组合可以按 任意顺序 排列
// 注意其实要求返回的不是子集,因为子集一定是不包含相同元素的,要返回的其实是不重复的组合
// 比如输入:nums = [1,2,2]
// 输出:[[],[1],[1,2],[1,2,2],[2],[2,2]]
// 测试链接 : https://leetcode.cn/problems/subsets-ii/
public class Code02_Combinations {

    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;
    }

    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++) {
                cur.add(path[j]);
            }
            ans.add(cur);
        } else {
            // 下一组的第一个数的位置
            int j = i + 1;
            while (j < nums.length && nums[i] == nums[j]) {
                j++;
            }
            // 当前数x,要0个
            f(nums, j, path, size, ans);
            // 当前数x,要1个、要2个、要3个...都尝试
            for (; i < j; i++) {
                path[size++] = nums[i];
                f(nums, j, path, size, ans);
            }
        }
    }

}

三.全排列

题目:全排列

算法原理

  • 总体思路
    • 通过递归交换数组元素的方式来生成所有可能的全排列。对于给定的数组,每次固定一个位置的元素,然后递归地对剩余元素进行全排列操作,最后通过交换操作还原数组状态,以进行下一轮的固定元素操作。
  • 具体步骤
    • permute方法
      • 创建一个空的结果列表ans,用于存储所有的全排列结果。
      • 调用f方法开始生成全排列,传入待排列的数组nums、起始索引0以及结果列表ans
      • 最后返回结果列表ans
    • f方法(递归生成全排列)
      • 递归终止条件(if (i == nums.length)
        • 当索引i等于数组nums的长度时,表示已经完成了一个全排列的构建。此时创建一个新的ArrayList,将数组nums中的所有元素添加进去(因为此时nums已经是一种全排列状态),然后将这个ArrayList添加到结果列表ans中。
      • 递归生成全排列(else
        • 对于当前索引i,通过一个循环(for (int j = i; j < nums.length; j++)),将索引i到数组末尾的每个元素依次与索引i位置的元素进行交换(swap(nums, i, j))。这一步的目的是固定当前位置i的元素为不同的值。
        • 在交换元素后,递归调用f方法,将起始索引更新为i + 1,继续对剩余的元素进行全排列操作。
        • 完成递归调用后,再次交换元素(swap(nums, i, j)),将数组还原到交换之前的状态。这一步非常重要,因为如果不还原数组,后续的交换操作将基于错误的数组状态进行,无法生成正确的全排列。例如,对于数组{1, 2, 3},当i = 0时,首先将11交换(无实际交换),然后递归对{2, 3}进行全排列操作;当对{2, 3}的全排列操作完成后,需要将数组还原到{1, 2, 3}的状态,以便进行下一次交换(如将12交换),从而继续生成不同的全排列。

 代码实现

import java.util.ArrayList;
import java.util.List;

// 没有重复项数字的全排列
// 测试链接 : https://leetcode.cn/problems/permutations/
public class Code03_Permutations {

    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++) {
                swap(nums, i, j);
                f(nums, i + 1, ans);
                swap(nums, i, j); // 特别重要,课上进行了详细的图解
            }
        }
    }

    public static void swap(int[] nums, int i, int j) {
        int tmp = nums[i];
        nums[i] = nums[j];
        nums[j] = tmp;
    }

    public static void main(String[] args) {
        int[] nums = { 1, 2, 3 };
        List<List<Integer>> ans = permute(nums);
        for (List<Integer> list : ans) {
            for (int num : list) {
                System.out.print(num + " ");
            }
            System.out.println();
        }
    }

}

四.全排列 II

题目:全排列 II

算法原理

  • 总体思路
    • 与无重复项数组的全排列类似,通过递归交换数组元素的方式来生成所有可能的全排列,但在交换元素时需要进行去重处理,以避免生成重复的全排列。
  • 具体步骤
    • permuteUnique方法
      • 创建一个空的结果列表ans,用于存储所有的去重全排列结果。
      • 调用f方法开始生成去重全排列,传入待排列的数组nums、起始索引0以及结果列表ans
      • 最后返回结果列表ans
    • f方法(递归生成去重全排列)
      • 递归终止条件(if (i == nums.length)
        • 当索引i等于数组nums的长度时,表示已经完成了一个全排列的构建。此时创建一个新的ArrayList,将数组nums中的所有元素添加进去(因为此时nums已经是一种全排列状态),然后将这个ArrayList添加到结果列表ans中。
      • 递归生成去重全排列(else
        • 创建一个HashSet集合set,用于记录已经在当前位置i使用过的元素,以避免重复使用。
        • 通过一个循环(for (int j = i; j < nums.length; j++)),遍历索引i到数组末尾的每个元素。
        • 在循环中,首先判断当前元素nums[j]是否已经在set中,如果不在(即nums[j]没有来到过i位置),则将其添加到set中,然后交换nums[i]nums[j]的位置(swap(nums, i, j)),接着递归调用f方法,将起始索引更新为i + 1,继续对剩余的元素进行去重全排列操作。
        • 完成递归调用后,再次交换元素(swap(nums, i, j)),将数组还原到交换之前的状态,以便进行下一次交换操作,生成不同的全排列。例如,对于数组{1, 1, 2},当i = 0时,首先将第一个1与自身交换(无实际交换),然后递归对{1, 2}进行去重全排列操作;当对{1, 2}的去重全排列操作完成后,需要将数组还原到{1, 1, 2}的状态,接着将第一个1与第二个1交换,再次递归对{1, 2}进行去重全排列操作,由于HashSet的去重作用,不会生成重复的全排列。

代码实现 

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;

// 有重复项数组的去重全排列
// 测试链接 : https://leetcode.cn/problems/permutations-ii/
public class Code04_PermutationWithoutRepetition {

    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])) {
                    set.add(nums[j]);
                    swap(nums, i, j);
                    f(nums, i + 1, ans);
                    swap(nums, i, j);
                }
            }
        }
    }

    public static void swap(int[] nums, int i, int j) {
        int tmp = nums[i];
        nums[i] = nums[j];
        nums[j] = tmp;
    }

}

五.用递归函数逆序栈

算法原理

总体思路
  • 这个算法主要是通过递归操作来实现栈的逆序。首先定义了两个递归操作,一个是reverse函数用于整体逆序栈,另一个是bottomOut函数用于获取栈底元素并调整栈结构。
具体步骤
  • reverse函数原理
    • 递归终止条件(if (stack.isEmpty())
      • 当栈为空时,表示已经完成了逆序操作(或者栈本身就是空的无需操作),直接返回。
    • 递归操作
      • 先调用bottomOut函数从栈中取出栈底元素(int num = bottomOut(stack))。这个操作会不断递归,直到找到栈底元素,并且在这个过程中会调整栈的结构,将栈底元素之上的元素依次下压一层。
      • 然后再次调用reverse函数自身(reverse(stack)),对剩余的栈(已经去除了栈底元素的栈)进行逆序操作。
      • 最后将之前取出的栈底元素重新压入栈中(stack.push(num))。通过这样的操作,每次将栈底元素取出,然后对剩余栈逆序,再将栈底元素放到栈顶,逐步实现整个栈的逆序。
  • bottomOut函数原理
    • 递归终止条件(if (stack.isEmpty())
      • 当栈为空时,说明已经找到了栈底元素(因为再没有元素可以弹出了),直接返回当前弹出的元素(ans)。
    • 递归操作
      • 先弹出栈顶元素并保存到ans中(int ans = stack.pop())。
      • 然后递归调用bottomOut函数(if (stack.isEmpty())判断不成立时),继续寻找下一个可能的栈底元素(此时的栈已经少了一个元素)。
      • 在递归返回后,将之前保存的ans元素重新压入栈中(stack.push(ans)),并返回找到的真正栈底元素(last)。例如,对于栈[1, 2, 3],首先弹出3保存到ans,然后递归处理[1, 2],找到栈底元素1,在返回过程中,将3重新压入栈,最终返回1作为栈底元素。

代码实现

import java.util.Stack;

// 用递归函数逆序栈
public class Code05_ReverseStackWithRecursive {

    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());
        }
    }

}

六.用递归函数排序栈

  • 总体思路
    • 这个算法通过一系列递归操作来对栈进行排序,在不使用额外容器(除了栈自身)的情况下,逐步将栈中的元素按照从小到大的顺序从栈顶到栈底排列。算法主要通过四个递归函数deepmaxtimesdown来实现排序过程。
  • 具体步骤
    • sort函数原理

      • 首先,通过deep函数获取栈的深度(int deep = deep(stack))。
      • 然后进入一个循环,只要栈的深度大于0就持续循环。在循环中:
        • 调用max函数找到当前栈顶往下deep层中的最大值(int max = max(stack, deep))。
        • 调用times函数确定这个最大值在这deep层中出现的次数(int k = times(stack, deep, max))。
        • 调用down函数将这k个最大值沉底(down(stack, deep, max, k))。
        • 最后更新栈的深度(deep -= k),表示已经处理了k个最大值,继续处理剩余的栈元素。
    • deep函数原理

      • 递归终止条件(if (stack.isEmpty())
        • 当栈为空时,栈的深度为0,直接返回0。
      • 递归操作
        • 先弹出栈顶元素(int num = stack.pop())。
        • 然后递归调用deep函数来获取剩余栈的深度(int deep = deep(stack)),此时得到的深度是剩余栈的深度,所以需要加1(deep = deep(stack) + 1)。
        • 最后将之前弹出的元素重新压入栈中(stack.push(num)),并返回栈的深度。
    • max函数原理

      • 递归终止条件(if (deep == 0)
        • 当要查找的层数为0时,表示已经没有元素可查找,返回Integer.MIN_VALUE,这是一个极小值,用于在比较中初始化为最小值。
      • 递归操作
        • 先弹出栈顶元素(int num = stack.pop())。
        • 然后递归调用max函数查找剩余deep - 1层中的最大值(int restMax = max(stack, deep - 1))。
        • 比较当前弹出的元素num和剩余栈中的最大值restMax,取较大值作为新的最大值(int max = Math.max(num, restMax))。
        • 最后将之前弹出的元素重新压入栈中(stack.push(num)),并返回最大值。
    • times函数原理

      • 递归终止条件(if (deep == 0)
        • 当要查找的层数为0时,表示已经没有元素可查找,返回0。
      • 递归操作
        • 先弹出栈顶元素(int num = stack.pop())。
        • 然后递归调用times函数查找剩余deep - 1层中最大值(之前通过max函数获取)出现的次数(int restTimes = times(stack, deep - 1, max))。
        • 如果当前弹出的元素num等于最大值max,则将剩余次数加1(int times = restTimes + (num == max? 1 : 0)),否则保持剩余次数不变。
        • 最后将之前弹出的元素重新压入栈中(stack.push(num)),并返回最大值出现的次数。
    • down函数原理

      • 递归终止条件(if (deep == 0)
        • 当要处理的层数为0时,表示已经到达了目标层,将k个最大值(之前通过times函数获取)依次压入栈中(for (int i = 0; i < k; i++) {stack.push(max);})。
      • 递归操作
        • 先弹出栈顶元素(int num = stack.pop())。
        • 然后递归调用down函数处理剩余deep - 1层,将最大值沉底(down(stack, deep - 1, max, k))。
        • 如果弹出的元素num不等于最大值max,则将其重新压入栈中(if (num!= max) {stack.push(num);}),这样就保证了除最大值之外的元素顺序不变。

代码实现

import java.util.Stack;

// 用递归函数排序栈
// 栈只提供push、pop、isEmpty三个方法
// 请完成无序栈的排序,要求排完序之后,从栈顶到栈底从小到大
// 只能使用栈提供的push、pop、isEmpty三个方法、以及递归函数
// 除此之外不能使用任何的容器,数组也不行
// 就是排序过程中只能用:
// 1) 栈提供的push、pop、isEmpty三个方法
// 2) 递归函数,并且返回值最多为单个整数
public class Code06_SortStackWithRecursive {

    public static void sort(Stack<Integer> stack) {
        int deep = deep(stack);
        while (deep > 0) {
            int max = max(stack, deep);
            int k = times(stack, deep, max);
            down(stack, deep, max, k);
            deep -= k;
        }
    }

    // 返回栈的深度
    // 不改变栈的数据状况
    public 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;
    }

    // 从栈当前的顶部开始,往下数deep层
    // 返回这deep层里的最大值
    public 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;
    }

    // 从栈当前的顶部开始,往下数deep层,已知最大值是max了
    // 返回,max出现了几次,不改变栈的数据状况
    public static int times(Stack<Integer> stack, int deep, int max) {
        if (deep == 0) {
            return 0;
        }
        int num = stack.pop();
        int restTimes = times(stack, deep - 1, max);
        int times = restTimes + (num == max ? 1 : 0);
        stack.push(num);
        return times;
    }

    // 从栈当前的顶部开始,往下数deep层,已知最大值是max,出现了k次
    // 请把这k个最大值沉底,剩下的数据状况不变
    public static void down(Stack<Integer> stack, int deep, int max, int k) {
        if (deep == 0) {
            for (int i = 0; i < k; i++) {
                stack.push(max);
            }
        } else {
            int num = stack.pop();
            down(stack, deep - 1, max, k);
            if (num != max) {
                stack.push(num);
            }
        }
    }

    // 为了测试
    // 生成随机栈
    public static Stack<Integer> randomStack(int n, int v) {
        Stack<Integer> ans = new Stack<Integer>();
        for (int i = 0; i < n; i++) {
            ans.add((int) (Math.random() * v));
        }
        return ans;
    }

    // 为了测试
    // 检测栈是不是从顶到底依次有序
    public static boolean isSorted(Stack<Integer> stack) {
        int step = Integer.MIN_VALUE;
        while (!stack.isEmpty()) {
            if (step > stack.peek()) {
                return false;
            }
            step = stack.pop();
        }
        return true;
    }

    // 为了测试
    public static void main(String[] args) {
        Stack<Integer> test = new Stack<Integer>();
        test.add(1);
        test.add(5);
        test.add(4);
        test.add(5);
        test.add(3);
        test.add(2);
        test.add(3);
        test.add(1);
        test.add(4);
        test.add(2);
        sort(test);
        while (!test.isEmpty()) {
            System.out.println(test.pop());
        }

        // 随机测试
        int N = 20;
        int V = 20;
        int testTimes = 20000;
        System.out.println("测试开始");
        for (int i = 0; i < testTimes; i++) {
            int n = (int) (Math.random() * N);
            Stack<Integer> stack = randomStack(n, V);
            sort(stack);
            if (!isSorted(stack)) {
                System.out.println("出错了!");
                break;
            }
        }
        System.out.println("测试结束");
    }

}

七. 打印n层汉诺塔问题的最优移动轨迹

算法原理

  • 总体思路
    • 汉诺塔问题是一个经典的递归问题。对于有(n)个圆盘的汉诺塔,其目标是将所有圆盘从起始柱(这里用“左”表示)移动到目标柱(这里用“右”表示),中间借助一个辅助柱(这里用“中”表示),并且在移动过程中要遵循大盘不能放在小盘上面的规则。
  • 具体步骤
    • hanoi函数原理
      • 首先判断(n)是否大于(0),如果是,则调用f函数开始解决汉诺塔问题,传入(n)以及表示起始柱、目标柱和辅助柱的字符串。
    • f函数原理
      • 递归终止条件(if (i == 1)
        • 当(i = 1)时,表示只剩下一个圆盘需要移动。此时直接输出移动这个圆盘的操作,即从起始柱移动到目标柱(System.out.println("移动圆盘 1 从 " + from + " 到 " + to);)。
      • 递归操作((i > 1))
        • 首先,递归调用f函数来移动(i - 1)个圆盘,将这(i - 1)个圆盘从起始柱移动到辅助柱,此时目标柱作为辅助柱,辅助柱作为目标柱(f(i - 1, from, other, to);)。
        • 然后,输出移动最大圆盘(第(i)个圆盘)的操作,将这个最大圆盘从起始柱移动到目标柱(System.out.println("移动圆盘 " + i + " 从 " + from + " 到 " + to);)。
        • 最后,再次递归调用f函数,将之前移动到辅助柱的(i - 1)个圆盘从辅助柱移动到目标柱,此时起始柱作为辅助柱(f(i - 1, other, to, from);)。
    • 例如,当(n = 3)时:
    • 首先,通过f(3, "左", "右", "中")进入f函数。
    • 因为(i = 3 > 1),所以先执行f(2, "左", "中", "右")
    • 由于(i = 2 > 1),进一步执行f(1, "左", "右", "中"),此时满足i = 1,输出“移动圆盘1从左到右”。
    • 然后回到f(2, "左", "中", "右"),输出“移动圆盘2从左到中”,再执行f(1, "右", "中", "左"),输出“移动圆盘1从右到中”。
    • 最后回到f(3, "左", "右", "中"),输出“移动圆盘3从左到右”,接着执行f(2, "中", "右", "左"),按照类似的递归过程完成剩下圆盘的移动。

代码实现

// 打印n层汉诺塔问题的最优移动轨迹
public class Code07_TowerOfHanoi {

    public static void hanoi(int n) {
        if (n > 0) {
            f(n, "左", "右", "中");
        }
    }

    public static void f(int i, String from, String to, String other) {
        if (i == 1) {
            System.out.println("移动圆盘 1 从 " + from + " 到 " + to);
        } else {
            f(i - 1, from, other, to);
            System.out.println("移动圆盘 " + i + " 从 " + from + " 到 " + to);
            f(i - 1, other, to, from);
        }
    }

    public static void main(String[] args) {
        int n = 3;
        hanoi(n);
    }

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值