一.题目类型简介
数组中数字出现的次数是一类经典的问题,通常让我们求数组中数字出现的次数及其衍生的问题,比如,只出现一次的数字,只出现两次的数字,在一个数组中只有一个数字出现一次,其他出现两次或者三次,数组中出现次数最多,数组中出现的次数超过半数等。
二.经常使用的方法
在这类题目中,我们可以使用暴力循环、HashMap、排序、异或运算等来解决,但是在面试等场景下,此类题目通常要求的时间复杂度和空间复杂度或者不使用额外空间,即比暴力解在时间和空间上要更优的方法。
三.典型例题
1.leetcode 136 只出现一次的数字
给你一个 非空 整数数组 nums ,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
你必须设计并实现线性时间复杂度的算法来解决此问题,且该算法只使用常量额外空间。
输入:nums = [2,2,1]
输出:1
输入:nums = [4,1,2,1,2]
输出:4
输入:nums = [1]
输出:1
排序
class Solution {
public int singleNumber(int[] nums) {
Arrays.sort(nums);
for(int i = 0; i < nums.length-1; i += 2){
if(nums[i] != nums[i+1]){
return nums[i];
}
}
return nums[nums.length-1];
}
}
HashMap
class Solution {
public int singleNumber(int[] nums) {
HashMap<Integer,Integer> map = new HashMap<>();
for(int i = 0; i < nums.length; i++){
if(map.containsKey(nums[i])){
map.put(nums[i],map.get(nums[i])+1);
}
else{
map.put(nums[i],1);
}
}
for(Map.Entry<Integer, Integer> entry : map.entrySet()){
if(entry.getValue() == 1){
return entry.getKey();
}
}
return nums[0];
}
}
HashSet优化 Ref.[1][2]
public static int singleNumber_HashSet(int[] nums) {
int len = nums.length;
Set<Integer> set = new HashSet<>();
for (int i = 0; i < len; i++) {
// 尝试将当前元素加入 set
if (!set.add(nums[i])) {
// 当前元已经存在于 set,即当前元素第二次出现,从 set 删除
set.remove(nums[i]);
}
}
// 最后只剩一个不重复的元素
return set.iterator().next();
}
异或运算
class Solution {
public int singleNumber(int[] nums) {
int ans = 0;
for(int i : nums){
ans ^= i;
}
return ans;
}
}
异或基本运算
和零运算:
本身运算:
交换律和结合律:
"必须设计并实现线性时间复杂度的算法来解决此问题,且该算法只使用常量额外空间。"按照题目的要求,只有此方法才满足,排序的时间是,而HashMap和HashSet的优化是,但是空间上不满足要求,开辟了Hash空间,空间复杂度是的,只有异或的方法,或者说,本质上就希望我们使用异或的方法。
当在一个数组中只有一个数字出现了一次,我们不妨假设数组已经是排好序的,那么对于排好序的数组来说,有着一对一对的数,比如1和1,2和2,9和9,我们发现,1 1=0,其他一对相同的数字也是如此,最后只剩下了0和一个单个的数字,而
2.剑指 Offer II 004 只出现一次的数字
给你一个整数数组 nums ,除某个元素仅出现 一次 外,其余每个元素都恰出现 三次 。请你找出并返回那个只出现了一次的元素。
输入:nums = [2,2,3,2]
输出:3
输入:nums = [0,1,0,1,0,1,100]
输出:100
排序
class Solution {
public int singleNumber(int[] nums) {
Arrays.sort(nums);
int n = nums.length;
for (int i = 0; i < n - 2; i += 3) {
if (nums[i + 1] != nums[i]) return nums[i];
}
return nums[n - 1];
}
}
HashMap
class Solution {
public int singleNumber(int[] nums) {
HashMap<Integer,Integer> map = new HashMap<>();
for(int i = 0; i < nums.length; i++){
if(map.containsKey(nums[i])){
map.put(nums[i],map.get(nums[i])+1);
}
else{
map.put(nums[i],1);
}
}
for(Map.Entry<Integer, Integer> entry : map.entrySet()){
if(entry.getValue() == 1){
return entry.getKey();
}
}
return nums[0];
}
}
数位运算{Ref.3}
class Solution {
public int singleNumber(int[] nums) {
int ret = 0;
for (int i = 0; i < 32; i++) {
int cnt = 0;
for (int num : nums) {
cnt += num >> i & 1;
}
if (cnt % 3 != 0) {
ret |= 1 << i;
}
}
return ret;
}
}
异或{Ref.4}
class Solution {
public int singleNumber(int[] nums) {
int one = 0, two = 0;
for(int x : nums){
one = one ^ x & ~two;
two = two ^ x & ~one;
}
return one;
}
}
本种方法思想牵扯到有限状态自动机,更多详情可参考[4]。
参考来源Ref.
[1] leetcode 神奇小超 通过哈希集、按位异或操作符解决(解题思路)
[2] leetcode ageovb 通过哈希集、按位异或操作符解决(解题思路)
[3] leetcode 清风Python 刷穿剑指offer-Day02-整数II 004.只出现一次的数字 位运算讲解
[4] leetcode Krahets 剑指 Offer II 004. 只出现一次的数字(有限状态自动机 + 位运算,清晰图解)