最大交换 图解题解
这道题到底在问什么
- 输入
- num = 2736
- 输出
- 7236 (交换 2 和 7)
- 输入
- num = 9973
- 输出
- 9973 (已是最大,不换)
- 输入
- num = 98368
- 输出
- 98863 (把第 3 位的 3 换到后面的 8)
最优解:为什么这么做
一句话答案:LeetCode 670 最大交换:最多对调一次两个数位、让整数尽量大。先记下每个数字最后出现的下标,再从最高位往右找第一个能被后面更大数字顶替的位、和最靠右的那个最大数字对调一次,时间 O(len)、空间 O(1)。
最多对调一次两个数位,能把整数抬到多大
给一个非负整数 num,允许挑其中两个数位对调一次,也允许一次都不换,要让最后得到的整数尽量大。题面给的 num = 2736,把 2 和 7 对调得到 7236;num = 9973 本身就是这几个数位能排出的最大值,一次都不用换、原样返回。数位最多 9 个,num 不超过一亿。
把每两位都试着对调一遍,哪里不划算
笨办法是枚举所有对调:任取两个位置换一下、拼出新整数,一路比下来留最大的那个。数位有 n 个就有大约 n×n/2 种对调,每种都得拼成整数比一次,是 O(len²)。位数最多 9,这么跑其实也能过,但它把每种对调都当成一样重要,漏掉一个直白的事实——越靠左的数位,一次变动对整数的影响越大,不必平等地全试一遍。
从最高位起,找第一个能被后面顶上来的数位
既然高位的话语权最大,就从最高位开始逐位看:只要当前这一位的后面存在严格更大、也就是 > 当前数位的数字,就把那个更大的数字换上来,高位一变大,整个数立刻上一个台阶。贪心就落在高位优先上:一次对调的名额留给话语权最大的高位,从最高位数起第一个能被后面更大数字顶替的位就是下手处,把它右边出现过的最大数字换上来,整数一步抬到最高。
如果后面有好几个同样大的目标数字,要换最靠右的那一个:它腾出来的高位由更靠后的数位补上,剩下的整数比换靠前的同值数字更大。
用一张 last 表,把后面每个数字的最右位置查出来
先从左到右扫一遍,为每个数字记下它最后一次出现的下标,存进一张长度 10 的 last 表;同一个数字出现多次时,后写的下标盖掉先写的,留下的正是它最靠右那次的位置。再从最高位往右逐位处理:对当前数位,从 9 往下试每个更大的候选数字,一旦某个候选在 last 表里的下标落在当前位右边,就把它换上来、直接返回这个整数。第一次成功对调就收工——最多只能换一次,而从高位第一次命中的对调,已是所有一次对调里收益最大的。
2736 与 9973,两趟各换没换
拿 2736 走一遍。先建 last 表:数字 2 在下标 0,7 在 1,3 在 2,6 在 3。再从最高位看起,最高位是 2,从 9 往下找比 2 大又落在它右边的数字——7 出现在下标 1、正好在右边,且 7 > 2,是能换到的最大数字,把最高位的 2 和这个 7 对调,得到 7236,立即返回。
9973 则相反:建好 last 表后从最高位逐位查,9、9、7、3 每一位往后都找不到更大的数字,一次都没换、原样交出 9973。
最容易把答案换坏的三处
找到更大的目标数字就换它第一次出现的位置,是最常见的写坏点:同样大的数字要换最靠右那个,换靠前的会把一个较大的位留在偏低处、整数反而更小。从低位往高位找也不行,低位换大对整数的抬升远不如高位,方向一反就拿不到最优。还有换完不肯停、继续往后找更优解:本题只能换一次,从高位第一次命中的对调已经封顶,接着换只会把到手的最大值改坏。整套流程扫两遍数位、每位最多看 10 个候选,位数不超过 9,全是常数级,时间 O(len)、空间 O(1),last 表固定十格、与位数无关。
▶ 动画逐步走查(共 54 步)——想跟着动画一帧帧对照就展开
- 3记住「高位换大、目标取最右」这两条贪心,下面每帧都在套它。
- 4第一步:预处理。建一张 last 表,记每个数字最后一次出现在哪个下标。先全置 -1。
- 5扫到下标 0 的数字 7,把 last[7] 更新为 0(后出现的会覆盖先前的,最终留下的就是最后下标)。
- 6扫到下标 1 的数字 6,把 last[6] 更新为 1(后出现的会覆盖先前的,最终留下的就是最后下标)。
- 7扫到下标 2 的数字 6,把 last[6] 更新为 2(后出现的会覆盖先前的,最终留下的就是最后下标)。
- 8扫到下标 3 的数字 5,把 last[5] 更新为 3(后出现的会覆盖先前的,最终留下的就是最后下标)。
- 9扫到下标 4 的数字 5,把 last[5] 更新为 4(后出现的会覆盖先前的,最终留下的就是最后下标)。
- 10扫到下标 5 的数字 4,把 last[4] 更新为 5(后出现的会覆盖先前的,最终留下的就是最后下标)。
- 11扫到下标 6 的数字 1,把 last[1] 更新为 6(后出现的会覆盖先前的,最终留下的就是最后下标)。
- 12扫到下标 7 的数字 2,把 last[2] 更新为 7(后出现的会覆盖先前的,最终留下的就是最后下标)。
- 13last 表完工:数字 2 最后在下标 7,1 在 6,4 在 5,5 在 4(出现两次,记的是最右那次的下标 4),这正是后面贪心要的「最右目标」。
- 14轮到下标 0 的数字 7。从 9 往下逐个看:谁比 7 大、又出现在 0 的右边?
- 15数字 9 整串里没出现,没法用,看下一个更小的候选。
- 16数字 8 整串里没出现,没法用,看下一个更小的候选。
- 17下标 0 的 7 右边找不到更大的数字,说明它已在最优位置,跳到下一位继续。
- 18轮到下标 1 的数字 6。从 9 往下逐个看:谁比 6 大、又出现在 1 的右边?
- 19数字 9 整串里没出现,没法用,看下一个更小的候选。
- 20数字 8 整串里没出现,没法用,看下一个更小的候选。
- 21数字 7 虽然更大,但它最后出现在下标 0,落在 1 的左边或同位,换过去高位反而变小,放弃。
- 22下标 1 的 6 右边找不到更大的数字,说明它已在最优位置,跳到下一位继续。
- 23轮到下标 2 的数字 6。从 9 往下逐个看:谁比 6 大、又出现在 2 的右边?
- 24数字 9 整串里没出现,没法用,看下一个更小的候选。
- 25数字 8 整串里没出现,没法用,看下一个更小的候选。
- 26数字 7 虽然更大,但它最后出现在下标 0,落在 2 的左边或同位,换过去高位反而变小,放弃。
- 27下标 2 的 6 右边找不到更大的数字,说明它已在最优位置,跳到下一位继续。
- 28轮到下标 3 的数字 5。从 9 往下逐个看:谁比 5 大、又出现在 3 的右边?
- 29数字 9 整串里没出现,没法用,看下一个更小的候选。
- 30数字 8 整串里没出现,没法用,看下一个更小的候选。
- 31数字 7 虽然更大,但它最后出现在下标 0,落在 3 的左边或同位,换过去高位反而变小,放弃。
- 32数字 6 虽然更大,但它最后出现在下标 2,落在 3 的左边或同位,换过去高位反而变小,放弃。
- 33下标 3 的 5 右边找不到更大的数字,说明它已在最优位置,跳到下一位继续。
- 34轮到下标 4 的数字 5。从 9 往下逐个看:谁比 5 大、又出现在 4 的右边?
- 35数字 9 整串里没出现,没法用,看下一个更小的候选。
- 36数字 8 整串里没出现,没法用,看下一个更小的候选。
- 37数字 7 虽然更大,但它最后出现在下标 0,落在 4 的左边或同位,换过去高位反而变小,放弃。
- 38数字 6 虽然更大,但它最后出现在下标 2,落在 4 的左边或同位,换过去高位反而变小,放弃。
- 39下标 4 的 5 右边找不到更大的数字,说明它已在最优位置,跳到下一位继续。
- 40轮到下标 5 的数字 4。从 9 往下逐个看:谁比 4 大、又出现在 5 的右边?
- 41数字 9 整串里没出现,没法用,看下一个更小的候选。
- 42数字 8 整串里没出现,没法用,看下一个更小的候选。
- 43数字 7 虽然更大,但它最后出现在下标 0,落在 5 的左边或同位,换过去高位反而变小,放弃。
- 44数字 6 虽然更大,但它最后出现在下标 2,落在 5 的左边或同位,换过去高位反而变小,放弃。
- 45数字 5 虽然更大,但它最后出现在下标 4,落在 5 的左边或同位,换过去高位反而变小,放弃。
- 46下标 5 的 4 右边找不到更大的数字,说明它已在最优位置,跳到下一位继续。
- 47轮到下标 6 的数字 1。从 9 往下逐个看:谁比 1 大、又出现在 6 的右边?
- 48数字 9 整串里没出现,没法用,看下一个更小的候选。
- 49数字 8 整串里没出现,没法用,看下一个更小的候选。
- 50数字 7 虽然更大,但它最后出现在下标 0,落在 6 的左边或同位,换过去高位反而变小,放弃。
- 51数字 6 虽然更大,但它最后出现在下标 2,落在 6 的左边或同位,换过去高位反而变小,放弃。
- 52数字 5 虽然更大,但它最后出现在下标 4,落在 6 的左边或同位,换过去高位反而变小,放弃。
- 53数字 4 虽然更大,但它最后出现在下标 5,落在 6 的左边或同位,换过去高位反而变小,放弃。
- 54数字 3 整串里没出现,没法用,看下一个更小的候选。
- 55找到了!数字 2 最后出现在下标 7,在 6 的右边,且 2 > 1。这是高位能换到的最大数字、又取了最靠右的它,立即交换。
- 56交换后高位的 1 变成了 2,整数从 76655412 变成 76655421。第一次成功交换就直接返回,绝不再换第二次。
⚠️ 容易写错的地方
✗ 错:找到更大数字就换第一个出现的位置
✓ 对:要换「最后出现」的位置
同样大的目标,换最右那个让出的位置最低、整数最大,所以要用 last 记最右下标
✗ 错:从低位开始换
✓ 对:从高位开始,越靠左换大收益越高
高位每变大一档,对整数的影响远大于低位
✗ 错:交换后继续往后找更优解
✓ 对:第一次成功交换后立即返回
本题最多只能换一次;从高位第一次命中的交换已是所有一次交换方案里的最优,所以必须立即返回
完整代码(Python / C++ / Java)
Python
class Solution:
def maximumSwap(self, num: int) -> int:
arr = list(str(num))
last = {int(ch): i for i, ch in enumerate(arr)}
for i, ch in enumerate(arr):
d = int(ch)
for bigger in range(9, d, -1):
if last.get(bigger, -1) > i:
j = last[bigger]
arr[i], arr[j] = arr[j], arr[i]
return int(''.join(arr))
return numC++
#include <string>
#include <vector>
using namespace std;
class Solution {
public:
int maximumSwap(int num) {
string s = to_string(num);
vector<int> last(10, -1);
for (int i = 0; i < (int)s.size(); ++i) last[s[i]-'0'] = i;
for (int i = 0; i < (int)s.size(); ++i) {
for (int d = 9; d > s[i]-'0'; --d) {
if (last[d] > i) {
swap(s[i], s[last[d]]);
return stoi(s);
}
}
}
return num;
}
};Java
import java.util.*;
class Solution {
public int maximumSwap(int num) {
char[] s = String.valueOf(num).toCharArray();
int[] last = new int[10];
Arrays.fill(last, -1);
for (int i = 0; i < s.length; i++) last[s[i] - '0'] = i;
for (int i = 0; i < s.length; i++) {
for (int d = 9; d > s[i] - '0'; d--) {
if (last[d] > i) {
char t = s[i]; s[i] = s[last[d]]; s[last[d]] = t;
return Integer.parseInt(new String(s));
}
}
}
return num;
}
}复杂度
时间
O(d)
d 是位数(≤9):扫一遍建表,再扫一遍每位最多看 9 个候选,都是常数级
空间
O(1)
last 表固定 10 个槽,与位数无关
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 最大交换 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
不预处理 last 表,直接对每一位往右暴力找最大可以吗?复杂度怎么样?+
可以。对每一位都往它右边扫一遍、找出最大且最靠右的数字,是 O(len²)。位数最多 9,实战里和 O(len) 没有可感的差别;但先用一张长度 10 的 last 表记下每个数字最后出现的下标,就把它压到 O(len),思路也更顺:一遍记最右位置,再一遍从高位贪心找更大的换上来。
如果允许对调任意多次、不只一次,答案会变成什么?+
那题目就退化成把所有数位从大到小排序,直接得到这些数位能拼出的最大整数,不再需要挑位置。正因为本题卡死了「最多一次」,才要这套从高位起、目标取最右的贪心:一次对调用在话语权最大的高位、又尽量少动到低位,收益才是最大的。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄把 最大交换 一步步讲透(全站已上线 905 份讲透,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
把这道题真正拿下
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。