当前位置: 首页 > news >正文

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

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

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

核心思路:贪心 + 定位

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

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

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

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

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

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

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

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

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

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

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=n10)目的:构建表格 (right_best_idx)。不执行交换。必须为所有可能的 iii 准备答案。 iii 可能是 0,1,2,…0, 1, 2, \dots0,1,2, 中的任意一个,所以必须为所有位置计算右侧的最佳替换位。
第二步 (查找交换)从左向右 (i=0→n−1i = 0 \to n-1i=0n1)目的:执行贪心决策,找到第一个需要优化的 iii执行交换。 这是唯一需要优先考虑 i=0i=0i=0 的地方,因为它是最高位。

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

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


🔍 代码片段回顾

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

📘 直观理解

举个例子,比如数字:

num = 2736
s = ['2', '7', '3', '6']

我们要知道:

  • 每个位置 右边 哪个数字是“最大且最靠右”的。
从右往左扫描过程:
is[i]当前最大数字索引 current_max_idxright_best_idx[i]
3633
2333
1711
0211

结果:

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 循环的核心逻辑

代码如下:

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. 一旦交换完,就返回结果。

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

我们前面得到了:

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

这意味着:
在第 i 位上,如果 s[i] < s[right_best_idx[i]],那就存在一个比它大的数在后面。
而我们只允许交换一次,所以我们就应该:

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

这样能保证结果最大。


📘 举个完整例子:num = 2736

第一步:预处理结果(来自第一个 for 循环)
s = ['2', '7', '3', '6']
right_best_idx = [1, 1, 3, 3]
第二步:逐位检查并决定交换
is[i]j = right_best_idx[i]s[j]比较结果操作
02172 < 7 ✅交换 → [‘7’,‘2’,‘3’,‘6’],返回
17177 == 7 ❌-
23363 < 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:从左向右扫描(寻找第一个可交换位置) │└─────────────────────────────────────────────┘

扫描每个位置:

is[i]right_best_idx[i]s[j]判断动作
02172 < 7 ✅交换(0,1) → 7236,结束
1717--
23363 < 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

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)) 就是将算法中处理好的字符列表变回整数并输出的最终步骤。

http://www.dtcms.com/a/499265.html

相关文章:

  • CentOS安装Node.js
  • 深入解析MCP:从基础配置到高级应用指南
  • 佛山网站建设服务wordpress 不能更换主题
  • Process Monitor 学习笔记(5.13):从 0 到 1 的排障剧本清单(可复用模板)
  • Fluent 重叠网格+UDF NACA0012翼型摆动气动仿真
  • 深圳网站建设 设计卓越迈wordpress一键采集文章
  • 理想汽车Java后台开发面试题及参考答案(下)
  • python|if判断语法对比
  • 全链路智能运维中的实时流处理架构与状态管理技术
  • 排序算法:详解快速排序
  • 安阳哪里做360网站科技感十足的网站
  • UV 紫外相机在半导体制造领域的应用
  • 突破亚微米光电子器件制造瓶颈!配体交换辅助打印技术实现全打印红外探测器
  • 可见光工业相机半导体制造领域中的应用
  • require和 import是两种不同的模块引入方式的区别
  • 半导体制造工艺基本认识 五 薄膜沉积
  • 矩阵及其应用
  • **发散创新:探索零信任网络下的安全编程实践**随着信息技术的飞速发展,网络安全问题日益凸显。传统的网络安全防护方式已难以
  • 网络营销方案毕业设计安卓手机性能优化软件
  • 建设企业网站价格建设银行北京市财满街分行网站
  • (Kotlin高级特性一)kotlin的扩展函数和属性在字节码层面是如何实现的
  • Spring Boot 3零基础教程,WEB 开发 静态资源默认配置 笔记27
  • 【论文精度-2】求解车辆路径问题的神经组合优化算法:综合展望(Yubin Xiao,2025)
  • 赣州哪里做网站域名注册备案
  • windows双系统下 ubutnu 20.04 启动项出问题无法进入ubuntu 20.04的解决方法
  • MQTT 协议全面学习笔记
  • 加权分位数直方图:提升机器学习效能的关键技术
  • 做分析图网站无锡seo优化
  • SQL CHECK约束详解
  • 【java接口实现】一个简单接口实现模板