一、题目描述
给定 M 个小写英文字符(0 < M ≤ 30),需从中选取字符拼接成长度为 N 的字符串(0 < N ≤ 5),满足以下规则:
- 每个字符只能使用一次;
- 字符串中相同字符不能相邻;
- 若输入非法(如
N > M、字符列表为空等)或无法拼接出满足条件的字符串,返回0;
计算出给定的字符列表能拼接出多少种满足条件的字符串
二、输入输出描述
输入描述
- 给定的字符列表和结果字符串长度,中间使用空格(" ")拼接
输出描述
- 满足条件的字符串个数
三、示例
|----|------------|
| 输入 | aab 2 |
| 输出 | 2 |
| 说明 | 只能构成ab,ba。 |
|----|--------------------------|
| 输入 | abc 2 |
| 输出 | 6 |
| 说明 | 可以构成:ab ac ba bc ca cb 。 |
四、解题思路
1. 核心思想
通过递归 + 回溯 生成所有符合规则的字符组合,利用HashSet自动去重,最终统计唯一组合的数量 ------ 核心是 "遍历选字符 + 标记去重 + 回溯恢复 + Set 去重",确保生成的组合满足 "长度达标、无重复字符、相邻字符不同" 的规则。
2. 问题本质分析
- 表层问题:统计从源字符串中选指定长度字符、无重复字符、相邻字符不同的唯一组合数;
- 深层问题:
- 组合生成问题:本质是 "无重复的排列组合"(选指定长度、字符不重复);
- 去重需求:源字符串可能有重复字符(如
"aab"),需过滤重复的组合(如"ab"仅算一次); - 回溯的必要性:递归选字符后需恢复标记,才能生成所有可能的组合(如选
a后取消标记,才能选b生成ba); - 相邻字符判断:虽因 "无重复字符" 可省略,但代码保留该逻辑,适配更通用的场景(如允许重复选字符时,避免相邻相同)。
3. 核心逻辑
- 标记去重:用
used数组标记已选字符,避免同一组合中重复选取; - 递归拼接:逐位拼接字符,直到组合长度达标;
- 回溯恢复:递归返回后取消字符标记,保证后续遍历能生成其他组合;
- 自动去重:用
HashSet存储组合,过滤源字符串重复字符导致的重复组合; - 相邻判断:额外过滤相邻字符相同的情况,增强规则适配性。
4. 步骤拆解
- 输入处理 :
- 拆分输入为源字符串和目标组合长度;
- 初始化工具 :
- 创建
HashSet存储唯一组合,创建used数组标记字符使用状态;
- 创建
- 递归生成组合 :
- 终止条件:组合长度等于目标长度,加入 Set;
- 遍历源字符串字符:
- 跳过已使用的字符或与组合最后一位相同的字符;
- 标记字符为已使用,递归拼接字符;
- 回溯:递归返回后取消字符标记;
- 统计结果 :
- 返回
HashSet的大小,即唯一组合的数量。
- 返回
五、代码实现
java
import java.util.*;
public class Main {
public static void main(String[] args) {
// 创建一个Scanner对象来读取用户的输入
Scanner sc = new Scanner(System.in);
// 读取用户输入的字符串
String input = sc.nextLine();
// 将输入的字符串按空格分割为两部分,分别为字符串和长度
String[] parts = input.split(" ");
String str = parts[0]; // 获取输入的字符串
int length = Integer.parseInt(parts[1]); // 将输入的长度部分转换为整数
// 调用countDistinctStrings方法计算满足条件的不同字符串的数量
int count = countDistinctStrings(str, length);
// 输出计算结果
System.out.println(count);
}
// 计算满足条件的不同字符串的数量
public static int countDistinctStrings(String str, int length) {
// 创建一个HashSet来存储不同的字符串
HashSet<String> set = new HashSet<>();
// 创建一个boolean数组来标记字符串中的字符是否已经被使用
boolean[] used = new boolean[str.length()];
// 调用generateDistinctStrings方法生成满足条件的不同字符串
generateDistinctStrings(str, length, "", set, used);
// 打印生成的所有不同的字符串
// for(String str1 : set){
// System.out.println(str1);
// }
// 返回不同字符串的数量
return set.size();
}
// 递归生成满足条件的不同字符串
public static void generateDistinctStrings(String str, int length, String current, HashSet<String> set, boolean[] used) {
// 当生成的字符串长度等于指定长度时,将其加入到HashSet中
if (current.length() == length) {
set.add(current);
return;
}
// 遍历字符串中的字符
for (int i = 0; i < str.length(); i++) {
// 判断字符是否已经被使用,或者当前字符与前一个字符相同
if (used[i] || (current.length() > 0 && current.charAt(current.length() - 1) == str.charAt(i))) {
continue; // 如果字符已被使用或与前一个字符相同,则跳过当前字符
}
used[i] = true; // 标记当前字符为已使用
// 递归调用生成下一个字符
generateDistinctStrings(str, length, current + str.charAt(i), set, used);
used[i] = false; // 取消标记当前字符的使用状态,以便下一次遍历
}
}
}