检查一个字符串是否可以打破另一个字符串 图解题解
这道题到底在问什么
- 输入
- s1="abc", s2="xya"
- 输出
- true
- 输入
- s1="abe", s2="acd"
- 输出
- false
- 输入
- s1="leetcodee", s2="interview"
- 输出
- true
最优解:一步一步想明白
- 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,两者有一个成立就算 true。这两件事并不对称:s1 打不破 s2,完全可能是 s2 反过来能打破 s1。本节就是这种,方向一第 0 位就断,但方向二全程成立。所以两个方向都要判,只有都失败才返回 false。
这道题复杂度还能再压吗?+
能。排序是 O(n log n) 的瓶颈,但这题字符只有 26 个小写字母,可以用计数排序:开一个长度 26 的桶数一遍每个字母出现多少次,再按字母顺序展开,排序就降到 O(n)。两个串都这么处理,再逐位比,整体就是 O(n) 时间、O(1) 额外计数空间。面试里能说到这一步是加分项。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄声线把 检查一个字符串是否可以打破另一个字符串 一步步讲透(全站 0 节语音精讲,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。