【小白笔记】最大交换 (Maximum Swap)问题

贪心算法 (Greedy Algorithm):这是一种在每一步选择中都采取在当前状态下最好或最优(即最有利)的选择,从而希望得到全局最优解的算法思想。

要用最多一次交换 得到最大值,我们的目标非常明确:用右侧最大的数字,去替换左侧第一个比它小的数字。

核心思路:贪心 + 定位

我们希望让高位 (最左边)的数字尽可能大。所以,我们应该从左向右遍历,找到第一个可以被交换的低位数字。

步骤一:找到第一个需要交换的位置 (左侧目标 iii)

从左到右扫描数字。如果当前位 iii 的数字小于它右侧的任何 数字,那么这一位就是可以被交换的位置。我们找到的第一个 这样的位置 iii 就是我们的左侧目标

如果整个数字都是降序或相等(例如 987 或 999),则不需要交换。

步骤二:找到右侧最大的替换数字 (右侧目标 jjj)

确定了左侧目标 iii 后,我们需要在 iii 的右侧(即 [i+1,n−1][i+1, n-1][i+1,n−1] 范围内)找到:

  1. 值最大的数字。
  2. 如果有多个最大值,我们选择最靠左 的那个(即索引最大的 jjj),这样可以保证交换后的数字排列是最大的。

注意: 我们要找的是最靠右 的最大的替换数字 jjj,因为:

  • 如果数字是 1993,i=0i=0i=0。右侧有两个 9。如果交换 1最左边 的 9 得到 9193
  • 如果交换 1最右边 的 9 得到 9913
    显然,交换最靠右的那个最大数字得到的结果更大。
步骤三:执行交换

将位置 iii 和位置 jjj 上的数字进行交换,得到最终的最大值。

Python 代码实现

python 复制代码
class Solution:
    def maximumSwap(self, num: int) -> int:
        # 将整数转换为字符列表
        s = list(str(num))
        n = len(s)
        
        # --- 步骤 1: 预处理 - 从右向左扫描,构建右侧最佳索引表 ---
        
        # right_best_idx
        # right_best_idx[i] 存储的是:在 s[i:] 区间内,最大数字所在的"最靠右"索引 j (j >= i)
        # 换句话说,它是给 s[i] 准备的"最佳替换目标"的索引。
        right_best_idx = [0] * n
        
        # current_max_idx
        # current_max_idx 实时追踪当前已扫描区域内最大值的索引
        current_max_idx = n - 1
        
        # 从右向左遍历 (i 从 n-1 递减到 0)
        for i in range(n - 1, -1, -1):
            
            # 如果 s[i] 大于或等于当前最大值 s[current_max_idx],
            # 则更新 current_max_idx 为 i,确保追踪的是最靠左的那个最大数字的索引
            if s[i] >= s[current_max_idx]:
                current_max_idx = i
            
            # 记录当前位置 i 的"最佳替换目标" (即当前区域的最大数字的位置)
            right_best_idx[i] = current_max_idx

        # --- 步骤 2 & 3: 寻找交换对 (i, j) 并执行交换 ---
        
        # 从左向右遍历 (i 是左侧的目标位置)
        for i in range(n):
            # j 是通过查表获得的右侧最佳替换位置
            j = right_best_idx[i]
            
            # 如果 s[i] 小于其右侧最大数字 s[j],则找到交换对
            # s[i] 是第一个需要优化的低位,s[j] 是右侧能找到的最好增益
            if s[i] < s[j]:
                # 执行交换
                s[i], s[j] = s[j], s[i]
                
                # 交换一次后立即返回
                return int("".join(s))
        
        # 如果没有找到交换机会,返回原数字
        return num

它涉及到两个独立的循环两个不同的目标

为什么第一步(预处理)要在 SSS 的右侧操作,而不是直接在最左侧(S[0]S[0]S[0])操作?

核心区分:第一步(预处理)不是交换!

请务必区分代码中的两个 O(N)O(N)O(N) 循环所承担的不同职责:

循环/步骤 遍历方向 核心目标 为什么不直接在左侧 S[0]S[0]S[0] 操作?
第一步 (预处理) 从右向左 (i=n−1→0i = n-1 \to 0i=n−1→0) 目的:构建表格 (right_best_idx)。不执行交换。 必须为所有可能的 iii 准备答案。 iii 可能是 0,1,2,...0, 1, 2, \dots0,1,2,... 中的任意一个,所以必须为所有位置计算右侧的最佳替换位。
第二步 (查找交换) 从左向右 (i=0→n−1i = 0 \to n-1i=0→n−1) 目的:执行贪心决策 ,找到第一个 需要优化的 iii。 执行交换。 这是唯一需要优先考虑 i=0i=0i=0 的地方,因为它是最高位。

你提到的这个第一个 for 循环是整个算法的核心预处理步骤,它的功能是:

为每一位数字,找到它右边所有数字中"最大数字"的索引位置(如果有多个相同最大数字,则取最靠右的那个)。


🔍 代码片段回顾

python 复制代码
right_best_idx = [0] * n
current_max_idx = n - 1

for i in range(n - 1, -1, -1):
    if s[i] >= s[current_max_idx]:
        current_max_idx = i
    right_best_idx[i] = current_max_idx

📘 直观理解

举个例子,比如数字:

复制代码
num = 2736
s = ['2', '7', '3', '6']

我们要知道:

  • 每个位置 右边 哪个数字是"最大且最靠右"的。
从右往左扫描过程:
i s[i] 当前最大数字索引 current_max_idx right_best_idx[i]
3 6 3 3
2 3 3 3
1 7 1 1
0 2 1 1

结果:

python 复制代码
right_best_idx = [1, 1, 3, 3]

含义:

  • 对于位置 0(数字2),右边的最大数在索引 1(数字7)
  • 对于位置 1(数字7),右边的最大数在索引 1(数字7本身)
  • 对于位置 2(数字3),右边的最大数在索引 3(数字6)
  • 对于位置 3(数字6),右边的最大数在索引 3(数字6本身)

⚙️ 这个循环的目的是什么?

它在为每一位数字准备信息

如果我要"最大化"这个数字,通过一次交换,我应该和谁换?

也就是说,right_best_idx[i] 告诉我们:

i 右边所有数字里,哪个是最大的(且最靠右)数字。


💡 为什么要从右往左扫描?

因为我们需要在每一步都知道 右边区域中最大的数字

而从右向左扫描时,可以"实时更新"右侧最大数字的索引 current_max_idx

这样时间复杂度是 O(n),非常高效。


🧠 逻辑总结一句话:

第一个 for 循环的功能是------为每个位置 i,找出它右边(包括自己)中最大的数字所在的索引。这样在第二个 for 循环中,我们只需 O(1) 时间就能知道"右边最有潜力交换的那个数字",从而高效地完成"最大交换"操作。


第一个 for 循环的预处理作用------为每个位置找出右边"最优交换目标"。

现在我们来看 第二个 for 循环 是如何利用这个结果来实现"最大交换"的。


🧩 第二个 for 循环的核心逻辑

代码如下:

python 复制代码
for i in range(n):
    j = right_best_idx[i]
    if s[i] < s[j]:
        s[i], s[j] = s[j], s[i]
        return int("".join(s))
return num

🔍 它在干什么?

它从左到右 依次检查每一位数字 s[i]

  1. 看看在右边有没有一个比它大的数字(通过 right_best_idx[i] 直接查到);
  2. 如果有,立刻交换;
  3. 因为我们是从左到右找的,所以第一次能交换的地方 就是能让整个数增大最多的位置;
  4. 一旦交换完,就返回结果。

🔧 结合第一个循环的结果理解

我们前面得到了:

python 复制代码
right_best_idx[i] = 在 s[i:] 右侧区间中,最大数字的最靠右索引

这意味着:

在第 i 位上,如果 s[i] < s[right_best_idx[i]],那就存在一个比它大的数在后面。

而我们只允许交换一次,所以我们就应该:

  • 选择 最左边 的那个可以变大的位置 i
  • 最右边 的那个最大数字位置 j 交换

这样能保证结果最大。


📘 举个完整例子:num = 2736

第一步:预处理结果(来自第一个 for 循环)
python 复制代码
s = ['2', '7', '3', '6']
right_best_idx = [1, 1, 3, 3]
第二步:逐位检查并决定交换
i s[i] j = right_best_idx[i] s[j] 比较结果 操作
0 2 1 7 2 < 7 ✅ 交换 → ['7','2','3','6'],返回
1 7 1 7 7 == 7 ❌ -
2 3 3 6 3 < 6 ✅(但不会执行,因为上面已返回) -

最终结果:

复制代码
7236

⚙️ 为什么"从左往右"找?

因为我们想要最大化最终结果

数的左边位权更高(比如千位 > 百位 > 十位),

所以:

  • 第一个能换大的位置,就是提升最大的地方;
  • 一旦找到,就不用再往后看了。

🧠 总结一句话:

第二个 for 循环利用第一个循环构建的"右侧最大值索引表",快速判断每个位置是否能通过一次交换变大;一旦找到第一个可以增大的位置,就与最右的最大数字交换,从而保证结果最大化。


画一个可视化的流程图,让你直观理解这两个循环是如何协同工作的。


示例:输入 num = 2736

复制代码
         ┌────────────────────────────────────┐
         │ Step 1:从右向左扫描(建立右侧最大值表) │
         └────────────────────────────────────┘

索引:     0     1     2     3
数字:     2     7     3     6

从右往左扫描构建 right_best_idx

复制代码
i=3: 当前最大值 = 6 → right_best_idx[3] = 3
i=2: s[2]=3 < 6 → right_best_idx[2] = 3
i=1: s[1]=7 >= 6 → 更新最大值索引=1 → right_best_idx[1] = 1
i=0: s[0]=2 < 7 → right_best_idx[0] = 1

结果:

复制代码
right_best_idx = [1, 1, 3, 3]

图形表示👇:

复制代码
索引:     0     1     2     3
数字:     2     7     3     6
指向:     ↓     ↓     ↓     ↓
最大值:   7     7     6     6

(每个箭头指向"右侧区域中最大且最靠右"的数字)


复制代码
         ┌─────────────────────────────────────────────┐
         │ Step 2:从左向右扫描(寻找第一个可交换位置) │
         └─────────────────────────────────────────────┘

扫描每个位置:

i s[i] right_best_idx[i] s[j] 判断 动作
0 2 1 7 2 < 7 ✅ 交换(0,1) → 7236,结束
1 7 1 7 - -
2 3 3 6 3 < 6(不会执行,因为前面已返回) -

执行过程可视化👇:

复制代码
初始:  2   7   3   6
         ↑   ↑
        i=0  j=1

→ 交换 → 7   2   3   6

输出:7236

🧠 全流程逻辑图总结

复制代码
            ┌────────────────────┐
            │ 输入 num → 转字符列表 │
            └───────┬────────────┘
                    │
                    ▼
          ┌────────────────────┐
          │ 从右向左构建 right_best_idx │
          └───────┬────────────┘
                    │
                    ▼
        ┌────────────────────────────┐
        │ 从左向右扫描 i:           │
        │   j = right_best_idx[i]    │
        │   若 s[i] < s[j] → 交换并返回 │
        └────────────────────────────┘
                    │
                    ▼
        ┌────────────────────────────┐
        │ 若未找到可交换位置 → 返回原数 │
        └────────────────────────────┘

这句代码 return int("".join(s)) 是 Python 中将字符列表 (在算法中我们用来表示数字)转换回最终整数结果的经典且高效的写法。

我将用中文为您拆解它的含义和功能。

1. 代码的含义和功能拆解

这句代码可以分解为三个连续的操作,是从内到外依次执行的:

操作 代码片段 英文词源 & 解释 功能
第一步 (内层) "".join(s) join (连接) 将列表 s 中所有的字符元素 ,使用最前面的空字符串 "" 连接起来,形成一个完整的字符串
第二步 (中层) int(...) int (Integer, 整数) 将第一步得到的字符串 (如 "7236")整体转换为一个整数
第三步 (外层) return ... return (返回) 将第二步得到的整数值作为函数或方法的最终结果返回。

2. 代码的功能和在 LCA 中的作用

A. 功能:高效的类型转换

这行代码实现了从 List of Characters →\to→ String →\to→ Integer 的两次类型转换。

转换前 示例 转换后
字符列表 s ['7', '2', '3', '6'] →""join(s)\xrightarrow{\text{""join(s)}}""join(s)
字符串 "7236" →int(...)\xrightarrow{\text{int(...)}}int(...) 整数 7236
B. 在"最大交换"算法中的作用

在"最大交换"的贪心算法中,我们为了方便地执行交换操作 ,不得不将输入的整数 num 转换为字符列表 s

python 复制代码
s = list(str(num))  # 例如:2736 -> ['2', '7', '3', '6']
# ... 执行交换操作 s[i], s[j] = s[j], s[i] ...

一旦我们找到了最优的交换对并执行了交换(此时 s 列表已经被修改为最大值形态,如 ['7', '2', '3', '6']),我们就必须将这个最终结果变回题目的要求格式:一个非负整数。

因此,return int("".join(s)) 就是将算法中处理好的字符列表 变回整数并输出的最终步骤。

相关推荐
程序员爱钓鱼5 小时前
Python编程实战 · 基础入门篇 | Python的缩进与代码块
后端·python
pr_note6 小时前
python|if判断语法对比
python
你要飞8 小时前
Hexo + Butterfly 博客添加 Live2D 看板娘指南
笔记
apocelipes8 小时前
golang unique包和字符串内部化
java·python·性能优化·golang
Geoking.9 小时前
NumPy zeros() 函数详解
python·numpy
Full Stack Developme9 小时前
java.text 包详解
java·开发语言·python
丁浩66610 小时前
Python机器学习---2.算法:逻辑回归
python·算法·机器学习
B站_计算机毕业设计之家10 小时前
计算机毕业设计:Python农业数据可视化分析系统 气象数据 农业生产 粮食数据 播种数据 爬虫 Django框架 天气数据 降水量(源码+文档)✅
大数据·爬虫·python·机器学习·信息可视化·课程设计·农业
Q_Q51100828510 小时前
python+uniapp基于微信小程序的旅游信息系统
spring boot·python·微信小程序·django·flask·uni-app·node.js