news 2026/10/21 10:14:47

交换数组最小值和最大值:核心是下标定位与边界条件处理

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
交换数组最小值和最大值:核心是下标定位与边界条件处理

1. 先把这个题目的真实考点说清楚

“交换最小值和最大值”这句题目,我见过太多人把它当成一道送分题来做。但实际写代码的时候,能一次写对并扛住所有边界测试的人,真没那么多。

先从最朴素的直觉说起。给你一个数组[2, 5, 1, 9, 3],要求交换最小值和最大值。多数人第一反应是:找出最小值 1,找出最大值 9,把这两个数对调,得到[2, 5, 9, 1, 3]。这个思路本身没错,但问题在于:在数组里,你能直接操作的并不是值,而是格子——也就是下标。要交换两个元素,必须知道它们在数组里的具体位置,否则你只是把两个局部变量值换了换,数组本身纹丝不动。

所以这道题真正想考察的核心只有一件事:定位。你不是在交换两个数字,而是在交换“最小值所在的位置”和“最大值所在的位置”上站着的两个元素。很多新手卡住,不是不会找最大最小值,而是没有建立起“值”和“位置(下标)”之间的映射关系。

这个题目常见于三个场景:

  • C/C++ 课程中数组章节的典型练习,教材里经常用这道题讲“数组下标”和“元素值”的区别。
  • 各类刷题网站的基础题,通常作为数组操作模块的第一道热身题。
  • 面试手写代码环节的暖场题,用来考察候选人是否细心、会不会考虑边界条件。

它适合刚接触数组语法的新手,也适合准备校招笔试的人,甚至适合那些自我感觉基础扎实、但好久没碰数组细节的老手。因为这道题的变体和边界条件,是真的能筛人的。

说句题外话,我带人的时候喜欢先用这道题去判断对方的代码习惯。不是因为题目本身有多难,而是因为它能在极短的时间内暴露三个问题:你会不会考虑空数组和单元素数组?你知道不知道重复值怎么处理?你写交换的时候是交换了数组元素还是交换了两个临时变量?这三个问题答利索了,基础基本靠谱。

2. 解题思路拆解:找“位置”才是题眼

2.1 标准解法就三步

正确解法是标准的“查找-交换”流程:

  1. 遍历数组,记录最小值第一次出现的下标,记为min_idx。
  2. 遍历数组,记录最大值第一次出现的下标,记为max_idx。
  3. 交换arr[min_idx]与arr[max_idx]。

把这个流程翻译成代码,核心其实只有两个变量。注意,这里刻意强调“下标”,不是“值”。因为一旦只记录值,你后续还得用index()或者再遍历一次去反查位置,既慢又容易出错。

我举个反例。假如你写了类似这样的代码:

min_val = min(arr) max_val = max(arr) min_val, max_val = max_val, min_val

然后输出arr,你会发现数组没有任何变化。原因一目了然:这个交换只是把两个临时变量手里的数值互相递了一下,但数组里的元素压根没动过。这就是典型的“交换了两个值,而不是交换两个位置”。

2.2 一遍遍历还是两遍遍历?

查找最小值和最大值,有两种写法。第一种是两遍独立遍历,先找min_idx,再找max_idx。第二种是一遍遍历同时更新两者:

min_idx = 0 max_idx = 0 for i in range(1, len(arr)): if arr[i] < arr[min_idx]: min_idx = i if arr[i] > arr[max_idx]: max_idx = i

两遍也是一样属于 O(n) 时间复杂度,但一遍遍历有个额外好处:只扫了一次,逻辑上更紧凑,而且min_idx和max_idx各自独立更新,先判断哪个都不影响结果,不容易出错。

不过有一类写法要特别小心:把两个判断写成if...elif...结构。

for i in range(1, len(arr)): if arr[i] < arr[min_idx]: min_idx = i elif arr[i] > arr[max_idx]: max_idx = i

这段代码在大部分数组上是正常的,因为某个位置上的元素不太会同时满足“比当前最小值小”和“比当前最大值大”。但在复杂逻辑里,elif很容易造成条件覆盖不完整,比如你后续还要同时更新其他状态时,就会漏掉一种分支。所以我个人统一建议:两个独立的if,别用elif,表达意图更清晰,也更好扩展。

提示:无论一遍还是两遍遍历,时间都是 O(n),额外空间 O(1)。这已经是最优解,因为你要挑出一筐苹果里最大和最小的,不每个看一眼不可能知道。

2.3 边界条件才是拉开差距的地方

任何一道简单题,拉开差距的都在边界。这道题的边界条件我整理成了表格:

场景例子正确行为
空数组[]不报错,原样返回
单元素数组[5]无需交换,原样返回
所有元素相等[7, 7, 7]min_idx等于max_idx,交换白交换,数组不变
存在多个最小值[3, 1, 4, 1]通常交换第一个最小值,即下标 1
存在多个最大值[2, 9, 3, 9]通常交换第一个最大值,即下标 1
最小值与最大值相邻[1, 9, 2]正常交换,结果是[9, 1, 2]

关于重复值,“通常交换第一个”只是一个默认约定,具体要看你面对的是哪一版题目。有些题库会明确写“交换最先出现的最小值和最大值”,有些则无所谓。你在代码里注释一行“重复值取首次出现”,是很加分的习惯,说明你思考过这个歧义。

还有个容易搞混的场景:如果题目说的是“把最小值放到数组开头,最大值放到数组末尾”,那是另一个变体,不是单纯的交换。此时如果最小值已经在末尾、最大值已经在开头,直接连续两次交换就会出错。这个变体我后面专门再讲。

3. 各语言实现与实操笔记

3.1 Python:最简洁,但也有坑

Python 版本可以写得很短:

def swap_min_max(arr): if len(arr) < 2: return arr min_idx = 0 max_idx = 0 for i in range(1, len(arr)): if arr[i] < arr[min_idx]: min_idx = i if arr[i] > arr[max_idx]: max_idx = i arr[min_idx], arr[max_idx] = arr[max_idx], arr[min_idx] return arr

也可以借助内建函数:

def swap_min_max_builtin(arr): if len(arr) < 2: return arr min_idx = arr.index(min(arr)) max_idx = arr.index(max(arr)) arr[min_idx], arr[max_idx] = arr[max_idx], arr[min_idx] return arr

两段代码功能一样,但第二段有个细节:arr.index()返回的是第一次出现的下标,正好符合“重复值取首次出现”的约定。性能上,min()、max()和index()各扫一遍数组,相当于三次 O(n) 遍历,但因为这些内建函数跑在底层,实际速度对于普通规模的数据完全够用。

一个容易翻车的所谓“Python 技巧”:

arr[min_idx], arr[max_idx] = arr[max_idx], arr[min_idx]

当min_idx和max_idx相等时,这行元组交换天然安全。但如果你图省事写成:

arr[min_idx] = arr[max_idx] arr[max_idx] = arr[min_idx]

那就会把最大值复制到最小值的位置,再用同一个值覆盖最大值的位置,数组直接坏掉。这就是典型的三步交换法手误,尤其当两个下标相邻或相等时,后果很隐蔽。

3.2 C 语言:教科书级的写法

C 语言版本是各类教材里的常客。完整写法:

#include <stdio.h> void swap_min_max(int arr[], int n) { if (n < 2) { return; } int min_idx = 0; int max_idx = 0; for (int i = 1; i < n; i++) { if (arr[i] < arr[min_idx]) { min_idx = i; } if (arr[i] > arr[max_idx]) { max_idx = i; } } int temp = arr[min_idx]; arr[min_idx] = arr[max_idx]; arr[max_idx] = temp; } int main() { int arr[] = {2, 5, 1, 9, 3}; int n = sizeof(arr) / sizeof(arr[0]); swap_min_max(arr, n); for (int i = 0; i < n; i++) { printf("%d ", arr[i]); } printf("\n"); return 0; }

这里必须留个心:C 里数组作为函数参数传递时,会退化成指针,函数内部直接修改arr[i]是能作用到原数组的。所以不需要返回一个“新数组”,原数组已经被改了。新手常犯的错是把数组复制一份、在副本上交换,然后期望主函数里的数组发生变化——在 C 里数组不是传值类型,这种做法只会让修改无声消失。

3.3 Java 与 C++:标准库帮你省一半功夫

Java 版本,手写循环版:

public static void swapMinMax(int[] arr) { if (arr.length < 2) { return; } int minIdx = 0; int maxIdx = 0; for (int i = 1; i < arr.length; i++) { if (arr[i] < arr[minIdx]) { minIdx = i; } if (arr[i] > arr[maxIdx]) { maxIdx = i; } } int temp = arr[minIdx]; arr[minIdx] = arr[maxIdx]; arr[maxIdx] = temp; }

Java 的数组对象是引用类型,方法里修改数组元素,调用方是能感知的。但注意,如果你在方法里写的是arr = new int[]{...}这样的重新赋值操作,那就只是把局部引用指向了新对象,原数组照样不动。这两者的区别,面试时经常被拿出来当追问点。

C++ 版可以用标准库一步到位:

#include <algorithm> #include <vector> void swapMinMax(std::vector<int>& v) { if (v.size() < 2) { return; } auto minIt = std::min_element(v.begin(), v.end()); auto maxIt = std::max_element(v.begin(), v.end()); std::iter_swap(minIt, maxIt); }

std::min_element和std::max_element返回的是迭代器,本质上也是一种“位置”,正好印证了前面说的:找位置,而不是找值。如果数组里所有元素相等,两个迭代器指向同一个元素,std::iter_swap对同位置操作是安全的,不会出问题。

C++ 里还有一个坑:别把std::swap(*minIt, *maxIt)写成std::swap(minIt, maxIt)。前者交换两个元素的值,后者交换的是迭代器本身,完全是两码事,编译能过,结果全错。

3.4 变体题:从“交换”到“移动”

这个题目有一大类变体,最常见的是:

将数组中的最小值移到第一个位置,最大值移到最后一个位置,其余元素保持相对顺序不变。

如果直接用“交换”的思维去解,很容易翻车。举个例子:

输入: [9, 2, 5, 1] 最小值 1 在末尾,最大值 9 在开头

如果先让最小值 1 与第一个元素 9 交换,得到[1, 2, 5, 9]。此时你会发现,最大值 9 已经从开头跑到了别的位置。如果你还用交换前记录下来的max_idx = 0去和最后一个位置交换,就会把原来的 1 又换回到开头,数组变回[9, 2, 5, 1],功亏一篑。

正确做法是:先记录初始的min_idx和max_idx,执行“最小值换到开头”后,检查一次max_idx是否被这次交换波及。具体规则是:如果max_idx正好等于这次交换的目标位置(也就是下标 0),那最大值已经被挪到了min_idx的位置,需要把max_idx更新成原来的min_idx,再执行第二次交换。如果最大值不在目标位置上,就不用更新。

这个细节极其容易忽略,因为普通样例很少撞上这个组合。我在实际测试中遇到这个坑时,整个人愣了一下才反应过来——从“交换”升级到“移动”,本质是把定位思想加深了一层:你必须在动手修改数组之前,把所有目标位置都记牢,就像搬家前先在箱子上贴好标签,而不是搬一半才想起来哪个箱子里有什么。

4. 常见问题与排查技巧实录

4.1 为什么我交换后数组没有任何变化

这是出现频率最高的现象。代码看起来操作了数组,实际却纹丝不动。常见原因有二:

第一,交换的是两个变量,不是数组元素。比如前面提到的min_val, max_val = max_val, min_val,这就是典型的“只换了纸上的数字,没动抽屉里的东西”。

第二,在传值语义的语言或写法中,你在函数内部修改的是数组副本。比如 Java 里方法参数是基本类型时天然传值,C++ 函数参数忘了写引用符号&,对std::vector的修改就不会传出去。排查思路很简单:在交换后立刻打印数组每一个元素,看看是“完全没变”还是“变了但变错”。前者是传值/引用问题,后者是下标或边界问题。

4.2 重复值出现时,交换结果和预期不一致

比如[1, 2, 3, 1],你可能发现交换后变成[1, 2, 3, 1]——没变化。原因是代码用max(arr)找到最大值 3(下标 2),用min(arr)找到最小值 1,但index(1)返回第一次出现的下标 0,所以最小值本来就在开头,交换了个寂寞。

更隐蔽的一种错误是在一次遍历里同时更新两个下标时手滑:

if arr[i] < arr[min_idx]: min_idx = i max_idx = i # 手滑

这种错误在样例数据上不太容易暴露,因为样例通常能给出正确结果;一旦数据里最小和最大位置比较偏,结果就错了。建议写完后用刁钻数据自测,比如[1, 1, 1]、[5, 1]、[9, 2, 5, 1]。

4.3 自测用例清单(可直接抄作业)

我每次写完这类数组操作题,都会带着下面这组用例过一遍:

用例期望结果
[][],不报错
[5][5]
[1, 1, 1][1, 1, 1]
[1, 2][2, 1]
[2, 1][1, 2]
[3, 1, 4, 1, 5][3, 5, 4, 1, 1]
[1, 2, 1]选第一个 1 交换得[2, 1, 1],选第二个得[1, 1, 2],两种都是合法的“第一次出现”约定

第七个用例的重点在于,最小值重复时选择哪个下标,结果可能不同。它比[3, 1, 4, 1, 5]更能检验你的约定是否一致,因为后者两个 1 交换后数组布局恰好相同,容易让错误约定蒙混过关。

注意:判断重复值处理是否正确,别只看输出结果,要看你的实现到底选的是哪个下标。写一行注释说明规则,会让你的代码更可信。

4.4 从这道题延伸到选择排序

写对了这道题,其实你就已经掌握了选择排序里最关键的一个环节。选择排序的每一轮,就是“找到剩余区间的最小值下标,然后与区间第一个位置交换”。把这个动作不断重复,就是完整的选择排序。

所以这道题表面看只有 O(n) 的数组遍历,但它背后连接的是一整套基础能力:通过下标操作元素、通过临时变量完成交换、通过遍历维护极值状态。这些能力在后续的排序算法、滑动窗口、双指针等话题里会反复出现。

我在带人时发现,能流畅写出这道题变体的人,多半已经形成了一个肌肉记忆:凡是数组内交换,第一步永远是明确下标;凡是遍历维护极值,第一步永远是确定初始值。这两个习惯,比这道题本身值钱得多。

最后分享一个实操中的小技巧:如果你在写 Python,并且函数需要原地修改数组,建议在函数第一行就把输入检查写全,包括空数组、单元素、全部相等三种情况。这不仅是防御性编程,更是在向看你代码的人传达一个信息——这些边界我都想到了。面试手写代码时,就凭这一点,你的评价往往就能上一个档次。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/21 18:09:45

Gitee仓库管理实战:从分支保护到故障排查的完整指南

上个月周会上&#xff0c;一个做了半年的项目在演示时翻了车&#xff1a;负责合并的同学一时手快&#xff0c;把还没评审的feature分支直接推上了主分支&#xff0c;回滚的时候又牵扯出远程地址绑错、本地和远程分叉等问题。我盯着满屏的红色报错&#xff0c;意识到一个普遍的误…

作者头像 李华
网站建设 2026/10/20 8:34:47

ISO 8373-2021机器人词汇标准解读与工程避坑指南

简介&#xff1a;ISO 8373:2021是国际标准化组织发布的机器人领域权威词汇标准&#xff0c;面向工业机器人研发、服务机器人设计、自动化工程及科研教学人员&#xff0c;旨在统一全球机器人的术语定义与沟通口径。这份资源收录的是最新第三版英文原版PDF&#xff0c;共1个文件&…

作者头像 李华
网站建设 2026/10/21 12:22:08

因果推断教材2024:AB测试与观测数据中的因果效应估计方法

简介&#xff1a;这份资源是斯坦福大学Stefan Wager于2024年9月更新的《Causal Inference: A Statistical Learning Approach》教科书草稿&#xff0c;面向数据科学、统计学、经济学与公共政策领域的学习者和研究者&#xff0c;帮助读者用统计学习方法系统理解因果关系的估计与…

作者头像 李华
网站建设 2026/10/21 10:14:27

MCTP over PCIe VDM传输绑定规范详解

简介&#xff1a;本资源是DMTF&#xff08;分布式管理任务组&#xff09;发布的《MCTP PCIe VDM Transport Binding Specification》1.2.0版官方规范文档&#xff0c;面向系统固件工程师、BMC开发人员及数据中心设备管理协议研究者&#xff0c;解决MCTP协议在PCIe物理层上的标准…

作者头像 李华
网站建设 2026/10/21 11:24:46

更新说明不是附属品:版本发布的第一道用户沟通界面

“更新说明”这四个字&#xff0c;很多项目维护者要么随便写几行&#xff0c;要么干脆不写。我最初也是那个“干脆不写”的人&#xff0c;直到有次发布后用户找上门来问“这个新版本到底改了什么&#xff1f;为什么我的调用方式要变&#xff1f;”我才意识到&#xff0c;更新说…

作者头像 李华
网站建设 2026/10/19 7:42:55

CTF逆向工程实战:re3题目解析与XOR加密破解

1. 逆向工程实战&#xff1a;re3题目深度解析最近在CTF逆向题目中遇到一个名为re3的挑战&#xff0c;这个二进制文件看似简单却暗藏玄机。作为逆向工程师&#xff0c;我们需要像侦探一样抽丝剥茧&#xff0c;逐步揭开程序的运行逻辑。下面我将完整记录解题过程&#xff0c;包含…

作者头像 李华