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

LeetCode2799 统计完全子数组的数目

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

在算法的世界里,常常会遇到各种有趣的数组问题,今天我们要探讨的是计算完全子数组的数目。这个问题来自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 到 1000
    int 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.kler.cn/a/504231.html

相关文章:

  • 51_Lua面向对象编程
  • 像JSONDecodeError: Extra data: line 2 column 1 (char 134)这样的问题怎么解决
  • rknn环境搭建之docker篇
  • 【MySQL实战】mysql_exporter+Prometheus+Grafana
  • 基于微信小程序的智能停车场管理系统设计与实现(LW+源码+讲解)
  • HarmonyOS NEXT开发进阶(六):HarmonyOS NEXT实现嵌套 H5 及双向通信
  • 播放音频文件同步音频文本
  • [mysql]日志
  • vscode 扩展Cline、Continue的差别?
  • fpga 的时钟管理模块pll 跟 dcm
  • J.U.C(2)
  • JSON简介与使用
  • golang 环境变量配置
  • CSS语言的计算机基础
  • 在 Azure 100 学生订阅中新建 Ubuntu VPS 并通过 Docker 部署 Mastodon 服务器
  • C#委托(Delegate)基本用法
  • GD32F470Z外部晶振不起振
  • 【Hive】新增字段(column)后,旧分区无法更新数据问题
  • 1.13 多线程编程
  • 社群团购项目运营策略的深度剖析:融合链动2+1模式、AI智能名片与S2B2C商城小程序的综合应用
  • EasyExcel - 行合并策略(二级列表)
  • Elasticsearch:向量数据库基础设施类别的兴衰
  • 易我视频照片修复EaseUS Fixo Technician
  • 简聊MySQL并发事务中幻读、虚读问题的解决方案
  • GPU算力平台|在GPU算力平台部署Qwen-2通义千问大模型的教程
  • sniffer 日志分析吞吐问题