关于数组的一些题目

1、 数组中和为 0 的三个数

  • 描述

    ​ 给定一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a ,b ,c ,使得 a + b + c = 0 ?请找出所有和为 0 且 不重复 的三元组。

  • 解法

    ​ 暴力来说,我们应该对数组使用三重循环,逐一判断。三重循环我们可以简化为一重循环和双指针。对每一个值使用双指针判断它后面是否存在两个值和它的和等于零。可以先进行排序,排完之后,大于 0 的值就可以直接舍弃了。要注意元素相同的情况,比如(-1,0,1,1,1),题目要求不重复的组合,得把这种情况排除。

  • 源码

    class Solution {
        public List<List<Integer>> threeSum(int[] nums) {
            Arrays.sort(nums);
            List<List<Integer>> res = new ArrayList<>();
            int len = nums.length;
            for(int i=0; i<len; i++){
                //大于 0 就不会存在该值了
                if(nums[i] > 0) break;
                //这里是为了防止重复值的出现,不对它进行判断而是直接下一个
                if(i>0 && nums[i]==nums[i-1]) continue;
                int start = i+1;
                int end = len-1;
                while(start < end){
                    int total = nums[i]+nums[start]+nums[end];
                    if(total == 0) {
                        res.add(Arrays.asList(nums[i],nums[start],nums[end]));
                        //这里不能直接 break; 
                        while(start<end && nums[start]==nums[start+1]) start++;
                        start++;
                    }
                    else if(total < 0) start++;
                    else if(total > 0) end--;
                }
            }
            return res;
        }
    }
    

2、和大于等于 target 的最短子数组

  • 描述

    ​ 给定一个含有 n 个正整数的数组和一个正整数 target 。找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl+1, ..., numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0 。

  • 解法

    ​ 寻找数组中满足条件的数据本质还是对数组的遍历,如何遍历满足条件而且更加高效,不同的题目有不同的技巧。对于这个题,对每一个后边界来寻找前边界。我们可以先让前边界不动,移动后边界,满足 > 的条件后,说明对于这个 end 边界存在 start 边界是条件成立,由于要最小值,所以 sum-=nums[start] , start++。

  • 源码

    class Solution {
        public int minSubArrayLen(int target, int[] nums) {
            int start = 0;
            int end = 0;
            int res = Integer.MAX_VALUE;
            int len = nums.length;
            int sum = 0; 
            while(end<len){
                //对于每一个后边界,来寻找合适的前边界,以保证数据遍历完整
                sum += nums[end];
                while(sum>=target){
                    sum -= nums[start];
                    res = Math.min(res,end-start+1);
                    start++;
                }
                end++;
            }
            return res==Integer.MAX_VALUE ? 0 : res;
        }
    }
    

3、乘积小于 K 的子数组

  • 描述

    ​ 给定一个正整数数组 nums和整数 k ,请找出该数组内乘积小于 k 的连续的子数组的个数。

  • 解法

    ​ 这也是一个求区间的问题,寻找还是要遍历。先按照上题的方法来寻找满足条件的区间,即里层的 while 循环,仍是对于 end 边界来寻找 start 边界。找到之后,按理说这个区间里面的所有组合都只满足的,res 应该 += (j-i+1)!,但是区间之间肯定会有重复部分,肯定是小于该值的。 (j-i+1)只是区间内元素的个数.... 不太理解这里

  • 源码

    class Solution {
        public int numSubarrayProductLessThanK(int[] nums, int k) {
            int res = 0;
            int start = 0;
            int end = 0;
            int add = 1;
            int len = nums.length;
            while(end < len){
                add *= nums[end];
                while(start<=end && add>=k){
                    add /= nums[start];
                    start++;
                }
                res += end-start+1;
                end++;
            }
            return res;
        }
    }
    

4、和为 K 的子数组

  • 描述

    ​ 给定一个整数数组和一个整数 k 请找到该数组中和为 k 的连续子数组的个数。

  • 解法

    ​ 连续区间的问题很多可以使用前缀和,上面的问题感觉也可以使用。前缀和和 Map 结合有时很有效果。子数组和为 K 就是 prefix[j]-prefix[i] == k 。本解法借助map 来快速寻找 prefix[i] 的出现的次数

  • 源码

    class Solution {
        public int subarraySum(int[] nums, int k) {
            int len = nums.length;
            int res = 0;
            int [] prefix = new int[len+1];
            Map<Integer,Integer> map = new HashMap<>();
            map.put(0,1);
            for(int i=1; i<=len; i++){
                //算出该点的前缀和,并判断 prefix[i]-k 是否在 map 中存储,如果有的话
                prefix[i] = prefix[i-1]+nums[i-1];
                if(map.containsKey(prefix[i]-k)) res+=map.get(prefix[i]-k);
                //可能存在 0 ,使得同值的前缀和存在
                map.put(prefix[i],map.getOrDefault(prefix[i],0)+1);
            }
            return res;
        }
    }
    

5、相同数量的 01 的最长连续子数组

  • 描述

    ​ 给定一个二进制数组 nums , 找到含有相同数量的 01 的最长连续子数组,并返回该子数组的长度。

  • 解法

    ​ 任然是区间,还是可以使用前缀和和Map的组合。但是 0 和 1 的前缀一直加没啥规律,这里用点技巧。遇到 0 时给前缀和 -1,那么当两个下标对应的前缀和相同时,就说明这个区间内的 0 和 1 个数时相同的。要注意的是,当该值在前面出现的时候,为了保证取得的是最长区间,不要将该值加入 map 里面。

  • 源码

    class Solution {
        public int findMaxLength(int[] nums) {
            int len = nums.length;
            int[] preSum = new int[len+1];
            Map<Integer,Integer> map = new HashMap<>();
            map.put(0,0);
            int max = 0;
            for(int i=1; i<=len; i++){
                preSum[i] = preSum[i-1] + (nums[i-1]==0 ? -1 : 1);
                if(map.containsKey(preSum[i])){
                    max = Math.max(max,i-map.get(preSum[i]));   
                }
                //前面没出现才加入map 里面,保证 map 里面存储的下标都是第一次出现的地方 
                else map.put(preSum[i],i);
            }
            return max;
        }
    }
    

6、左右两边的数组值相等

  • 描述

    ​ 给你一个整数数组 nums ,请计算数组的 中心下标 。数组 中心下标 是数组的一个下标,其左侧所有元素相加的和等于右侧所有元素相加的和。如果中心下标位于数组最左端,那么左侧数之和视为 0 ,因为在下标的左侧不存在元素。这一点对于中心下标位于数组最右端同样适用。如果数组有多个中心下标,应该返回 最靠近左边 的那一个。如果数组不存在中心下标,返回 -1 。

  • 解法

    ​ 左右两边仍然是区间,还是可以使用前缀和。只不过这里根据题目要求, preSum*2 + nums[i] == total,来计算判断。

  • 源码

    class Solution {
         public int pivotIndex(int[] nums) {
            int total = Arrays.stream(nums).sum();
            int sum = 0;
            for(int i=0; i<nums.length; i++){
                if(2*sum + nums[i] == total){
                    return i;
                }
                //累计前缀和
                sum += nums[i];
            } 
            return -1;
        }
    }
    

7、二维子矩阵的和

  • 描述

    ​ 给定一个二维矩阵 matrix,以下类型的多个请求:

    ​ 计算其子矩形范围内元素的总和,该子矩阵的左上角为 (row1, col1) ,右下角为 (row2, col2) 。
    ​ 实现 NumMatrix 类:

    ​ NumMatrix(int[][] matrix) 给定整数矩阵 matrix 进行初始化int sumRegion(int row1, int col1, int row2, int col2) 返回左上角 (row1, col1) 、右下角 (row2, col2) 的子矩阵的元素总和。

  • 解法

    ​ 关于矩阵的一个题目,要注意的是前缀和 preSum[i+1][j+1]是加到矩阵的 matrix[i][j]。因此求sumRegion(int row1, int col1, int row2, int col2)时是用 preSum[col2+1][rol+1]

  • 源码

    class NumMatrix {
        private int[][] preSum;
        public NumMatrix(int[][] matrix) {
            int m = matrix.length;
            if(m > 0 ){
                 this.preSum = new int[m+1][matrix[0].length+1];
            for(int i=0; i<m; i++){
                for(int j=0; j<matrix[0].length; j++){
                    preSum[i+1][j+1] = preSum[i][j+1]+preSum[i+1][j]-preSum[i][j]+matrix[i][j];  
                }
            }
            }
        }
        
        public int sumRegion(int row1, int col1, int row2, int col2) {
            return preSum[row2+1][col2+1] + preSum[row1][col1] - preSum[row1][col2+1]-preSum[row2+1][col1];
        }
    }
    
posted @ 2022-05-07 14:43  心是冰冰的  阅读(108)  评论(0)    收藏  举报