
本文介绍如何用Java高效判断一串0-9数字字符是否能恰好划分为一个数字对(2张相同)和零个或多个数字 triplet(3张相同),所有字符必须全部使用且不可重复分配。核心思路是统计频次、模3化简、验证唯一余数为2的出现次数。
本文介绍如何用java高效判断一串0-9数字字符是否能恰好划分为**一个数字对(2张相同)**和**零个或多个数字 triplet(3张相同)**,所有字符必须全部使用且不可重复分配。核心思路是统计频次、模3化简、验证唯一余数为2的出现次数。
要解决该 Mahjong 简化版“完整手牌”判定问题,关键在于理解数学约束:
- 每个数字 d 出现 count[d] 次;
- 所有数字必须被完全分组为若干个 triplet(3张相同) 和 恰好一个 pair(2张相同);
- 因此,对每个数字 d,其频次 count[d] 在扣除若干个 3 的倍数后,余数只能是 0、1 或 2;
- 若余数为 1 → 无法用 pair 或 triplet 消耗(因 1 ≠ 2 且 1 < 3),直接非法;
- 若余数为 2 → 必须由唯一的 pair 贡献,且全局只能出现一次;
- 若余数为 0 → 全部组成 triplet,合法;
因此,最优解法无需回溯或枚举,只需:
- 统计各数字('0'–'9')出现频次;
- 对每个频次 v 计算 v % 3;
- 若余数为 1 → 立即返回 false;
- 若余数为 2 → 标记已找到 pair,若再次出现则非法;
- 最终确认恰好找到一次余数为 2。
以下是精简、健壮的 Java 实现:
import java.util.HashMap;
public static String isComplete(String s) {
// 统计每个数字字符的出现次数(转为 int: '0'→0, ..., '9'→9)
HashMap<Integer, Integer> countMap = new HashMap<>();
for (char c : s.toCharArray()) {
int digit = c - '0';
countMap.put(digit, countMap.getOrDefault(digit, 0) + 1);
}
boolean hasExactlyOnePair = false;
for (int freq : countMap.values()) {
int remainder = freq % 3;
if (remainder == 1) {
return "NOT COMPLETE"; // 余1无法构成pair或triplet
} else if (remainder == 2) {
if (hasExactlyOnePair) {
return "NOT COMPLETE"; // 已存在pair,不能再有第二个
}
hasExactlyOnePair = true;
}
// remainder == 0:可全部组成triplet,无需处理
}
return hasExactlyOnePair ? "COMPLETE" : "NOT COMPLETE";
}✅ 正确性说明:
- "33344466" → counts: {3:3, 4:3, 6:2} → remainders: {0,0,2} → ✅
- "55555555" → count[5]=8 → 8%3=2 → 仅一个余2 → ✅(对应 555+555+55)
- "22" → count[2]=2 → 2%3=2 → 唯一余2 → ✅
- "335577" → 各频次均为2 → 余数全为2 → 第二个2触发失败 → ❌
- "666888" → 各频次为3 → 余数全为0 → 无余2 → ❌(缺少pair)
⚠️ 注意事项:
- 输入保证只含 '0'–'9',无需额外校验;
- 空字符串 "" 频次全0 → 无余2 → 返回 "NOT COMPLETE"(符合题意:必须有且仅有一个 pair);
- 单字符如 "5" → 1%3=1 → 直接返回 "NOT COMPLETE";
- 时间复杂度 O(n),空间复杂度 O(1)(最多10个键);
该解法摒弃暴力搜索,直击模运算本质,是典型「贪心+数论约束」的优雅应用。

















