贪心题目:交换一次的先前排列

本文讲解如何通过贪心策略求解‘交换一次的先前排列’问题:给定正整数数组,仅允许一次元素交换,得到字典序小于原数组的最大排列。核心思路是反向找首个下降位置left,再在右侧找小于arr[left]的最大值的最小下标right,交换二者。时间复杂度O(n),空间复杂度O(1)。

题目

标题和出处

标题:交换一次的先前排列

出处:1053. 交换一次的先前排列

难度

5 级

题目描述

要求

给定一个正整数数组 arr \texttt{arr} arr(其中的元素可能重复),返回可在一次交换(交换两数字 arr[i] \texttt{arr[i]} arr[i] 和 arr[j] \texttt{arr[j]} arr[j] 的位置)后得到的按字典序排列小于 arr \texttt{arr} arr 的最大可能排列。如果无法得到字典序更小的排列,返回原数组。

示例

示例 1:

输入: arr   =   [3,2,1] \texttt{arr = [3,2,1]} arr = [3,2,1]
输出: [3,1,2] \texttt{[3,1,2]} [3,1,2]
解释:交换 2 \texttt{2} 2 和 1 \texttt{1} 1。

示例 2:

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

示例 3:

输入: arr   =   [1,9,4,6,7] \texttt{arr = [1,9,4,6,7]} arr = [1,9,4,6,7]
输出: [1,7,4,6,9] \texttt{[1,7,4,6,9]} [1,7,4,6,9]
解释:交换 9 \texttt{9} 9 和 7 \texttt{7} 7。

数据范围

  • 1 ≤ arr.length ≤ 10 4 \texttt{1} \le \texttt{arr.length} \le \texttt{10}^\texttt{4} 1≤arr.length≤104
  • 1 ≤ arr[i] ≤ 10 4 \texttt{1} \le \texttt{arr[i]} \le \texttt{10}^\texttt{4} 1≤arr[i]≤104

解法

思路和算法

用 n n n 表示数组 arr \textit{arr} arr 的长度。对于 0 ≤ i < j < n 0 \le i < j < n 0≤i<j<n,考虑交换 arr [ i ] \textit{arr}[i] arr[i] 和 arr [ j ] \textit{arr}[j] arr[j] 之后的字典序变化。

  • 如果 arr [ i ] ≤ arr [ j ] \textit{arr}[i] \le \textit{arr}[j] arr[i]≤arr[j],则交换 arr [ i ] \textit{arr}[i] arr[i] 和 arr [ j ] \textit{arr}[j] arr[j] 之后,下标 i i i 处的元素值不变或增加,因此字典序不可能更小。

  • 如果 arr [ i ] > arr [ j ] \textit{arr}[i] > \textit{arr}[j] arr[i]>arr[j],则交换 arr [ i ] \textit{arr}[i] arr[i] 和 arr [ j ] \textit{arr}[j] arr[j] 之后,下标 i i i 处的元素值减少,因此字典序更小。

因此需要找到两个下标 left \textit{left} left 和 right \textit{right} right,满足 left < right \textit{left} < \textit{right} left<right 且 arr [ left ] > arr [ right ] \textit{arr}[\textit{left}] > \textit{arr}[\textit{right}] arr[left]>arr[right],才能在交换 arr [ left ] \textit{arr}[\textit{left}] arr[left] 和 arr [ right ] \textit{arr}[\textit{right}] arr[right] 之后得到字典序更小的排列。

为了得到字典序小于原始数组的最大可能排列,应使用贪心策略找到上述下标 left \textit{left} left 和 right \textit{right} right。

  1. 寻找 left \textit{left} left 时,应寻找符合上述要求的最大下标。理由如下:当 left < right \textit{left} < \textit{right} left<right 且 arr [ left ] > arr [ right ] \textit{arr}[\textit{left}] > \textit{arr}[\textit{right}] arr[left]>arr[right] 时,假设存在 left ′ \textit{left}' left′ 满足 left ′ < left \textit{left}' < \textit{left} left′<left 且 arr [ left ′ ] > arr [ right ] \textit{arr}[\textit{left}'] > \textit{arr}[\textit{right}] arr[left′]>arr[right],则交换 arr [ left ] \textit{arr}[\textit{left}] arr[left] 和 arr [ right ] \textit{arr}[\textit{right}] arr[right] 与交换 arr [ left ′ ] \textit{arr}[\textit{left}'] arr[left′] 和 arr [ right ] \textit{arr}[\textit{right}] arr[right] 都可以得到字典序更小的排列,其中交换 arr [ left ′ ] \textit{arr}[\textit{left}'] arr[left′] 和 arr [ right ] \textit{arr}[\textit{right}] arr[right] 得到的排列的字典序更小,一定不是可能的最大排列。寻找 left \textit{left} left 的可行做法是寻找满足 arr [ i ] > arr [ i + 1 ] \textit{arr}[i] > \textit{arr}[i + 1] arr[i]>arr[i+1] 的最大下标作为 left \textit{left} left,此时的 left \textit{left} left 为符合上述要求的最大下标。

  2. 在确定 left \textit{left} left 之后,记 leftNum = arr [ left ] \textit{leftNum} = \textit{arr}[\textit{left}] leftNum=arr[left],寻找 right \textit{right} right 时,应在数组 arr \textit{arr} arr 的下标大于 left \textit{left} left 的所有元素中寻找小于 arr [ left ] \textit{arr}[\textit{left}] arr[left] 的最大元素所在的最小下标作为 right \textit{right} right。理由如下:记 rightNum = arr [ right ] \textit{rightNum} = \textit{arr}[\textit{right}] rightNum=arr[right],为了通过交换 arr [ left ] \textit{arr}[\textit{left}] arr[left] 和 arr [ right ] \textit{arr}[\textit{right}] arr[right] 得到字典序更小的排列,必须满足 arr [ left ] > arr [ right ] \textit{arr}[\textit{left}] > \textit{arr}[\textit{right}] arr[left]>arr[right],在下标大于 left \textit{left} left 的所有元素中,下标小于 right \textit{right} right 的所有元素都小于 rightNum \textit{rightNum} rightNum,下标大于 right \textit{right} right 的所有元素都小于等于 rightNum \textit{rightNum} rightNum,如果与 arr [ left ] \textit{arr}[\textit{left}] arr[left] 交换的元素在下标 right \textit{right} right 的左侧则交换后位于下标 left \textit{left} left 处的元素一定小于 rightNum \textit{rightNum} rightNum,如果与 arr [ left ] \textit{arr}[\textit{left}] arr[left] 交换的元素在下标 right \textit{right} right 的右侧则交换后位于下标 right \textit{right} right 处的元素一定小于 leftNum \textit{leftNum} leftNum,因此如果与 arr [ left ] \textit{arr}[\textit{left}] arr[left] 交换的元素在下标 right \textit{right} right 的左侧或右侧则一定不是可能的最大排列。

具体做法如下。

  1. 反向遍历数组 arr \textit{arr} arr 并找到满足 arr [ i ] > arr [ i + 1 ] \textit{arr}[i] > \textit{arr}[i + 1] arr[i]>arr[i+1] 的最大下标 i i i 作为下标 left \textit{left} left。如果不存在这样的下标 i i i,则记 left = − 1 \textit{left} = -1 left=−1。

  2. 当 left ≥ 0 \textit{left} \ge 0 left≥0 时,从下标 left + 1 \textit{left} + 1 left+1 开始正向遍历数组 arr \textit{arr} arr 并找到小于 arr [ left ] \textit{arr}[\textit{left}] arr[left] 的最大元素的最小下标作为 right \textit{right} right,然后交换 arr [ left ] \textit{arr}[\textit{left}] arr[left] 和 arr [ right ] \textit{arr}[\textit{right}] arr[right]。

  3. 经过上述操作之后得到的 arr \textit{arr} arr 即为答案。

代码

class Solution {
    public int[] prevPermOpt1(int[] arr) {
        int length = arr.length;
        int left = -1;
        for (int i = length - 2; i >= 0 && left < 0; i--) {
            if (arr[i] > arr[i + 1]) {
                left = i;
            }
        }
        if (left >= 0) {
            int right = left + 1;
            int rightMax = arr[left + 1];
            for (int i = left + 2; i < length; i++) {
                if (arr[i] < arr[left] && arr[i] > rightMax) {
                    right = i;
                    rightMax = arr[i];
                }
            }
            int temp = arr[left];
            arr[left] = arr[right];
            arr[right] = temp;
        }
        return arr;
    }
}

复杂度分析

  • 时间复杂度: O ( n ) O(n) O(n),其中 n n n 是数组 arr \textit{arr} arr 的长度。最多需要遍历数组两次。

  • 空间复杂度: O ( 1 ) O(1) O(1)。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

伟大的车尔尼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值