文章目录

  • 剑指offer题解汇总 Java实现
  • 本题链接
  • 题目
  • 方案 递归+回溯



剑指offer题解汇总 Java实现


本题链接

知识分类篇 - 搜索算法 - JZ38 字符串的排列

题目

java 判断对象字节长度_开发语言


题目主要信息

  • 给定一个长度为n的字符串,求其中所有字符的全排列
  • 字符串中可能有重复字符,打印顺序任意
  • 字符串中只包含大小写字母

注意

如果输入是"aa",那么输出是[“aa”],不是[“aa”,“aa”]

方案 递归+回溯

递归

递归是一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解。因此递归过程,最重要的就是查看能不能将原本的问题分解成更小的子问题,这是使用递归的关键。

回溯

如果是线性递归,子问题直接回到父问题不需要回溯,但如果是树型递归,父问题有很多分支,需要从子问题回到父问题,进入另一个子问题。因此回溯是指在递归过程中,从某一分支的子问题回到父问题,进入父问题的另一个子问题分支,因为有时候进入第一个子问题的时候修改过一些变量,因此回溯的时候会要求改回父问题时的样子才能进入第二子问题的分支。

思路

都是求元素的全排列,字符串与数组没有区别,一个是数字全排列,一个是字符全排列。为了便于去掉重复情况,还是参照数组全排列,优先考虑字典序排序,因为排序后重复的字符就会相邻,后序递归找起来也很方便

使用临时变量去组装一个全排列情况:每当我们选取一个字符以后,就确定了其位置,相当于对字符串中剩下的元素进行全排列添加在该元素的后面,给剩余部分进行全排列就是一个子问题,因此可以使用递归。

  • 终止条件:临时字符串中选取了n个元素,已经形成了一种排列情况,可以将其加入输出数组中。
  • 返回值:每一层给上一层返回的就是本层级在临时字符串中添加的元素,递归到末尾的时候,就能添加全部元素
  • 本级任务:每一级都需要选择一个元素加入到临时字符串末尾(遍历全字符串选择)

具体做法

  1. 先对字符串按照字典序排序,获得第一个排列情况
  2. 准备一个空串,暂存递归过程中组装的排列情况。使用额外的vis数组用于记录哪些位置的字符被加入了
  3. 每次递归从头遍历字符串,获取字符加入:首先根据vis数组,已经加入的元素不能再加入了;同时,如果当前的元素str[i]与同一层的前一个元素str[i-1]相同,且str[i-1]已经用,也不需要将其加入
  4. 进入下一等递归前将vis数组当前位置标记为使用过
  5. 回溯时,需要修改vis数组当前位置标记,同时去掉刚刚加入的字符串元素
  6. 临时字符串长度达到原串长度就是一种排列情况
import java.util.ArrayList;
import java.util.Arrays;

public class Solution {

    private StringBuilder builder = new StringBuilder();

    private ArrayList<String> res = new ArrayList<>();

    public ArrayList<String> Permutation(String str) {

        if (str == null || str.length() == 0) {
            return res;
        }

        char[] chars = str.toCharArray();
        Arrays.sort(chars);
        String sortedStr = new String(chars);

        //当vis[i]=0,表示index为0的字符没有被使用
        //当vis[i]=1,表示index为1的字符被使用了
        int[] vis = new int[chars.length];

        StringBuilder builder = new StringBuilder();

        dfs(sortedStr, vis, 0);

        return res;
    }

    private void dfs(String str, int[] vis, int depth) {

        if (builder.length() == str.length()) {
            res.add(builder.toString());
            return;
        }

        for (int i = 0; i < str.length(); i++) {
            if (vis[i] == 1) {
                continue;
            }
            //当前的元素str[i]与同一层的前一个元素str[i-1]相同且str[i-1]已经用过了
            if (i != 0 && str.charAt(i) == str.charAt(i - 1) && vis[i - 1] == 1) {
                continue;
            }
            builder.append(str.charAt(i));
            vis[i] = 1;
            dfs(str, vis, depth + 1);
            builder.deleteCharAt(builder.length() - 1);
            vis[i] = 0;
        }
    }
}