检查一个字符串是否可以打破另一个字符串 图解题解
这道题到底在问什么
- 输入
- s1="abc", s2="xya"
- 输出
- true
- 输入
- s1="abe", s2="acd"
- 输出
- false
- 输入
- s1="leetcodee", s2="interview"
- 输出
- true
最优解:为什么这么做
一句话答案:LeetCode 1433 检查一个字符串能否打破另一个:双排序后逐位比,只要 s1 每位都不小于 s2、或反过来每位都不小于,就能打破,时间 O(n log n)、空间 O(n)。
打破一个字符串,到底在要求什么
给两个长度相等的字符串 s1 和 s2。说「s1 打破 s2」,指的是能把 s1 重新排列,让它每位都不小于 s2 对应位的字母,这里按字母表算大小,a 最小、z 最大,两位相等也算打破。题目只要求两个方向里成立一个:s1 能打破 s2,或者 s2 能打破 s1,任一为真就返回 true,两个都做不到才返回 false。
逐位比较其实不必枚举排列
想穷举的话,得把 s1 的所有排法都摆出来一个个和 s2 比,长度到 n 就有 n 的阶乘种排法,稍长一点根本算不完。另一个想当然的错是只验一个方向:s1 打不破 s2 就收手报 false,可题目允许反过来让 s2 打破 s1,漏掉这层会把本该 true 的情形判死。
排好序小对小,能打破就一定看得出来
用不着把排法试遍。把两个串各自从小到大排好,再挨位比一次就够。排序给的是最省力的对齐:s1 最小的字母去顶 s2 最小的、次小对次小,每一位的差距都摊得最匀。这靠贪心撑着,贪心就是每步把能定死的顺序先定死、不回头。想更严实可以用交换论证:假设有个更绕的排列能打破,把它调成小对小、大对大,只会更容易满足。排好序这版都打不破,别的排法更没戏。
两串各排一次序,两个方向轮着验
落到步骤上很干脆。先给 s1、s2 各排一次序,再试方向一:看 s1 是不是每位都不小于 s2 的对应位,只要撞见某位 s1 的字母比 s2 小,方向一当场作废。方向一没成也别急着报 false,掉头试方向二:反过来看 s2 是不是每位都不小于 s1。有一个方向全程扛住就是 true,两个都半路断掉才是 false。这里判的是不小于,某位上两个字母相等这位照样算过,若写成严格的 >,「aaa」打破「aaa」这种全等情形会被误杀成 false。
「abc」对「xya」,true 怎么逐位走出来
拿题面第一组「abc」对「xya」走一遍。先排序,「abc」本就是升序不动,「xya」理成「axy」。试方向一,要求 s1 每位都不小于 s2:第 0 位 a 对 a,相等算过;第 1 位 b 对 x,b 在字母表里排在 x 前面比它小,方向一断在这。掉头试方向二,要求 s2 每位都不小于 s1:第 0 位 a 对 a 过,第 1 位 x 对 b、x 比 b 大过,第 2 位 y 对 c、y 比 c 大过,三位全扛住。方向二成立,结果 true,和题面一致。
O(n log n) 的账,和三处必判的边界
排序 O(n log n) 是大头:给两个串各排一次是 O(n log n),之后逐位比是 O(n),合起来仍是 O(n log n),n 是字符串长度;额外空间 O(n),花在排序的字符副本上。收尾时几处最容易栽。只判一个方向就下结论会漏,像「abe」对「acd」,排序后方向一栽在 b 对 c、方向二栽在 d 对 e,两头都断,得都判完才敢返回 false。不排序直接拿原串比也不行,题目要的是存在某个排列,不是原顺序。还有相等这一层,「aaa」对「aaa」每位都相等、按不小于算就是打破,返回 true,这正是不能把条件写成严格大于的原因。
▶ 动画逐步走查(共 22 步)——想跟着动画一帧帧对照就展开
- 3记牢一句:两个串各自排好序,逐位比;s1 全程 ≥ s2,或者 s2 全程 ≥ s1,任一成立就能打破。下面每帧都在套这句。
- 4上排是 s1 = 「dbace」,右边侧栏是 s2 = 「fcbed」,两个串长度都是 5。直接看原始顺序没法逐位比,所以第一步,把它们各自从小到大排好序。
- 5先排 s1。把 「dbace」 从小到大理一遍,得到 「abcde」,现在上排就是排好的样子。接着排 s2。
- 6s2 也排一遍,「fcbed」 理顺成 「bcdef」,放进右边侧栏。现在上排 s1 是 「abcde」,侧栏 s2 是 「bcdef」,位置一一对齐,可以逐位比了。
- 7两行都排好了。接下来要试两个方向:方向一,看 s1 是不是每一位都 ≥ s2;方向二,看 s2 是不是每一位都 ≥ s1。任意一个方向全程成立,就能打破。先试方向一。
- 8方向一的要求很明确:排好序后,s1 的每一位都要 ≥ s2 的对应位。只要有一位 s1 比 s2 小,方向一当场就不成立。从第 0 位开始。
- 9第 0 位,上排 s1 是 「a」,侧栏 s2 是 「b」。方向一要的是 s1 ≥ s2,也就是 「a」 要不小于 「b」。比一下。
- 10「a」 在字母表里排在 「b」 前面,也就是 「a」 < 「b」。s1 的第 0 位反而比 s2 小,方向一的「每位都 ≥」当场被打破,这一格标红。方向一不成立。
- 11方向一断了,可别急着说答案是 false。题目是「两个方向有一个成立就行」,s1 打不破 s2,还要反过来看 s2 能不能打破 s1。清空颜色,试方向二。
- 12方向二的要求对称过来:排好序后,s2 的每一位都要 ≥ s1 的对应位。同样从第 0 位开始,一位一位往后比,中途有一位栽了就算方向二也失败。
- 13看第 0 位。侧栏 s2 是 「b」,上排 s1 是 「a」。方向二要 s2 ≥ s1,也就是 「b」 要不小于 「a」。
- 14「b」 在字母表里不比 「a」 靠前,也就是 「b」 ≥ 「a」,这一位满足方向二的要求,标绿。已经连过 1 位,接着看下一位。
- 15看第 1 位。侧栏 s2 是 「c」,上排 s1 是 「b」。方向二要 s2 ≥ s1,也就是 「c」 要不小于 「b」。
- 16「c」 在字母表里不比 「b」 靠前,也就是 「c」 ≥ 「b」,这一位满足方向二的要求,标绿。已经连过 2 位,接着看下一位。
- 17看第 2 位。侧栏 s2 是 「d」,上排 s1 是 「c」。方向二要 s2 ≥ s1,也就是 「d」 要不小于 「c」。
- 18「d」 在字母表里不比 「c」 靠前,也就是 「d」 ≥ 「c」,这一位满足方向二的要求,标绿。已经连过 3 位,接着看下一位。
- 19看第 3 位。侧栏 s2 是 「e」,上排 s1 是 「d」。方向二要 s2 ≥ s1,也就是 「e」 要不小于 「d」。
- 20「e」 在字母表里不比 「d」 靠前,也就是 「e」 ≥ 「d」,这一位满足方向二的要求,标绿。已经连过 4 位,接着看下一位。
- 21看第 4 位。侧栏 s2 是 「f」,上排 s1 是 「e」。方向二要 s2 ≥ s1,也就是 「f」 要不小于 「e」。
- 22「f」 在字母表里不比 「e」 靠前,也就是 「f」 ≥ 「e」,这一位满足方向二的要求,标绿。已经连过 5 位。
- 23五位全部走完,s2 的每一位都 ≥ s1 的对应位,一格都没栽。方向二成立,s2 可以打破 s1,上排整排标绿。
- 24回看全程:方向一在第 0 位就断了,但方向二每一位都成立。题目只要两个方向有一个能打破就行,所以答案是 true,跟开头说的对上了。这就是排序后逐位比、两个方向二选一的全过程。
⚠️ 容易写错的地方
✗ 错:只检查一个方向就下结论:发现 s1 打不破 s2 就直接返回 false
✓ 对:两个方向都要试,s1 打破 s2「或」s2 打破 s1,任一成立即 true
题目允许两个方向。本节方向一在第 0 位就断,要是就此返回 false 就错了,因为方向二其实全程成立。必须两个方向都判完、都失败,才能返回 false
✗ 错:不排序,直接拿原串逐位比
✓ 对:必须先各自从小到大排序,再逐位比
题目要的是「存在某个排列」能打破,不是按原顺序比。排序后让小的对小的,是对判定方最有利的对齐;这种贪心配对都做不到,任何排列都做不到
✗ 错:把打破条件写成严格大于,要求 x[i] > y[i]
✓ 对:是 ≥,x[i] 等于 y[i] 也算打破
题目写的是 x[i] ≥ y[i]。两位字母相等时这一位仍然满足打破要求。写成严格大于会把 「aaa」 打破 「aaa」 这种全相等的情形误判成 false
完整代码(Python / C++ / Java)
Python
from __future__ import annotations
from typing import *
from collections import *
from functools import *
from itertools import *
from math import *
from heapq import *
from bisect import *
class Solution:
def checkIfCanBreak(self, s1: str, s2: str) -> bool:
cs1 = sorted(s1)
cs2 = sorted(s2)
return all(a >= b for a, b in zip(cs1, cs2)) or all(
a <= b for a, b in zip(cs1, cs2)
)C++
#include <algorithm>
#include <array>
#include <cctype>
#include <climits>
#include <cmath>
#include <deque>
#include <functional>
#include <iostream>
#include <limits>
#include <map>
#include <numeric>
#include <queue>
#include <set>
#include <sstream>
#include <stack>
#include <string>
#include <tuple>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>
using namespace std;
class Solution {
public:
bool checkIfCanBreak(string s1, string s2) {
sort(s1.begin(), s1.end());
sort(s2.begin(), s2.end());
return check(s1, s2) || check(s2, s1);
}
bool check(string& s1, string& s2) {
for (int i = 0; i < s1.size(); ++i) {
if (s1[i] < s2[i]) {
return false;
}
}
return true;
}
};Java
import java.util.*;
class Solution {
public boolean checkIfCanBreak(String s1, String s2) {
char[] cs1 = s1.toCharArray();
char[] cs2 = s2.toCharArray();
Arrays.sort(cs1);
Arrays.sort(cs2);
return check(cs1, cs2) || check(cs2, cs1);
}
private boolean check(char[] cs1, char[] cs2) {
for (int i = 0; i < cs1.length; ++i) {
if (cs1[i] < cs2[i]) {
return false;
}
}
return true;
}
}复杂度
时间
O(n log n)
n 是字符串长度。主要开销是给两个串各排一次序,各 O(n log n);之后逐位比最多扫一两遍,是 O(n)。合起来由排序主导,O(n log n)。字符集只有 26 个小写字母,想更快可以改用计数排序把排序降到 O(n),整体就成 O(n)
空间
O(n)
按峰值算:三种语言都为排序生成了串的副本。Python 的 sorted 返回两个长度 n 的字符列表;Java 用 toCharArray 得到两个 char 数组;C++ 的 check 按值或对已排序串操作,排序本身也在长度 n 的串上进行。这些 O(n) 的副本是空间主项,排序内部的递归栈 O(log n) 被它盖过
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 检查一个字符串是否可以打破另一个字符串 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
为什么排序后逐位比就一定对,不会漏掉某个更好的排列?+
因为排序后是最优配对。把两个串都从小到大排好,让 s1 最小的对 s2 最小的、次小对次小,这种对齐对「让 s1 每位都不小于 s2」最有利。用交换论证能说通:假如有个更花哨的排列能打破,把它一步步调成小对小、大对大,只会更容易满足。所以排好序这一版都做不到,别的排法更做不到,判定只看排序后逐位的结果就够。
为什么必须试两个方向?只看 s1 能不能打破 s2 不够吗?+
不够。题目说的是 s1 的某个排列打破 s2,或者 s2 的某个排列打破 s1,两件事并不对称。s1 打不破 s2,完全可能是 s2 反过来能打破 s1,前面「abc」对「xya」就是这样,方向一在第 1 位断,方向二却全程扛住。所以两个方向都得判,只有都失败才返回 false。
这道题的复杂度还能再压吗?+
能。排序是 O(n log n) 的瓶颈,但这题字母只有 26 个小写字符,可以用计数排序:开一个长度 26 的桶数一遍每个字母出现几次,再按字母顺序展开,排序就降到 O(n)。两个串都这么处理,再逐位比,整体是 O(n) 时间、额外只用一个常数大小的计数空间。面试里能顺着说到这一步是加分项。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄把 检查一个字符串是否可以打破另一个字符串 一步步讲透(全站已上线 905 份讲透,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。