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

算法中子数组问题详解,多种解法,包含对应题目!

子数组系列问题

      • 子数组问题详解
    • 1. 基本概念
    • 2. 常见子数组问题及解法
      • (1) 最大/最小子数组
      • (2) 固定长度的子数组问题
      • (3) 满足条件的子数组个数
      • (4) 最长无重复字符子数组
    • 3. 总结
    • 用java实现
    • 1. 最大子数组和(Kadane 算法)
    • 2. 滑动窗口最大值(单调队列)
    • 3. 子数组和等于 K(前缀和 + 哈希表)
    • 4. 最长无重复字符子串(滑动窗口)
    • 5. 乘积小于 K 的子数组(滑动窗口)
    • 总结

java可以直接跳转到下面,内容一样!
本文练习题目可以对应 LeetCode 上的#53, #76, #209, #560, #713, #904题目。

子数组问题详解

子数组(Subarray)是指数组中一个或多个连续元素组成的序列。子数组问题是算法中常见的一类问题,通常涉及求和、乘积、最大/最小值、特定条件满足等操作。


1. 基本概念

  • 子数组:必须是连续的,例如 [1, 2, 3] 的子数组包括 [1], [2], [1, 2], [2, 3], [1, 2, 3],但 [1, 3] 不是子数组(因为它不连续)。
  • 子序列:可以不连续,如 [1, 3] 是子序列但不是子数组。
  • 子集:任意元素的组合,不要求顺序或连续。

2. 常见子数组问题及解法

(1) 最大/最小子数组

问题:给定一个整数数组(可能有负数),求子数组的最大和。
示例

输入: [-2, 1, -3, 4, -1, 2, 1, -5, 4]
输出: 6(对应子数组 [4, -1, 2, 1])

解法Kadane 算法(动态规划)

  • 时间复杂度:O(n)
  • 空间复杂度:O(1)
    代码
def max_subarray(nums):
    max_sum = current_sum = nums[0]
    for num in nums[1:]:
        current_sum = max(num, current_sum + num)  # 是否重新开始子数组
        max_sum = max(max_sum, current_sum)
    return max_sum

变种

  • 最小子数组和:类似,但取 min 代替 max
  • 环形子数组最大和(首尾相连):可以拆解为 max(最大子数组和, 总和 - 最小子数组和)

(2) 固定长度的子数组问题

问题:求所有长度为 k 的子数组的最大值/平均值等。
示例

输入: [1, 3, -1, -3, 5, 3, 6, 7], k = 3
输出: [3, 3, 5, 5, 6, 7](每个窗口的最大值)

解法滑动窗口(Sliding Window)

  • 时间复杂度:O(n)
  • 空间复杂度:O(1)(或 O(n) 如果需要存储结果)
    代码(求最大值,使用单调队列优化):
from collections import deque

def max_sliding_window(nums, k):
    q = deque()
    res = []
    for i, num in enumerate(nums):
        while q and nums[q[-1]] <= num:  # 维护单调递减队列
            q.pop()
        q.append(i)
        if q[0] == i - k:  # 移除窗口外的元素
            q.popleft()
        if i >= k - 1:
            res.append(nums[q[0]])
    return res

(3) 满足条件的子数组个数

问题:统计满足某条件的子数组数量,如:

  • 和等于 k 的子数组个数。
  • 和不超过 k 的子数组个数。
  • 乘积小于 k 的子数组个数。

示例(和为 k 的子数组个数):

输入: nums = [1, 1, 1], k = 2
输出: 2([1,1] 和 [1,1])

解法:前缀和 + 哈希表

  • 时间复杂度:O(n)
  • 空间复杂度:O(n)
    代码
from collections import defaultdict

def subarray_sum(nums, k):
    prefix_sum = 0
    count = 0
    sum_map = defaultdict(int)
    sum_map[0] = 1  # 初始情况,前缀和为 0 出现 1 次
    for num in nums:
        prefix_sum += num
        if prefix_sum - k in sum_map:  # 查找是否有 prefix_sum - k 存在
            count += sum_map[prefix_sum - k]
        sum_map[prefix_sum] += 1
    return count

变种

  • 乘积小于 k 的子数组个数:滑动窗口 + 累积乘积。
  • 最长子数组满足某条件:滑动窗口或双指针。

(4) 最长无重复字符子数组

问题:给定一个字符串/数组,求最长子数组,其中元素不重复。
示例

输入: "abcabcbb"
输出: 3("abc")

解法滑动窗口 + 哈希表

  • 时间复杂度:O(n)
  • 空间复杂度:O(text{字符集大小})
    代码
def length_of_longest_substring(s):
    char_map = {}
    left = 0
    max_len = 0
    for right, char in enumerate(s):
        if char in char_map and char_map[char] >= left:  # 如果重复,移动左指针
            left = char_map[char] + 1
        char_map[char] = right
        max_len = max(max_len, right - left + 1)
    return max_len

3. 总结

问题类型典型解法时间复杂度
最大子数组和Kadane 算法O(n)
固定长度子数组极值滑动窗口 + 单调队列O(n)
子数组和等于 k前缀和 + 哈希表O(n)
最长无重复子数组滑动窗口 + 哈希表O(n)

关键技巧

  • 滑动窗口:适用于连续子数组的最值、计数问题。
  • 前缀和:用于快速计算子数组和。
  • 哈希表:存储中间结果,优化查找。

用java实现

在 Java 中,子数组问题的解决方法与 Python 类似,但语法和部分数据结构(如 DequeHashMap)的使用方式有所不同。


1. 最大子数组和(Kadane 算法)

问题:求数组中连续子数组的最大和。
示例

输入: [-2, 1, -3, 4, -1, 2, 1, -5, 4]
输出: 6(子数组 [4, -1, 2, 1]

Java 代码

public int maxSubArray(int[] nums) {
    int maxSum = nums[0], currentSum = nums[0];
    for (int i = 1; i < nums.length; i++) {
        currentSum = Math.max(nums[i], currentSum + nums[i]);
        maxSum = Math.max(maxSum, currentSum);
    }
    return maxSum;
}

2. 滑动窗口最大值(单调队列)

问题:给定数组和窗口大小 k,返回每个窗口的最大值。
示例

输入: nums = [1, 3, -1, -3, 5, 3, 6, 7], k = 3
输出: [3, 3, 5, 5, 6, 7]

Java 代码

import java.util.Deque;
import java.util.LinkedList;

public int[] maxSlidingWindow(int[] nums, int k) {
    if (nums == null || nums.length == 0) return new int[0];
    Deque<Integer> deque = new LinkedList<>();
    int[] res = new int[nums.length - k + 1];
    for (int i = 0; i < nums.length; i++) {
        while (!deque.isEmpty() && nums[deque.peekLast()] <= nums[i]) {
            deque.pollLast(); // 维护单调递减队列
        }
        deque.offerLast(i);
        if (deque.peekFirst() == i - k) {
            deque.pollFirst(); // 移除窗口外的元素
        }
        if (i >= k - 1) {
            res[i - k + 1] = nums[deque.peekFirst()];
        }
    }
    return res;
}

3. 子数组和等于 K(前缀和 + 哈希表)

问题:统计和为 k 的子数组个数。
示例

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

Java 代码

import java.util.HashMap;
import java.util.Map;

public int subarraySum(int[] nums, int k) {
    Map<Integer, Integer> prefixSumMap = new HashMap<>();
    prefixSumMap.put(0, 1); // 初始前缀和为 0 出现 1 次
    int prefixSum = 0, count = 0;
    for (int num : nums) {
        prefixSum += num;
        if (prefixSumMap.containsKey(prefixSum - k)) {
            count += prefixSumMap.get(prefixSum - k);
        }
        prefixSumMap.put(prefixSum, prefixSumMap.getOrDefault(prefixSum, 0) + 1);
    }
    return count;
}

4. 最长无重复字符子串(滑动窗口)

问题:求字符串中最长无重复字符的子串长度。
示例

输入: "abcabcbb"
输出: 3"abc"

Java 代码

import java.util.HashMap;
import java.util.Map;

public int lengthOfLongestSubstring(String s) {
    Map<Character, Integer> charIndexMap = new HashMap<>();
    int left = 0, maxLen = 0;
    for (int right = 0; right < s.length(); right++) {
        char c = s.charAt(right);
        if (charIndexMap.containsKey(c) && charIndexMap.get(c) >= left) {
            left = charIndexMap.get(c) + 1; // 移动左指针
        }
        charIndexMap.put(c, right);
        maxLen = Math.max(maxLen, right - left + 1);
    }
    return maxLen;
}

5. 乘积小于 K 的子数组(滑动窗口)

问题:统计乘积小于 k 的连续子数组个数。
示例

输入: nums = [10, 5, 2, 6], k = 100
输出: 8[10], [5], [2], [6], [10,5], [5,2], [2,6], [5,2,6]

Java 代码

public int numSubarrayProductLessThanK(int[] nums, int k) {
    if (k <= 1) return 0;
    int left = 0, product = 1, count = 0;
    for (int right = 0; right < nums.length; right++) {
        product *= nums[right];
        while (product >= k) {
            product /= nums[left++]; // 收缩窗口
        }
        count += right - left + 1; // 新增的子数组数量
    }
    return count;
}

总结

问题类型Java 解法时间复杂度
最大子数组和Kadane 算法O(n)
滑动窗口最大值单调队列(DequeO(n)
子数组和等于 K前缀和 + HashMapO(n)
最长无重复子串滑动窗口 + HashMapO(n)
乘积小于 K 的子数组滑动窗口 + 累积乘积O(n)

关键点

  • 滑动窗口:适用于连续子数组问题(如最大值、无重复字符)。
  • 前缀和:用于快速计算子数组和。
  • 哈希表:存储中间结果(如前缀和、字符索引)。

看完理解可以尝试刷题巩固!

相关文章:

  • Windows上使用bash脚本
  • RFID技术在工业生产线自动化中的应用方案
  • DeepSeek+QuickAPI:MySQL AI 智能体终极篇(三)
  • uniapp微信小程序封装navbar组件
  • 一文读懂 UML:基础概念与体系框架
  • 【区块链安全 | 第二十二篇】类型之字面量和基础类型之间的转换
  • Springboot 中使用 List<Integer> 与 JSONArray 处理 JSON 数组的性能与实践
  • ZGC 参数优化与 GC 触发机制解析分享
  • 《混沌钟的RISC-V指令集重构》
  • 盛铂国产SCP4000 射频微波功率计与 SPP5000脉冲峰值功率计:高性价比,探头式功率计功率测量
  • Open GL ES ->GLSurfaceView在正交投影下的图片旋转、缩放、位移
  • 【go】异常处理panic和recover
  • 【Android开发基础】手机传感器信息的获取
  • Postman 变量全解析:实现数据传递和共享,提升 API 测试效率
  • 【1】搭建k8s集群系列(二进制部署)之系统初始化
  • 01 - spring security自定义登录页面
  • 51c嵌入式~单片机~合集7~※
  • ffmpeg滤镜使用
  • 从架构角度谈谈云原生架构
  • 笔记:代码随想录算法训练营day63:prim算法精讲、kruskal算法精讲
  • 国际货代做网站/行业关键词搜索量排名
  • 南宁学网站建设/百度网站收录链接提交
  • 县城服务网站如何做/百度指数教程
  • 网站开发前台和后台/营销培训机构哪家最专业
  • 制作网站详细步骤/企业营销型网站建设
  • dedecms 网站地图生成/石狮seo