news 2026/6/7 10:56:55

力扣实训 _ [169].多数元素 _ [42].接雨水

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
力扣实训 _ [169].多数元素 _ [42].接雨水

多数元素

1. 题目回顾

题目描述:
给定一个大小为n的数组nums,返回其中的多数元素。多数元素是指在数组中出现次数大于⌊ n/2 ⌋的元素。

约束条件:

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

示例:

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

2. 核心思路:频次统计与“两两抵消”

这道题是算法思维的经典训练场,主要分为两个层次的解法:

  • 常规思维(哈希表计数):最自然的想法是利用哈希表(HashMap)统计每个元素出现的频次,然后遍历哈希表找出频次大于n/2的元素。这种方法逻辑清晰,但需要额外的空间来存储频次。
  • 进阶思维(Boyer-Moore 摩尔投票法):题目给出了一个极其强大的条件:“多数元素的数量严格大于其余所有元素的总和”。基于此,我们可以将问题转化为“两军对垒”:把多数元素看作“友军”,其他元素看作“敌军”。每遇到一个相同的元素,票数加一;遇到不同的元素,票数减一(相互抵消)。因为友军数量占绝对优势,即使不断抵消,最后剩下的“候选人”必定是多数元素。这种方法将空间复杂度降到了极致。

3. 算法详细步骤

3.1 哈希表法步骤

  1. 初始化:创建一个哈希表用于存储元素及其对应的出现次数。
  2. 遍历统计:遍历数组,若元素已在哈希表中,则其计数加 1;若不在,则将其加入哈希表并设置计数为 1。
  3. 查找返回:在遍历过程中或遍历结束后,检查当前元素的计数是否大于n/2,满足条件则直接返回该元素。

3.2 摩尔投票法步骤

  1. 初始化状态:设定一个候选人candidate(初始可为任意值)和一个计数器count(初始为 0)。
  2. 遍历数组:依次读取数组中的每一个元素:
    • 如果count == 0,说明之前的候选人已经被完全抵消,将当前元素设为新的候选人,并将count置为 1。
    • 如果当前元素等于候选人,说明是“友军”,count加 1。
    • 如果当前元素不等于候选人,说明是“敌军”,count减 1(发生抵消)。
  3. 返回结果:遍历结束后,由于题目保证多数元素一定存在,当前保留的candidate即为最终答案。(若题目不保证存在,则需再遍历一次验证其真实频次)。

4. 代码实现

以下是两种解法的 Java 代码实现:

解法一:哈希表法

class Solution { public int majorityElement(int[] nums) { Map<Integer, Integer> map = new HashMap<>(); int n = nums.length; for (int num : nums) { // 获取当前元素的频次,若不存在则默认为0 int count = map.getOrDefault(num, 0) + 1; map.put(num, count); // 如果频次超过一半,直接返回 if (count > n / 2) { return num; } } return -1; // 理论上不会执行到这里 } }

解法二:摩尔投票法(最优解)

class Solution { public int majorityElement(int[] nums) { int candidate = 0; int count = 0; for (int num : nums) { if (count == 0) { // 票数归零,更换候选人 candidate = num; count = 1; } else if (num == candidate) { // 遇到相同元素,票数加一 count++; } else { // 遇到不同元素,票数减一(相互抵消) count--; } } // 题目保证多数元素一定存在,直接返回候选人 return candidate; } }

5. 复杂度分析

哈希表法

  • 时间复杂度: O(n)
    只需要对数组进行一次线性遍历,每次哈希表的插入和查找操作平均时间复杂度为 O(1) 。
  • 空间复杂度: O(n)
    在最坏的情况下(例如数组前半部分全是不相同的元素),哈希表需要存储大约 n/2 个键值对,因此空间复杂度为线性级别。

摩尔投票法

  • 时间复杂度: O(n)
    同样只需要对数组进行一次线性遍历。
  • 空间复杂度: O(1)
    只使用了常数级别的额外空间(仅维护candidatecount两个变量),完美满足进阶要求。

接雨水

1. 题目回顾

题目描述:
给定n个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。

核心概念:

  • 木桶效应:对于任何一个位置,它能接住的水量取决于它左侧最高柱子和右侧最高柱子中较矮的那个(即“短板”)。
  • 积水量计算:当前位置能接的水量 =min(左侧最高, 右侧最高) - 当前位置高度(若结果为正,否则为 0)。

示例:

  • 输入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
  • 输出:6
  • 解释:在这种高度排列下,凹陷处总共可以接 6 个单位的雨水。

2. 核心思路:双指针与贪心策略

这道题是算法面试中的经典高频题。相比于使用额外数组记录左右最大值的动态规划法,双指针法是空间复杂度最优的“天花板”解法。

  • 核心洞察:每个位置的实际蓄水量是由左右两侧最大值中较小的那个决定的。这意味着,如果我们已经知道了左右两侧最大值中较小的那一个,我们就能立刻确定当前短板侧当前位置的积水量,而无需知道另一侧完整的最大值。
  • 贪心策略:使用左右双指针向中间逼近,总是优先处理最大值较小的一侧。因为该侧的蓄水量已经可以确定,处理完后移动指针即可。这种“走一步看一步,优先处理短板”的贪心策略,使得我们可以在 O(1)O(1) 的空间内完成计算。

3. 算法详细步骤

3.1 初始化状态

  • 设定左指针left = 0,右指针right = n - 1
  • 设定两个变量leftMax = 0rightMax = 0,分别记录左指针左侧(含自身)的历史最大高度,以及右指针右侧(含自身)的历史最大高度。
  • 初始化总积水量ans = 0

3.2 双指针向中间移动

left < right时,持续进行以下操作:

  1. 更新历史最大值:每次移动指针前,先用当前柱子的高度更新对应侧的leftMaxrightMax
  2. 比较并结算短板:
    • 如果leftMax < rightMax:说明左侧是短板。此时左指针位置的积水量已确定为leftMax - height[left]。将其累加到ans,并将左指针右移(left++)。
    • 否则(rightMax <= leftMax):说明右侧是短板。此时右指针位置的积水量已确定为rightMax - height[right]。将其累加到ans,并将右指针左移(right--)。

3.3 返回结果

当左右指针相遇时,说明所有位置都已处理完毕,返回累加的总和ans

4. 代码实现

以下是基于双指针法的 Java 代码实现,包含了详细的逻辑注释:

class Solution { public int trap(int[] height) { // 初始化总积水量 int ans = 0; // 初始化左右双指针,分别指向数组的头和尾 int left = 0; int right = height.length - 1; // 记录左指针左侧(含自身)的历史最大高度 int leftMax = 0; // 记录右指针右侧(含自身)的历史最大高度 int rightMax = 0; // 当左右指针未相遇时,持续向中间逼近 while (left < right) { // 1. 更新当前左右指针位置遇到的历史最大高度 leftMax = Math.max(leftMax, height[left]); rightMax = Math.max(rightMax, height[right]); // 2. 核心贪心策略:优先处理较小的一侧(短板决定蓄水量) if (leftMax < rightMax) { // 左侧是短板: // 因为右侧至少有一个 rightMax 的柱子,而左侧最高只有 leftMax, // 所以左指针当前位置的水位最高只能被托到 leftMax。 // 积水量 = 左侧天花板 - 当前柱子高度 ans += leftMax - height[left]; // 左指针当前位置处理完毕,向右移动 left++; } else { // 右侧是短板(或左右天花板相等): // 同理,右指针当前位置的水位由 rightMax 决定。 // 积水量 = 右侧天花板 - 当前柱子高度 ans += rightMax - height[right]; // 右指针当前位置处理完毕,向左移动 right--; } } // 3. 返回最终累加的总积水量 return ans; } }

5. 复杂度分析

假设数组的长度为 n 。

  • 时间复杂度: O(n)
    左右指针总共移动 nn 次,每次操作(更新最大值、计算积水量、移动指针)的时间复杂度均为 O(1)。
  • 空间复杂度: O(1)
    只使用了常数级别的额外空间(left,right,leftMax,rightMax,ans这五个变量),是空间最优的解法。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/7 10:56:17

28kHz压电片贴壁激励钢槽内水声场的COMSOL建模与参数影响分析

本文还有配套的精品资源&#xff0c;点击获取 简介&#xff1a;一套开箱即用的COMSOL Multiphysics仿真模型&#xff0c;模拟28kHz压电陶瓷片阵列粘贴在2mm厚钢制矩形水槽外壁时&#xff0c;在槽内水中产生的超声压力场分布。模型完整定义了压电材料的机电耦合本构、钢-水界…

作者头像 李华
网站建设 2026/6/7 10:53:51

魔兽争霸3终极优化指南:5步彻底告别卡顿,重获丝滑体验

魔兽争霸3终极优化指南&#xff1a;5步彻底告别卡顿&#xff0c;重获丝滑体验 【免费下载链接】WarcraftHelper Warcraft III Helper , support 1.20e, 1.24e, 1.26a, 1.27a, 1.27b 项目地址: https://gitcode.com/gh_mirrors/wa/WarcraftHelper 还在为魔兽争霸3的卡顿、…

作者头像 李华
网站建设 2026/6/7 10:51:36

彻底搞懂六边形架构:告别代码耦合,让系统永远可扩展

在日常开发中&#xff0c;我们大概率都遇到过这些糟心问题&#xff1a;项目初期代码写得飞快&#xff0c;迭代几个版本后&#xff0c;改一个数据库字段要动三层代码、换个第三方接口需要大面积重构、单元测试写得举步维艰、业务逻辑被各种框架、中间件、外部接口代码裹挟&#…

作者头像 李华
网站建设 2026/6/7 10:46:03

N皇后问题的遗传算法Python实战:从原理到可复现工程实现

1. 这不是教科书&#xff0c;而是一次真实的GA项目复盘&#xff1a;从Matlab到Python的N皇后实战手记你点开这篇文章&#xff0c;大概率不是为了背诵“遗传算法是模拟生物进化过程的优化方法”这种定义。你真正想搞懂的是&#xff1a;当一个真实问题摆在面前——比如让100个皇后…

作者头像 李华
网站建设 2026/6/7 10:45:24

如何快速配置大麦网智能抢票脚本:面向新手的完整指南

如何快速配置大麦网智能抢票脚本&#xff1a;面向新手的完整指南 【免费下载链接】DamaiHelper 大麦网演唱会演出抢票脚本。 项目地址: https://gitcode.com/gh_mirrors/dama/DamaiHelper 还在为抢不到心仪的演唱会门票而烦恼吗&#xff1f;每次热门演出开票时&#xff…

作者头像 李华