技巧题
31. 下一个排列
难度:⭐️⭐️⭐️
整数数组的一个 排列 就是将其所有成员以序列或线性顺序排列。
例如,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 <= 100
- 0 <= nums[i] <= 100
解法一 两遍扫描
从后往前搜索,找到第一个降序序列(顶点 i + 1),第二遍搜索,倒序找到比顶点前一个元素小的元素交换,从 i 到数组末尾是降序的,将剩下的元素反转。
以 1 4 6 5 4 3 2 1 为例:
y
6 | ○
5 | ○
4 | ○ ○
3 | ○
2 | ○
1 | ○ ○
---------------------------------------- x
0 1 2 3 4 5 6 7
找上坡 → 找稍大 → 交换 → 反转右侧。
折线图上就是:找到最后一个上升的起点,用右边比它稍大的数替换,再把右边从降序翻成升序。
时间复杂度:O(n)
空间复杂度:O(1)
两遍扫描
public void nextPermutation(int[] nums) {
// 1 4 6 5 4 3 2 1
// 找到顶点元素的前一个元素 i
int i = nums.length - 2;
while (i >= 0 && nums[i] >= nums[i + 1]) {
i--;
}
// 找到比 num[i] 更小的元素,反转
if (i >= 0) {
int j = nums.length - 1;
while (j >= 0 && nums[i] >= nums[j]) {
j--;
}
swap(nums, i, j);
}
// 反转剩下的元素
int left = i + 1;
int right = nums.length - 1;
while (left < right) {
swap(nums, left, right);
left++;
right--;
}
}
private void swap(int[] nums, int i, int j) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
75.颜色分类
难度:⭐️⭐️
给定一个包含红色、白色和蓝色、共 n 个元素的数组 nums ,原地 对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。
我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。
必须在不使用库内置的 sort 函数的情况下解决这个问题。
解法一 双指针
左右指针分别指向头尾,遇到 2 移动到数组尾部,遇到 0 移动到数组头部。
双指针
public void sortColors(int[] nums) {
int p0 = 0;
int p2 = nums.length - 1;
for (int i = 0; i <= p2; i++) {
while (i <= p2 && nums[i] == 2) {
int temp = nums[p2];
nums[p2] = nums[i];
nums[i] = temp;
p2--;
}
if (nums[i] == 0) {
int temp = nums[p0];
nums[p0] = nums[i];
nums[i] = temp;
p0++;
}
}
}
// 另外一种简单的写法
public void sortColors(int[] nums) {
int left = 0;
int right = nums.length - 1;
for (int i = 0; i <= right; i++) {
if (nums[i] == 0) {
swap(nums, left, i);
left++;
} else if (nums[i] == 2) {
swap(nums, i, right);
right--;
i--;
}
}
}
解法二 计数排序
第一次循环计算每种颜色的数量,第二次循环根据数量分别设置值。
记录次数
解法三 荷兰国旗问题(三指针)
使用三个指针 left、right、cur,一次遍历完成。
三指针
public void sortColors(int[] nums) {
int left = 0;
int right = nums.length - 1;
int cur = 0;
while (cur <= right) {
if (nums[cur] == 0) {
swap(nums, cur, left);
cur++;
left++;
} else if (nums[cur] == 1) {
cur++;
} else {
swap(nums, right, cur);
right--;
}
}
}
private void swap(int[] nums, int x, int y) {
int t = nums[x];
nums[x] = nums[y];
nums[y] = t;
}
136.只出现一次的数字
难度:⭐️
给你一个 非空 整数数组 nums ,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
你必须设计并实现线性时间复杂度的算法来解决此问题,且该算法只使用常量额外空间。
解法一 异或
数字 n^n = 0,n^0 = n,将所有的数字异或一遍,结果就是只出现一次的数字。
异或
169.多数元素
难度:⭐️
给定一个大小为 n 的数组 nums ,返回其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。
你可以假设数组是非空的,并且给定的数组总是存在多数元素。
解法一 Boyer-Moore 投票算法
遍历数组,对每个数字进行投票,与自身数字相同的,票数+1,否则票数-1,票数归零后重新选择数字。则众数得到的票数一定>0。
投票算法
解法二 哈希表
对每个数字计数,取value最大的数。
哈希表
解法三 排序
对数组进行排序,排序后,最中间的位置一定是众数。
解法四 分治递归
将数组分成两半,分别求两边的众数,两边众数相同时,结果即为该值;否则计算刚刚算出来的2个数组中的众数的数量,取数量更多的数组。递归该过程,当数组只有一个元素时,递归结束。
排序
public int majorityElement(int[] nums) {
return majority(nums, 0, nums.length);
}
private int majority(int[] nums, int start, int end) {
if (end - start == 1) {
return nums[start];
}
int mid = start + (end - start) / 2;
int left = majority(nums, start, mid);
int right = majority(nums, mid, end);
if (left == right) {
return left;
}
int leftCount = count(nums, start, end, left);
int rightCount = count(nums, start, end, right);
if (leftCount < rightCount) {
return right;
} else {
return left;
}
}
private int count(int[] nums, int start, int end, int target) {
int count = 0;
for (int i = start; i < end; i++) {
if (nums[i] == target) {
count++;
}
}
return count;
}
287.寻找重复数
难度:⭐️⭐️⭐️
给定一个包含 n + 1 个整数的数组 nums ,其数字都在 [1, n] 范围内(包括 1 和 n),可知至少存在一个重复的整数。
假设 nums 只有 一个重复的整数 ,返回 这个重复的数 。
你设计的解决方案必须 不修改 数组 nums 且只用常量级 O(1) 的额外空间。
示例 1:
输入:nums = [1,3,4,2,2]
输出:2
示例 2:
输入:nums = [3,1,3,4,2]
输出:3
示例 3 :
输入:nums = [3,3,3,3,3]
输出:3
提示:
- 1 <= n <= 105
- nums.length == n + 1
- 1 <= nums[i] <= n
- nums 中 只有一个整数 出现 两次或多次 ,其余整数均只出现 一次
解法一 快慢指针,Floyd判圈法
和链表类似,通过快慢指针,先让快慢指针相遇,然后快指针回到头部,快慢指针以相同的速度走,相遇点即为环的入口。和链表的写法的差别是结束的终点一定会相遇,可以用do-while写法或者是while(true)的写法。