数组:小而美的算法技巧 前缀和、差分数组、花式遍历
本文介绍有关数组的算法三个技巧:前缀和、差分、花式遍历
包含三道力扣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--;
}
}
} -------------------- 温故知新,刷过的题也要好好复习~

查看5道真题和解析