LeetCode2799 统计完全子数组的数目

devtools/2025/1/15 16:18:38/

计算完全子数组的数目:从暴力到优化的算法实现

算法的世界里,常常会遇到各种有趣的数组问题,今天我们要探讨的是计算完全子数组的数目。这个问题来自LeetCode,题目如下:

给你一个由正整数组成的数组 nums。如果数组中的某个子数组满足下述条件,则称之为完全子数组:子数组中不同元素的数目等于整个数组不同元素的数目。我们的任务是返回数组中完全子数组的数目。子数组是数组中的一个连续非空序列。

一、问题分析

1. 基本思路

首先,我们需要理解什么是完全子数组。对于一个数组,我们要找出其中的子数组,并且这些子数组必须满足一个重要条件,即子数组内不同元素的数量等于整个数组中不同元素的数量。

2. 朴素方法

最直观的想法就是使用两层循环来遍历所有可能的子数组。对于每个子数组,我们需要计算其中不同元素的数量。以下是最初的 C 语言实现:

#include <stdio.h>
#include <stdlib.h>// 计算数组中不同元素的数目
int countDistinct(int* nums, int start, int end) {int* visited = (int*)calloc(1001, sizeof(int));  // 假设元素范围是 1 到 1000int count = 0;for (int i = start; i <= end; i++) {if (visited[nums[i]] == 0) {visited[nums[i]] = 1;count++;}}free(visited);return count;
}// 计算完全子数组的数目
int countCompleteSubarrays(int* nums, int numsSize) {int totalDistinct = countDistinct(nums, 0, numsSize - 1);int count = 0;for (int i = 0; i < numsSize; i++) {for (int j = i; j < numsSize; j++) {if (countDistinct(nums, i, j) == totalDistinct) {count++;}}}return count;
}

在 countDistinct 函数中,我们使用了一个 visited 数组来标记元素是否已出现。这个数组假设元素的范围是 1 到 1000,我们遍历 start 到 end 的元素,如果元素未被标记,则标记它并增加 count

在 countCompleteSubarrays 函数中,我们先计算整个数组的不同元素数量 totalDistinct,然后使用两层循环遍历所有可能的子数组。对于每个子数组,我们调用 countDistinct 函数来计算其不同元素的数量,如果等于 totalDistinct,我们就增加 count 的值。

然而,这种方法的时间复杂度是 ,因为两层循环和 countDistinct 函数内部的循环,对于较大的数组,性能会变得很差。

3. 遇到的问题

在实际测试中,当输入的数组包含元素大于 1000 时,会出现运行时错误,如

Line 9: Char 20: runtime error: load of address 0x5210000271ac with insufficient space for an object of type 'int' [solution.c]

这是因为我们的 visited 数组大小只考虑了元素范围是 1 到 1000,而实际输入的元素可能超出这个范围,导致了越界访问。

二、优化方案

1. 滑动窗口

为了优化这个问题,我们可以使用滑动窗口的方法。以下是优化后的 C 语言代码:

#include <stdio.h>
#include <stdlib.h>// 计算数组中不同元素的数目
int countDistinct(int* hashTable, int maxNum) {int count = 0;for (int i = 1; i <= maxNum; i++) {if (hashTable[i] > 0) {count++;}}return count;
}// 计算完全子数组的数目
int countCompleteSubarrays(int* nums, int numsSize) {int maxNum = 0;for (int i = 0; i < numsSize; i++) {if (nums[i] > maxNum) {maxNum = nums[i];}}int* hashTable = (int*)calloc(maxNum + 1, sizeof(int));if (hashTable == NULL) {fprintf(stderr, "Memory allocation failed for hashTable\n");return -1;}int totalDistinct = 0;for (int i = 0; i < numsSize; i++) {if (hashTable[nums[i]] == 0) {totalDistinct++;}hashTable[nums[i]]++;}int count = 0;int* windowHashTable = (int*)calloc(maxNum + 1, sizeof(int));if (windowHashTable == NULL) {fprintf(stderr, "Memory allocation failed for windowHashTable\n");free(hashTable);return -1;}int left = 0;int windowDistinct = 0;for (int right = 0; right < numsSize; right++) {if (windowHashTable[nums[right]] == 0) {windowDistinct++;}windowHashTable[nums[right]]++;while (windowDistinct == totalDistinct) {count += numsSize - right;windowHashTable[nums[left]]--;if (windowHashTable[nums[left]] == 0) {windowDistinct--;}left++;}}free(hashTable);free(windowHashTable);return count;
}

2. 优化代码解释

  • 首先,我们遍历数组 nums 找到最大元素 maxNum,这将决定我们需要的 hashTable 和 windowHashTable 的大小。
  • 我们为 hashTable 和 windowHashTable 分配 maxNum + 1 大小的内存,以确保能存储所有元素的出现次数,并对内存分配进行了错误检查。
  • 计算整个数组中不同元素的数目 totalDistinct,存储在 hashTable 中。
  • 我们使用左右指针 left 和 right 来表示滑动窗口。右指针 right 不断向右移动,将元素加入 windowHashTable 并更新 windowDistinct
  • 当 windowDistinct 等于 totalDistinct 时,说明找到了一个满足条件的子数组,我们更新 count 并移动左指针缩小窗口,同时更新 windowDistinct

三、时间复杂度分析

使用滑动窗口后,时间复杂度得到了显著优化。因为左右指针最多遍历数组一次,所以时间复杂度从 O(n^{2})降低到了O(n)

四、总结

这个问题展示了如何从一个简单的暴力算法逐步优化到一个更高效的算法。在处理数组问题时,滑动窗口是一个非常强大的工具,它可以帮助我们避免不必要的重复计算,提高算法的效率。同时,对于内存分配和数组范围的考虑也很重要,确保代码在处理各种输入时都能稳定运行。

在实际编程中,我们需要时刻关注可能出现的边界情况和性能瓶颈,像这里的元素范围和内存分配问题,都是需要注意的细节。


http://www.ppmy.cn/devtools/150720.html

相关文章

matlab实现了一个优化的遗传算法,用于求解注汽站最优位置的问题

function [best_chromosome, best_fitness] optimized_genetic_algorithm()%% 遗传算法参数初始化% 定义井信息&#xff0c;包括坐标、管道长度、流量、压力等wells defineWells(); % 返回井的结构体数组N length(wells); % 注汽井数量% 遗传算法相关参数L_chromosome 20; …

FPGA车牌识别

基于FPGA的车牌识别主要包含以下几个步骤&#xff1a;图像采集、颜色空间转换、边缘检测、形态学处理&#xff08;腐蚀和膨胀&#xff09;、特征值提取、模板匹配、结果显示。先用matlab对原理进行仿真&#xff0c;后用vivado和modelsim进行设计和仿真。 一、1.图像采集采用ov…

STM32第6章、WWDG

一、简介 WWDG&#xff1a;全称Window watchdog&#xff0c;即窗口看门狗&#xff0c;本质上是一个能产生系统复位信号和提前唤醒中断的计数器。 特性&#xff1a; 是一个递减计数器。 看门狗被激活后&#xff0c; 当递减计数器值从 0x40减到0x3F时会产生复位&#xff08;即T6位…

朴素贝叶斯分类器

一、生成模型&#xff08;学习&#xff09;&#xff08;Generative Model&#xff09; vs 判别模型&#xff08;学习&#xff09;&#xff08;Discriminative Model&#xff09; 结论&#xff1a;贝叶斯分类器是生成模型 1、官方说明 生成模型对联合概率 p(x, y)建模&#x…

Python----Python爬虫(Scrapy的应用:CrawlSpider 使用,爬取小说,CrawlSpider版)

一、CrawlSpider 使用 1.1、CrawlSpider CrawSpiders 是 Scrapy 框架中的一个特殊爬虫类&#xff0c;它用于处理需要跟随链接并抓取多个页面的情况。相比于基本的 Spider 类&#xff0c;CrawSpiders 提供了一个更灵活、更强大的方式来定义爬取规则。 在Scrapy中Spider是所有爬…

Redis十大数据类型详解

Redis&#xff08;一&#xff09; 十大数据类型 redis字符串&#xff08;String&#xff09; string是redis最基本的类型&#xff0c;一个key对应一个value string类型是二进制安全的&#xff0c;意思是redis的string可以包含任何数据。例如说是jpg图片或者序列化对象 一个re…

XML通过HTTP POST 请求发送到指定的 API 地址,进行数据回传

代码结构说明 这段代码的主要功能是&#xff1a; 从指定文件夹中读取所有 XML 文件。 将每个 XML 文件的内容通过 HTTP POST 请求发送到指定的 API 地址。 处理服务器的响应&#xff0c;并记录每个文件的处理结果。 using System; using System.IO; using System.Net; usin…

Browser-Use Web UI:浏览器自动化与AI的完美结合

Browser-Use Web UI:浏览器自动化与AI的完美结合 前言简介一、克隆项目二、安装与环境配置1. Python版本要求2. 安装依赖3. 安装 Playwright4. 配置环境变量(非必要步骤)三、启动 WebUI四、配置1. Agent设置2. 大模型设置3. 浏览器相关设置4. 运行 Agent结语前言 Web UI是在…