【题目】
整数数组的一个 排列 就是将其所有成员以序列或线性顺序排列。
- 例如,
arr = [1,2,3],以下这些都可以视作arr的排列:[1,2,3]、[1,3,2]、[3,1,2]、[2,3,1]。
整数数组的 下一个排列 是指其整数的下一个字典序更大的排列。更正式地,如果数组的所有排列根据其字典顺序从小到大排列在一个容器中,那么数组的 下一个排列 就是在这个有序容器中排在它后面的那个排列。如果不存在下一个更大的排列,那么这个数组必须重排为字典序最小的排列(即,其元素按升序排列)。
- 例如,
arr = [1,2,3]的下一个排列是[1,3,2]。 - 类似地,
arr = [2,3,1]的下一个排列是[3,1,2]。 - 而
arr = [3,2,1]的下一个排列是[1,2,3],因为[3,2,1]不存在一个字典序更大的排列。
给你一个整数数组 nums ,找出 nums 的下一个排列。
必须 原地 修改,只允许使用额外常数空间。
示例 1:
输入: nums = [1,2,3]
输出: [1,3,2]
示例 2:
输入: nums = [3,2,1]
输出: [1,2,3]
示例 3:
输入: nums = [1,1,5]
输出: [1,5,1]
提示:
1 <= nums.length <= 1000 <= nums[i] <= 100
【题目解析】
思路
本问题可以通过观察排列的字典序性质来解决。核心思想是从后向前查找,以最小的改动达到下一个排列。主要步骤如下:
- 找到非递增前缀:从数组末尾开始向前查找,找到第一个满足
nums[i] < nums[i+1]的索引i。这个位置之后的所有元素都是按降序排列的。 - 找到交换位置:如果找到了这样的
i,则再次从数组末尾向前查找,找到第一个大于nums[i]的元素nums[j]。这意味着在nums[i]之后的部分,nums[j]是最接近nums[i]且大于nums[i]的元素。 - 交换并重排:交换
nums[i]和nums[j],然后反转i+1到数组末尾的部分。反转后的部分必然是字典序最小的排列,因为之前是降序的。
执行
【总结】
“下一个排列”问题是一个典型的算法设计问题,它要求在给定的整数序列中找到字典序的下一个更大的排列。如果不存在更大的排列,则需要将序列重排为最小的排列(即升序排列)。这个问题不仅是一个关于排列组合的问题,而且还深刻涉及到算法设计和问题解决策略的选择。
适用问题类型
这种解题方法适用于一系列与排列组合、序列变换相关的问题,尤其是那些涉及到字典序、序列下一个状态的寻找等问题。例如:
- 寻找全排列问题中的下一个排列或上一个排列。
- 在特定规则下生成序列的下一个状态,如数学中的格雷码(Gray Code)生成。
- 排序问题中,对给定序列进行特定条件下的最小重排。
解决算法
解决“下一个排列”问题的算法是一种基于观察和数学逻辑的方法,主要包括以下几个步骤:
- 逆序查找:从序列末尾开始向前查找,找到第一个违反递增趋势的元素,这一步骤确保找到了需要进行调整的最高位。
- 元素交换:再次从末尾向前查找,找到第一个大于上一步骤找到的元素的位置,并进行交换,这保证了在当前位进行最小程度的增加。
- 序列反转:将上一步骤交换元素位置之后的所有元素反转,确保这部分序列是升序的,也就是在当前字典序下最小的。
这个算法实际上是一种贪心算法的应用,通过局部最优选择,达到全局最优解的目的。
算法性能
- 时间复杂度:O(N),其中N是序列的长度。在最坏的情况下,算法需要遍历整个序列来找到需要交换的位置,并进行一次反转操作。
- 空间复杂度:O(1)。算法在原地进行操作,不需要额外的空间来存储数据。
总结与扩展
“下一个排列”问题的解决方法展示了如何通过简单的观察和逻辑分析来解决看似复杂的问题。这种方法不仅适用于本题,还可以扩展到其他需要寻找序列下一个状态或者进行序列变换的问题上。通过学习这种算法,开发者可以提高自己在排列组合、序列变换等问题上的解题能力,同时也能够更好地理解贪心算法及其应用场景。此外,这个问题和解决方案对于准备技术面试的候选人来说也是一个宝贵的学习资源,因为它涉及到了算法设计中的关键思想和常用策略。