leetcode-数组

目录

数组求和问题

303. Range Sum Query - Immutable

304. Range Sum Query 2D - Immutable

涉及子数组时的思考方向:

560. Subarray Sum Equals K

1094. Car Pooling

1109. Corporate Flight Bookings

考虑双指针

209. Minimum Size Subarray Sum

1456. Maximum Number of Vowels in a Substring of Given Length

数组逆转

 344. Reverse String

二维数组

48. Rotate Image

54. Spiral Matrix


数组求和问题

303. Range Sum Query - Immutable

(1) Range Sum Query - Immutable - LeetCode

初始思路是直接计算累加和

class NumArray {
public:
    NumArray(vector<int>& nums) {
        for(int elem:nums)
            arr.push_back(elem);
        
    }
    
    int sumRange(int left, int right) {
        int sum = 0;
        for(int i=left;i<=right;i++){
            sum = sum + arr[i];
        }
        return sum;
    }
private:
    vector<int> arr;
};

由于使用了一个for循环,每调用一次sumRange()的时间复杂度是O(n)

实现时间复杂度O(1)的方法:使用累加和数组

class NumArray {
public:
    NumArray(vector<int>& nums) {
       accusum.push_back(0);
        for(int elem:nums){
            accusum.push_back(accusum.back()+elem);
        }
    }
    
    int sumRange(int left, int right) {
        return accusum[right+1]-accusum[left];
    }
private:
    vector<int> accusum;
};

实现了求任意子数组的和

同类题处理二维数组:

304. Range Sum Query 2D - Immutable

(1) Range Sum Query 2D - Immutable - LeetCode

class NumMatrix {
public:
    NumMatrix(vector<vector<int>>& matrix) {
        //第0行和第0列设置为0
        //累积和数组从第1行和第1列开始
        col = matrix[0].size()+1;
        row = matrix.size()+1;
        accusum = vector<vector<int>>(row,vector<int>(col,0));//初始化为0
        for(int i=1;i<row;i++){
            for(int j=1;j<col;j++){
                accusum[i][j] = accusum[i-1][j]+accusum[i][j-1]-accusum[i-1][j-1]+matrix[i-1][j-1];
            }
        }
        
    }
    
    int sumRegion(int row1, int col1, int row2, int col2) {
        row1++;col1++;row2++;col2++;
        return accusum[row2][col2]-accusum[row2][col1-1]-accusum[row1-1][col2]+accusum[row1-1][col1-1];
    }
private:
    vector<vector<int>> accusum;//前[0,0,i,j]矩形累积和
    int col;//累积和数组的列数
    int row;//累计和数组的行数
};

涉及子数组时的思考方向:

560. Subarray Sum Equals K

(1) Subarray Sum Equals K - LeetCode

前面的303实现了任意一个子数组的求和,而本题是查找所有可能的子数组的和

利用累积和数组可以想到暴力解法:

public:
    int subarraySum(vector<int>& nums, int k) {
        //穷举法——基于累积数组
        //创建累积数组
        vector<int> accusum ;
        accusum.push_back(0);//前0个元素的和为0
        for(int elem:nums){
            accusum.push_back(accusum.back()+elem);
        }
        int count= 0;
        //基于累积数组进行查找
        for(int i=1;i<accusum.size();i++){
            for(int j=0;j<i;j++){
                int res = accusum[i]-accusum[j];
                if(res==k)  count++;
            }
        }
        return count;
    }

因为有两层循环进行遍历,所以时间复杂度是O(n^2),过高未通过

是否能消掉一层循环?

k与累积和的关系:k == accusum[i]-accusum[j];可见,两次查找 accusum数组代价 高

修改后:accusum[j] = accusum[i]-k;这样关于任意子数组和的查找问题就转换为累积数组的查找问题

考虑使用哈希,只要累计和数组中存在一个元素值等于 accusum[i]-k则说明存在子数组和为k

按照i遍历时,对于每个accusum[i],存在m个元素值等于accusum[i]-k则说明存在m个子数组和为k。可以用哈希记录元素值出现的次数,这样只用找到这个元素就找到了对应的出现次数,元素即累计和的元素,有[accusum[i]:出现次数]

在哈希中找到这个元素的时间为O(1)

边求sum[i]边更新哈希表:

所求sum已经出现过→出现次数+1

所求sum未出现过→加入哈希表,出现次数1

同时查找哈希表:存在key为accusum[i]-k的哈希表项目则说明存在子数组和为k,将出现次数

最终遍历完整个数组

int subarraySum(vector<int>& nums, int k) {
        map<int,int> res_sum;
        res_sum[0]++;//初始化sum=0
        int count = 0;
        int sum = 0;
        for(int i=0;i<nums.size();i++){
            //计算累积数组
            sum = nums[i]+sum;
            //查找map
            if(res_sum[sum-k])
                count = count + res_sum[sum-k];
            //更新map——放在查找map后面,防止[[1],0]的特殊情况
            res_sum[sum]++;
        }
        return count;
    }

将对不同子区间同时加一个数转换为O(1)时间内的差分数组处理

原理是一个子区间同时加一个数时,这整个子区间元素之间的差值不变,子区间首元素与其前一个元素之间的差值改变,子区间末尾元素与其后一个元素之间的差值也改变

利用区间首元素diff[i]与其前一个元素diff[i-1]之间的差值改变diff,与diff[i-1]可以diff[i]:

利用整个区间首元素可以求整个数组最终的值——累加数组的技巧

1094. Car Pooling

(1) Car Pooling - LeetCode

 bool carPooling(vector<vector<int>>& trips, int capacity) {
        //使用一个动态数组保存每个trip上的乘客数量
        //计算trip上的 乘客数量相当于在做子数组动态求和→使用差分数组的 思路
        vector<int> diff(1001,0);
        for(int i=0;i<trips.size();i++){
            int num= trips[i][0];
            int start = trips[i][1];
            int end = trips[i][2] - 1;//toi时这一站已经下车了
            
            diff[start] += num;
            if(end+1 < diff.size())  diff[end+1] -= num;
        }
        
        for(int i=1;i<diff.size();i++){
            diff[i] += diff[i-1];
            if(diff[i-1] > capacity)  return false;
        }
        return true;
    }

1109. Corporate Flight Bookings

(1) Corporate Flight Bookings - LeetCode

 vector<int> corpFlightBookings(vector<vector<int>>& bookings, int n) {
        //差分数组计算最后的累加情况
        vector<int> diff(n+1,0);//下标0不使用
        for(int i=0;i<bookings.size();i++){
            //子数组范围
            int start = bookings[i][0];
            int end = bookings[i][1];
            //所加的值
            int k = bookings[i][2];
            //处理累加数组
            diff[start] += k;
            if(end<n)   diff[end+1] -= k;
        }
        
        //通过差分数组求arr
        
        for(int i=1;i<diff.size();i++){
            diff[i] += diff[i-1];
            diff[i-1] = diff[i];
        }
        diff.pop_back();
        return diff;
    }

考虑双指针

11. Container With Most Water

(1) Container With Most Water - LeetCode

class Solution {
public:
    int maxArea(vector<int>& height) {
       //头尾指针读取
        int begin = 0;
        int end = height.size()-1;
        int len = 0;
        int hi = 0;
        int res = 0;
        while(begin < end){
            len = end - begin;
            hi = findMin(height[begin],height[end]);
            res = findMax(res,len*hi);
            //小的往前找到一个比它大的
            while(height[begin] <= hi && begin < end)
                begin++;
            while(height[end] <= hi && begin < end)
                end--;
        }
        return res;
    }
    int findMin(int a,int b){
        return a>b?b:a;
    }
    int findMax(int a,int b){
        return a>b?a:b;
    }
};

209. Minimum Size Subarray Sum

(1) Minimum Size Subarray Sum - LeetCode

初始思路:

i维护子数组的初始位置,j维护子数组的结束位置。

保持向前遍历,并判断i,j移动位置:

i和j维护的子数组区间累计和sum<target,继续向前加入一个元素,所以j++。

sum>=target,满足条件,更新length。同时扔掉一个后再继续

time complexity:O(n)

int minSubArrayLen(int target, vector<int>& nums) {
        if(nums.size()==0)  return 0;
        //双指针维护子数组长度
        int i = 0;
        int j = 0;
        int length = 0;
        int sum = nums[0];
        while(j<nums.size()){
            if(sum >= target){
                if(length==0 || length > j-i+1){
                    length = j-i+1; 
                }
                sum -= nums[i];
                i++;//仍掉一个,继续向前检索
            }
            else{
                j++;
                if(j<nums.size()){
                    sum += nums[j]; 
                }
            }
        }
        return length;
    }

1456. Maximum Number of Vowels in a Substring of Given Length

(1) Maximum Number of Vowels in a Substring of Given Length - LeetCode

借鉴字符串的子串匹配思路,双指针跳跃判断

runcode成功但是admit超时

int maxVowels(string s, int k) {
        int vowels[26] = {1,0,0,0,1,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,1};
        //双指针——借鉴字符串匹配的思路
        int res = 0;
        int first = 0;//first维护第一个vowels
       
        while(first < s.size()){
            int cur_res = 0;
            
            while(first<s.size()){
                if(vowels[s[first]-'a']==1){
                    cur_res = 1;
                    break;
                }
                first++;
            }//找到first维护的第一个vowels
            
            int next = first;//next维护下一个vowels
            bool next_change = false;
            for(int i=1;i<k;i++){
                if(first+i<s.size() && vowels[s[first+i]-'a']==1){
                    cur_res++;
                    if(!next_change){
                        next = first + i;//如果存在下一个vowels,first下一步跳转到此
                        next_change = true;
                    }
                }
            }
            if(cur_res > res)   res = cur_res;
            if(!next_change){//k个中不存在vowels
                first += k;
            }
            else{
                first = next;
            }
        }
        return res;
    }

一步一步往前挪,不跳

转换为此前的子数组求和,由于固定长度,不需要双指针动态维护

往前走增加一个,为保证length,后面扔一个

int maxVowels(string s, int k) {
        //转换为固定长度的子数组求和问题-元音个数和正好为sum
        int vowels[26] = {1,0,0,0,1,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,1};
        int res = 0;
        for(int i=0,cur_res=0;i<s.size();i++){
            cur_res += vowels[s[i]-'a'];//增加前面一个
            if(i>=k){
                //往前移动丢掉后面一个,增加前面一个
                cur_res -= vowels[s[i-k]-'a'];
            }
            if(res<cur_res) res = cur_res;
        }
        return res;
    }

数组逆转

 344. Reverse String

(1) Reverse String - LeetCode

思路一:

数组可获取下标——使用尾指针方便,故可考虑首尾指针

void reverseString(vector<char>& s) {
        int last = s.size()-1;
        int first = 0;
        while(first < last){
            char temp = s[first];
            s[first] = s[last];
            s[last] = temp;
            first++;
            last--;
        }
    }

二维数组

48. Rotate Image

(1) Rotate Image - LeetCode

通过观察可得,顺时针旋转90度相当于先按列逆转,再将第i列放到第i行

第i列放到第i行==>联系镜像对称,只用交换对角线

 void rotate(vector<vector<int>>& matrix) {
        //按列进行逆转
        int col = matrix[0].size();//列数
        int row = matrix.size();//行数
        for(int i=0;i<col;i++){
            for(int first = 0,last = row-1;first<last;first++,last--){
                int temp = matrix[first][i];
                matrix[first][i] = matrix[last][i];
                matrix[last][i] = temp;
            }
        }
        //对角线交换——只用遍历上对角线
        for(int i=0;i<row;i++){
            for(int j=i+1;j<col;j++){
                int temp = matrix[i][j];
                matrix[i][j] = matrix[j][i];
                matrix[j][i] = temp;
            }
        }
    }

由于这两个操作互逆,考虑能否先对角线交换进行镜像对称再逆转

镜像对称处理后由于行列已互换,故原来的按列逆转现在可以换位按行逆转

思路二:镜像对称+按行逆转

仍基于此思路,逆时针旋转的处理思路则正好是先按行逆转,再镜像对称

总结:

逆时针旋转90度:镜像对称+按行逆转

顺时针旋转90度:按行旋转+镜像对称

54. Spiral Matrix

(1) Spiral Matrix - LeetCode

螺旋遍历就是按照4个方向:右下左上一圈,直到遍历完所有元素

循环圈数:(row+1)/2

基于此思路代码:

vector<int> spiralOrder(vector<vector<int>>& matrix) {
        
        int row = matrix.size();
        int col = matrix[0].size();
        int n = row*col;
        vector<int> res;
        //按圈循环——(row+1)/2次
        int toright = 0;
        int toleft = row-1;
        int up = 0;
        int down = col-1;
        while(n){
        //遍历一圈
            //1.toright从[i,i]开始 
            for(int i=toright;i<=down;i++){
                res.push_back(matrix[toright][i]);
                n--;
            }
            if(n==0)    break;
            //2.down
            for(int i=toright+1;i<=toleft;i++){
                res.push_back(matrix[i][down]);
                n--;
            }
            if(n<=0)    break;
            //3.toleft
            for(int i=down-1;i>=up;i--){
                res.push_back(matrix[toleft][i]);
                n--;
            }
            if(n<=0)    break;
            //4.up
            for(int i=toleft-1;i>=toright+1;i--){
                res.push_back(matrix[i][up]);
                n--;
            }
            //圈不断缩窄
            toright++;
            toleft--;
            up++;
            down--;
            
        }
        return res;
    }

观察到,一圈中的4个方向循环:右下左上存在共性

每次只是方向不同+遍历起点和终点不同。

方向改变可以维护一个数组

遍历位置如何确定?

观察四个方向遍历,发现:

5*4数组:

右4下4左3上3

右2下2左1上1

5*5数组:

右5下4左4上3

右3下2左2上1

右1

规律:

水平方向遍历:右col左col-1右col-2左col-3直到0;

竖直方向遍历:下row-1上row-2下row-3上row-4直到0;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值