寻找可能的主元
问题背景
在著名的快速排序算法中,一个核心步骤是划分(Partition)操作。该操作通常会选择一个元素作为主元(Pivot) ,然后重新排列数组,使得所有比主元小的元素都移动到主元的左侧,而所有比主元大的元素都移动到主元的右侧。
本问题要求我们分析一个已经划分完成的序列,找出其中哪些元素可能是当初执行划分操作时所选择的主元。
“可能的主元”的定义
在一个给定的序列中,一个元素 numbers[i] 如果满足以下两个条件,就被认为是一个“可能的主元”:
- 其左侧的所有元素(如果存在)都小于或等于
numbers[i]。 - 其右侧的所有元素(如果存在)都大于或等于
numbers[i]。
注意:如果一个元素左侧或右侧没有元素,则相应条件自动满足。
任务要求
给定一个划分后的正整数序列 numbers,请找出所有“可能的主元”,并将它们按升序排列后返回。如果不存在任何可能的主元,则返回一个空列表 []。
输入格式
-
numbers: 一个正整数数组(或列表)。- 数组长度:
1 <= numbers.length <= 10^5 - 元素大小:
1 <= numbers[i] <= 10^9
- 数组长度:
输出格式
- 一个整数列表,包含所有找到的可能主元,并按升序排列。如果不存在,则返回空列表
[]。
资源限制
- 内存限制: C/C++ 语言为
256MB,其他语言为512MB。
样例说明
样例输入
numbers = [1, 3, 2, 4, 5]
样例输出
[1, 4, 5]
解释
我们逐一检查数组中的每个元素是否满足“可能的主元”定义:
-
元素
1(位于索引 0):- 左侧: 没有元素,条件满足。
- 右侧:
[3, 2, 4, 5]。所有元素都比1大。条件满足。 - 结论:
1是一个可能的主元。
-
元素
3(位于索引 1):- 左侧:
[1]。1 < 3,条件满足。 - 右侧:
[2, 4, 5]。其中2比3小,不满足“右侧所有元素都大于等于它”的条件。 - 结论:
3不是主元。
- 左侧:
-
元素
2(位于索引 2):- 左侧:
[1, 3]。其中3比2大,不满足“左侧所有元素都小于等于它”的条件。 - 结论:
2不是主元。
- 左侧:
-
元素
4(位于索引 3):- 左侧:
[1, 3, 2]。所有元素都比4小。条件满足。 - 右侧:
[5]。5 > 4,条件满足。 - 结论:
4是一个可能的主元。
- 左侧:
-
元素
5(位于索引 4):- 左侧:
[1, 3, 2, 4]。所有元素都比5小。条件满足。 - 右侧: 没有元素,条件满足。
- 结论:
5是一个可能的主元。
- 左侧:
综上所述,所有可能的主元是 1, 4, 5。按升序排列后,最终输出为 [1, 4, 5]。
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* 解决“快速排序”主元查找问题的实现类.
*/
public class QuickSortPivotFinder {
/**
* 主方法,查找一个序列中所有可能是快速排序主元的元素.
* @param numbers 一个划分后的正整数序列
* @return 按升序排列的所有可能主元的列表
*/
public List<Integer> findPotentialPivots(int[] numbers) {
// --- 处理边界情况 ---
if (numbers == null || numbers.length == 0) {
return new ArrayList<>();
}
if (numbers.length == 1) {
// 如果只有一个元素,它自身就是唯一可能的主元
return Collections.singletonList(numbers[0]);
}
int n = numbers.length;
// --- 步骤 1: 预处理,计算从左到右每个位置及其左边的最大值 ---
// maxFromLeft[i] 将存储 numbers[0...i] 中的最大值
int[] maxFromLeft = new int[n];
maxFromLeft[0] = numbers[0];
for (int i = 1; i < n; i++) {
maxFromLeft[i] = Math.max(maxFromLeft[i - 1], numbers[i]);
}
// --- 步骤 2: 预处理,计算从右到左每个位置及其右边的最小值 ---
// minFromRight[i] 将存储 numbers[i...n-1] 中的最小值
int[] minFromRight = new int[n];
minFromRight[n - 1] = numbers[n - 1];
for (int i = n - 2; i >= 0; i--) {
minFromRight[i] = Math.min(minFromRight[i + 1], numbers[i]);
}
// --- 步骤 3: 遍历数组,利用预处理的结果找出所有可能的主元 ---
List<Integer> pivots = new ArrayList<>();
for (int i = 0; i < n; i++) {
// 获取当前元素左边所有元素的最大值
// 对于第一个元素(i=0),其左边没有元素,可以认为条件总是满足
int leftMax = (i == 0) ? Integer.MIN_VALUE : maxFromLeft[i - 1];
// 获取当前元素右边所有元素的最小值
// 对于最后一个元素(i=n-1),其右边没有元素,可以认为条件总是满足
int rightMin = (i == n - 1) ? Integer.MAX_VALUE : minFromRight[i + 1];
// 判断条件:当前元素必须严格大于其左边的所有元素,并严格小于其右边的所有元素
// 对于i=0,leftMax是MIN_VALUE,numbers[i] > leftMax 恒成立
// 对于i=n-1,rightMin是MAX_VALUE,numbers[i] < rightMin 恒成立
if (numbers[i] > leftMax && numbers[i] < rightMin) {
pivots.add(numbers[i]);
}
}
// 由于我们是按数组从左到右的顺序查找的,并且已证明如果 i < j 且 numbers[i] 和 numbers[j] 都是主元,
// 那么必然有 numbers[i] < numbers[j],所以找到的主元列表 pivots 自然就是升序的。
return pivots;
}
}