liuxuhelloworld's notebook

题目链接

https://leetcode.cn/problems/previous-permutation-with-one-swap/

解答过程

这个题目从题目本身的要求来看,并不难理解,交换任意两个位置的元素称为一次swap,现在需要找到一组swap,swap后的数组,以lexicographically的方式来比较,小于当前数组,但是要越大越好。lexicographically嘛,简单来说就是挨个元素比较,以第一个不相等的元素的大小关系为整个数组的大小关系。

题目的要求理解了,那么如何找到一组swap,能满足题目要求呢?首先比较容易确认的一点是,我们需要找到的是一对逆序的元素,比如,(…,3,…,2),swap后是(…,2,…,3),这样才能保证swap后得到的数组小于原数组。

那么下一个问题就是哪一对逆序元素swap后得到的数组在小于当前数组的限制下能尽可能大?答案是这一对逆序元素要尽可能靠右。因为swap一对逆序元素是把一个较小的值挪到了高位,那么要想让swap后的数组尽可能大,就要尽量不影响较高位的元素,即要尽量swap靠右的逆序元素对。

那么如何找到最靠右的逆序元素对?其实并不难想到,逆序遍历原数组,每次遍历比较当前值与前值的大小关系,当碰到第一个前值大于当前值时,即找到了最靠右的逆序元素对,暂时称为(prevVal, curVal)。是这样吗?乍一看好像没什么问题,但其实不是。我们确实找到了最靠右的逆序元素对的左元素,即prevVal,但是最靠右的逆序元素对的右元素可能还能更靠右,因为在已遍历的元素中可能存在大于curVal但是小于prevVal的元素,比如,(…,9,3,4,5),当我们找到(9,3)时,(9,3)其实并不是我们需要的答案,而是要在已遍历的元素中再去找到大于3而小于9的最大的那个元素,这样才满足题目要求,即(9,…,5)。

	public int[] prevPermOpt1(int[] arr) {
		int i = arr.length - 1;
		while (i > 0) {
			if (arr[i - 1] > arr[i]) {
				break;
			}
			i--;
		}

		if (i == 0) {
			return arr;
		}

		int leftIdx = i - 1;
		int rightIdx = i;

		int curVal = arr[i];
		int prevVal = arr[i - 1];

		int maxVal = curVal;
		int j = i + 1;
		while (j < arr.length) {
			if (arr[j] > curVal && arr[j] < prevVal) {
				if (arr[j] > maxVal) {
					rightIdx = j;
					maxVal = arr[j];
				}
			}
			j++;
		}

		swap(arr, leftIdx, rightIdx);

		return arr;
	}

	private void swap(int[] arr, int left, int right) {
		int tmp = arr[left];
		arr[left] = arr[right];
		arr[right] = tmp;
	}

这个题目前前后后花了将近一个半小时才做出来,每次都是提交以后不通过,根据bad case再去修补,直到最后才整理出合理的思路,归根结底还是翻译问题并抽象出计算思路的能力不足。

看了一下官方题解,表述不太一样,但思路其实是差不多的。