算法:【前缀和+Hash表】

前缀和模板

S[i] = a[1] + a[2] + ... a[i]
a[l] + ... + a[r] = S[r] - S[l - 1]

         // 计算前缀和数组
         for(int i=0; i<nums.length; i++)
             preSum[i+1] = preSum[i] + nums[i];

1. 和为K的子数组

给定一个整数数组和一个整数 k,你需要找到该数组中和为 k 的连续的子数组的个数。

输入:nums = [1,1,1], k = 2
输出: 2 , [1,1] 与 [1,1] 为两种不同的情况。

class Solution {
    // 1. 暴力枚举
    // public int subarraySum(int[] nums, int k) {
    //     int res = 0;
    //     if(nums == null || nums.length == 0)
    //         return res;
    //     for(int i=0; i<nums.length; i++){
    //         int sum = 0;
    //         for(int j=i; j>=0; j--){
    //             sum += nums[j];
    //             if(sum == k)
    //                 res++;
    //         }
    //     }
    //     return res;
    // }

    // 2. 前缀和
    // public int subarraySum(int[] nums, int k) {
    //     int res = 0;
    //     if(nums == null || nums.length == 0)
    //         return res;
    //     int[] preSum = new int[nums.length+1];
    //     // 计算前缀和数组
    //     for(int i=0; i<nums.length; i++)
    //         preSum[i+1] = preSum[i] + nums[i];

    //     for(int i=0; i<nums.length; i++){
    //         for(int j=i; j>=0; j--){
    //             if(preSum[i+1] - preSum[j] == k)
    //                 res++;
    //         }
    //     }
    //     return res;
    // }

    // 3. 前缀和+Hash表
    public int subarraySum(int[] nums, int k) {
        int res = 0, pre = 0;
        if(nums == null || nums.length == 0)
            return res;
        HashMap<Integer, Integer> map = new HashMap<>();
        map.put(0, 1); // 表示前缀和为0,个数为1
        for(int i=0; i<nums.length; i++){
            pre = pre + nums[i];
            if(map.containsKey(pre - k)){
                res += map.get(pre - k);
            }
            map.put(pre, map.getOrDefault(pre, 0)+1);
        }
        return res;
    }
}

2. 两数之和

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。

输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。

class Solution {
    public int[] twoSum(int[] nums, int target) {
        if(nums == null || nums.length == 0)
            return new int[0];
        int pre = 0;
        int[] res = new int[2];
        HashMap<Integer, Integer> map = new HashMap<>();
        for(int i=0; i<nums.length; i++){
            if(map.containsKey(target - nums[i])){
                res[0] = map.get(target - nums[i]);
                res[1] = i;
                break;
            }
            map.put(nums[i], i);
        }
        return res;
    }
}

3. 统计「优美子数组」

给你一个整数数组 nums 和一个整数 k。
如果某个 连续 子数组中恰好有 k 个奇数数字,我们就认为这个子数组是「优美子数组」。
请返回这个数组中「优美子数组」的数目。

输入:nums = [1,1,2,1,1], k = 3
输出:2
解释:包含 3 个奇数的子数组是 [1,1,2,1] 和 [1,2,1,1] 。

class Solution {
    // 前缀和+hash表
    public int numberOfSubarrays(int[] nums, int k) {
        if(nums == null || nums.length == 0)    
            return 0;
        // 数组preSum的下标是前缀和(即当前奇数的个数),值是前缀和的个数
        int[] preSum = new int[nums.length+1]; // 用数组表示Hash表
        // 遍历原数组,计算当前的前缀和,统计到prefixCnt数组中,并且在res中累加上与当前前缀和差值为k的前缀和的个数
        int sum = 0, res = 0;
        preSum[0] = 1; // 表示奇数为0的个数
        for(int i=0; i<nums.length; i++){
            sum += nums[i] & 1;
            preSum[sum]++;
            if(sum >= k){
                res += preSum[sum-k];
            }
        }
        return res;
    }
}

4. 四数相加 II

给定四个包含整数的数组列表 A , B , C , D ,计算有多少个元组 (i, j, k, l) ,使得 A[i] + B[j] + C[k] + D[l] = 0。
为了使问题简单化,所有的 A, B, C, D 具有相同的长度 N,且 0 ≤ N ≤ 500 。所有整数的范围在 -228 到 228 - 1 之间,最终结果不会超过 231 - 1 。

输入:
A = [ 1, 2]
B = [-2,-1]
C = [-1, 2]
D = [ 0, 2]
输出:
2
解释:两个元组如下:
1.(0, 0, 0, 1) -> A[0] + B[0] + C[0] + D[1] = 1 + (-2) + (-1) + 2 = 0
2.(1, 1, 0, 0) -> A[1] + B[1] + C[0] + D[0] = 2 + (-1) + (-1) + 0 = 0

class Solution {
    // 分组+Hash表
    public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
        HashMap<Integer, Integer> mapAB = new HashMap<>();
        int res = 0;
        // nums1, nums2一组
        for(int i=0; i<nums1.length; i++){
            for(int j=0; j<nums2.length; j++){
                int tmp = nums1[i]+nums2[j];
                mapAB.put(tmp, mapAB.getOrDefault(tmp, 0) + 1);
            }
        }

        // nums3, nums4一组
        for(int i=0; i<nums3.length; i++){
            for(int j=0; j<nums4.length; j++){
                int tmp = nums3[i]+nums4[j];
                if(mapAB.containsKey(-tmp)){
                    res += mapAB.get(-tmp);
                }
            }
        }
        return res;
    }
}

5.和至少为 K 的最短子数组

返回 A 的最短的非空连续子数组的长度,该子数组的和至少为 K 。
如果没有和至少为 K 的非空子数组,返回 -1 。

输入:A = [1], K = 1
输出:1

class Solution {
    // 前缀和 + 变种单调栈结构(栈底到栈顶单调递增)
    public int shortestSubarray(int[] nums, int k) {
        int[] sum = new int[nums.length+1]; // 前缀和数组
        for(int i=0; i<nums.length; i++){
            sum[i+1] = sum[i] + nums[i];
        }
        int res = -1;
        Deque<Integer> stack = new LinkedList<>();
        for(int i=0; i<=nums.length; i++){
            while(!stack.isEmpty() && sum[i] < sum[stack.peekLast()]){
                stack.removeLast();
            }
            while(!stack.isEmpty()){
                if(sum[i] - sum[stack.peekFirst()] >= k){
                    res = res == -1 ? i-stack.peekFirst() : Math.min(res,i-stack.peekFirst());
                    stack.removeFirst();
                }else{
                    break;
                }
            }
            if(sum[i] >= k){
                res = res==-1?i+1:Math.min(res,i+1);
            }
            stack.addLast(i);
        }
        return res;
    }
}

6.连续数组

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

输入: nums = [0,1,0]
输出: 2
说明: [0, 1] (或 [1, 0]) 是具有相同数量0和1的最长连续子数组。

思路:
由于「00 和 11 的数量相同」等价于「11 的数量减去 00 的数量等于 00」,我们可以将数组中的 00 视作 -1−1,则原问题转换成「求最长的连续子数组,其元素和为 00」。

class Solution {
    // 前缀和+hash
    public int findMaxLength(int[] nums) {
        HashMap<Integer, Integer> map = new HashMap<>();
        map.put(0, -1);
        int cur = 0, ans = 0;
        for(int i=0; i<nums.length; i++){
            if(nums[i] == 0){
                cur += -1;
            }else{
                cur += 1;
            }
            if(map.containsKey(cur)){
                ans = Math.max(ans, i-map.get(cur));
            }else{
                map.put(cur, i);
            }
        }
        return ans;
    }
}

7.和可被 K 整除的子数组

思路:
通常,涉及连续子数组问题的时候,我们使用前缀和来解决。

class Solution {
    public int subarraysDivByK(int[] nums, int k) {
        Map<Integer, Integer> record = new HashMap<Integer, Integer>();
        record.put(0, 1);
        int sum = 0, ans = 0;
        for (int elem : nums) {
            sum += elem;
            // 注意 Java 取模的特殊性,当被除数为负数时取模结果为负数,需要纠正
            int modulus = (sum % k + k) % k;
            int same = record.getOrDefault(modulus, 0);
            ans += same;
            record.put(modulus, same + 1);
        }
        return ans;
    }
}
全部评论

相关推荐

WhiteAlbum...:学院本2中大厂垂直实习➕acm比赛 秋招0面试
点赞 评论 收藏
分享
09-25 00:00
已编辑
电子科技大学 Java
球球与墩墩:这不是前端常考的对象扁平化吗,面试官像是前端出来的 const flattern = (obj) => { const res = {}; const dfs = (curr, path) => { if(typeof curr === 'object' && curr !== null) { const isArray = Array.isArray(curr); for(let key in curr) { const newPath = path ? isArray ? `${path}[${key}]` : `${path}.${key}` : key; dfs(curr[key], newPath); } } else { res[path] = curr } } dfs(obj); return res; }
查看3道真题和解析
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务