「这是我参与2022首次更文挑战的第37天,活动详情查看:2022首次更文挑战」
前言
笔者除了大学时期选修过《算法设计与分析》和《数据结构》还是浑浑噩噩度过的(当时觉得和编程没多大关系),其他时间对算法接触也比较少,但是随着开发时间变长对一些底层代码/处理机制有所接触越发觉得算法的重要性,所以决定开始系统的学习(主要是刷力扣上的题目)和整理,也希望还没开始学习的人尽早开始。
系列文章收录《算法》专栏中。
问题描述
给你一个整数数组 nums ,除某个元素仅出现 一次 外,其余每个元素都恰出现 三次 。请你找出并返回那个只出现了一次的元素。
示例 1:
输入:nums = [2,2,3,2]
输出:3
示例 2:
输入:nums = [0,1,0,1,0,1,100]
输出:100
提示:
- 1 <= nums.length <= 3 * 104
- -231 <= nums[i] <= 231 - 1
- nums 中,除某个元素仅出现 一次 外,其余每个元素都恰出现 三次
进阶:你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?
剖析
方法一
这道题,第一反应就是遍历一遍,把数组元素全部放进map中,key为元素,value为元素出现次数。我们可以,在遍历的过程中把已经知道出现三次的从map中移除,这样最后省去遍历map同时又节约了空间。
空间复杂度为O(n):情况最差的情况下需要最后才能从map中移除非目标key。所以为(⌈n/3⌉+1)*2。但是这个方法需要put和remove map,需要计算hash、存在扩容等,走一趟会比较慢。
时间复杂度为O(n):遍历一遍数组存放map需要n次。代码如下:
public static int getOnlyOnceNum(int[] nums) {
Map<Integer, Integer> numCountMap = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
Integer numCount = numCountMap.get(nums[i]);
if (numCount == null) {
numCountMap.put(nums[i], 1);
} else if (numCount == 2) {
numCountMap.remove(nums[i]);
} else {
numCountMap.put(nums[i], ++numCount);
}
}
for (Integer key : numCountMap.keySet()) {
return key;
}
return 0;
}
方法二
那么有没有办法降低空间又走一趟比较快的方式呢?如果把数字代入到二进制世界,可以利用题目的约束条件“除某个元素仅出现 一次 外,其余每个元素都恰出现 三次”,对所有数字的对应二进制的每一位进行统计,因为只有一个元素出现1次,所以对应位如果整除3不等于0的话就能确定只出现1次元素的所有1的位置,只出现一次的元素的所有1都能确定位置,那么这个数就能确定了。代码如下:
public static int getOnlyOnceNum1(int[] nums) {
int oneNum = 0;
for (int i = 0; i < 32; ++i) {
int everyBitCount = 0;
for (int num : nums) {
everyBitCount += (num >> i & 1);
}
if (everyBitCount % 3 != 0) {
oneNum |= (1 << i);
}
}
return oneNum;
}
时间复杂度O(n):32*n
空间复杂度O(1)
可以发现方法二的时间复杂度还不如方法一,但是位运算比map的操作速度会快很多,空间复杂度明显比方法一低,所以时间空间的综合效率方法二比1好。