目录
以下摘自leetcode Top100精选题目--技巧篇
只出现一次的数字
给你一个 非空 整数数组 nums
,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
你必须设计并实现线性时间复杂度的算法来解决此问题,且该算法只使用常量额外空间。
示例:
输入:nums = [2,2,1]
输出:1
Solution:
可以使用异或运算来解决。异或运算有一个性质,就是任何数和自身异或的结果都是0,任何数和0异或的结果都是自身。可以对数组中的所有元素进行异或运算,最终的结果就是那个只出现一次的元素。
class Solution {
public int singleNumber(int[] nums) {
// 初始化结果为0
int result = 0;
// 遍历数组中的所有元素
for (int num : nums) {
// 将当前元素和result进行异或运算,然后将结果赋值给result
result ^= num;
}
// 返回result,这就是那个只出现一次的元素
return result;
}
}
多数元素
给定一个大小为 n
的数组 nums
,返回其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋
的元素。
你可以假设数组是非空的,并且给定的数组总是存在多数元素。
示例:
输入:nums = [3,2,3]
输出:3
输入:nums = [2,2,1,1,1,2,2]
输出:2
Solution:
使用摩尔投票法来解决。摩尔投票法是一种在O(n)时间复杂度和O(1)空间复杂度内找出候选的众数的算法。
class Solution {
public int majorityElement(int[] nums) {
// 初始化候选的众数和计数器
int candidate = nums[0];
int count = 1;
// 遍历数组中的所有元素
for (int i = 1; i < nums.length; i++) {
// 如果计数器为0,那么将当前元素设为候选的众数,计数器设为1
if (count == 0) {
candidate = nums[i];
count = 1;
}
// 如果当前元素等于候选的众数,那么计数器加1
else if (nums[i] == candidate) {
count++;
}
// 如果当前元素不等于候选的众数,那么计数器减1
else {
count--;
}
}
// 返回候选的众数
return candidate;
}
}
颜色分类
给定一个包含红色、白色和蓝色、共 n
个元素的数组 nums
,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。
我们使用整数 0
、 1
和 2
分别表示红色、白色和蓝色。
必须在不使用库内置的 sort 函数的情况下解决这个问题。
示例:
输入:nums = [2,0,2,1,1,0]
输出:[0,0,1,1,2,2]
Solution:
可以使用三向切分的快速排序来解决。使用三个指针,low,mid和high,来将数组分为三个部分,nums[0..low-1]是0,nums[low..mid-1]是1,nums[mid..high]是未知的,nums[high+1..n-1]是2。
class Solution {
public void sortColors(int[] nums) {
// 定义三个指针,low,mid和high
int low = 0, mid = 0, high = nums.length - 1;
// 当mid小于等于high时,继续循环
while (mid <= high) {
// 根据nums[mid]的值进行不同的操作
switch (nums[mid]) {
case 0:
// 如果nums[mid]等于0,交换nums[low]和nums[mid]的值,然后将low和mid都向后移动一位
swap(nums, low++, mid++);
break;
case 1:
// 如果nums[mid]等于1,只是将mid向后移动一位
mid++;
break;
case 2:
// 如果nums[mid]等于2,交换nums[mid]和nums[high]的值,然后将high向前移动一位
swap(nums, mid, high--);
break;
}
}
}
// 交换数组中两个位置的元素
private void swap(int[] nums, int i, int j) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
}
下一个排列
整数数组的一个 排列 就是将其所有成员以序列或线性顺序排列。
- 例如,
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
的下一个排列。
必须 原地 修改,只允许使用额外常数空间。
示例:
输入:nums = [1,2,3]
输出:[1,3,2]
Solution:
class Solution {
public void nextPermutation(int[] nums) {
// 第一步:从右向左找到第一个比其下一个元素小的元素,我们称之为'支点'
int i = nums.length - 2;
while (i >= 0 && nums[i] >= nums[i + 1]) {
i--;
}
// 如果没有找到'支点',说明数组已经是降序排列,我们需要反转整个数组
if (i == -1) {
reverse(nums, 0, nums.length - 1);
} else {
// 第二步:从右向左找到第一个比'支点'大的元素,我们称之为'后继'
int j = nums.length - 1;
while (nums[j] <= nums[i]) {
j--;
}
// 第三步:交换'支点'和'后继'
swap(nums, i, j);
// 第四步:反转'支点'之后的所有元素
reverse(nums, i + 1, nums.length - 1);
}
}
// 反转数组的某一部分
private void reverse(int[] nums, int start, int end) {
while (start < end) {
swap(nums, start, end);
start++;
end--;
}
}
// 交换数组中的两个元素
private void swap(int[] nums, int i, int j) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
}
-
从右到左遍历数组,找到第一个比其下一个元素小的元素,称这个元素为'pivot'。如果找不到这样的元素,那么数组已经是降序排列,需要将其反转为升序排列。
-
从右到左遍历数组,找到第一个比'pivot'大的元素,称这个元素为'successor'。
-
交换'pivot'和'successor'。
-
将'pivot'之后的所有元素反转,以确保得到的是字典序最小的排列。
寻找重复数
给定一个包含 n + 1
个整数的数组 nums
,其数字都在 [1, n]
范围内(包括 1
和 n
),可知至少存在一个重复的整数。
假设 nums
只有 一个重复的整数 ,返回 这个重复的数 。
你设计的解决方案必须 不修改 数组 nums
且只用常量级 O(1)
的额外空间。
示例:
输入:nums = [1,3,4,2,2]
输出:2
Solution:
可以使用Floyd's Tortoise and Hare (乌龟和兔子) 算法来解决,也称为Floyd's Cycle-Finding Algorithm(Floyd的循环查找算法)。这个算法通常用来检测链表中的循环,但是在这个问题中,可以看作是一个有向图,其中每个节点都有一个指向值相同的节点的箭头。例如,如果数组为[2, 3, 1],则它表示从1到2,从2到3,从3到1的箭头。
class Solution {
public int findDuplicate(int[] nums) {
// 第一步:使用Floyd's Tortoise and Hare (乌龟和兔子) 算法找到两个跑者的交点
// 初始化两只跑者(乌龟和兔子)在起始位置
int tortoise = nums[0];
int hare = nums[0];
// 乌龟每次移动一步,兔子每次移动两步
// 由于数组中存在重复的数,所以一定会形成一个环,乌龟和兔子最终会在环中的某个点相遇
do {
tortoise = nums[tortoise];
hare = nums[nums[hare]];
} while (tortoise != hare);
// 第二步:找到环的入口,也就是重复的数
// 让乌龟回到起点,兔子留在交点
tortoise = nums[0];
// 两者都以相同的速度前进,当他们再次相遇时,就是环的入口,也就是重复的数
while (tortoise != hare) {
tortoise = nums[tortoise];
hare = nums[hare];
}
// 返回重复的数
return hare;
}
}
先使用Floyd's Tortoise and Hare (乌龟和兔子) 算法找到两个跑者的交点。初始化两只跑者(乌龟和兔子)在起始位置,乌龟每次移动一步,兔子每次移动两步。由于数组中存在重复的数,所以一定会形成一个环,乌龟和兔子最终会在环中的某个点相遇。
找到环的入口,也就是重复的数。让乌龟回到起点,兔子留在交点,然后两者都以相同的速度前进,当再次相遇时,就是环的入口,也就是重复的数。
这个算法利用数学中的一个有趣的现象:在一个有向图中,如果有两条路径从同一个起点出发,到达同一个终点,那么这两条路径的起点到交点的距离,一定等于交点到终点的距离。在这个问题中,起点是数组的第一个元素,终点是环中的某个点,两条路径是乌龟和兔子走过的路径。