数组:小而美的算法技巧 前缀和、差分数组、花式遍历
本文介绍有关数组的算法三个技巧:前缀和、差分、花式遍历
包含三道力扣mid 【303 区域和检索-数组不可变】、【370 区间加法】、【48 旋转图像】
----------------------------
技巧一:前缀和(用于快速频繁的计算一个索引区间内的元素之和)
力扣第303题 :【 区域和检索 - 数组不可变 】https://leetcode-cn.com/problems/range-sum-query-immutable/
题目强调了多次调用sumRange()方法,自然而然会想到使用for循环进行遍历,但是时间复杂度会比较高O(N),N代表数组的长度,会出现超时。
这时可以考虑前缀和技巧。把sumRange()的时间复杂度给降下来,通俗的说就是不在sumRange()里使用for循环。
这时我们可以重新定义一个数组preSum[],n=nums.length,令preSum的长度为n+1, preSum[i]表示nums数组从0到第i-1个数字的和,preSum[0]=0;
这样在sumRange()中计算索引left和right之间的nums元素的和,preSum[right+1]-preSum[left] 即为所求。(如果你搞不清为什么是right+1,你自己举个例子,多练就知道为什么了!)
class NumArray { //定义前缀和数组 int [] preSum; /*构造前缀和数组 */ public NumArray(int[] nums) { //nums数组的长度 int n=nums.length; //定义 前缀和preSum数组,preSum[i]表示nums数组从0到i-1个元素的和 preSum=new int [n+1]; //preSum第一个元素为0 preSum[0]=0; for(int i=1;i<preSum.length;i++){ preSum[i]=preSum[i-1]+nums[i-1]; } } /* 查询闭区间 [left, right] 的累加和 */ public int sumRange(int left, int right) { return preSum[right+1]-preSum[left]; } }
技巧二:差分数组(用于频繁对原始数组的某个区间的元素进行增减)
举个例子:
输入一个数组nums,然后又要求给区间nums[2..6]全部加 1,再给nums[3..9]全部减 3,再给nums[0..4]全部加 2,再给…问最后nums数组的值是什么?
常规思路就是使用for循环多次对nums数组进行修改,这种思路时间复杂度为O(n),效率很低。
类别前缀和数组,我们可以构造一个差分数组diff,diff[i]表示nums[i]和nums[i-1]之差。
因为我们是可以通过diff推断出nums的,那就将对nums的增减可以转化成对差分数组进行操作,从而快速的对nums进行区间内的增减操作。
比如你想对区间nums[i,j]中所有的元素+3,那么你只需要diff[i]之后的所有元素+3,再将diff[j+1]之后的元素-3即可。
现在把差分数组抽象成一个类,包含increment方法(对diff数组进行操作)和result(还原nums数组)
//差分数组 class Difference{ //定义差分数组 int [] diff; /*构造差分数组*/ public Difference(int []nums){ diff =new int[nums.length]; //根据初始数据构造差分数组 diff[0]=nums[0]; for(int i=1;i<nums.length;i++){ diff[i]=nums[i]-nums[i-1]; } } /*对区间[i,j]进行增减,val为+,则为加法,val为-,则为减法*/ public void increment(int i,int j,int val){ diff[i]=diff[i]+val; if(j+1<diff.length){//当j+1>=diff.length时,说明对nums[i]及以后的整个数组都进行了修改,所以就不需要给diff减val了 diff[j+1]=diff[j+1]-val; } } /*返回结果数组*/ public int []result(){ int res=new int[diff.length]; //根据差分数组构造结果数组 //nums[0]=diff[0]=res[0]; res[0]=diff[0]; for(int i=1;i<diff.length;i++){ res[i]=res[i-1]+diff[i]; } return res; } }力扣第370题 【 区间加法 】(力扣恰烂钱,不给做,直接看题吧)
直接复用刚刚的Difference方法
int []getModifiedArray(int length,int[][] updates){ //将nums全部初始化为0 int []nums=new int[length]; //构造差分解法 Difference df=new Difference(nums); for(int []update :updates){ int i=update[0]; int j=upfate[1]; int val=update[2]; df.increment(i,j,val); } return df.result(); }技巧三:二维数组的花式遍历(针对二维数组进行旋转)
力扣第 48 题 【 旋转图像 】https://leetcode-cn.com/problems/rotate-image/
注意是难点在于进行【原地修改】,不许使用辅助数组
思路:可以将这个n*n的方阵按照正对角线进行对称,再将矩阵的列进行左右反转。如图:
如果还是抽象的话,可以去力扣题解看那个百草味面包的反转,很形象;
具体代码实现:
class Solution { /* 二维数组顺时针旋转90度 */ public void rotate(int[][] matrix) { //获取数组的行数行:matrix.length //获取数组的列数:matrix[0].length int n=matrix.length; //延对角线镜像翻转二维矩阵 for(int i=0;i<n;i++){ for(int j=i;j<n;j++){ //交换matrix[i][j]和matrix[j][i] int temp=matrix[i][j]; matrix[i][j]=matrix[j][i]; matrix[j][i]=temp; } } //反转二维数组的每一行 for(int []row :matrix){ reverse(row); } } //反转一维数组 public void reverse(int [] arr){ int i=0,j=arr.length-1; while(j>i){ //交换i,j的顺序 int temp=arr[i]; arr[i]=arr[j]; arr[j]=temp; i++; j--; } } }--------------------
温故知新,刷过的题也要好好复习~