leetcode-3周攻克[数据结构] Day1
136. 只出现一次的数字
难度:简单
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
说明:你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?
示例 1:
1 | 输入: [2,2,1] |
示例 2:
1 | 输入: [4,1,2,1,2] |
题目出处:https://leetcode-cn.com/problems/single-number
###题解:
这道题一开始我想的是用哈希表记录每个整数出现的次数,但是这样有额外的o(n)复杂度的空间,看过题解后发现可以用位运算中的异或运算。
解法代码:
1 | class Solution { |
复杂度分析
- 时间复杂度:O(n)O(n),其中 nn 是数组长度。只需要对数组遍历一次。
- 空间复杂度:O(1)O(1)。
提交结果 | 执行时间 | 内存消耗 | 语言 | 提交时间 |
---|---|---|---|---|
通过 | 8 ms | 16.4 MB | C++ | 2022/05/01 20:38 |
169. 多数元素
难度 简单
给定一个大小为 n 的数组 nums ,返回其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。
你可以假设数组是非空的,并且给定的数组总是存在多数元素。
示例 1:
1 | 输入:nums = [3,2,3] |
示例 2:
1 | 输入:nums = [2,2,1,1,1,2,2] |
提示:
- n == nums.length
- 1 <= n <= 5 * 104
- -109 <= nums[i] <= 109
进阶:尝试设计时间复杂度为 O(n)、空间复杂度为 O(1) 的算法解决此问题。
题解:
解法一:
排序法:
用sort函数排序,无论数组个数数组是否奇偶,中位数就是众数。
解法代码:
1 | class Solution { |
提交结果 | 执行时间 | 内存消耗 | 语言 | 提交时间 |
---|---|---|---|---|
通过 | 24 ms | 19 MB | C++ | 2022/05/01 20:38 |
解法二:
摩尔排序法:
- 一开始错误思路:
1
2
3
4
5
6
7
8
9
10
11
12
13class Solution {
public:
int majorityElement(vector<int>& nums) {
int tag,count=0;
for(int i=0;i<nums.size();i++){
if(count==0) tag=nums[i];
if(tag=nums[i]) count++;
else count--;
}
return tag;
}
};
正确思路及解法
如果我们把众数记为 +1+1,把其他数记为 -1−1,将它们全部加起来,显然和大于 0,从结果本身我们可以看出众数比其他数多。
算法
Boyer-Moore 算法的本质和方法四中的分治十分类似。我们首先给出 Boyer-Moore 算法的详细步骤:
我们维护一个候选众数 candidate 和它出现的次数 count。初始时 candidate 可以为任意值,count 为 0;
我们遍历数组 nums 中的所有元素,对于每个元素 x,在判断 x 之前,如果 count 的值为 0,我们先将 x 的值赋予 candidate,随后我们判断 x:
如果 x 与 candidate 相等,那么计数器 count 的值增加 1;
如果 x 与 candidate 不等,那么计数器 count 的值减少 1。
在遍历完成后,candidate 即为整个数组的众数。
我们举一个具体的例子,例如下面的这个数组:
1 | [7, 7, 5, 7, 5, 1 | 5, 7 | 5, 5, 7, 7 | 7, 7, 7, 7] |
在遍历到数组中的第一个元素以及每个在 | 之后的元素时,candidate 都会因为 count 的值变为 0 而发生改变。最后一次 candidate 的值从 5 变为 7,也就是这个数组中的众数。
Boyer-Moore 算法的正确性较难证明,这里给出一种较为详细的用例子辅助证明的思路,供读者参考:
首先我们根据算法步骤中对 count 的定义,可以发现:在对整个数组进行遍历的过程中,count 的值一定非负。这是因为如果 count 的值为 0,那么在这一轮遍历的开始时刻,我们会将 x 的值赋予 candidate 并在接下来的一步中将 count 的值增加 1。因此 count 的值在遍历的过程中一直保持非负。
那么 count 本身除了计数器之外,还有什么更深层次的意义呢?我们还是以数组
1 | [7, 7, 5, 7, 5, 1 | 5, 7 | 5, 5, 7, 7 | 7, 7, 7, 7] |
作为例子,首先写下它在每一步遍历时 candidate 和 count 的值:
1 | nums: [7, 7, 5, 7, 5, 1 | 5, 7 | 5, 5, 7, 7 | 7, 7, 7, 7] |
我们再定义一个变量 value,它和真正的众数 maj 绑定。在每一步遍历时,如果当前的数 x 和 maj 相等,那么 value 的值加 1,否则减 1。value 的实际意义即为:到当前的这一步遍历为止,众数出现的次数比非众数多出了多少次。我们将 value 的值也写在下方:
1 | nums: [7, 7, 5, 7, 5, 1 | 5, 7 | 5, 5, 7, 7 | 7, 7, 7, 7] |
有没有发现什么?我们将 count 和 value 放在一起:
1 | nums: [7, 7, 5, 7, 5, 1 | 5, 7 | 5, 5, 7, 7 | 7, 7, 7, 7] |
发现在每一步遍历中,count 和 value 要么相等,要么互为相反数!并且在候选众数 candidate 就是 maj 时,它们相等,candidate 是其它的数时,它们互为相反数!
正确代码:
1 | class Solution { |
复杂度分析
时间复杂度:O(n)O(n)。Boyer-Moore 算法只对数组进行了一次遍历。
空间复杂度:O(1)O(1)。Boyer-Moore 算法只需要常数级别的额外空间。
题解链接:https://leetcode-cn.com/problems/majority-element/solution/duo-shu-yuan-su-by-leetcode-solution/
15. 三数之和
难度 中等
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组。
示例 1:
1 | 输入:nums = [-1,0,1,2,-1,-4] |
示例 2:
1 | 输入:nums = [] |
示例 3:
1 | 输入:nums = [0] |
提示:
- 0 <= nums.length <= 3000
- -105 <= nums[i] <= 105
这题没有思路,直接看题解吧。
题解:
方法:排序 + 双指针
题目中要求找到所有「不重复」且和为 0 的三元组,这个「不重复」的要求使得我们无法简单地使用三重循环枚举所有的三元组。这是因为在最坏的情况下,数组中的元素全部为 0,即
1 | [0, 0, 0, 0, 0, ..., 0, 0, 0] |
任意一个三元组的和都为 00。如果我们直接使用三重循环枚举三元组,会得到 O(N^3)个满足题目要求的三元组(其中 NN 是数组的长度)时间复杂度至少为 O(N^3)。在这之后,我们还需要使用哈希表进行去重操作,得到不包含重复三元组的最终答案,又消耗了大量的空间。这个做法的时间复杂度和空间复杂度都很高,因此我们要换一种思路来考虑这个问题。
「不重复」的本质是什么?我们保持三重循环的大框架不变,只需要保证:
第二重循环枚举到的元素不小于当前第一重循环枚举到的元素;
第三重循环枚举到的元素不小于当前第二重循环枚举到的元素。
也就是说,我们枚举的三元组 (a, b, c) 满足a≤b≤c,保证了只有 (a, b, c)这个顺序会被枚举到,而 (b, a, c)、(c, b, a)等等这些不会,这样就减少了重复。要实现这一点,我们可以将数组中的元素从小到大进行排序,随后使用普通的三重循环就可以满足上面的要求。
同时,对于每一重循环而言,相邻两次枚举的元素不能相同,否则也会造成重复。举个例子,如果排完序的数组为
1 | [0, 1, 2, 2, 2, 3] |
我们使用三重循环枚举到的第一个三元组为 (0, 1, 2),如果第三重循环继续枚举下一个元素,那么仍然是三元组 (0, 1, 2),产生了重复。因此我们需要将第三重循环「跳到」下一个不相同的元素,即数组中的最后一个元素 3,枚举三元组 (0, 1, 3)。
1 | class Solution { |
题解链接:https://leetcode-cn.com/problems/3sum/solution/san-shu-zhi-he-by-leetcode-solution/