百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

数组-一文搞定前缀和数组(数组前缀和c语言)

wxin55 2025-02-04 16:34 6 浏览 0 评论

前言

??就从数组开始,以后会一直更新算法。数组有下图这些知识点与技巧。本文主要讲解其中的前缀和知识点。

思路

??适合的场景:原始数组不会被修改,且频繁查询某个区间的累加和。 ??创建一个prefixSum数组,长度比原数组nums长度多1。prefixSum[i]存储nums[0]到nums[i]的和。 ??尤其要注意prefixSum与nums的坐标换算,如下图所示。

区域和检索 - 数组不可变(一维前缀和)

leetcode第303题

解题思路
??常规思路是通过遍历i到j。但这样时间复杂度就是O(n)。 ??采用前缀和。
sumRange = prefixSum[right + 1] - prefixSum[left]。注意原数组与前缀和数组的下标换算,例如nums[i]的前缀和是preSum[i + 1]。如下图所示。

复杂度分析
??时间复杂度:初始化O(n),每次检索O(1),n是数组长度。 ??空间复杂度:O(n)。
代码

class NumArray {
    private int[] prefixSum;
    public NumArray(int[] nums) {
        prefixSum = new int[nums.length + 1];
        for (int i = 0; i < nums.length; i++) {
            prefixSum[i + 1] = prefixSum[i] + nums[i];
        }
    }
    public int sumRange(int left, int right) {
        return prefixSum[right + 1] - prefixSum[left];
    }
}

二维区域和检索 - 矩阵不可变(二维前缀和)

leetcode第304题

解题思路
??题中要求值设为下图中的红框部分,则有下图红框部分和 = 下图蓝框部分的和 - 下图绿框部分的和 - 下图黄框部分的和 + 下图灰框部分的和。

image.png

??定义原二维数组的前缀和数组为preSum。则preSum[i][j]表示原数组中(0, 0)坐标与(i - 1, j - 1)坐标组成的矩形区域的和,如下图所示,紫色框对应的前缀和为17。 这里需要注意原数组与前缀和数组的坐标换算。比如原数组坐标为(i - 1, j - 1),则在前缀和数组中的坐标为(i, j)。 而上面一开始提到的蓝框的和,绿框的和,黄框的和,灰框的和的求法都一样。也就是对应位置二维数组的前缀和。

??所以上图中紫色部分前缀和又如何求呢?答案:上图紫色部分前缀和 = 下图黄色部分前缀和 + 下图红色部分前缀和 - 重叠部分前缀和 + 原数组(i - 1, j - 1)位置的值 ,即preSum[i][j] = preSum[i - 1][j] + preSum[i][j - 1] - preSum[i - 1][j - 1] + matrix[i - 1][j - 1] = 9 + 14 - 8 + 2 = 17

??所以只要求出原数组中每个位置的前缀和,就解决了本题。 复杂度分析
??时间复杂度:初始化O(rc),每次检索O(1),其中r与c分别为matrix的行数和列数。 ??空间复杂度:O(rc)。
代码

class NumMatrix {
    private int[][] preSum;

    public NumMatrix(int[][] matrix) {
        int r = matrix.length;
        if (r == 0) {
            return;
        }
        int c = matrix[0].length;
        if (c == 0) {
            return;
        }
        preSum = new int[r + 1][c + 1];
        for (int i = 1; i <= r; i++) {
            for (int j = 1; j <= c; j++) {
                preSum[i][j] = preSum[i - 1][j] + preSum[i][j - 1] - preSum[i - 1][j - 1] + matrix[i - 1][j - 1];
            }
        }
    }

    public int sumRegion(int row1, int col1, int row2, int col2) {
        return preSum[row2 + 1][col2 + 1] - preSum[row1][col2 + 1] - preSum[row2 + 1][col1] + preSum[row1][col1];
    }
}

和为 K 的子数组

leetcode第560题

解题思路
??
思路
??常规思路是通过双重循环遍历前缀和数组,j < i,若当
preSum[j] + k == preSum[i]时,说明数组从j - 1到i的和为k,则count++。但这样时间复杂度就是O(n^2)。 ??采用HashMap + 前缀和数组方式,时间复杂度可达到O(n)。 ??由preSum[j] + k == preSum[i]移项得preSum[j] == preSum[i] - k。所以只要统计有多少个前缀和为preSum[i] - k即可以统计出有多少个子串的和为k。 ??建立map,其中key=preSum[i],value=满足preSum[i] - k的preSum[j]有多少个(有多少个满足,就代表有多少个子串的和为k),其中必须j < i。
?? 示例
??示例nums = [1,2,3],k = 3。流程如下。 ??1.由于不会事先构建前缀和数组,所以此处先添加前缀和的第0个元素。如下图所示。

??2.从nums的第0项(对应前缀和数组的第1项),开始遍历。此时preSum - k = -2。map中不存在key = -2的键值对。所以此时count = 0。并将key = preSum = 1, value = 1加入map。如下图所示。

??3.访问nums数组的第1项(对应前缀和数组的第2项)。此时preSum - k = 0。map中存在key =0的键值对,且value = 1。所以此时count = count + value = 1。并将key = preSum = 3, value = 1加入map。如下图所示。

??4.访问nums数组的第2项(对应前缀和数组的第3项)。此时preSum - k = 3。map中存在key = 3的键值对,且value = 1。所以此时count = count + value = 2。并将key = preSum = 6, value = 1加入map。如下图所示。

复杂度分析
??时间复杂度:O(n),其中n为数组的长度 ??空间复杂度:O(n),哈希表在最坏情况下可能有n个不同的键值,因此为O(n)。
代码

class Solution {
   public int subarraySum3(int[] nums, int k) {
      HashMap map = new HashMap<>();
      //初始情况,由于此处没有使用前缀和数组,因此需要先将前缀和为0的,出现了1次的的情况记录在map里面,也就是前缀数组中的第0项
      map.put(0, 1);
      int count = 0, sum0i = 0;
      for (int i = 0; i < nums.length; i++) {
         sum0i += nums[i];
         count += map.getOrDefault(sum0i - k, 0);
         //以下两句代码,必须在以上两句代码的后边,这样变相保证了j < i
         int c = map.getOrDefault(sum0i, 0);
         map.put(sum0i, ++c);
      }
      return count;
   }
}

结尾

??好了数组中的前缀和技巧就讲这三道。下一篇算法文章讲差分数组。

微信扫描下方二维码,关注公众号后回复【笔记】,有我准备的15万字Java面试笔记。

??感谢各位人才的点赞、收藏和评论,干货文章持续更新中,下篇文章再见!

相关推荐

ES6中 Promise的使用场景?(es6promise用法例子)

一、介绍Promise,译为承诺,是异步编程的一种解决方案,比传统的解决方案(回调函数)更加合理和更加强大在以往我们如果处理多层异步操作,我们往往会像下面那样编写我们的代码doSomething(f...

JavaScript 对 Promise 并发的处理方法

Promise对象代表一个未来的值,它有三种状态:pending待定,这是Promise的初始状态,它可能成功,也可能失败,前途未卜fulfilled已完成,这是一种成功的状态,此时可以获取...

Promise的九大方法(promise的实例方法)

1、promise.resolv静态方法Promise.resolve(value)可以认为是newPromise方法的语法糖,比如Promise.resolve(42)可以认为是以下代码的语...

360前端一面~面试题解析(360前端开发面试题)

1.组件库按需加载怎么做的,具体打包配了什么-按需加载实现:借助打包工具(如Webpack的require.context或ES模块动态导入),在使用组件时才引入对应的代码。例如在V...

前端面试-Promise 的 finally 怎么实现的?如何在工作中使用?

Promise的finally方法是一个非常有用的工具,它无论Promise是成功(fulfilled)还是失败(rejected)都会执行,且不改变Promise的最终结果。它的实现原...

最简单手写Promise,30行代码理解Promise核心原理和发布订阅模式

看了全网手写Promise的,大部分对于新手还是比较难理解的,其中几个比较难的点:状态还未改变时通过发布订阅模式去收集事件实例化的时候通过调用构造函数里传出来的方法去修改类里面的状态,这个叫Re...

前端分享-Promise可以中途取消啦(promise可以取消吗)

传统Promise就像一台需要手动组装的设备,每次使用都要重新接线。而Promise.withResolvers的出现,相当于给开发者发了一个智能遥控器,可以随时随地控制异步操作。它解决了三大...

手写 Promise(手写输入法 中文)

前言都2020年了,Promise大家肯定都在用了,但是估计很多人对其原理还是一知半解,今天就让我们一起实现一个符合PromiseA+规范的Promise。附PromiseA+规范地址...

什么是 Promise.allSettled()!新手老手都要会?

Promise.allSettled()方法返回一个在所有给定的promise都已经fulfilled或rejected后的promise,并带有一个对象数组,每个对象表示对应的pr...

前端面试-关于Promise解析与高频面试题示范

Promise是啥,直接上图:Promise就是处理异步函数的API,它可以包裹一个异步函数,在异步函数完成时抛出完成状态,让代码结束远古时无限回掉的窘境。配合async/await语法糖,可...

宇宙厂:为什么前端离不开 Promise.withResolvers() ?

大家好,很高兴又见面了,我是"高级前端进阶",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发。1.为什么需要Promise.with...

Promise 新增了一个超实用的 API!

在JavaScript的世界里,Promise一直是处理异步操作的神器。而现在,随着ES2025的发布,Promise又迎来了一个超实用的新成员——Promise.try()!这个新方法简...

一次搞懂 Promise 异步处理(promise 异步顺序执行)

PromisePromise就像这个词的表面意识一样,表示一种承诺、许诺,会在后面给出一个结果,成功或者失败。现在已经成为了主流的异步编程的操作方式,写进了标准里面。状态Promise有且仅有...

Promise 核心机制详解(promise机制的实现原理)

一、Promise的核心状态机Promise本质上是一个状态机,其行为由内部状态严格管控。每个Promise实例在创建时处于Pending(等待)状态,此时异步操作尚未完成。当异步操作成功...

javascript——Promise(js实现promise)

1.PromiseES6开始支持,Promise对象用于一个异步操作的最终完成(包括成功和失败)及结果值的表示。简单说就是处理异步请求的。之所以叫Promise,就是我承诺,如果成功则怎么处理,失败怎...

取消回复欢迎 发表评论: