剑指offer39 数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。
力扣官方题解
示例 1:
输入: [1, 2, 3, 2, 2, 2, 5, 4, 2]
输出: 2
可以使用排序,这里说一定存在出现次数超过一半的数字了,那么先对数组进行排序。在一个有序数组中次数超过一半的必定是中位数,所以可以直接取出中位数。
使用Hash,我们先创建一个HashMap的key是元素的值,value是已经出现的次数,然后遍历数组来统计所有元素出现的次数。最后再次遍历Hash,找到出现次数超过一半的数字。OK,第二种方法出来了,代码就是:
public int moreThanHalfNum(int [] array) {
if(array==null)
return 0;
Map<Integer,Integer> res=new HashMap<>();
int len = array.length;
for(int i=0;i<array.length;i++){
res.put(array[i],res.getOrDefault(array[i],0)+1);
if(res.get(array[i])>len/2)
return array[i];
}
return 0;
}
Boyer-Moore 投票算法
根据数组特点,数组中有一个数字出现的次数超过数组长度的一半,也就是说它出现的次数比其他所有数字出现的次数之和还要多。因此,我们可以在遍历数组的时候设置两个值:一个是数组中的数result,另一个是出现次数times。当遍历到下一个数字的时候,如果与result相同,则次数加1,不同则次数减一,当次数变为0的时候说明该数字不可能为多数元素,将result设置为下一个数字,次数设为1。这样,当遍历结束后,最后一次设置的result的值可能就是符合要求的值(如果有数字出现次数超过一半,则必为该元素,否则不存在),因此,判断该元素出现次数是否超过一半即可验证应该返回该元素还是返回0。这种思路是对数组进行了两次遍历,复杂度为O(n)。
在这里times最小为0,如果等于0了,遇到下一个元素就开始+1。看两个例子, [1,2,1,3,1,4,1]和[2,1,1,3,1,4,1]两个序列。
首先看 [1,2,1,3,1,4,1],
开始的时候result=1,times为1
然后result=2,与上一个不一样,所以times减一为0
然后result=1,与上一个不一样,times已经是0了,遇到新元素就加一为1
然后result=3,与上一个不一样,times减一为0
然后result=1,与上一个不一样,times已经是0了,遇到新元素就加一为1
然后result=4,与上一个不一样,times减一为0
然后result=1,与上一个不一样,times加一为1
所以最终重复次数超过一半的就是1了
这里可能有人会有疑问,假如1不是刚开始的元素会怎样呢?例如假如是序列[2,1,1,3,1,4,1],你按照上面的过程写一下,会发现扛到最后的还是result=1,此时times为1。
public int majorityElement(int[] nums) {
int count = 0;
Integer candidate = null;
for (int num : nums) {
if (count == 0) {
candidate = num;
}
count += (num == candidate) ? 1 : -1;
}
return candidate;
}
LeetCode136.给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次,找出那个只出现了一次的元素。
示例1:
输入:[2,2,1] 输出:1
示例2:
输入:[4,1,2,1,2] 输出:4
使用set集合 利用Set集合不存重复值的特性,当要添加的元素key与集合中已存在的数重复时,不再进行添加操作,而是将集合中的key一起删掉,这样整个数组遍历完后,集合中就只剩下了那个只出现了一次的数字了。
public static Integer findOneNum(int[] arr) {
Set<Integer> set = new HashSet<Integer>();
for(int i : arr) {
if(!set.add(i))//添加不成功返回false,前加上!运算符变为true
set.remove(i);//移除集合中与这个要添加的数重复的元素
}
//注意边界条件的处理
if(set.size() == 0)
return null;
//如果Set集合长度为0,返回null表示没找到
return set.toArray(new Integer[set.size()])[0];
}
使用位运算
异或运算的几个规则是:
0^0 = 0;
0^a = a;
a^a = 0;
a ^ b ^ a = b.
0与其他数字异或的结果是那个数字,相等的数字异或得0。要操作的数组中除了某个数字只出现了一次之外,其他数字都出现了两次,所以可以定义一个变量赋初始值为0,用这个变量与数组中每个数字做异或运算,并将这个变量值更新为那个运算结果,直到数组遍历完毕,最后得到的变量的值就是数组中只出现了一次的数字了。这种方法只需遍历一次数组即可,代码如下:
public static int findOneNum(int[] arr) {
int flag = 0;
for(int i : arr) {
flag ^= i;
}
return flag;
}
在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。
示例 1:
输入:
[2, 3, 1, 0, 2, 5, 3]
输出:2 或 3
使用hash
public int findRepeatNumber(int[] nums) {
HashSet set = new HashSet<>();
for(int num : nums){
if(set.contains(num)) return num;
set.add(num);
}
return -1;
}
原地交换
遍历数组 nums ,设索引初始值为 i=0 :
若遍历完毕尚未返回,则返回 −1。
public int findRepeatNumber(int[] nums) {
int i = 0;
while(i < nums.length) {
if(nums[i] == i) {
i++;
continue;
}
if(nums[nums[i]] == nums[i]) return nums[i];
int tmp = nums[i];
nums[i] = nums[tmp];
nums[tmp] = tmp;
}
return -1;
}
给你一个整数数组 nums
,除某个元素仅出现 一次 外,其余每个元素都恰出现 **三次 。**请你找出并返回那个只出现了一次的元素。
示例 1:
输入:nums = [2,2,3,2]
输出:3
示例 2:
输入:nums = [0,1,0,1,0,1,99]
输出:99
使用hash
我们可以使用哈希映射统计数组中每个元素的出现次数。对于哈希映射中的每个键值对,键表示一个元素,值表示其出现的次数。
第一轮遍历数组 把数组中的数放入hashMap中并统计其出现次数
第二轮遍历map 找出出现次数为1的数
public int singleNumber(int[] nums) {
HashMap<Integer,Integer> map = new HashMap<>();
for(int num : nums){
map.put(num, map.getOrDefault(num, 0) + 1);
}
int ans = 0;
Set<Map.Entry<Integer, Integer>> entries = map.entrySet();
for (Map.Entry<Integer, Integer> entry : entries) {
if (entry.getValue() == 1)
return entry.getKey();
}
return -1;
}
位数统计
使用一个长度为 32 的数组 cnt[] 记录下所有数值的每一位共出现了多少次 1,再对 cnt[] 数组的每一位进行 mod 3 操作,重新拼凑出只出现一次的数值。
举个 ,考虑样例 [1,1,1,3],1 和 3 对应的二进制表示分别是 00…001 和 00…011,存入 cnt[] 数组后(00…001 + 00…001 + 00…001 + 00…011)得到 [4,1,0…0]。进行 mod 3 操作后得到 [1,1,0…0],再转为十进制数字即可得「只出现一次」的答案 3。
public int singleNumber(int[] nums) {
int[] cnt = new int[32];
for (int x : nums) {
for (int i = 0; i < 32; i++) {
if (((x >> i) & 1) == 1) {
cnt[i]++;
}
}
}
int ans = 0;
for (int i = 0; i < 32; i++) {
if ((cnt[i] % 3 & 1) == 1) {
ans += (1 << i);
}
}
return ans;
}