算法通关村第二关——继续讨论数组问题
数组中出现次数超过一半的数字
题目:剑指offer,找出数组中超过一半的数字
思路:总共有三种思路,
- 排序算法,取中位数(不够优化,具体解法略)
- 利用map进行计数
- 利用出现次数,如果下一个数字和当前数字相同则+1,不同则-1,为0则开始下一个数字的统计(累加法)
解法:
-
map计数法
/** * 数组中出现次数超过一半的数字 * 方法一:排序算法,取中位数 * 方法二:利用map进行计数 * @param array * @return */ public int moreThanHalfNum(int[] array) { //1.利用map进行计数(方法二) int length = array.length; HashMap<Integer, Integer> countMap = new HashMap<>(12); for (int i = 0; i < array.length; i++) { countMap.put(array[i],countMap.getOrDefault(array[i],0)+1); if (countMap.get(array[i])>length/2){ return array[i]; } } return 0; }
-
累加法
/** * 数组中出现次数超过一半的数字 * 方法一:排序算法,取中位数 * 方法二:利用map进行计数 * 方法三:利用出现次数,如果下一个数字和当前数字相同则+1,不同则-1,为0则开始下一个数字的统计(累加法) * @param array * @return */ public int moreThanHalfNum2(int[] array) { //1.利用方法三累加法计算 int count = 0; Integer candidate = 0; for (int num : array) { if (count == 0) { candidate = num; } count += (num == candidate) ? 1 : -1; } return candidate; }
数组中只出现一次的数字
题目:leetCode:136
思路:有多种思路,
- 使用set集合不重复的特点,最后留在set集合当中的就是只出现一次的数字
- 利用异或的位运算去解决
解法:
-
set集合
/** * 只出现一次数字 * 1.方法一使用set集合 * 2.方法二 * @param nums * @return */ public int singleNumber(int[] nums) { //使用set集合 HashSet<Integer> integerHashSet = new HashSet<>(); for (int num : nums) { if (integerHashSet.contains(num)){ integerHashSet.remove(num); }else { integerHashSet.add(num); } } for (Integer i : integerHashSet) { return i; } return 0; }
-
利用异或运算
/** * 只出现一次数字 * 1.方法一使用set集合 * 2.方法二 使用位运算 * @param nums * @return */ public int singleNumber(int[] nums) { //使用位进行异或运算 int i = 0; //遍历数组进行位运算 for (int num : nums) { i = i^num; } return i; }
数组中只出现一次的数字2
题目:leetCode:137,现在其他数字均会重复3次,只有一个数字会重复一次
思路:对于该题目有多种思路
- 常规,利用map来进行计数
- 利用有限状态自动机+位运算来解决,因为重复数字转换成二进制会在每一位上出现 3次或者是3的倍数(多个重复数字,)所以只要把每一位上求3的余算出,那 就是对应只出现一次的数字
解法:
-
利用map
/** * 只出现一次数字 * 1.方法一使用map集合 * 2.方法二 使用位运算 * @param nums * @return */ public int singleNumber(int[] nums) { //使用map集合 HashMap<Integer, Integer> map = new HashMap<>(12); for (int num : nums) { int i = map.getOrDefault(num, 0) + 1; map.put(num,i); } //循环遍历,找出map中等于1的 for (Map.Entry<Integer, Integer> entry : map.entrySet()) { if (entry.getValue()==1){ return entry.getKey(); } } return 0; }
-
利用异或+与进行位运算(以后在研究)
class Solution { public int singleNumber(int[] nums) { int ones = 0, twos = 0; for(int num : nums){ ones = ones ^ num & ~twos; twos = twos ^ num & ~ones; } return ones; } } //具体解题过程参考:https://leetcode.cn/problems/single-number-ii/solutions/8944/single-number-ii-mo-ni-san-jin-zhi-fa-by-jin407891/?envType=list&envId=Bln79jJg
颜色分类问题(荷兰国旗问题)
题目:leetCdoe:75
思路:
- 基于冒泡排序的双指针(快慢指针),先处理0的位置,在处理1的位置,总共遍历两次
- 只要求一次遍历,可以利用三指针的思路,一个指针是0的代表,一个是1的代表,一个是2的代表
解法:
-
基于冒泡排序的双指针(快慢指针)
/** * 方法一:基于冒泡排序的双指针(快慢指针) * 方法二: * @param nums */ public void sortColors(int[] nums) { //基于冒泡排序双指针 int slow = 0; //1.第一次排序,把0交换到最前 for (int fast = 0; fast < nums.length; fast++) { if (nums[fast]==0){ int tmp = nums[fast]; nums[fast] = nums[slow]; nums[slow] =tmp; slow++; } } for (int fast = slow; fast < nums.length; fast++) { if (nums[fast]==1){ int tmp = nums[fast]; nums[fast] = nums[slow]; nums[slow] =tmp; slow++; } } }
-
基于三指针的排序算法(还有一种类似于双指针的一次循环)
/** * 方法一:基于冒泡排序的双指针(快慢指针) * 方法二:用三个指针解决一次遍历 * @param nums */ public void sortColors(int[] nums) { //基于三个指针解决一次遍历 int left = 0; int right = nums.length-1; int index = 0; //1.第一次排序,把0交换到最前 while ( index<=right) { //先处理2的情况 if (nums[index]==0){ swap(nums,index++,left++); }else if (nums[index]==2&&index!=right){ swap(nums,index,right--); }else { index++; } } } private void swap(int[]nums,int i,int j){ int tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; }