废话不多说,喊一句号子鼓励自己:程序员永不失业,程序员走向架构!本篇Blog的主题是【数组合并】,使用【数组】这个基本的数据结构来实现,这个高频题的站点是:CodeTop,筛选条件为:目标公司+最近一年+出现频率排序,由高到低的去牛客TOP101去找,只有两个地方都出现过才做这道题(CodeTop本身汇聚了LeetCode的来源),确保刷的题都是高频要面试考的题。
明确目标题后,附上题目链接,后期可以依据解题思路反复快速练习,题目按照题干的基本数据结构分类,且每个分类的第一篇必定是对基础数据结构的介绍。
下一个排列【MID】
又是一道考验数组结构技法的题
题干
直接粘题干和用例
解题思路
原题解出处,下一个排列的定义是:给定数字序列的字典序中下一个更大的排列。如果不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列)。
我们可以将该问题形式化地描述为:给定若干个数字,将其组合为一个整数。如何将这些数字重新排列,以得到下一个更大的整数。如 123 下一个更大的数为 132。如果没有更大的整数,则输出最小的整数。以 1,2,3,4,5,6 为例,其排列依次为:
123456 123465 123546 ... 654321
可以看到有这样的关系:123456 < 123465 < 123546 < ... < 654321
,如何得到这样的排列顺序?这是重点。我们可以这样来分析:
- 我们希望下一个数 比当前数大,这样才满足 “下一个排列” 的定义。因此只需要 将后面的「大数」与前面的「小数」交换,就能得到一个更大的数。比如 123456,将 5 和 6 交换就能得到一个更大的数 123465
- 我们还希望下一个数 增加的幅度尽可能的小,这样才满足“下一个排列与当前排列紧邻“的要求。为了满足这个要求,我们需要:
- 在 尽可能靠右的低位 进行交换,需要 从后向前 查找
- 将一个 尽可能小的「大数」 与前面的「小数」交换。比如 123465,下一个排列应该把 5 和 4 交换而不是把 6 和 4 交换
- 将「大数」换到前面后,需要将「大数」后面的所有数 重置为升序,升序排列就是最小的排列。以 123465 为例:首先按照上一步,交换 5 和 4,得到 123564;然后需要将 5 之后的数重置为升序,得到 123546。显然 123546 比 123564 更小,123546 就是 123465 的下一个排列
以上就是求 “下一个排列” 的分析过程。标准的 “下一个排列” 算法可以描述为:
从后向前
查找第一个相邻升序
的元素对(i,j)
,满足A[i] < A[j]
。此时[j,end)
必然是降序- 在
[j,end)
从后向前 查找第一个满足A[i] < A[k]
的 k。A[i]、A[k]
分别就是上文所说的「小数」、「大数」 - 将
A[i]
与A[k]
交换 - 可以断定这时
[j,end)
必然是降序,逆置[j,end)
,使其升序 - 如果在步骤 1 找不到符合的相邻元素对,说明当前
[begin,end)
为一个降序顺序,则直接跳到步骤 4
该方法支持数据重复
以求 12385764 的下一个排列为例
首先从后向前查找第一个相邻升序的元素对 (i,j)。这里 i=4,j=5,对应的值为 5,7:我们要找到比5大的数
然后在 [j,end) 从后向前查找第一个大于 A[i] 的值 A[k]。这里 A[i] 是 5,故 A[k] 是 6:
将 A[i] 与 A[k] 交换。这里交换 5、6
这时 [j,end) 必然是降序,逆置 [j,end),使其升序。这里逆置 [7,5,4]:
因此,12385764 的下一个排列就是 12386457。最后再可视化地对比一下这两个相邻的排列(橙色是蓝色的下一个排列)
代码实现
给出代码实现基本档案
基本数据结构:数组
辅助数据结构:无
算法:迭代
技巧:双指针(逆序双指针)
其中数据结构、算法和技巧分别来自:
- 10 个数据结构:数组、链表、栈、队列、散列表、二叉树、堆、跳表、图、Trie 树
- 10 个算法:递归、排序、二分查找、搜索、哈希算法、贪心算法、分治算法、回溯算法、动态规划、字符串匹配算法
- 技巧:双指针、滑动窗口、中心扩散
当然包括但不限于以上
class Solution { public void nextPermutation(int[] nums) { // 1 从后往前,找到第一个后边数大于前一个数的位置,target就是要交换的值 int findIndex = nums.length - 1; while (findIndex >= 1) { if (nums[findIndex] > nums[findIndex - 1]) { break; } findIndex--; } int targetIndex = findIndex - 1; // 2 从目标值后边的区间里找到第一个比目标值大的数,因为后边区间已经是降序了,所以从后往前找即可 if (targetIndex >= 0) { // 这里设定targetIndex>=0是为了防止没有找到目标值即整个数组是降序,则下一个排列就是整个数组的反转 int swapIndex = nums.length - 1; while (swapIndex >= 0) { if (nums[swapIndex] > nums[targetIndex]) { break; } swapIndex--; } // 3 交换目标值与稍大的值 swap(nums, targetIndex, swapIndex); } // 4 将目标值后续区间反转 reverse(nums, findIndex, nums.length - 1); } // 交换数组元素方法 private void swap(int[] nums, int left, int right) { int temp = nums[left]; nums[left] = nums[right]; nums[right] = temp; } // 反转数组方法 private void reverse(int[] nums, int left, int right) { while (left < right) { swap(nums, left, right); left++; right--; } } }
复杂度分析
- 时间复杂度:O(N)。遍历了一遍数组,时间复杂度为O(N)
- 空间复杂度:O(1)。 需要建立长度为 m+n 的中间数组