在算法学习的道路上,LeetCode 热题 100 是检验和提升算法能力的重要题库。今天,我将分享几道经典的数组与排列相关题目,涵盖只出现一次的数字 、多数元素 、下一个排列 和寻找重复数,并解析它们的解题思路与实现细节。
一、136. 只出现一次的数字
题目大意 :给定一个非空整数数组 nums,除了某个元素只出现一次外,其余每个元素均出现两次。找出那个只出现一次的元素。要求线性时间复杂度,且只使用常量额外空间。
解题思路:异或运算的特性
异或(^)运算有两个关键性质:
-
任何数与自身异或结果为
0(即a ^ a = 0); -
任何数与
0异或结果为自身(即a ^ 0 = a)。
因此,遍历数组时,将所有元素依次异或,最终结果就是只出现一次的那个数(因为成对出现的元素会相互抵消为 0,只剩单独的元素)。
代码实现(C++)
class Solution {
public:
int singleNumber(vector<int>& nums) {
int result = 0;
for (int num : nums) {
result ^= num; // 异或运算,抵消成对元素
}
return result;
}
};
二、169. 多数元素
题目大意 :给定一个大小为 n的数组 nums,返回其中的多数元素。多数元素是指在数组中出现次数大于 ⌊n/2⌋的元素。假设数组非空,且一定存在多数元素。
解题思路:摩尔投票法(Boyer-Moore Voting Algorithm)
摩尔投票法的核心思想是抵消:
-
维护一个候选元素
candidate和一个计数count。 -
遍历数组时,若当前元素与
candidate相同,则count++;否则count--。 -
若
count减为0,则更换candidate为当前元素,并重置count=1。
由于多数元素出现次数超过 n/2,最终剩下的 candidate一定是多数元素(其他元素的"票数"无法完全抵消多数元素的"票数")。
代码实现(C++)
class Solution {
public:
int majorityElement(vector<int>& nums) {
int candidate = 0;
int count = 0;
for (int num : nums) {
if (count == 0) { // 计数为0,更换候选
candidate = num;
count = 1;
} else if (num == candidate) { // 与候选相同,计数+1
count++;
} else { // 与候选不同,计数-1
count--;
}
}
return candidate;
}
};
三、31. 下一个排列
题目大意:整数数组的一个排列是其所有成员的序列或线性顺序。下一个排列是指其整数的下一个字典序更大的排列。如果不存在更大的排列,将数组重排为字典序最小的排列(升序)。要求原地修改,且只使用常量额外空间。
解题思路:四步法
-
从右往左找第一个下降点 :找到最大的索引
i,使得nums[i] < nums[i+1](即"拐点",拐点左侧是降序,右侧是升序)。 -
从右往左找第一个大于
nums[i]的元素 :找到最大的索引j > i,使得nums[j] > nums[i]。 -
交换
nums[i]和nums[j]:此时拐点右侧仍保持升序。 -
反转
i+1到末尾的部分:使右侧变为升序(保证字典序最小)。
代码实现(C++)
class Solution {
public:
void nextPermutation(vector<int>& nums) {
int n = nums.size();
int i = n - 2; // 从倒数第二个元素开始找
// Step 1: 找第一个下降点 i (nums[i] < nums[i+1])
while (i >= 0 && nums[i] >= nums[i + 1]) {
i--;
}
// Step 2: 若找到下降点,找第一个大于 nums[i] 的元素 j
if (i >= 0) {
int j = n - 1;
while (j > i && nums[j] <= nums[i]) {
j--;
}
swap(nums[i], nums[j]); // 交换
}
// Step 3: 反转 i+1 到末尾的部分(无论是否找到下降点都要做)
reverse(nums.begin() + i + 1, nums.end());
}
};
四、287. 寻找重复数
题目大意 :给定一个包含 n + 1个整数的数组 nums,数字都在 [1, n]范围内(包括 1和 n),可知至少有一个重复的整数。假设只有一个重复的整数,返回这个重复的数。要求不修改数组,且只使用常量额外空间。
解题思路:快慢指针(模拟环形链表)
将数组视为环形链表 :每个元素的值是下一个节点的索引(如 nums[0]指向 nums[nums[0]])。由于存在重复数,链表中必然有环,且重复数是环的入口。
步骤:
-
第一阶段:快慢指针相遇 :慢指针每次走一步(
slow = nums[slow]),快指针每次走两步(fast = nums[nums[fast]]),直到相遇(进入环内)。 -
第二阶段:找环的入口:将其中一个指针放回起点,两个指针每次都走一步,再次相遇的位置即为重复数(环的入口)。
代码实现(C++)
class Solution {
public:
int findDuplicate(vector<int>& nums) {
// 第一阶段:快慢指针相遇(找环内相遇点)
int slow = nums[0];
int fast = nums[0];
do {
slow = nums[slow]; // 慢指针走一步
fast = nums[nums[fast]]; // 快指针走两步
} while (slow != fast);
// 第二阶段:找环的入口(重复数)
int p1 = nums[0];
int p2 = slow; // 或 fast,此时两者相等
while (p1 != p2) {
p1 = nums[p1];
p2 = nums[p2];
}
return p1;
}
};
总结
这几道题分别考察了位运算 、投票算法 、排列的字典序规律 和链表成环问题的转化。通过掌握这些经典解法,不仅能解决对应的题目,还能加深对数组操作、算法思想(如抵消、双指针、模拟链表)的理解。
后续我会继续分享更多热题的解析,希望能和大家一起在算法学习中进步!
如果觉得内容有帮助,欢迎点赞、收藏和关注~