96.只出现一次的数字
题目描述
给你一个 非空 整数数组 nums
,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
你必须设计并实现线性时间复杂度的算法来解决此问题,且该算法只使用常量额外空间。
示例 1 :
**输入:**nums = [2,2,1]
**输出:**1
示例 2 :
**输入:**nums = [4,1,2,1,2]
**输出:**4
示例 3 :
**输入:**nums = [1]
**输出:**1
提示:
1 <= nums.length <= 3 * 10^4
-3 * 10^4 <= nums[i] <= 3 * 10^4
- 除了某个元素只出现一次以外,其余每个元素均出现两次。
思路:异或
异或操作有以下性质:
- 任何数和 0 异或,结果仍然是它本身:
a ^ 0 = a
。 - 任何数和自身异或,结果是 0:
a ^ a = 0
。 - 异或操作满足交换律和结合律:
a ^ b ^ a = (a ^ a) ^ b = 0 ^ b = b
。
基于以上性质,我们可以将所有数字进行异或操作,最终的结果就是只出现一次的数字。
代码
int singleNumber(int* nums, int numsSize) {
int ans=0;
for(int i=0;i<numsSize;i++){
ans^=nums[i];
}
return ans;
}
97.多数元素
题目描述
给定一个大小为 n
的数组 nums
,返回其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋
的元素。
你可以假设数组是非空的,并且给定的数组总是存在多数元素。
示例 1:
输入:nums = [3,2,3]
输出:3
示例 2:
输入:nums = [2,2,1,1,1,2,2]
输出:2
提示:
n == nums.length
1 <= n <= 5 * 10^4
-10^9 <= nums[i] <= 10^9
**进阶:**尝试设计时间复杂度为 O(n)、空间复杂度为 O(1) 的算法解决此问题。
思路: Boyer-Moore 投票算法
- 初始化候选元素
candidate
和计数器count
。 - 遍历数组:
- 如果
count == 0
,则将当前元素设为候选元素。 - 如果当前元素等于候选元素,则
count++
,否则count--
。
- 如果
- 最终候选元素就是多数元素。
有疑问的可以举例分析一遍,这里就不给证明了。
代码
#include <stdio.h>
int majorityElement(int* nums, int numsSize) {
int candidate = nums[0]; // 初始化候选元素
int count = 1; // 初始化计数器
// 遍历数组
for (int i = 1; i < numsSize; i++) {
if (count == 0) {
// 如果计数器为 0,更新候选元素
candidate = nums[i];
count = 1;
} else if (nums[i] == candidate) {
// 如果当前元素等于候选元素,计数器加 1
count++;
} else {
// 否则计数器减 1
count--;
}
}
// 返回候选元素
return candidate;
}
98.颜色分类
题目描述
给定一个包含红色、白色和蓝色、共 n
个元素的数组 nums
,原地 对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。
我们使用整数 0
、 1
和 2
分别表示红色、白色和蓝色。
必须在不使用库内置的 sort 函数的情况下解决这个问题。
示例 1:
输入:nums = [2,0,2,1,1,0]
输出:[0,0,1,1,2,2]
示例 2:
输入:nums = [2,0,1]
输出:[0,1,2]
提示:
n == nums.length
1 <= n <= 300
nums[i]
为0
、1
或2
进阶:
- 你能想出一个仅使用常数空间的一趟扫描算法吗?
方法 1:计数排序
- 统计数组中
0
、1
和2
的个数。 - 根据统计结果,重新填充数组。
代码1
void sortColors(int* nums, int numsSize) {
int hash[3];
memset(hash,0,sizeof(int));
for(int i=0;i<numsSize;i++){
hash[nums[i]]++;
}
int index=0;
for(int i=0;i<3;i++){
int cnt=hash[i];
while(cnt--){
nums[index++]=i;
}
}
}
方法 2:双指针(荷兰国旗问题)
- 使用三个指针:
left
、right
和curr
。left
:指向已排序的0
的末尾。right
:指向已排序的2
的开头。curr
:当前遍历的元素。
- 遍历数组:
- 如果
nums[curr] == 0
,将其与nums[left]
交换,left++
,curr++
。 - 如果
nums[curr] == 2
,将其与nums[right]
交换,right--
。 - 如果
nums[curr] == 1
,直接curr++
。
- 如果
代码
#include <stdio.h>
void swap(int* a, int* b) {
int temp = *a;
*a = *b;
*b = temp;
}
void sortColors(int* nums, int numsSize) {
int left = 0; // 指向已排序的 0 的末尾
int right = numsSize - 1; // 指向已排序的 2 的开头
int curr = 0; // 当前遍历的元素
while (curr <= right) {
if (nums[curr] == 0) {
// 如果当前元素是 0,将其与 nums[left] 交换
swap(&nums[curr], &nums[left]);
left++;
curr++;
} else if (nums[curr] == 2) {
// 如果当前元素是 2,将其与 nums[right] 交换
swap(&nums[curr], &nums[right]);
right--;
} else {
// 如果当前元素是 1,直接跳过
curr++;
}
}
}
99.下一个排列
题目描述
整数数组的一个 排列 就是将其所有成员以序列或线性顺序排列。
- 例如,
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
思路:双指针
- 从后向前查找第一个降序的位置:
- 找到第一个满足
nums[i] < nums[i+1]
的位置i
。 - 如果找不到这样的位置,说明整个数组是降序排列的,直接反转数组即可。
- 找到第一个满足
- 从后向前查找第一个大于
nums[i]
的位置:- 找到第一个满足
nums[j] > nums[i]
的位置j
。
- 找到第一个满足
- 交换
nums[i]
和nums[j]
:- 交换这两个元素。
- 反转
i+1
到末尾的部分:- 将
i+1
到末尾的部分反转,使其升序排列。
- 将
代码
#include <stdio.h>
// 交换两个元素
void swap(int* a, int* b) {
int temp = *a;
*a = *b;
*b = temp;
}
// 反转数组的一部分
void reverse(int* nums, int start, int end) {
while (start < end) {
swap(&nums[start], &nums[end]);
start++;
end--;
}
}
void nextPermutation(int* nums, int numsSize) {
int i = numsSize - 2;
// 从后向前查找第一个降序的位置
while (i >= 0 && nums[i] >= nums[i + 1]) {
i--;
}
if (i >= 0) {
int j = numsSize - 1;
// 从后向前查找第一个大于 nums[i] 的位置
while (j >= 0 && nums[j] <= nums[i]) {
j--;
}
// 交换 nums[i] 和 nums[j]
swap(&nums[i], &nums[j]);
}
// 反转 i+1 到末尾的部分
reverse(nums, i + 1, numsSize - 1);
}
100.寻找重复数
题目描述
给定一个包含 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 <= 10^5
nums.length == n + 1
1 <= nums[i] <= n
nums
中 只有一个整数 出现 两次或多次 ,其余整数均只出现 一次
进阶:
- 如何证明
nums
中至少存在一个重复的数字? - 你可以设计一个线性级时间复杂度
O(n)
的解决方案吗?
思路:哈希
用hash表记录某个数是否出现过,先将hash表全部初始化为false,表示全部都没有出现过,接着遍历数组:
- 对于每个数字
nums[i]
:- 如果
hash[nums[i]]
为true
,说明该数字已经出现过,直接返回nums[i]
。 - 否则,将
hash[nums[i]]
设置为true
。
- 如果
代码
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
int findDuplicate(int* nums, int numsSize) {
bool hash[numsSize]; // 定义哈希表
memset(hash, false, sizeof(hash)); // 初始化哈希表为 false
for (int i = 0; i < numsSize; i++) {
if (hash[nums[i]]) {
// 如果当前数字已经出现过,返回该数字
return nums[i];
} else {
// 否则,标记该数字为已出现
hash[nums[i]] = true;
}
}
return 0; // 如果没有找到重复数字,返回 0
}