数组:小而美的算法技巧 前缀和、差分数组、花式遍历

本文介绍有关数组的算法三个技巧:前缀和、差分、花式遍历
包含三道力扣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--;

        }
    } 
}
--------------------
温故知新,刷过的题也要好好复习~


#算法学习##Java#
全部评论
哈哈哈,不错的技巧
点赞 回复 分享
发布于 2022-04-12 12:44

相关推荐

6 11 评论
分享
牛客网
牛客企业服务