寻找可能的主元

64 阅读4分钟

寻找可能的主元

问题背景

在著名的快速排序算法中,一个核心步骤是划分(Partition)操作。该操作通常会选择一个元素作为主元(Pivot) ,然后重新排列数组,使得所有比主元小的元素都移动到主元的左侧,而所有比主元大的元素都移动到主元的右侧。

本问题要求我们分析一个已经划分完成的序列,找出其中哪些元素可能是当初执行划分操作时所选择的主元。

“可能的主元”的定义

在一个给定的序列中,一个元素 numbers[i] 如果满足以下两个条件,就被认为是一个“可能的主元”:

  1. 左侧的所有元素(如果存在)都小于或等于 numbers[i]
  2. 右侧的所有元素(如果存在)都大于或等于 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]。其中 23 小,不满足“右侧所有元素都大于等于它”的条件。
    • 结论: 3 不是主元。
  • 元素 2 (位于索引 2):

    • 左侧: [1, 3]。其中 32 大,不满足“左侧所有元素都小于等于它”的条件。
    • 结论: 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;
    }
}