引言:数据处理的艺术与科学
在编程面试和实际开发中,数据处理是永恒的主题。从用户行为分析到矩阵操作,从数组去重到数学建模,每个问题都考验着我们对数据的理解和处理能力。本文将深入解析四道经典的算法问题,涵盖哈希统计、数学建模、矩阵操作和双指针技巧等多个重要领域。
1. 用户活跃分钟数统计
问题描述
给定用户操作日志,统计用户活跃分钟数的分布情况。用户活跃分钟数定义为用户执行操作的唯一分钟数。
示例分析:
输入:logs = [[0,5],[1,2],[0,2],[0,5],[1,3]], k = 5
输出:[0,2,0,0,0]
解释:
用户0的操作时间:[5, 2, 5] → 唯一分钟数:2(5和2)
用户1的操作时间:[2, 3] → 唯一分钟数:2
两个用户的活跃分钟数都是2,因此answer[2] = 2
解法:哈希表 + 集合统计
import java.util.*;
class Solution {
public int[] findingUsersActiveMinutes(int[][] logs, int k) {
// 使用HashMap,key为用户ID,value为时间集合(自动去重)
Map<Integer, Set<Integer>> userMinutes = new HashMap<>();
// 统计每个用户的操作时间(去重)
for (int[] log : logs) {
int userId = log[0];
int time = log[1];
userMinutes.putIfAbsent(userId, new HashSet<>());
userMinutes.get(userId).add(time);
}
// 统计用户活跃分钟数的分布
int[] answer = new int[k];
for (Set<Integer> minutes : userMinutes.values()) {
int uam = minutes.size(); // 用户活跃分钟数
if (uam >= 1 && uam <= k) {
answer[uam - 1]++; // 下标从1开始,所以要减1
}
}
return answer;
}
}
算法步骤详解
- 数据预处理:使用HashMap>存储每个用户的唯一操作时间
- 去重统计:利用HashSet自动去重特性统计唯一分钟数
- 分布计算:遍历所有用户,根据活跃分钟数更新结果数组
复杂度分析
- 时间复杂度:O(n),其中n是日志数量
- 空间复杂度:O(n),存储用户和对应的时间集合
思维拓展
- 网站访问次数统计
- 用户会话分析
- 行为模式识别
2. 彩色三角形构建问题
问题描述
输入:red = 2, blue = 4输出:3可能的构建方式:第1行:红(1个)第2行:蓝(2个)第3行:红(3个)总计:红球1+3=4个?等等,重新计算...
解法:数学建模 + 贪心策略
class Solution {
public int maxHeight(int red, int blue) {
return Math.max(
calculateMaxHeight(red, blue, true), // 第一行用红色
calculateMaxHeight(red, blue, false) // 第一行用蓝色
);
}
private int calculateMaxHeight(int red, int blue, boolean startWithRed) {
int currentRed = red;
int currentBlue = blue;
int height = 0;
int row = 1;
boolean useRed = startWithRed;
while (true) {
if (useRed) {
if (currentRed >= row) {
currentRed -= row;
height++;
} else {
break;
}
} else {
if (currentBlue >= row) {
currentBlue -= row;
height++;
} else {
break;
}
}
useRed = !useRed; // 切换颜色
row++; // 下一行需要更多球
}
return height;
}
}
优化解法:数学公式
class Solution {
public int maxHeight(int red, int blue) {
// 两种起始颜色的最大高度
int height1 = calculate(red, blue, 1);
int height2 = calculate(blue, red, 1);
return Math.max(height1, height2);
}
private int calculate(int a, int b, int row) {
if (a < row && b < row) return 0;
if (row % 2 == 1) {
// 奇数行用颜色a
if (a >= row) {
return 1 + calculate(a - row, b, row + 1);
} else {
return 0;
}
} else {
// 偶数行用颜色b
if (b >= row) {
return 1 + calculate(a, b - row, row + 1);
} else {
return 0;
}
}
}
}
数学建模思路
- 方案1:红、蓝、红、蓝...(红球用于奇数行)
- 方案2:蓝、红、蓝、红...(蓝球用于奇数行)
思维拓展
- 资源调度问题
- 生产计划优化
- 投资组合分配
3. 矩阵零值设置
问题描述

解法:标记位法
class Solution {
public void setZeroes(int[][] matrix) {
int m = matrix.length;
int n = matrix[0].length;
boolean firstRowZero = false;
boolean firstColZero = false;
// 检查第一行是否有0
for (int j = 0; j < n; j++) {
if (matrix[0][j] == 0) {
firstRowZero = true;
break;
}
}
// 检查第一列是否有0
for (int i = 0; i < m; i++) {
if (matrix[i][0] == 0) {
firstColZero = true;
break;
}
}
// 使用第一行和第一列作为标记位
for (int i = 1; i < m; i++) {
for (int j = 1; j < n; j++) {
if (matrix[i][j] == 0) {
matrix[i][0] = 0; // 标记行
matrix[0][j] = 0; // 标记列
}
}
}
// 根据标记位设置矩阵内部为零
for (int i = 1; i < m; i++) {
for (int j = 1; j < n; j++) {
if (matrix[i][0] == 0 || matrix[0][j] == 0) {
matrix[i][j] = 0;
}
}
}
// 处理第一行
if (firstRowZero) {
for (int j = 0; j < n; j++) {
matrix[0][j] = 0;
}
}
// 处理第一列
if (firstColZero) {
for (int i = 0; i < m; i++) {
matrix[i][0] = 0;
}
}
}
}
算法核心思想
- 标记位技巧:使用第一行和第一列作为标记数组
- 分步处理:先标记,再根据标记设置零值
- 边界处理:特殊处理第一行和第一列
复杂度分析
- 时间复杂度:O(m × n)
- 空间复杂度:O(1)
思维拓展
- 图像处理中的像素操作
- 数据压缩算法
- 内存受限环境下的数据处理
4. 删除有序数组中的重复项 II
问题描述
输入:nums = [1,1,1,2,2,3]输出:5, nums = [1,1,2,2,3]输入:nums = [0,0,1,1,1,1,2,3,3]输出:7, nums = [0,0,1,1,2,3,3]
解法:双指针法
class Solution {
public int removeDuplicates(int[] nums) {
if (nums.length <= 2) {
return nums.length;
}
int i = 0; // 慢指针 - 指向下一个有效元素的位置
for (int num : nums) {
// 如果当前元素可以加入(前两个元素,或者与倒数第二个不同)
if (i < 2 || num > nums[i - 2]) {
nums[i++] = num;
}
}
return i;
}
}
算法执行过程
以 nums = [1,1,1,2,2,3] 为例:
初始:i=0, nums=[1,1,1,2,2,3]
第1步:i<2,加入1 → i=1, [1,1,1,2,2,3]
第2步:i=1<2,加入1 → i=2, [1,1,1,2,2,3]
第3步:num=1, nums[i-2]=1,相等不加入 → i=2, [1,1,1,2,2,3]
第4步:num=2 > nums[i-2]=1,加入2 → i=3, [1,1,2,2,2,3]
第5步:num=2 = nums[i-2]=1?不,nums[i-2]=1,2>1,加入2 → i=4, [1,1,2,2,2,3]
第6步:num=3 > nums[i-2]=2,加入3 → i=5, [1,1,2,2,3,3]
结果:长度5, [1,1,2,2,3]
通用解法模板
// 允许最多k个重复元素的通用解法
public int removeDuplicates(int[] nums, int k) {
int i = 0;
for (int num : nums) {
if (i < k || num > nums[i - k]) {
nums[i++] = num;
}
}
return i;
}
思维拓展
- 有序数组去重
- 移动零元素
- 合并有序数组
- 快慢指针检测循环
总结:算法思维模式
1. 哈希统计模式
- 问题1:用户活跃分钟数统计
- 核心思想:使用合适的数据结构进行分组统计
- 适用场景:需要按类别聚合数据的场景
2. 数学建模模式
- 问题2:彩色三角形构建
- 核心思想:将实际问题抽象为数学模型
- 适用场景:资源分配、优化问题
3. 标记位技巧
- 问题3:矩阵零值设置
- 核心思想:利用现有空间存储状态信息
- 适用场景:空间受限的原地操作
4. 双指针技巧
- 问题4:有序数组去重
- 核心思想:维护读写指针,原地修改数据
- 适用场景:数组遍历与修改
实战建议
- 理解数据特性:有序、唯一、矩阵等特性决定了解法选择
- 空间复杂度考虑:明确是否允许使用额外空间
- 边界情况处理:空输入、极值、边界条件
- 测试用例设计:覆盖正常情况、边界情况和特殊情况
复杂度总结
|
问题 |
时间复杂度 |
空间复杂度 |
关键技巧 |
|
用户活跃统计 |
O(n) |
O(n) |
哈希表+集合 |
|
三角形构建 |
O(√n) |
O(1) |
数学建模 |
|
矩阵置零 |
O(m×n) |
O(1) |
标记位法 |
|
数组去重 |
O(n) |
O(1) |
双指针 |
通过这四个问题的深入分析,我们掌握了处理不同类型数据问题的核心技巧。这些方法不仅适用于具体的算法问题,更能帮助我们在实际开发中设计出高效、优雅的解决方案。
542

被折叠的 条评论
为什么被折叠?



