数组中1个数出现一次其他出现2次
Leetcode136 https://leetcode.com/problems/single-number/
思路:有一个异常数:整体抑或一遍。适合只有1个数出现奇数次,其他数出现偶数次
public int singleNumber(int[] nums) {
for(int i = 1; i < nums.length; i++) nums[0] ^= nums[i];
return nums[0];
}
如果使用哈希,有两种可行的方法:
第一,就是存储每个数的个数,最后遍历一遍输出所需个数的数。这个适用于所有找数组异常数情况,但空间复杂度很高。
第二,设置一个32位数组(或者int)存储所有数不同位上1的个数,最后按情况取余。这个要视情况而定,可以节省很多空间。
2. 数组中2个数出现一次其他出现2次
剑指offer40_数组中两个数出现一次其他出现两次
思路:有两个异常数:将数组按两个异常数分开(按照两个异常数某个不同位分),再分别抑或。适合有2个数出现奇数次,其他数出现偶数次。
Trick: 找不同位可以找第一个不同的位置,即抑或后右数第一个1,用(i&(i-1))注意位运算优先级最低。!!
public void twoNumber(int[] nums){
int a = 0, b = 0, rs = 0;
for(int i = 0; i < nums.length; i++) rs ^= nums[i];
rs = rs ^ rs & rs - 1;
for(int i = 0; i < nums.length; i++){
if((nums[i] & rs) == 0) a ^= nums[i];
else b ^= nums[i];
}
System.out.println(a+" "+b);
}
3. 数组中1个数出现一次其他出现3次
Leetcode137 https://leetcode.com/problems/single-number-ii/
Given an array of integers, every element appears three times except for one. Find that single one.
思路: 第一种想法:与第1题hash思路一样,可以利用第二个hash思路记录每位1出现次数。开辟32位数组,记录每个位上1出现的个数,最后各位模k,最后剩的数组转二进制就是这个数。
@Checked
public int singleNumber(int[] nums) {
int[] flag = new int[32];
for(int i = 0; i < nums.length; i++){
for(int j = 0; j < 32; j++, nums[i] >>>= 1){
flag[j] += (nums[i] & 1);
if(flag[j] == 3) flag[j] = 0;
}
}
int rs = 0;
for(int j = 31; j >= 0; j--) rs = rs<<1 | flag[j];
return rs;
}
如果再简化一点:用变量来存储出现一个1、两个1和三个1的分布,最终返回一个1的分布就是这个数
public int singleNumber(int[] nums) {
int one = 0, two = 0, three = 0;
for(int i = 0; i < nums.length; i++){
two |= one & nums[i]; //A[i]&one得到有两个1的位分布,与two或,增加two的位
one ^= nums[i]; //A[i]^one得到一个1的位分布
three = one & two; //one&two可以得到三个1的位分布
one &= ~three; //取反来对one和two的对应位进行清零操作
two &= ~three; //取反来对one和two的对应位进行清零操作
}
}
4. 数组中1个数出现了n/2多次
Leetcode169 https://leetcode.com/problems/majority-element/
剑指offer29_数组中出现超过一半的数字
Given an array of size n, find the majority element. The majority element is the element that appears more than n/2 times.
You may assume that the array is non-empty and the majority element always exist in the array.
本题的思路和方法很多
第一种方法:用hash记录每个数字出现次数,最后遍历一遍得到次数符合条件的数字。时间复杂度o(n),空间复杂度o(n)
@Checked
public int majorityElement(int[] nums) {
if(nums != null){
Map<Integer, Integer> hm = new HashMap<Integer, Integer>();
for(int i = 0; i < nums.length; i++){
int num = hm.containsKey(nums[i]) ? hm.get(nums[i]) : 0;
hm.put(nums[i], ++num);
if(num > nums.length/2) return nums[i];
}
}
return 0;
}
第二种方法:将数组排序,排序后index为n/2的就是该元素。时间复杂度o(nlogn),空间复杂度o(1)
/**
用自己的快排,stackOverFlow了
*/
public int majorityElement(int[] nums) {
if(nums != null){
quickSort(nums, 0, nums.length-1);
return nums[nums.length/2];
}
return 0;
}
public void quickSort(int[] nums, int start, int end){
if(start >= end) return;
int pivot = nums[end], mid = start -1;
for(int i = start; i < end; i++)
if(nums[i] < pivot) swap(nums, ++mid, i);
swap(nums, ++mid, end);
quickSort(nums, start, mid-1);
quickSort(nums, mid+1, end);
}
public void swap(int[] nums, int i, int j){
if(i == j) return;
nums[i] ^= nums[j];
nums[j] ^= nums[i];
nums[i] ^= nums[j];
}
/**
用java提供的sort工具,AC了,果然是效率问题。
*/
public int majorityElement(int[] nums) {
if(nums != null){
Arrays.sort(nums);
return nums[nums.length/2];
}
return 0;
}
第三种方法:利用随机快速排序中找中位数的思想,即分治查找来找到n/2的元素。时间复杂度o(n),空间复杂度o(1)
@Checked
public int majorityElement(int[] nums) {
if(nums != null){
quickSort(nums, 0, nums.length-1);
return nums[nums.length/2];
}
return 0;
}
public void quickSort(int[] nums, int start, int end){
if(start >= end) return;
int pivot = nums[end], mid = start -1;
for(int i = start; i < end; i++)
if(nums[i] < pivot) swap(nums, ++mid, i);
swap(nums, ++mid, end);
if(mid == nums.length/2) return; // 如果找到的刚好index为n/2,返回。
else if(mid < nums.length/2) quickSort(nums, mid+1, end);// 如果该元素在左半边,则继续在右半边找
else quickSort(nums, start, mid-1);//如果该元素在右半边,则继续在左半边找
}
public void swap(int[] nums, int i, int j){
if(i == j) return;
nums[i] ^= nums[j];
nums[j] ^= nums[i];
nums[i] ^= nums[j];
}
第四种方法:利用Moore投票的算法,记录当前元素C和当前元素数量count。遍历数组元素num[i]:
若num[i] == C, 则count++;//增加主元素数量
若num[i] != C, count == 0, 则 num[i] = C, count=1;//更换主元素
若num[i] != C, count != 0, 则 count–;//减少主元素数量
@Checked
public int majorityElement(int[] nums) {
if(nums == null) return 0;
int curN = 0 , count = 0;
for(int i = 0; i < nums.length; i++){
if(count <= 0) {
curN = nums[i];
count = 1;
}else if(nums[i] == curN) count++;
else count--;
}
return curN;
}
第五种方法:利用位特点,记录每个位上1和0的数量,由于同一个数出现了一半以上,统计所有数在每个位上0和1的个数时,1和0哪个多说明该数在该位上是什么。
//代码:用一个数组记录每位上的1与0之差
@Checked
public int majorityElement(int[] nums) {
if(nums == null) return 0;
int[] flag = new int[32];
for(int i = 0; i < nums.length; i++){
for(int j = 0; j < 32; j++, nums[i] >>= 1)
flag[j] = (nums[i] & 1) == 1 ? flag[j] + 1 : flag[j] -1;
}
int rs = 0;
for(int i = 31; i >= 0; i--){
int p = flag[i] < 0 ? 0 : 1;
rs = (rs << 1) | p;
}
return rs;
}