LeetCode100天Day13-移除元素与多数元素

LeetCode100天Day13-移除元素与多数元素:双指针移除与排序计数

摘要:本文详细解析了LeetCode中两道经典数组题目------"移除元素"和"多数元素"。通过双指针实现原地移除元素,以及使用排序和计数查找多数元素,帮助读者掌握数组元素操作和查找的技巧。

目录

文章目录

  • LeetCode100天Day13-移除元素与多数元素:双指针移除与排序计数
    • 目录
    • [1. 移除元素(Remove Element)](#1. 移除元素(Remove Element))
      • [1.1 题目描述](#1.1 题目描述)
      • [1.2 解题思路](#1.2 解题思路)
      • [1.3 代码实现](#1.3 代码实现)
      • [1.4 代码逐行解释](#1.4 代码逐行解释)
      • [1.5 执行流程详解](#1.5 执行流程详解)
      • [1.6 算法图解](#1.6 算法图解)
      • [1.7 复杂度分析](#1.7 复杂度分析)
      • [1.8 边界情况](#1.8 边界情况)
    • [2. 多数元素(Majority Element)](#2. 多数元素(Majority Element))
      • [2.1 题目描述](#2.1 题目描述)
      • [2.2 解题思路](#2.2 解题思路)
      • [2.3 代码实现](#2.3 代码实现)
      • [2.4 代码逐行解释](#2.4 代码逐行解释)
      • [2.5 执行流程详解](#2.5 执行流程详解)
      • [2.6 算法图解](#2.6 算法图解)
      • [2.7 复杂度分析](#2.7 复杂度分析)
      • [2.8 边界情况](#2.8 边界情况)
    • [3. 两题对比与总结](#3. 两题对比与总结)
      • [3.1 算法对比](#3.1 算法对比)
      • [3.2 双指针移除模板](#3.2 双指针移除模板)
      • [3.3 排序+计数模板](#3.3 排序+计数模板)
      • [3.4 摩尔投票法](#3.4 摩尔投票法)
      • [3.5 整数除法陷阱](#3.5 整数除法陷阱)
    • [4. 总结](#4. 总结)
    • 参考资源
    • 文章标签

1. 移除元素(Remove Element)

1.1 题目描述

给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。

不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。

示例 1

复制代码
输入:nums = [3,2,2,3], val = 3
输出:2, nums = [2,2,_,_]
解释:函数应该返回新的长度2,并且nums中的前两个元素均为2。你不需要考虑数组中超出新长度后面的元素。

示例 2

复制代码
输入:nums = [0,1,2,2,3,0,4,2], val = 2
输出:5, nums = [0,1,3,0,4,_,_,_]
解释:函数应该返回新的长度5,并且nums中的前五个元素为0, 1, 3, 0, 4。注意这五个元素可以任意顺序。你不需要考虑数组中超出新长度后面的元素。

1.2 解题思路

这道题使用双指针的方法:

  1. 使用慢指针k指向下一个非val元素应该放置的位置
  2. 使用快指针i遍历数组
  3. 当遇到不等于val的元素时,将其放到k的位置,然后k++
  4. 返回k

解题步骤

  1. 初始化k=0
  2. 遍历数组,检查每个元素
  3. 如果当前元素不等于val,将其放到nums[k],k++
  4. 返回k

1.3 代码实现

java 复制代码
class Solution {
    public int removeElement(int[] nums, int val) {
        int k = 0;

        for (int i = 0; i < nums.length; i++) {
            if (nums[i] != val) {
                nums[k] = nums[i];
                k++;
            }
        }
        return k;
    }
}

1.4 代码逐行解释

第一部分:初始化指针
java 复制代码
int k = 0;

功能

  • k是慢指针(写指针),指向下一个非val元素应该放置的位置
  • 初始值为0,表示从数组开头开始写入
第二部分:遍历数组
java 复制代码
for (int i = 0; i < nums.length; i++) {
    if (nums[i] != val) {
        nums[k] = nums[i];
        k++;
    }
}

指针说明

指针 作用 说明
i 快指针(读指针) 遍历整个数组
k 慢指针(写指针) 指向写入位置

判断逻辑

java 复制代码
if (nums[i] != val)
条件 操作
nums[i] != val 保留元素,写入nums[k]
nums[i] == val 跳过,不写入

写入逻辑

java 复制代码
nums[k] = nums[i];
k++;
操作 说明
nums[k] = nums[i] 将非val元素复制到k位置
k++ 移动到下一个写入位置

1.5 执行流程详解

示例1nums = [3,2,2,3], val = 3

复制代码
初始状态:
nums = [3, 2, 2, 3]
k = 0

i=0:
  nums[0] = 3
  3 != 3? 否,跳过
  k = 0

i=1:
  nums[1] = 2
  2 != 3? 是
  nums[0] = 2
  nums = [2, 2, 2, 3]
  k = 1

i=2:
  nums[2] = 2
  2 != 3? 是
  nums[1] = 2
  nums = [2, 2, 2, 3]
  k = 2

i=3:
  nums[3] = 3
  3 != 3? 否,跳过
  k = 2

循环结束,返回 k = 2

最终数组前2个元素: [2, 2]

示例2nums = [0,1,2,2,3,0,4,2], val = 2

复制代码
初始状态:
nums = [0, 1, 2, 2, 3, 0, 4, 2]
k = 0

i=0: nums[0]=0, 0 != 2? 是
     nums[0] = 0, k = 1

i=1: nums[1]=1, 1 != 2? 是
     nums[1] = 1, k = 2

i=2: nums[2]=2, 2 != 2? 否,跳过

i=3: nums[3]=2, 2 != 2? 否,跳过

i=4: nums[4]=3, 3 != 2? 是
     nums[2] = 3, k = 3

i=5: nums[5]=0, 0 != 2? 是
     nums[3] = 0, k = 4

i=6: nums[6]=4, 4 != 2? 是
     nums[4] = 4, k = 5

i=7: nums[7]=2, 2 != 2? 否,跳过

循环结束,返回 k = 5

最终数组前5个元素: [0, 1, 3, 0, 4]

1.6 算法图解

复制代码
初始数组: [3, 2, 2, 3], val = 3
索引:       0  1  2  3

步骤1: i=0, k=0
数组: [3,  2,  2,  3]
索引:   ↑
       i=0, k=0

nums[0] = 3, 等于val,跳过

步骤2: i=1, k=0
数组: [3,  2,  2,  3]
索引:   ↑   ↑
       k=0 i=1

nums[1] = 2, 不等于val
nums[0] = 2
数组: [2,  2,  2,  3]
k = 1

步骤3: i=2, k=1
数组: [2,  2,  2,  3]
索引:       ↑   ↑
          k=1 i=2

nums[2] = 2, 不等于val
nums[1] = 2
数组: [2,  2,  2,  3]
k = 2

步骤4: i=3, k=2
数组: [2,  2,  2,  3]
索引:       ↑       ↑
          k=2     i=3

nums[3] = 3, 等于val,跳过

最终结果: k = 2
数组前2个元素: [2, 2]

1.7 复杂度分析

分析维度 复杂度 说明
时间复杂度 O(n) 遍历数组一次
空间复杂度 O(1) 只使用常数空间

优化思路:当遇到val时,可以与数组末尾元素交换,减少写入次数

java 复制代码
// 优化版本:双指针从两端
class Solution {
    public int removeElement(int[] nums, int val) {
        int left = 0;
        int right = nums.length - 1;

        while (left <= right) {
            if (nums[left] == val) {
                nums[left] = nums[right--];
            } else {
                left++;
            }
        }

        return left;
    }
}

1.8 边界情况

nums val 说明 输出
[] 1 空数组 0
[1] 1 单个待删除 0
[1] 2 单个保留 1
[3,3,3] 3 全部删除 0
[1,2,3] 4 无需删除 3

2. 多数元素(Majority Element)

2.1 题目描述

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

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

示例 1

复制代码
输入:nums = [3,2,3]
输出:3

示例 2

复制代码
输入:nums = [2,2,1,1,1,2,2]
输出:2

2.2 解题思路

这道题使用排序和计数的方法:

  1. 对数组进行排序
  2. 遍历排序后的数组,统计每个连续元素的出现次数
  3. 如果某个元素的出现次数大于n/2,返回该元素

解题步骤

  1. 计算阈值compare = n * 1.0 / 2
  2. 对数组排序
  3. 遍历数组,统计每个元素的出现次数
  4. 如果次数大于compare,返回该元素

2.3 代码实现

java 复制代码
import java.util.*;

class Solution {
    public int majorityElement(int[] nums) {
        double compare = nums.length*(1.0)/2;
        Arrays.sort(nums);
        int temp = nums[0];
        int k = 0;
        int answer = nums[0];
        for(int i = 0;i < nums.length;i++){
            if(temp == nums[i]){
                k++;
                if(k > compare){
                    answer = nums[i];
                }
            }
            else{
                temp = nums[i];
                k = 1;
            }
        }
        return answer;
    }
}

2.4 代码逐行解释

第一部分:计算阈值
java 复制代码
double compare = nums.length*(1.0)/2;

功能:计算多数元素的阈值

nums.length 计算 compare
3 3 * 1.0 / 2 1.5
7 7 * 1.0 / 2 3.5

为什么要用1.0

java 复制代码
// 错误写法
int compare = nums.length / 2;  // 整数除法,7/2 = 3

// 正确写法
double compare = nums.length * 1.0 / 2;  // 7.0 / 2 = 3.5
第二部分:排序
java 复制代码
Arrays.sort(nums);

功能:对数组进行升序排序

排序前 排序后
[3, 2, 3] [2, 3, 3]
[2, 2, 1, 1, 1, 2, 2] [1, 1, 1, 2, 2, 2, 2]
第三部分:统计计数
java 复制代码
int temp = nums[0];
int k = 0;
int answer = nums[0];
for(int i = 0;i < nums.length;i++){
    if(temp == nums[i]){
        k++;
        if(k > compare){
            answer = nums[i];
        }
    }
    else{
        temp = nums[i];
        k = 1;
    }
}

变量说明

变量 初始值 作用
temp nums[0] 当前统计的元素
k 0 当前元素的计数
answer nums[0] 多数元素的结果

统计逻辑

java 复制代码
if(temp == nums[i]){
    k++;  // 相同,计数加1
    if(k > compare){
        answer = nums[i];  // 超过阈值,记录答案
    }
}
else{
    temp = nums[i];  // 不同,切换到新元素
    k = 1;  // 重置计数
}

2.5 执行流程详解

示例1nums = [3,2,3]

复制代码
初始状态:
nums = [3, 2, 3]
排序后: nums = [2, 3, 3]
compare = 3 * 1.0 / 2 = 1.5
temp = nums[0] = 2
k = 0
answer = 2

i=0:
  nums[0] = 2
  temp(2) == nums[0](2)? 是
  k = 1
  1 > 1.5? 否

i=1:
  nums[1] = 3
  temp(2) == nums[1](3)? 否
  temp = 3
  k = 1

i=2:
  nums[2] = 3
  temp(3) == nums[2](3)? 是
  k = 2
  2 > 1.5? 是
  answer = 3

循环结束,返回 answer = 3

输出: 3

示例2nums = [2,2,1,1,1,2,2]

复制代码
初始状态:
nums = [2, 2, 1, 1, 1, 2, 2]
排序后: nums = [1, 1, 1, 2, 2, 2, 2]
compare = 7 * 1.0 / 2 = 3.5
temp = 1
k = 0
answer = 1

i=0: nums[0]=1, temp=1
     1 == 1? 是
     k = 1
     1 > 3.5? 否

i=1: nums[1]=1, temp=1
     1 == 1? 是
     k = 2
     2 > 3.5? 否

i=2: nums[2]=1, temp=1
     1 == 1? 是
     k = 3
     3 > 3.5? 否

i=3: nums[3]=2, temp=1
     1 == 2? 否
     temp = 2
     k = 1

i=4: nums[4]=2, temp=2
     2 == 2? 是
     k = 2
     2 > 3.5? 否

i=5: nums[5]=2, temp=2
     2 == 2? 是
     k = 3
     3 > 3.5? 否

i=6: nums[6]=2, temp=2
     2 == 2? 是
     k = 4
     4 > 3.5? 是
     answer = 2

循环结束,返回 answer = 2

输出: 2

2.6 算法图解

复制代码
初始数组: [2, 2, 1, 1, 1, 2, 2]
排序后:   [1, 1, 1, 2, 2, 2, 2]
阈值:     3.5

步骤1: i=0, temp=1, k=0
数组: [1,  1,  1,  2,  2,  2,  2]
索引:   ↑
       i=0

nums[0]=1 == temp=1
k = 1
1 > 3.5? 否

步骤2: i=1, temp=1, k=1
数组: [1,  1,  1,  2,  2,  2,  2]
索引:       ↑
           i=1

nums[1]=1 == temp=1
k = 2
2 > 3.5? 否

步骤3: i=2, temp=1, k=2
数组: [1,  1,  1,  2,  2,  2,  2]
索引:           ↑
               i=2

nums[2]=1 == temp=1
k = 3
3 > 3.5? 否

步骤4: i=3, temp=1, k=3
数组: [1,  1,  1,  2,  2,  2,  2]
索引:               ↑
                   i=3

nums[3]=2 != temp=1
temp = 2
k = 1

步骤5-7: 继续统计2
k = 2, 3, 4
4 > 3.5? 是
answer = 2

最终结果: 2

2.7 复杂度分析

分析维度 复杂度 说明
时间复杂度 O(n log n) 排序的复杂度
空间复杂度 O(1) 取决于排序实现

优化思路:可以使用摩尔投票法优化到O(n)

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;
    }
}

2.8 边界情况

nums 说明 输出
[1] 单元素 1
[1,1] 两元素 1
[1,2,1] 交替出现 1
[2,2,1,1,1,2,2] 复杂情况 2

3. 两题对比与总结

3.1 算法对比

对比项 移除元素 多数元素
核心算法 双指针 排序+计数
数据结构 数组 数组
时间复杂度 O(n) O(n log n)
空间复杂度 O(1) O(1)
应用场景 删除特定元素 查找多数元素

3.2 双指针移除模板

java 复制代码
// 双指针移除元素模板
int k = 0;  // 写指针

for (int i = 0; i < nums.length; i++) {  // 读指针
    if (nums[i] != target) {  // 保留条件
        nums[k++] = nums[i];
    }
}

return k;

3.3 排序+计数模板

java 复制代码
// 排序+计数模板
Arrays.sort(array);

Type current = array[0];
int count = 0;

for (int i = 0; i < array.length; i++) {
    if (array[i] == current) {
        count++;
        if (count > threshold) {
            return array[i];
        }
    } else {
        current = array[i];
        count = 1;
    }
}

3.4 摩尔投票法

核心思想

  • 不同元素相互抵消
  • 剩下的元素就是多数元素
java 复制代码
// 摩尔投票法模板
int count = 0;
Type candidate = null;

for (Type num : array) {
    if (count == 0) {
        candidate = num;
    }
    count += (num == candidate) ? 1 : -1;
}

return candidate;

投票过程

复制代码
数组: [2, 2, 1, 1, 1, 2, 2]

num=2: count=0, candidate=2, count=1
num=2: count=1, candidate=2, count=2
num=1: count=2, candidate=2, count=1 (抵消)
num=1: count=1, candidate=2, count=0 (抵消)
num=1: count=0, candidate=1, count=1
num=2: count=1, candidate=1, count=0 (抵消)
num=2: count=0, candidate=2, count=1

最终: candidate=2

3.5 整数除法陷阱

java 复制代码
// 整数除法
int a = 7 / 2;  // a = 3 (向下取整)

// 浮点数除法
double b = 7 * 1.0 / 2;  // b = 3.5

// 比较时
if (count > n / 2) {  // 错误:7/2=3,需要>3
    // count=4时才满足,但实际需要>3.5
}

if (count > n * 1.0 / 2) {  // 正确
    // count=4时满足
}

4. 总结

今天我们学习了两道数组操作题目:

  1. 移除元素:掌握双指针移除特定元素,理解原地修改的技巧
  2. 多数元素:掌握排序+计数查找多数元素,理解统计计数的逻辑

核心收获

  • 双指针可以实现原地移除元素,空间复杂度O(1)
  • 排序后相同元素会连续,便于统计
  • 计数时注意整数除法的陷阱
  • 摩尔投票法是查找多数元素的高效方法

练习建议

  1. 尝试用双指针从两端优化移除元素
  2. 学习摩尔投票法并实现
  3. 思考如何找到出现次数前k多的元素

参考资源

文章标签

#LeetCode #算法 #Java #数组 #双指针

喜欢这篇文章吗?别忘了点赞、收藏和分享!你的支持是我创作的最大动力!

相关推荐
二哈喇子!2 小时前
Java Web项目怎么创建 & 没有出现web.xml的解决方法
java·web·web.xml
ACERT3333 小时前
10.吴恩达机器学习——无监督学习01聚类与异常检测算法
python·算法·机器学习
小北方城市网3 小时前
Spring Security 认证授权实战(JWT 版):从基础配置到权限精细化控制
java·运维·python·微服务·排序算法·数据库架构
诗词在线3 小时前
从算法重构到场景复用:古诗词数字化的技术破局与落地实践
python·算法·重构
不穿格子的程序员3 小时前
从零开始写算法——二叉树篇7:从前序与中序遍历序列构造二叉树 + 二叉树的最近公共祖先
数据结构·算法
青槿吖3 小时前
Java 集合操作:HashSet、LinkedHashSet 和 TreeSet
java·开发语言·jvm
刘联其3 小时前
Prism Region注册父子区域 子区域初始化导航没生效解决
java·开发语言
hetao17338373 小时前
2026-01-12~01-13 hetao1733837 的刷题笔记
c++·笔记·算法
二哈喇子!3 小时前
controller & service & dao之间的关系
java·eclipse·intellij-idea