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))
- [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 解题思路
这道题使用双指针的方法:
- 使用慢指针k指向下一个非val元素应该放置的位置
- 使用快指针i遍历数组
- 当遇到不等于val的元素时,将其放到k的位置,然后k++
- 返回k
解题步骤:
- 初始化k=0
- 遍历数组,检查每个元素
- 如果当前元素不等于val,将其放到nums[k],k++
- 返回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 执行流程详解
示例1 :nums = [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]
示例2 :nums = [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 解题思路
这道题使用排序和计数的方法:
- 对数组进行排序
- 遍历排序后的数组,统计每个连续元素的出现次数
- 如果某个元素的出现次数大于n/2,返回该元素
解题步骤:
- 计算阈值compare = n * 1.0 / 2
- 对数组排序
- 遍历数组,统计每个元素的出现次数
- 如果次数大于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 执行流程详解
示例1 :nums = [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
示例2 :nums = [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. 总结
今天我们学习了两道数组操作题目:
- 移除元素:掌握双指针移除特定元素,理解原地修改的技巧
- 多数元素:掌握排序+计数查找多数元素,理解统计计数的逻辑
核心收获:
- 双指针可以实现原地移除元素,空间复杂度O(1)
- 排序后相同元素会连续,便于统计
- 计数时注意整数除法的陷阱
- 摩尔投票法是查找多数元素的高效方法
练习建议:
- 尝试用双指针从两端优化移除元素
- 学习摩尔投票法并实现
- 思考如何找到出现次数前k多的元素
参考资源
文章标签
#LeetCode #算法 #Java #数组 #双指针
喜欢这篇文章吗?别忘了点赞、收藏和分享!你的支持是我创作的最大动力!