leetcode 多数元素

文章讨论了在解决O(n^2)复杂度问题时如何优化,提到了Boyer-Moore投票算法,这是一种用于寻找数组中出现次数超过n/2的元素的线性时间复杂度算法。作者表达了对高效算法设计的思考,并分享了C语言实现的代码示例。

用两个for就要o(n^2),想要提高效率就要明白其中的道道,哎呀,有点力不从心,主要是一直碰壁哈哈哈哈。

下面是超时的代码,我现在好像还是只能想到这个。

 超时了之后我就知道可以先排序之后再做了,可是哈希表我是真的不行用哈哈哈哈。突然想起来听一个b站上的老师讲操作系统的时候,他用了一个图来讲述以后遇到(我忘记那个类型叫什么了,但是我记得那个图)这类问题,就用那个图,那个图是万能的办法。昨天又有同学说到哈希表的图接口实现,会不会有关系呢,可能是有的,但是我还没有了解。

言归正传,下面的这个算法很巧妙,真的很想知道他们到底是怎么想出来了,大脑的结构不都是一样的吗。

Boyer-Moore 投票算法(真的很简单,而且时间复杂度是线性的)

算法思路

第一步,初始化候选人们candidates以及候选人的票数。
第二步,count
扫描过程中候选人的替换以及票数增减规则如下

  1. 如果与某个候选人匹配,该候选人票数加1,继续循环。
  2. 如果与所有候选人都不匹配,检查候选人票数,如果为0,替换该候选人,不再往下检查。
  3. 如果与所有候选人都不匹配,检查候选人票数,如果不为0,继续检查一个候选人。 

从算法思路上来看,其实摩尔投票算法的核心思想就是相互抵消

C语言的代码如下:

int majorityElement(int* nums, int numsSize){
   //Boyer-Moore 算法
   int count,candidate;
   int i;
   candidate = nums[0];
   count = 0;
   for (i = 0; i < numsSize; i++)
   {
       if (nums[i] == candidate)
       {
           count++;
       }
       else
       {
           count--;
       }
       if (count == 0)
       {
           candidate = nums[i + 1];
       }
   }
   return(candidate);
}

LeetCode 169题多数元素要求在给定大小为 n 的数组中,找到出现次数大于 ⌊ n/2 ⌋ 的元素,且假设数组非空且总是存在多数元素。以下是几种不同复杂度的解法: ### 哈希表法 通过哈希表统计每个元素的出现次数,然后遍历哈希表找出出现次数大于 ⌊ n/2 ⌋ 的元素。时间复杂度为 $O(n)$,空间复杂度为 $O(n)$。 ```java class Solution { public int majorityElement(int[] nums) { //1.哈希表统计元素出现次数 Map<Integer, Integer> numTimes = new HashMap<>(); int n = nums.length; for(int num : nums){ numTimes.put(num, numTimes.getOrDefault(num, 0) + 1); } for(Map.Entry<Integer, Integer> entry: numTimes.entrySet()){ if(entry.getValue() > n/2){ return entry.getKey(); } } return -1; } } ``` ### 排序法 对数组进行排序,由于多数元素出现次数大于 ⌊ n/2 ⌋,排序后位于中间位置的元素即为多数元素。时间复杂度为 $O(nlogn)$,空间复杂度为 $O(logn)$(栈空间)。 ```java class Solution { public int majorityElement(int[] nums) { //2.排序 Arrays.sort(nums); return nums[nums.length/2]; } } ``` ### Boyer - Moore 投票算法算法可以在时间复杂度为 $O(n)$、空间复杂度为 $O(1)$ 的条件下解决问题。其核心思想是维护一个候选多数元素和一个计数器,遍历数组时,若当前元素与候选元素相同则计数器加 1,不同则减 1,当计数器为 0 时更换候选元素。 ```java class Solution { public int majorityElement(int[] nums) { int candidate = null; int count = 0; for (int num : nums) { if (count == 0) { candidate = num; } count += (num == candidate) ? 1 : -1; } return candidate; } } ``` ### 分治法 将数组分成左右两部分,分别求出左右两部分的多数元素,然后合并结果。时间复杂度为 $O(nlogn)$,空间复杂度为 $O(logn)$。 ```java class Solution { // 计算子数组中多数元素出现的个数 private int count(int[] nums, int start, int end, int num){ int count = 0; for(int i = start; i <= end; i++) { if(nums[i] == num) count++; } return count; } private int Rec(int[] nums, int start, int end) { // 当数组只有一个元素时,结束递归 if(start == end) return nums[start]; // 算出中间点,进行划分 int mid = (start + end) /2; // 得到左子数组的多数元素 int left = Rec(nums, start, mid); // 得到右子数组的多数元素 // 注意这里起点时mid+1 int right = Rec(nums, mid+1, end); // 合并过程 // 若左右子数组的多数元素相同,则直接返回 if(left == right) return left; // 否则算出左右多数元素在整个数组出现的次数,次数多的则是大数组的多数元素 int leftcount = count(nums, start, end, left); int rightcount = count(nums, start, end, right); return leftcount > rightcount ? left : right; } public int majorityElement(int[] nums) { return Rec(nums, 0, nums.length-1); } } ``` ### 位运算 将数组中的元素写成二进制形式,统计每一位上 1 出现的次数,若某一位上 1 的个数超过数组长度的一半,则该位为 1,最后组合起来得到多数元素。时间复杂度为 $O(n)$,空间复杂度为 $O(1)$。 ```java public int majorityElement3(int[] nums) { int cur = 0; for (int i = 0, mask = 1; i < 32; i++, mask <<= 1) { int bit = 0; // 计算当前列 1 的个数 for (int j = 0; j < nums.length; j++) { if ((mask & nums[j]) == mask) { bit++; } } // 判断当前列的 1的个数 if (bit > (nums.length / 2)) { cur |= mask; } } return cur; } ``` ### 概率法 随机选择数组中的一个元素,判断其是否为多数元素,重复多次以提高找到多数元素的概率。时间复杂度平均为 $O(n)$,空间复杂度为 $O(1)$。 ```java class Solution { public int majorityElement(int[] nums) { Random r = new Random(); for (int i = 0; i < 10; i++) { int index = r.nextInt(nums.length); if(isMajor(nums,index)) { return nums[index]; } } return -1; } public boolean isMajor(int[] nums ,int index ) { int cnt = 0 ; int halfLen = nums.length/2; for (int num: nums) { if (num == nums[index]) { cnt++; } if (cnt > halfLen) { return true; } } return false; } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值