【1053. 交换一次的先前排列】

来源:力扣(LeetCode)

描述:

给你一个正整数数组 arr(可能存在重复的元素),请你返回可在 一次交换(交换两数字 arr[i]arr[j] 的位置)后得到的、按字典序排列小于 arr 的最大排列。

如果无法这么操作,就请返回原数组。

示例 1:

输入:arr = [3,2,1]
输出:[3,1,2]
解释:交换 21

示例 2:

输入:arr = [1,1,5]
输出:[1,1,5]
解释:已经是最小排列

示例 3:

输入:arr = [1,9,4,6,7]
输出:[1,7,4,6,9]
解释:交换 97

提示:

  • 1 <= arr.length <= 104
  • 1 <= arr[i] <= 104

方法:贪心

记数组 arr 的长度为 n,对于 0 ≤ i < j < n,如果交换 arr[i] 和 arr[j] 后得到的新数组按字典序排列比原数组小,显然有 arr[i] > arr[j] 成立。因此符合题意要求的交换会使得数组 arr 在下标 i 处的元素变小。那么为了得到按字典序排列小于原数组的最大新数组,尽可能地保持前面的元素不变是最优的,即让 i 最大化。

  • 如何最大化 i
    • 我们可以从大到小枚举 i ∈ [0, n − 2],然后枚举 j ∈ [i + 1, n),如果存在 j 使 arr[i] > arr[j] 成立,那么说明当前枚举的 i 是最大化的。这里只需要判断 arr[i] > arr[i+1] 是否成立即可,因为后面的元素是符合非递减的,即 arr[i+1] 是 arr[i] 后面的最小元素。

当我们枚举 i 时,显然不存在 k > i,使得 arr[k] > arr[k+1] 成立,因此 arr[i] 后面的元素是符合非递减的。

  • i 最大化后,j ∈ [i + 1, n) 应该怎么选择
    • 显然在满足 arr[j] < arr[i] 的条件下,取最大的 arr[j] 是最优的。但是题目并没有元素不重复的要求,最大的 arr[j] 可能有重复值,那么选择其中下标最小的 arr[j] 是最优的。
    • 由前面推导可知,区间 [i + 1, n) 的元素是非递减的,因此我们从大到小枚举 j,直到 arr[j] < arr[i] 且 arr[j] ≠ arr[j−1] 成立,那么得到的 j 就是符合要求的。交换 arr[i] 和 arr[j]。

代码:

class Solution {
public:
    vector<int> prevPermOpt1(vector<int>& arr) {
        int n = arr.size();
        for (int i = n - 2; i >= 0; i--) {
            if (arr[i] > arr[i + 1]) {
                int j = n - 1;
                while (arr[j] >= arr[i] || arr[j] == arr[j - 1]) {
                    j--;
                }
                swap(arr[i], arr[j]);
                break;
            }
        }
        return arr;
    }
};

执行用时:24 ms, 在所有 C++ 提交中击败了58.99%的用户
内存消耗:24.1 MB, 在所有 C++ 提交中击败了52.52%的用户
复杂度分析
时间复杂度:O(n),其中 n 是数组 arr 的长度。查找 i 需要 O(n) 的时间复杂度,查找 j 需要 O(n) 的时间复杂度。
空间复杂度:O(1)。返回值不计入空间复杂度。
author:LeetCode-Solution

你可能感兴趣的:(LeetCode,算法,leetcode,C++,数据结构)