当前位置: 首页 > news >正文

【LeetCode 热题 100】(五)普通数组

53. 最大子数组和

class Solution {public int maxSubArray(int[] nums) {int length = nums.length;if(length == 1){return nums[0];}// 1.定义全局最大和int ans = nums[0];int pre = 0;for (int i = 0; i < nums.length; i++) {pre = Math.max(nums[i], nums[i] + pre);ans = Math.max(ans, pre);}  return ans;}
}

解题思路:动态规划(Kadane算法)

问题分析

寻找数组中连续子数组的最大和(至少包含一个元素)。例如:[-2,1,-3,4,-1,2,1,-5,4] 的最大子数组和为 6(子数组 [4,-1,2,1])。

核心算法:动态规划
  1. 状态定义

    • pre:表示以当前元素结尾的最大子数组和
    • ans:记录全局最大子数组和
  2. 状态转移方程

    pre = Math.max(nums[i],         // 选项1:从当前元素重新开始nums[i] + pre)    // 选项2:延续前面的子数组
    ans = Math.max(ans, pre)        // 更新全局最大值
    
  3. 算法流程

    graph TD
    A[初始化 ans=nums[0], pre=0] --> B[遍历数组]
    B --> C{计算以nums[i]结尾的最大和}
    C --> D[选择1:独立子数组 nums[i]]
    C --> E[选择2:延续前序子数组 nums[i]+pre]
    D --> F[取较大值更新pre]
    E --> F
    F --> G[更新全局最大值ans]
    G --> B
    
关键步骤解析
  1. 初始化

    • ans = nums[0](至少包含一个元素)
    • pre = 0(初始无前序子数组)
  2. 遍历数组

    • 对每个元素 nums[i] 计算:
      • 选择1:以当前元素作为新起点(nums[i]
      • 选择2:将当前元素加入前序子数组(nums[i] + pre
    • 取两者较大值更新 pre
    • pre 更新全局最大值 ans
  3. 终止条件

    • 遍历完成后返回 ans
时间复杂度
  • O(n):仅需一次遍历数组
  • 空间复杂度 O(1):仅用常数空间存储状态
示例演算(nums = [-2,1,-3,4,-1,2,1])
inums[i]决策preans
0-2max(-2, 0-2) = -2-2-2
11max(1, -2+1) = 111
2-3max(-3, 1-3) = -2-21
34max(4, -2+4) = 444
4-1max(-1, 4-1) = 334
52max(2, 3+2) = 555
61max(1, 5+1) = 666

最终结果:6(子数组 [4,-1,2,1]

算法优势
  1. 高效处理负值

    • pre 为负时自动重置子数组(Math.max(nums[i], nums[i]+pre)
    • 避免负值拖累后续子数组和
  2. 实时更新

    • 在遍历过程中即时计算最大值
    • 无需额外存储空间
  3. 边界处理

    • 单元素数组直接返回该元素
    • 全负数数组返回最大负数
典型应用场景
  1. 股票买卖(最大利润区间)
  2. 信号处理(最大连续波形)
  3. 数据分析(最大收益区间)

此算法由Joseph Born Kadane提出(1984),是解决最大子数组问题的最优方案,体现了动态规划的核心思想:利用历史状态避免重复计算

56. 合并区间

class Solution {public int[][] merge(int[][] intervals) {if(intervals.length == 0){int[][] ints = new int[0][2];return ints;}Arrays.sort(intervals, new Comparator<int[]>() {@Overridepublic int compare(int[] o1, int[] o2) {return o1[0] - o2[0];}});ArrayList<int[]> merged = new ArrayList<>();for (int i = 0; i < intervals.length; i++) {int L = intervals[i][0], R = intervals[i][1];if(merged.size()==0 || merged.get(merged.size() - 1)[1] < L){merged.add(new int[]{L,R});}else{merged.get(merged.size()-1)[1] = Math.max(merged.get(merged.size() - 1)[1], R);}}int[][] ints1 = merged.toArray(new int[merged.size()][]);return ints1;}
}

解题思路:区间合并算法

核心思想

通过排序和线性扫描合并重叠区间,将多个可能重叠的区间合并为互不重叠的区间集合。

关键步骤
  1. 边界处理

    • 当输入为空数组时,直接返回空二维数组
    if(intervals.length == 0){return new int[0][2];
    }
    
  2. 区间排序

    • 按照区间左端点升序排序
    Arrays.sort(intervals, (o1, o2) -> o1[0] - o2[0]);
    
  3. 合并重叠区间

    graph TD
    A[开始遍历区间] --> B{是否第一个区间 或<br>当前左端点 > 上一个右端点?}
    B -- 是 --> C[添加新区间]
    B -- 否 --> D[合并区间]
    C --> E[继续遍历]
    D --> E
    E --> F[遍历结束]
    
  4. 具体合并逻辑

    • 初始化存储合并结果的列表 merged
    • 遍历每个区间:
      for (int i = 0; i < intervals.length; i++) {int L = intervals[i][0], R = intervals[i][1];// 不重叠情况if(merged.isEmpty() || lastRight < L) {merged.add(new int[]{L, R});} // 重叠情况else {lastRight = Math.max(lastRight, R);}
      }
      
算法特点
  1. 时间复杂度:O(n log n)

    • 排序消耗 O(n log n)
    • 合并扫描消耗 O(n)
  2. 空间复杂度:O(n)

    • 存储合并结果需要额外空间
  3. 关键优势

    • 处理任意数量区间
    • 正确处理全包含/部分重叠/边界相接等情况
    • 结果区间有序且不重叠
示例解析(输入:[[1,3],[2,6],[8,10],[15,18]])
当前区间合并状态操作说明
[1,3][]添加初始状态
[2,6][[1,3]]合并3≥2 → 更新为[1,6]
[8,10][[1,6]]添加6<8 → 无重叠
[15,18][[1,6],[8,10]]添加10<15 → 无重叠

输出:[[1,6],[8,10],[15,18]]

处理各种重叠情况
  1. 完全包含:[[1,4],[2,3]] → [[1,4]]
  2. 部分重叠:[[1,3],[2,4]] → [[1,4]]
  3. 边界相接:[[1,2],[2,3]] → [[1,3]]
  4. 多重重叠:[[1,4],[2,5],[3,6]] → [[1,6]]

算法应用场景

  1. 日程安排合并
  2. 资源占用时间段优化
  3. 交通线路区间合并
  4. 任何需要处理重叠时间段的问题

该算法通过排序+单次扫描的巧妙设计,高效解决了区间合并问题,是处理这类问题的标准解法。

199. 轮转数组

class Solution {public void rotate(int[] nums, int k) {int length = nums.length;int[] arr = new int[length];for (int i = 0; i < nums.length; i++) {arr[(i+k)%length] = nums[i];}for (int i = 0; i < arr.length; i++) {nums[i] = arr[i];}}
}

这段代码实现了数组的旋转功能,即将数组元素向右移动 k 个位置。以下是代码的解题思路详解:

解题思路

  1. 问题分析

    • 向右旋转数组 k 次意味着每个元素会移动到其右侧 k 个位置后的新位置。
    • 若移动位置超出数组末尾,元素会循环移动到数组开头(例如,末尾元素移动后可能出现在数组头部)。
    • 关键点:新位置 = (原位置 + k) % 数组长度,取模操作确保循环移动。
  2. 核心算法

    • 临时数组辅助:创建一个与原数组等长的临时数组 arr
    • 计算新位置:遍历原数组,将每个元素 nums[i] 放入临时数组的 (i + k) % length 位置。
    • 复制回原数组:将临时数组的内容复制回原数组 nums
  3. 处理大 k 值

    • k 大于数组长度时,实际有效移动次数为 k % length(例如,数组长度为 7,k=10 等价于 k=3)。
    • 代码中的 (i+k)%length 自动处理了这种情况,无需单独调整 k
  4. 示例说明

    • 假设 nums = [1, 2, 3, 4, 5], k = 2
      • 长度 length = 5
      • 计算新位置:
        • i=0: (0+2)%5 = 2arr[2] = 1
        • i=1: (1+2)%5 = 3arr[3] = 2
        • i=2: (2+2)%5 = 4arr[4] = 3
        • i=3: (3+2)%5 = 0arr[0] = 4
        • i=4: (4+2)%5 = 1arr[1] = 5
      • 结果:arr = [4, 5, 1, 2, 3],复制回 nums 完成旋转。

代码特点

  • 时间复杂度:O(n),遍历数组两次(一次填充临时数组,一次复制回原数组)。
  • 空间复杂度:O(n),使用了一个临时数组。
  • 优点:逻辑清晰,直观体现旋转的本质(位置映射)。
  • 改进点:若要求空间复杂度 O(1),可使用反转法(先整体反转,再分段反转)。

关键代码解释

public void rotate(int[] nums, int k) {int length = nums.length;int[] arr = new int[length]; // 创建临时数组for (int i = 0; i < length; i++) {arr[(i + k) % length] = nums[i]; // 元素移动到新位置}for (int i = 0; i < length; i++) {nums[i] = arr[i]; // 复制回原数组}
}

此方法通过位置映射公式高效解决了数组旋转问题,适合理解旋转的本质逻辑。

238. 除自身以外数组的乘积

class Solution {public int[] productExceptSelf(int[] nums) {int length = nums.length;int[] left = new int[length];int[] right = new int[length];int[] answer = new int[length];// 1. 获取左边的累积left[0] = 1;for (int i = 1; i < length; i++) {left[i] = nums[i-1] * left[i-1];}// 2. 获取右边的累积right[length-1] = 1;for (int i = length-2; i >= 0; i--) {right[i] = nums[i+1]*right[i+1];}for (int i = 0; i < length; i++) {answer[i] = left[i] * right[i];}      return answer;}
}

解题思路描述

这段代码实现了 “除自身以外数组的乘积” 问题。给定一个整数数组 nums,要求返回一个新数组 answer,其中 answer[i] 等于 nums 中除 nums[i] 之外所有元素的乘积。代码满足以下约束:

  • 不能使用除法(避免除以零问题并符合题目要求)。
  • 时间复杂度为 O(n)(高效的单次遍历)。
  • 空间复杂度为 O(n)(使用额外数组,但非原地修改输入)。
核心思想:分解问题

将每个位置的答案拆分为两部分:

  1. 左侧所有元素的乘积left[i]):索引 0i-1 的累积乘积。
  2. 右侧所有元素的乘积right[i]):索引 i+1n-1 的累积乘积。
    最终结果即为两者的乘积:answer[i] = left[i] * right[i]
步骤详解
  1. 初始化数组

    • left[]:存储每个位置左侧的累积乘积。
    • right[]:存储每个位置右侧的累积乘积。
    • answer[]:存储最终结果。
  2. 计算左侧乘积(从左向右遍历)

    • 起始点:left[0] = 1(第一个元素左侧无元素)。
    • 递推公式:left[i] = nums[i-1] * left[i-1]
    • 意义left[i] 表示 nums[0] × nums[1] × ... × nums[i-1]
    • 示例:若 nums = [1, 2, 3, 4],则 left = [1, 1×1=1, 2×1=2, 3×2=6]
  3. 计算右侧乘积(从右向左遍历)

    • 起始点:right[length-1] = 1(最后一个元素右侧无元素)。
    • 递推公式:right[i] = nums[i+1] * right[i+1]
    • 意义right[i] 表示 nums[i+1] × nums[i+2] × ... × nums[n-1]
    • 示例:nums = [1, 2, 3, 4],则 right = [2×3×4=24, 3×4=12, 4×1=4, 1]
  4. 合并结果

    • 对每个索引 i,计算 answer[i] = left[i] * right[i]
    • 意义:左侧乘积 × 右侧乘积 = 排除 nums[i] 的总乘积。
    • 示例:i=1 时,answer[1] = left[1] × right[1] = 1 × 12 = 12(即 1×3×4)。
为什么有效?
  • 避免除法:通过分解为左右两部分,无需除法操作。
  • 时间复杂度 O(n):三次独立遍历(左、右、合并),每次 O(n)。
  • 空间复杂度 O(n):使用 leftright 两个辅助数组(各长度 n)。
示例演算

nums = [1, 2, 3, 4] 为例:

步骤索引 0索引 1索引 2索引 3
left[] 计算left[0]=1left[1]=nums[0]×left[0]=1×1=1left[2]=nums[1]×left[1]=2×1=2left[3]=nums[2]×left[2]=3×2=6
right[] 计算right[0]=nums[1]×right[1]=2×12=24right[1]=nums[2]×right[2]=3×4=12right[2]=nums[3]×right[3]=4×1=4right[3]=1
answer[] 结果1×24=241×12=122×4=86×1=6
最终 answer = [24, 12, 8, 6](即 [2×3×4, 1×3×4, 1×2×4, 1×2×3])。
http://www.lryc.cn/news/614909.html

相关文章:

  • 版本控制的详细说明介绍(已有github账号版)
  • 【数学归纳法】证明数列极限
  • 模拟人脑处理文本——从分句到分词,从段落到时间线叙事
  • 小米开源大模型 MiDashengLM-7B:不仅是“听懂”,更能“理解”声音
  • 力扣前200题字符串总结
  • Effective C++ 条款31: 将文件间的编译依存关系降至最低
  • Matlab系列(004) 一 Matlab分析正态分布(高斯分布)
  • DBSCAN聚类算法实战全解析
  • 制作 VSCode 插件
  • React Native jpush-react-native极光推送 iOS生产环境接收不到推送
  • 计算机网络:如何将/22的CIDR地址块划分为4个子网
  • 华数杯C题:可调控生物节律的LED光源研究——数学建模与Python实战
  • 2025年华数杯评审标准发布
  • 2025华数杯B题一等奖方案:网络切片无线资源管理全解析(附Python/MATLAB代码)
  • 计算机网络1-6:计算机网络体系结构
  • 4深度学习Pytorch-神经网络--损失函数(sigmoid、Tanh、ReLU、LReLu、softmax)
  • 等保测评-RabbitMQ中间件
  • 直接插入排序算法:可视化讲解与C语言实现
  • Android MediaMetadataRetriever取视频封面,Kotlin(1)
  • 记一次奇异的bug
  • 自动化一键部署 LNMP 环境
  • 【n8n教程笔记——工作流Workflow】文本课程(第二阶段)——5 自动化业务工作流——0 用例 (Use case)
  • 五、RuoYi-Cloud-Plus 前端项目部署以及如何改后端请求地址。
  • 线上排查问题的一般流程是怎么样的?
  • 集成电路学习:什么是RQT图形用户界面工具
  • 搭建商城的关键注意事项:从定位到运营的全链路指南
  • 基于 InfluxDB 的服务器性能监控系统实战(二)
  • 深入解析进程创建与终止机制
  • Linux 信号处理标志sa_flags详解
  • 有限元方法中的数值技术:Cholesky矩阵分解