剑指offer-27、字符串的排列

题⽬描述

输⼊⼀个字符串,按字典序打印出该字符串中字符的所有排列。例如输⼊字符串 abc ,则按字典序打印出由字符 a , b , c 所能排列出来的所有字符串 abc , acb , bac , bca , cab 和 cba 。

输⼊描述:输⼊⼀个字符串,⻓度不超过9(可能有字符重复),字符只包括⼤⼩写字⺟

思路及解答

递归回溯(使用Set去重)

看到题目,应该就知道要使⽤回溯了。

通过 ​回溯算法 ​ 生成所有排列,配合 ​剪枝条件​ 实现去重和字典序输出。关键步骤包括:

  1. 固定位置法:逐个固定每个位置的字符
  2. 交换策略:通过交换字符位置生成不同排列
  3. 去重处理:使用集合(Set)或排序后跳过重复字符来避免重复排列
  4. 字典序排序:最后对结果进行排序
java 复制代码
import java.util.ArrayList;
import java.util.Collections;

public class StringPermutation {
    public ArrayList<String> permutation(String str) {
        ArrayList<String> result = new ArrayList<>();
        if (str == null || str.length() == 0) {
            return result;
        }
        
        char[] chars = str.toCharArray();
        permute(chars, 0, result);
        Collections.sort(result); // 按字典序排序
        return result;
    }
    
    private void permute(char[] chars, int begin, ArrayList<String> result) {
        if (begin == chars.length - 1) {
            result.add(new String(chars));
            return;
        }
        
        for (int i = begin; i < chars.length; i++) {
            // 跳过重复字符,避免重复排列
            if (i != begin && chars[i] == chars[begin]) {
                continue;
            }
            
            swap(chars, begin, i);
            permute(chars, begin + 1, result);
            swap(chars, i, begin); // 回溯,恢复原始状态
        }
    }
    
    private void swap(char[] chars, int i, int j) {
        char temp = chars[i];
        chars[i] = chars[j];
        chars[j] = temp;
    }
}
  • 时间复杂度:O(n*n!),n为字符串长度,n!是排列总数,每次排列需要O(n)时间
  • 空间复杂度:O(n!),需要存储所有排列结果

回溯+剪枝法(优化去重)

上面方法主要是通过Set进行去重,也可以将字符数组排序来跳过重复字符:

  1. 先排序:将字符数组排序,使相同字符相邻
  2. 剪枝策略:在递归过程中跳过相同字符的重复分支
  3. 标记数组:使用boolean数组记录已使用字符
java 复制代码
public class StringPermutation {
    public ArrayList<String> permutation(String str) {
        ArrayList<String> result = new ArrayList<>();
        if (str == null || str.length() == 0) {
            return result;
        }
        
        char[] chars = str.toCharArray();
        Arrays.sort(chars); // 先排序便于去重
        boolean[] used = new boolean[chars.length];
        backtrack(chars, used, new StringBuilder(), result);
        return result;
    }
    
    private void backtrack(char[] chars, boolean[] used, 
                         StringBuilder path, ArrayList<String> result) {
        if (path.length() == chars.length) {
            result.add(path.toString());
            return;
        }
        
        for (int i = 0; i < chars.length; i++) {
            // 剪枝条件:跳过已使用字符或相同字符的重复分支
            if (used[i] || (i > 0 && chars[i] == chars[i-1] && !used[i-1])) {
                continue;
            }
            
            used[i] = true;
            path.append(chars[i]);
            backtrack(chars, used, path, result);
            path.deleteCharAt(path.length() - 1); // 回溯
            used[i] = false;
        }
    }
}
  • 时间复杂度:O(n*n!),但剪枝减少了不必要的递归调用
  • 空间复杂度:O(n!),结果存储空间

非递归

此方法算法理解难度较大,非标准解法

用字典序生成下一个排列的算法:

  1. 初始排序:将字符数组按字典序排序
  2. 找下一个排列
    • 从后向前找到第一个升序对
    • 交换适当元素
    • 反转后缀
  3. 循环生成:直到无法生成下一个排列
java 复制代码
import java.util.ArrayList;
import java.util.Arrays;

public class StringPermutation {
    public ArrayList<String> permutation(String str) {
        ArrayList<String> result = new ArrayList<>();
        if (str == null || str.length() == 0) {
            return result;
        }
        
        char[] chars = str.toCharArray();
        Arrays.sort(chars); // 初始排序
        result.add(new String(chars));
        
        while (true) {
            int i = chars.length - 2;
            // 从后向前找第一个升序对
            while (i >= 0 && chars[i] >= chars[i + 1]) {
                i--;
            }
            if (i < 0) break; // 已是最大排列
            
            int j = chars.length - 1;
            // 找到第一个大于chars[i]的字符
            while (chars[j] <= chars[i]) {
                j--;
            }
            
            swap(chars, i, j);
            reverse(chars, i + 1, chars.length - 1);
            result.add(new String(chars));
        }
        
        return result;
    }
    
    private void swap(char[] chars, int i, int j) {
        char temp = chars[i];
        chars[i] = chars[j];
        chars[j] = temp;
    }
    
    private void reverse(char[] chars, int start, int end) {
        while (start < end) {
            swap(chars, start++, end--);
        }
    }
}
  • 时间复杂度:O(n*n!),每次生成下一个排列需要O(n)时间
  • 空间复杂度:O(n!),结果存储空间
相关推荐
一叶飘零_sweeeet11 小时前
Java 项目 HTTP+WebSocket 统一权限控制实战
java·websocket·http·权限控制
7澄111 小时前
深入解析 LeetCode 数组经典问题:删除每行中的最大值与找出峰值
java·开发语言·算法·leetcode·intellij idea
ysyxg11 小时前
设计模式-策略模式
java·开发语言
Felix_XXXXL11 小时前
Spring Security安全框架原理与实战
java·后端
一抓掉一大把11 小时前
秒杀-StackExchangeRedisHelper连接单例
java·开发语言·jvm
升鲜宝供应链及收银系统源代码服务11 小时前
升鲜宝生鲜配送供应链管理系统--- 《多语言商品查询优化方案(Redis + 翻译表 + 模糊匹配)》
java·数据库·redis·bootstrap·供应链系统·生鲜配送·生鲜配送源代码
青山的青衫11 小时前
【JavaWeb】Tlias后台管理系统
java·web
蒟蒻的工具人11 小时前
SSE实时推送订单状态
java·eventsource·sse协议
小蒜学长12 小时前
springboot基于Java的校园导航微信小程序的设计与实现(代码+数据库+LW)
java·spring boot·后端·微信小程序
王元_SmallA12 小时前
IDEA + Spring Boot 的三种热加载方案
java·后端