169. 多数元素

给定一个大小为 n 的数组 nums ,返回其中的多数元素。

多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。

你可以假设数组是非空的,并且给定的数组总是存在多数元素。

示例 1:

输入:nums = [3,2,3]
输出:3

示例 2:

输入:nums = [2,2,1,1,1,2,2]
输出:2

提示:

  • n == nums.length
  • 1 <= n <= 5 * 104
  • -109 <= nums[i] <= 109

进阶:尝试设计时间复杂度为 O(n)、空间复杂度为 O(1) 的算法解决此问题。

我的想法:

之前一眼hash的时候用的是异或,这个题目不是简单的计数,所以大概hash也是可行的吧,嗯...异或只能判断出现的奇偶性。

1 分析一下异或,hash的解法可行性

你的想法尝试使用哈希或异或操作来解决问题,不过这两种方法在这个问题上有一些限制:

1. 异或操作的局限性

你提到 "异或只能判断出现的奇偶性",这是正确的。异或(XOR)的性质是:

  • 相同元素异或结果为 0(如 a ^ a = 0
  • 任何元素与 0 异或结果为自身(如 a ^ 0 = a

因此,如果一个元素出现奇数次,异或结果会保留该元素;但如果出现偶数次,异或结果会抵消为 0。然而,这个问题要求的是 " 出现次数大于 n/2" 的元素,而异或无法直接统计次数或比较频率,所以异或在这里不适用。

2. 哈希表的正确用法

虽然你提到 "hash 也是可行的吧",但需要注意哈希表的具体用法:

  • 错误用法:若用哈希表存储元素的异或结果,无法解决问题(与直接异或数组相同)。
  • 正确用法:用哈希表统计每个元素的出现次数,然后遍历哈希表找到次数超过 n/2 的元素。

例如:

java

运行

import java.util.HashMap;
import java.util.Map;class Solution {public int majorityElement(int[] nums) {Map<Integer, Integer> countMap = new HashMap<>();for (int num : nums) {countMap.put(num, countMap.getOrDefault(num, 0) + 1);}for (Map.Entry<Integer, Integer> entry : countMap.entrySet()) {if (entry.getValue() > nums.length / 2) {return entry.getKey();}}return -1; // 题目保证存在多数元素,不会执行到这里}
}

这个方法的时间和空间复杂度都是 O (n),符合题目要求,但不是最优解。

3. 最优解法:摩尔投票法(Boyer-Moore Voting Algorithm)

如果追求 O (1) 空间复杂度,可以使用摩尔投票法:

java

运行

class Solution {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;}
}

原理

  • 遍历数组时维护一个候选元素 candidate 和计数器 count
  • 遇到与 candidate 相同的元素,count +1;否则 count -1
  • 当 count 减为 0 时,更换候选元素为当前元素。
  • 由于多数元素出现次数超过一半,最终候选元素一定是多数元素。

总结

你的方向(哈希或异或)有一定合理性,但需要注意:

  • 异或无法直接解决该问题,因为它不统计次数,只判断奇偶性。
  • 哈希表需要用于统计次数,而不是异或操作。
  • 摩尔投票法是最优解,满足 O (n) 时间和 O (1) 空间。

2 hash表解法

import java.util.HashMap;
import java.util.Map;class Solution {public int majorityElement(int[] nums) {// 1. 创建一个哈希表,键为数组中的元素,值为该元素出现的次数Map<Integer, Integer> countMap = new HashMap<>();// 2. 遍历数组中的每个元素for (int num : nums) {// 3. 对于当前元素num,获取它在哈希表中已有的计数//    getOrDefault方法:如果num存在,返回其计数;否则返回默认值0int currentCount = countMap.getOrDefault(num, 0);// 4. 将当前元素的计数加1,并更新到哈希表中//    put方法:将键值对 (num, currentCount + 1) 存入哈希表//    如果键num已存在,会覆盖旧值;如果不存在,则创建新的键值对countMap.put(num, currentCount + 1);}// 5. 遍历哈希表,找出出现次数超过数组长度一半的元素for (Map.Entry<Integer, Integer> entry : countMap.entrySet()) {// entry.getKey() 返回当前键(元素值)// entry.getValue() 返回当前值(出现次数)if (entry.getValue() > nums.length / 2) {return entry.getKey();}}// 根据题目假设,数组中总是存在多数元素,因此不会执行到这里return -1;}
}

关于put方法的详细解释

put是 HashMap 类的核心方法,用于存储或更新键值对:

java

运行

map.put(key, value);
  • 功能

    • 如果key不存在于哈希表中:创建新的键值对(key, value)
    • 如果key已存在:用新的value覆盖旧值(旧值会被丢弃)。
  • 示例

    java

    运行

    Map<String, Integer> scores = new HashMap<>();
    scores.put("Alice", 90);    // 添加新键值对:Alice -> 90
    scores.put("Bob", 85);      // 添加新键值对:Bob -> 85
    scores.put("Alice", 95);    // 更新已有键:Alice -> 95(旧值90被覆盖)
    
  • 在本题中的作用

    java

    运行

    countMap.put(num, currentCount + 1);
    
     

    这行代码会将元素num的计数更新为currentCount + 1,无论num之前是否存在于哈希表中。

复杂度分析

  • 时间复杂度:O (n) —— 遍历数组一次,遍历哈希表一次。
  • 空间复杂度:O (n) —— 最坏情况下,哈希表需要存储所有元素。

getOrDefault 是 Java 中 Map 接口提供的一个实用方法,用于安全地获取键对应的值,避免 NullPointerException。我来详细解释它的用法和在这个问题中的作用。

getOrDefault 方法详解

方法签名

java

运行

V getOrDefault(Object key, V defaultValue);
  • 参数
    • key:要查找的键。
    • defaultValue:如果键不存在时返回的默认值。
  • 返回值
    • 如果键存在,返回对应的值;
    • 如果键不存在,返回 defaultValue

为什么需要 getOrDefault

在统计元素次数时,传统写法需要先检查键是否存在,不存在则初始化为 0,存在则加 1:

java

运行

// 传统写法(不使用 getOrDefault)
if (countMap.containsKey(num)) {// 键存在,取出旧值并加 1int oldCount = countMap.get(num);countMap.put(num, oldCount + 1);
} else {// 键不存在,初始化为 1countMap.put(num, 1);
}

使用 getOrDefault 可以简化这个逻辑,一行代码完成统计

java

运行

// 等效写法(使用 getOrDefault)
int currentCount = countMap.getOrDefault(num, 0); // 不存在则默认 0
countMap.put(num, currentCount + 1); // 直接更新计数

在多数元素问题中的应用

让我结合原题代码,逐行解释 getOrDefault 的作用:

java

运行

import java.util.HashMap;
import java.util.Map;class Solution {public int majorityElement(int[] nums) {Map<Integer, Integer> countMap = new HashMap<>();for (int num : nums) {// 1. 获取当前元素 num 的已有计数//    如果 num 第一次出现,getOrDefault 返回默认值 0//    如果 num 已存在,返回其当前计数int currentCount = countMap.getOrDefault(num, 0);// 2. 将计数加 1 后存入哈希表//    如果 num 是第一次出现,这行代码会创建新的键值对 (num, 1)//    如果 num 已存在,这行代码会覆盖旧值,更新为新的计数countMap.put(num, currentCount + 1);}// ...后续代码不变...}
}

对比实验:理解 getOrDefault 的行为

假设我们有一个空的哈希表 countMap,执行以下操作:

java

运行

Map<Integer, Integer> countMap = new HashMap<>();// 第一次遇到元素 3
int count1 = countMap.getOrDefault(3, 0); // 返回 0(键不存在)
countMap.put(3, count1 + 1); // 存入 (3, 1)// 第二次遇到元素 3
int count2 = countMap.getOrDefault(3, 0); // 返回 1(键已存在)
countMap.put(3, count2 + 1); // 存入 (3, 2)// 最终哈希表中:{3=2}

其他常见用法场景

  1. 统计字符串中字符出现次数

    java

    运行

    String s = "hello";
    Map<Character, Integer> charCount = new HashMap<>();
    for (char c : s.toCharArray()) {charCount.put(c, charCount.getOrDefault(c, 0) + 1);
    }
    // 结果:{h=1, e=1, l=2, o=1}
    
  2. 分组统计

    java

    运行

    List<String> words = Arrays.asList("apple", "banana", "apple");
    Map<Integer, Integer> lengthCount = new HashMap<>();
    for (String word : words) {int length = word.length();lengthCount.put(length, lengthCount.getOrDefault(length, 0) + 1);
    }
    // 结果:{5=1, 6=2}(长度为5的单词1个,长度为6的单词2个)
    

总结

getOrDefault 是处理哈希表计数问题的 “语法糖”,它的核心优势是:

  • 简化代码:避免了手动检查键是否存在的繁琐逻辑。
  • 防止空指针异常:当键不存在时返回默认值(如 0),而不是 null
  • 代码可读性:让统计逻辑更加清晰直观。

处理需要初始化默认值的场景时,它是 Java 程序员的常用工具之一!

3 摩尔投票法(Boyer-Moore Voting Algorithm)

摩尔投票法(Boyer-Moore Voting Algorithm),这个算法的核心思想可以用一句话概括:"多数元素在数组中出现的次数超过一半,因此即使与其他所有元素两两抵消,最终也会剩下至少一个自身"

class Solution {public int majorityElement(int[] nums) {// 初始化计数器,用于记录当前候选人的票数int count = 0;// 初始化候选人,Integer类型允许为null,表示尚未选出候选人Integer candidate = null;// 遍历数组中的每个元素for (int num : nums) {// 当计数器为0时,意味着当前候选人的票数已被完全抵消// 需要选择当前元素作为新的候选人if (count == 0) {candidate = num;}// 根据当前元素是否等于候选人来更新计数器:// - 如果相等,说明遇到了支持者,票数+1// - 如果不等,说明遇到了反对者,票数-1count += (num == candidate) ? 1 : -1;}// 遍历结束后,最终的候选人即为多数元素// 由于多数元素的数量超过n/2,即使中途被抵消,最终也会胜出return candidate;}
}

核心逻辑解释

  1. 候选人交替

    • count减为 0 时,说明当前候选人的支持票被反对票完全抵消,此时需要更换候选人为当前元素。
  2. 票数更新

    • 遇到与候选人相同的元素时,count增加 1(支持票)
    • 遇到与候选人不同的元素时,count减少 1(反对票)
  3. 最终胜利条件

    • 由于多数元素的出现次数超过一半,即使在遍历过程中被其他元素抵消,最终也会因为自身数量优势重新成为候选人并保持到最后。

算法核心思想

想象一场投票选举,每个候选人需要和对手 "一对一拼消耗":

  • 每次遇到自己的支持者,票数 +1
  • 每次遇到对手,票数 -1
  • 当票数减为 0 时,当前候选人 "下台",由新的候选人接替。

由于多数元素的支持者超过半数,即使其他所有候选人联合起来抵消票数,多数元素最终仍会胜出。

算法步骤拆解

我们用数组 [2, 2, 1, 1, 1, 2, 2] 为例,逐步演示算法过程:

  1. 初始化

    • candidate(当前候选人):null
    • count(当前票数):0
  2. 遍历数组

    • 第 1 个元素 2

      • 当前 count = 0,任命 2 为新候选人,count +1 → 1
      • 状态:candidate = 2count = 1
    • 第 2 个元素 2

      • 与候选人相同,count +1 → 2
      • 状态:candidate = 2count = 2
    • 第 3 个元素 1

      • 与候选人不同,count -1 → 1
      • 状态:candidate = 2count = 1
    • 第 4 个元素 1

      • 与候选人不同,count -1 → 0
      • 状态:candidate = 2count = 0(候选人下台)。
    • 第 5 个元素 1

      • 当前 count = 0,任命 1 为新候选人,count +1 → 1
      • 状态:candidate = 1count = 1
    • 第 6 个元素 2

      • 与候选人不同,count -1 → 0
      • 状态:candidate = 1count = 0(候选人下台)。
    • 第 7 个元素 2

      • 当前 count = 0,任命 2 为新候选人,count +1 → 1
      • 状态:candidate = 2count = 1
  3. 遍历结束:最终候选人是 2,返回 2

代码实现(Java)

java

运行

class Solution {public int majorityElement(int[] nums) {int count = 0;        // 记录当前候选人的票数Integer candidate = null;  // 当前候选人(初始为 null)for (int num : nums) {// 当票数为 0 时,更换候选人if (count == 0) {candidate = num;}// 根据当前元素是否等于候选人,更新票数count += (num == candidate) ? 1 : -1;}return candidate;  // 最终剩下的候选人即为多数元素}
}

关键细节解释

  1. 为什么 count = 0 时更换候选人?

    • 当 count = 0 时,说明当前候选人的票数已被完全抵消,需要重新选择候选人。
    • 由于多数元素的数量超过一半,即使中途被其他元素抵消,最终仍会重新成为候选人并胜出。
  2. 为什么不需要验证最终候选人?

    • 根据算法逻辑,多数元素的数量超过 n/2,因此无论其他元素如何抵消,多数元素最终的票数至少为 1
    • 因此,最后剩下的候选人必然是多数元素。

复杂度分析

  • 时间复杂度:O (n) —— 只需遍历一次数组。
  • 空间复杂度:O (1) —— 只需要常数级的额外空间。

进阶思考:如果没有多数元素会怎样?

如果数组中不存在多数元素,摩尔投票法会返回一个错误的结果。例如:

  • 数组 [1, 2, 3]:最终返回 3,但 3 并不是多数元素。

因此,如果题目没有明确保证存在多数元素,需要在算法结束后额外验证最终候选人是否真的超过半数:

java

运行

// 增加验证步骤
int candidate = majorityElement(nums);  // 调用摩尔投票法
int count = 0;
for (int num : nums) {if (num == candidate) count++;
}
if (count > nums.length / 2) {return candidate;
} else {return -1;  // 不存在多数元素
}

总结

摩尔投票法的美在于:

  • 无需额外空间:通过巧妙的抵消策略,直接在遍历中找出多数元素。
  • 线性时间复杂度:一次遍历即可完成,效率极高。
  • 思想深刻:将问题转化为 "投票抵消" 模型,直观且优雅。

这个算法在处理大规模数据时尤为高效,是面试中的高频考点,建议反复理解其核心思想!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/90766.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/90766.shtml
英文地址,请注明出处:http://en.pswp.cn/pingmian/90766.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

CS231n-2017 Lecture6训练神经网络(一)笔记

本节主要讲的是模型训练时的算法设计数据预处理&#xff1a;关于数据预处理&#xff0c;我们有常用的3个符号&#xff0c;数据矩阵X&#xff0c;假设其尺寸是&#xff0c;N是数据样本的数量&#xff0c;D是数据的维度均值减法(Mean subtraction)&#xff1a;是预处理最常用的形…

C++ 中实现 `Task::WhenAll` 和 `Task::WhenAny` 的两种方案

&#x1f4da; C 中实现 Task::WhenAll 和 Task::WhenAny 的两种方案 引用&#xff1a; 拈朵微笑的花 想一番人世變換 到頭來輸贏又何妨日與夜互消長 富與貴難久長 今早的容顏老於昨晚C 标准库异步编程示例&#xff08;一&#xff09;C TAP&#xff08;基于任务的异步编程…

【学习】Codeforces Global Round 15 C. Maximize the Intersections

题意&#xff1a;给出一个圆&#xff0c;顺时针排布1~2*n&#xff0c;已知连了k条边&#xff0c;问这个圆最好情况下有多少个线的交点&#xff0c;要求线与线之间不能有重复的连接点&#xff0c;也就是每个点只能被一条线连接 思路&#xff1a; 1.考虑没有线的时候&#xff0…

图论:Dijkstra算法

昨天介绍了最小生成树的两个算法&#xff0c;最小生成树的两个算法旨在求解无向有权图中的最小代价联通图的问题&#xff0c;那么对于有向有权图&#xff0c;从起点到终点的最小花费代价问题就可以用 Dijkstra 算法来解决而且Dijkstra算法可以求出来从起始点开始到所有节点的最…

WPFC#超市管理系统(2)顾客管理、供应商管理、用户管理

超市管理系统3. 顾客管理3.1 顾客新增3.2 DataGrid样式3.3 顾客删除3.4 顾客修改4. 供应商管理4.1 供应商管理主界面4.2 新增供应商4.3 修改供应商5. 用户管理5.1 用户管理主界面5.2 新增用户5.3 修改用户总结3. 顾客管理 在CustomerView.xaml使用命令绑定方式添加页面加载Loa…

Windows本地部署DeepSeek

1、Ollama1、下载Ollama安装包https://ollama.com/download&#xff08;如果下载很慢 可以直接找我拿安装包&#xff09;2、使用命令行安装打开cmd 将下载的安装包OllamaSetup.exe 放到想要安装的目录下。&#xff08;如果直接双击&#xff0c;会装到C盘&#xff09;例如想装到…

基于Python的新闻爬虫:实时追踪行业动态

引言 在信息时代&#xff0c;行业动态瞬息万变。金融从业者需要实时了解政策变化&#xff0c;科技公司需要跟踪技术趋势&#xff0c;市场营销人员需要掌握竞品动向。传统的人工信息收集方式效率低下&#xff0c;难以满足实时性需求。Python爬虫技术为解决这一问题提供了高效方…

阿里视频直播解决方案VS(MediaMTX + WebRTC) 流媒体解决方案

背景&#xff1a; 公司采购了新的摄像头&#xff0c;通过rtsp或者rtmp推流到云平台&#xff0c;云平台内部进行转码处理&#xff0c;客户端使用HLS或HTTP-FLV播放&#xff0c;移动App可能使用HLS或私有SDK&#xff0c;超低延时则采用WebRTC。 技术选型&#xff1a; RTSP&…

day33:零基础学嵌入式之网络——TCP并发服务器

一、服务器1.服务器分类单循环服务器&#xff1a;只能处理一个客户端任务的服务器并发服务器&#xff1a;可同时处理多个客户端任务的服务器二、TCP并发服务器的构建1.如何构建&#xff1f;&#xff08;1&#xff09;多进程&#xff08;每一次创建都非常耗时耗空间&#xff0c;…

VR全景制作的流程?VR全景制作可以用在哪些领域?

VR全景制作的流程&#xff1f;VR全景制作可以用在哪些领域&#xff1f;VR全景制作&#xff1a;流程、应用与未来虚拟现实&#xff08;VR&#xff09;全景制作正迅速改变我们的感官体验&#xff0c;使我们能够身临其境地探索虚拟世界&#xff0c;享受沉浸式的奇妙感受。那么&…

用LangChain重构客服系统:腾讯云向量数据库+GPT-4o实战

人们眼中的天才之所以卓越非凡&#xff0c;并非天资超人一等而是付出了持续不断的努力。1万小时的锤炼是任何人从平凡变成超凡的必要条件。———— 马尔科姆格拉德威尔 目录 一、传统客服系统痛点与重构价值 1.1 传统方案瓶颈分析 1.2 新方案技术突破点 二、系统架构设计&…

主要分布在腹侧海马体(vHPC)CA1区域(vCA1)的混合调谐细胞(mixed-tuning cells)对NLP中的深层语义分析的积极影响和启示

腹侧海马体CA1区&#xff08;vCA1&#xff09;的混合调谐细胞&#xff08;mixed-tuning cells&#xff09;通过整合情感、社会关系、空间概念等多模态信息&#xff0c;形成动态的情景化语义表征&#xff0c;为自然语言处理&#xff08;NLP&#xff09;的深层语义分析提供了重要…

ESP32的ADF详解:6. Audio Processing的API

一、Downmix 1. 核心功能 将基础音频流和新加入音频流混合为单一输出流&#xff0c;支持动态增益控制和状态转换。输出声道数与基础音频一致&#xff0c;新加入音频自动转换声道匹配。2. 关键特性声道处理 输出声道数 基础音频声道数新加入音频自动转换声道&#xff08;如立体…

Qt(基本组件和基本窗口类)

一、基本组件1. Designer设计师为什么要上来先将这个东西呢&#xff0c;这个是QT外置的设计界面的工具&#xff0c;没啥用&#xff0c;所以了解一下。我们用的多的是QT内置的界面设计&#xff0c;只需要我们双击我们创建的项目的.ui文件就可以进入这个界面&#xff0c;你对界面…

docker与k8s的容器数据卷

Docker容器数据卷 特性 docker镜像由多个只读层叠加而成&#xff0c;启动容器时&#xff0c;Docker会加载只读镜像层并在镜像栈顶部添加一个读写层。如果运行中的容器修改了现有的一个已经存在的文件&#xff0c;那么该文件将会从读写层下面的只读层复制到读写层&#xff0c;该…

自然语言处理技术应用领域深度解析:从理论到实践的全面探索

1. 引言:自然语言处理的技术革命与应用前景 自然语言处理(Natural Language Processing,NLP)作为人工智能领域的核心分支,正在以前所未有的速度改变着我们的数字化生活。从最初的规则基础系统到如今基于深度学习的大语言模型,NLP技术经历了从理论探索到实际应用的深刻变…

OpenGLRender开发记录(二): 阴影(shadowMap,PCF,PCSS)

目录已实现功能阴影shadowMapPCFPCSS实现shadowMapPCFPCSS阴影GitHub主页&#xff1a;https://github.com/sdpyy1 OpenGLRender:https://github.com/sdpyy1/CppLearn/tree/main/OpenGL 已实现功能 除了上次实现IBL之外&#xff0c;项目目前新增了imGUI的渲染&#xff0c;更方便…

Linux:日志乱码

1、Linux日志乱码可能是XShell客户端编码没设置为UTF-8引起的&#xff0c;按照以下步骤&#xff0c;设置终端格式&#xff1a;中文版&#xff1a;打开Xshell会话属性&#xff08;文件→属性→终端→编码&#xff09;&#xff0c;选择与服务器一致的编码格式&#xff08;如UTF-8…

Rouge:面向摘要自动评估的召回导向型指标——原理、演进与应用全景

“以n-gram重叠量化文本生成质量&#xff0c;为摘要评估提供可计算标尺” Rouge&#xff08;Recall-Oriented Understudy for Gisting Evaluation&#xff09; 是由 南加州大学信息科学研究所&#xff08;ISI&#xff09;的Chin-Yew Lin 于2004年提出的自动文本摘要评估指标&am…

[STM32][HAL]stm32wbxx 超声波测距模块实现(HY-SRF05)

前言 在电子技术应用中,距离测量是一个常见且重要的需求。超声波模块因其测量精度较高、成本较低、易于使用等优点,被广泛应用于机器人避障、液位检测、智能停车系统等领域。该文主要讲解以stm32wb芯片为主控,用HAL库来对HY-SRF05超声波模块进行代码编写,实现基本的驱动和测…