如有缺漏谬误,还请批评指正。
1.只出现一次的数字
利用异或运算相同得0的特点。所有出现过两次的数字都会在异或运算累加过程中被抵消。
class Solution {
public:
int singleNumber(vector<int>& nums) {
int res=0;
for(int i=0;i<nums.size();i++) res^=nums[i];
return res;
}
};
2.多数元素
随机取数,然后判断是否是答案。因为众数被选中的概率大于50%,所以时间复杂度虽然理论上可能出现O(∞)的情况,但实际上可以达到O(n)。
class Solution {
public:
int majorityElement(vector<int>& nums) {
while(true){
int candidate=nums[rand()%nums.size()];
int count=0;
for(int i=0;i<nums.size();i++){
if(nums[i]==candidate){
count++;
if (count>nums.size()/2) return candidate;
}
}
}
return -1;
}
};
3.颜色分类
经典的“荷兰国旗”问题。
(1)单指针解法
class Solution {
public:
void sortColors(vector<int>& nums) {
int i=0;
for(int j=0;j<nums.size();j++){
if(nums[j]==0){
swap(nums[i],nums[j]);
i++;
}
}
for(int j=i;j<nums.size();j++){
if(nums[j]==1){
swap(nums[i],nums[j]);
i++;
}
}
}
};
(2)双指针解法
比单指针少了一趟遍历。
①两个同向指针分别记录0的交换位置和1的交换位置。
class Solution {
public:
void sortColors(vector<int>& nums) {
int n=nums.size();
int ptr0=0,ptr1=0;
for(int i=0;i<n;i++){
if(nums[i]==0){
swap(nums[i],nums[ptr0++]);
if(ptr1<ptr0) ptr1++;
}
if(nums[i]==1) swap(nums[i],nums[ptr1++]);
}
}
};
②左右指针分别记录0的交换位置和2的交换位置。
class Solution {
public:
void sortColors(vector<int>& nums) {
int n = nums.size();
int r0=0,l2=n-1; // r0: 0的右边界,l2:2的左边界
for(int i=0;i<=l2;i++){ // 必须包含等于,因为nums[r]可能还没处理
if(nums[i]==0) swap(nums[i],nums[r0++]); // 0交换到左边,r0右移
//换完后的理想状态:换过后nums[i]是1(1处在0的右边界和2的左边界之间)
else if(nums[i]==2){
swap(nums[i],nums[l2--]); // 2交换到右边,l2左移
if(nums[i]!=1) i--;
//num[i]==0的情况:2和0换,换过后的0需再换一遍到左边界
//num[i]==2的情况:2和2换,换过后的2需重新处理
}
}
}
};
4.下一个排列
三个关键步骤:
-
找到
i:
i
是第一个破坏从后向前升序的位置,这意味着nums[i]
可以增大以得到更大的排列。 -
找到
j
:nums[j]
是i
之后比nums[i]
大的最小数,交换后nums[i]
增大,但i
之后的部分仍然降序。 -
反转:反转
i+1
之后的部分使其升序,这样这部分最小,确保了整个排列是“下一个”排列。
class Solution {
public:
void nextPermutation(vector<int>& nums) {
int n=nums.size();
int i=n-2;
// 1. 找到第一个不符合逆序升的nums[i]
while(i>=0&&nums[i]>=nums[i+1]) i--;
// 2. 再找第一个比nums[i]大的nums[j],swap两个数
if(i>=0){
int j=n-1;
while(j>=0&&nums[j]<=nums[i]) j--;
swap(nums[i], nums[j]);
}
// 3.反转 i+1 到末尾的部分(使其升序)
reverse(nums.begin()+i+1, nums.end());
}
};
5.多数问题
核心思想:将数组视为链表,并利用快慢指针检测环。
相遇的地点是环的入口(即重复数字):推导如下。
class Solution {
public:
int findDuplicate(vector<int>& nums) {
// 初始化快慢指针,初始位置在数组的起始位置(索引0)
int slow=0,fast=0;
// 第一阶段:检测环的存在(Floyd's Tortoise and Hare算法)
do{
slow=nums[slow]; // 慢指针每次移动一步
fast=nums[nums[fast]]; // 快指针每次移动两步
} while (slow!=fast); // 当快慢指针相遇时,说明存在环
// 第二阶段:找到环的入口(即重复的数字)
slow=0; // 将慢指针重置到起点
while(slow!=fast){
slow=nums[slow]; // 慢指针每次移动一步
fast=nums[fast]; // 快指针每次移动一步
}
// 最终相遇点就是重复的数字
return slow;
}
};