字符串原地排序
问题背景
我们需要对一个仅包含英文字母和数字的字符串进行排序,但排序过程必须遵循一套特殊的规则,尤其需要保持原字符串中"字母"和"数字"的位置类型不变。
排序规则
-
位置类型保持不变 (Positional Integrity):
这是最核心的规则。排序后的字符串,其每个位置上的字符类型必须与原字符串相同。
- 如果原字符串中
inputStr[i]
是一个数字 ,那么排序后新字符串的第i
个位置也必须是一个数字。 - 如果原字符串中
inputStr[i]
是一个字母 ,那么排序后新字符串的第i
个位置也必须是一个字母。
- 如果原字符串中
-
数字排序规则:
将原字符串中所有的数字提取出来,统一按升序(0, 1, 2, ..., 9)排列。
-
字母排序规则 (自定义):
将原字符串中所有的字母提取出来,按照一个三级的自定义规则进行排列:
- 主规则 : 所有小写字母 都排在所有大写字母之前。
- 次规则 (小写字母内部) : 小写字母之间按字典序升序(a, b, c, ...)。
- 次规则 (大写字母内部) : 大写字母之间按字典序升序(A, B, C, ...)。
- 最终的字母排序顺序为:
a, b, ..., z, A, B, ..., Z
。
任务要求
给定一个输入字符串 inputStr
,请按照上述规则进行排序,并输出最终的字符串。
输入格式
-
inputStr
: 一个字符串。1 <= inputStr.length < 1000
- 字符串中仅包含英文字母(
a-z
,A-Z
)和数字(0-9
)。
输出格式
- 一个字符串,表示输入字符串经过排序后的结果。
限制与要求
- 时间限制 : C/C++
1000ms
, 其他语言2000ms
- 内存限制 : C/C++
64MB
, 其他语言128MB
样例说明
样例 1
-
输入 :
"a2CB1c"
-
输出 :
"a1cB2C"
-
解释:
-
分解与识别:
- 数字 :
2
,1
。它们位于索引1
和4
。 - 字母 :
a
,C
,B
,c
。它们位于索引0
,2
,3
,5
。 - 位置类型模板为:
[字母, 数字, 字母, 字母, 数字, 字母]
- 数字 :
-
独立排序:
- 对数字列表
[2, 1]
进行升序排序,得到[1, 2]
。 - 对字母列表
[a, C, B, c]
应用自定义规则排序,得到[a, c, B, C]
。
- 对数字列表
-
重构字符串: 将排序后的列表按顺序填充回对应的位置类型。
- 第1个字母位 (索引0) -> 填充
a
- 第1个数字位 (索引1) -> 填充
1
- 第2个字母位 (索引2) -> 填充
c
- 第3个字母位 (索引3) -> 填充
B
- 第2个数字位 (索引4) -> 填充
2
- 第4个字母位 (索引5) -> 填充
C
- 第1个字母位 (索引0) -> 填充
-
最终结果 : 组合起来得到
"a1cB2C"
。
-
样例 2
-
输入 :
"ab12C4Ac3B"
-
输出 :
"ab12c3AB4C"
-
解释:
-
分解与识别:
- 数字 :
1, 2, 4, 3
- 字母 :
a, b, C, A, c, B
- 数字 :
-
独立排序:
- 数字排序后:
[1, 2, 3, 4]
- 字母排序后:
[a, b, c, A, B, C]
- 数字排序后:
-
重构字符串 : 将排序结果依次填入原字符串的字母和数字"插槽"中,得到
"ab12c3AB4C"
。
-
java
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Scanner;
/**
* 解决"字符排序"问题的方案类。
*/
public class Solution {
/**
* 对包含字母和数字的字符串按特定规则进行排序。
*
* 算法思路:
* 1. **分离**: 遍历输入字符串一次,将所有数字字符和字母字符分别提取到两个独立的列表中。同时,使用一个布尔数组记录每个原始位置的字符类型(是数字还是字母)。
* 2. **排序**:
* - 对数字列表进行标准的升序排序。
* - 对字母列表使用一个自定义的比较器 (Comparator)进行排序,以满足"小写字母优先、同类字母按字典序"的规则。
* 3. **重构**: 创建一个新的 `StringBuilder`。再次遍历原始字符串的长度(从 0 到 n-1),根据布尔数组中记录的位置类型,依次从排序好的数字列表或字母列表中取出元素,填充到 `StringBuilder` 中。
*
* @param inputStr 待排序的输入字符串。
* @return 排序后的新字符串。
*/
public String sortString(String inputStr) {
// --- 1. 处理边界情况 ---
if (inputStr == null || inputStr.isEmpty()) {
return ""; // 如果输入为空或 null,返回空字符串
}
int n = inputStr.length();
// --- 2. 分离数字和字母,并记录位置类型 ---
List<Character> letters = new ArrayList<>(); // 用于存储所有字母
List<Character> digits = new ArrayList<>(); // 用于存储所有数字
boolean[] isPositionDigit = new boolean[n]; // 标记每个位置的原始类型 (true:数字, false:字母)
// 遍历原始字符串,进行分离
for (int i = 0; i < n; i++) {
char c = inputStr.charAt(i);
if (Character.isDigit(c)) {
digits.add(c);
isPositionDigit[i] = true; // 标记此位置为数字
} else { // 题目保证只有字母和数字
letters.add(c);
isPositionDigit[i] = false; // 标记此位置为字母
}
}
// --- 3. 对分离出的列表进行排序 ---
// a. 对数字列表进行升序排序。
// 对于 '0'-'9' 字符,其字典序与数值大小顺序一致。
Collections.sort(digits);
// b. 对字母列表按自定义规则排序。
// 规则:小写字母 > 大写字母;同为小写或同为大写时,按字典序。
// 即排序结果为 a, b, ..., z, A, B, ..., Z
letters.sort((c1, c2) -> {
boolean c1IsLower = Character.isLowerCase(c1);
boolean c2IsLower = Character.isLowerCase(c2);
// 如果一个是小写,另一个是大写
if (c1IsLower && !c2IsLower) {
return -1; // 小写字母 c1 排在前面
}
if (!c1IsLower && c2IsLower) {
return 1; // 大写字母 c1 排在后面
}
// 如果两者同为小写或同为大写,则按标准的字典序 (a-z, A-Z) 排序
return Character.compare(c1, c2);
});
// --- 4. 重构最终字符串 ---
// 使用 StringBuilder 高效构建字符串
StringBuilder resultBuilder = new StringBuilder(n);
int digitPtr = 0; // 指向已排序数字列表的当前位置
int letterPtr = 0; // 指向已排序字母列表的当前位置
// 遍历原始字符串的每个位置
for (int i = 0; i < n; i++) {
// 检查该位置原始的类型
if (isPositionDigit[i]) {
// 如果原来是数字,从排序好的数字列表中取一个,并追加到结果
resultBuilder.append(digits.get(digitPtr));
digitPtr++; // 指针后移
} else {
// 如果原来是字母,从排序好的字母列表中取一个,并追加到结果
resultBuilder.append(letters.get(letterPtr));
letterPtr++; // 指针后移
}
}
// --- 5. 返回结果 ---
return resultBuilder.toString();
}
}
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
String inputStr = scanner.nextLine(); // 读取输入字符串
scanner.close();
// 创建 Solution 类的实例
Solution solution = new Solution();
// 调用核心方法
String result = solution.sortString(inputStr);
// 输出结果
System.out.println(result);
}
}