【C++】前缀和方法系列问题求解

一、解题背景

  在刷leetcode的相关算法题的时候,对于求解数组的连续子数列的和的相关问题。常能想到的就是双层遍历,暴力求解,但是这样的效率不高,所以我们这里引入哈希表来降低时间复杂度,只用遍历一次便能求解出答案。

二、相关问题

1. 两数之和

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

关键点:
这道题我们可以直接采用暴力方法求解;或者再进一步我们可以想到先将数组排序,然后前后双指针来求解,但这种情况下保存原始数组最初的位置索引是个麻烦事;最后一种方法是我想说的,直接利用map来存储之前出现过的数组中的数字,看是否能配对成和为target的答案对。

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        unordered_map<int, int> mp;
        int sum=0;
        for (int i=0; i<nums.size(); i++)  {
            sum = nums[i];
            auto iter = mp.find(target-sum); // 查找是否能成对的答案
            if (iter!=mp.end()) {
                return {iter->second, i};
            }
            mp.insert(make_pair(nums[i], i));
        }
        return {};
    }
};
560. 和为 K 的子数组

给你一个整数数组 nums 和一个整数 k ,请你统计并返回该数组中和为 k 的连续子数组的个数。
输入:nums = [1,1,1], k = 2
输出:2
‘********************’
输入:nums = [1,2,3], k = 3
输出:2

关键点:首先我们需要明白何为k的连续子数组可能出现的位置,1.可能是中间部分的一段;2.可能是从开头到之后某个数的一段;然后这两种情况的话,第一种情况下,我们求解前缀和来减去k则一定会在之前插入map中的key中出现,但是第二种情况下,会出现减去k之后变成了0,此时的map中是没有key为0的pair,因此我们需要提前在map中添加key为0的键值对。

class Solution {
public:
    int subarraySum(vector<int>& nums, int k) {
      
        unordered_map<int, int> mp;
        int sum = 0;
        int n = 0;
        mp.insert(make_pair(0, 1));
        for (int i=0; i<nums.size(); i++) {
            sum += nums[i];

            int sum_pre = sum-k;
            auto iter = mp.find(sum_pre);
            if (iter!=mp.end()) { 
                n += mp[sum_pre];
            }
            // 这里是将对应前缀和为sum的数量增加1,这样当后边出现sum-k为 sum_pre的时候 就知道存在多少种情况的连续子数组满足和为k。
            mp[sum]++; 
        }
        return n;
    }
};
974. 和可被 K 整除的子数组

给定一个整数数组 nums 和一个整数 k ,返回其中元素之和可被 k 整除的(连续、非空) 子数组 的数目。
子数组 是数组的 连续 部分。
输入:nums = [4,5,0,-2,-3,1], k = 5
输出:7
解释:
这个时候我们将该列表对应的前缀和对k取余后为,[4,4,4,2,4,0] 从这个出发我们观察最终的结果是那些范围。第二个4有一种情况,第三个4有两种情况,第四个4有三种情况,最后一个0是一种情况,则总共1+2+3+1=7种。
‘’********************************************************
有 7 个子数组满足其元素之和可被 k = 5 整除:
[4, 5, 0, -2, -3, 1], [5], [5, 0], [5, 0, -2, -3], [0], [0, -2, -3], [-2, -3]
‘’********************************************************
输入:nums = [-1, 2, 9], k = 2
输出:2

class Solution {
public:
    int subarraysDivByK(vector<int>& nums, int k) {
        unordered_map<int,int> mp;
        int sum=0;
        int res=0;
        
        mp.insert(make_pair(0,1));
        for (int i=0; i<nums.size(); i++) {
            sum+=nums[i];
            // 注意 C++ 取模的特殊性,当被除数为负数时取模结果为负数,需要纠正
            int mod = (sum%k+k) %k;
            auto iter = mp.find(mod);
            if (iter!=mp.end()) {    
                res +=  mp[mod];
                mp[mod]++;
            } else {
				mp[mod] = 1;
			}	
            
        }
        return res;
    }
};
523. 连续的子数组和(和为k的倍数且大小至少为2)

给你一个整数数组 nums 和一个整数 k ,编写一个函数来判断该数组是否含有同时满足下述条件的连续子数组:
子数组大小 至少为 2 ,且
子数组元素总和为 k 的倍数。
如果存在,返回 true ;否则,返回 false 。
如果存在一个整数 n ,令整数 x 符合 x = n * k ,则称 x 是 k 的一个倍数。0 始终视为 k 的一个倍数
输入:nums = [23,2,4,6,7], k = 6
输出:true
解释:[2,4] 是一个大小为 2 的子数组,并且和为 6 。
'*************************************************************
输入:nums = [23,2,6,4,7], k = 13
输出:false
'*************************************************************
输入:nums = [2,2,2,2,2], k = 2
输出:true

class Solution {
public:
    bool checkSubarraySum(vector<int>& nums, int k) {
        unordered_map<int, int> mp;
        if (mp.size()<2) return false;
        int sum=0;
        mp.insert(make_pair(0, -1));
        for (int i=0; i<nums.size(); i++) {
            sum += nums[i];
            sum %= k;

            auto iter = mp.find(sum);
            if (iter!=mp.end()) {
                if (i-iter->second >= 2) return true;
            }
            mp.insert(make_pair(sum, i)); 
            // 维持一个前缀和对k取余的一个列表若出现相同的前缀和说明存在
            // 这里有个关键点,insert插入数据时,如果已经存在key的时候是不会对原始的k-v对进行修改的,因此这里保存的是最早出现的前缀和的那个k-v值
            // 或者可以改写成
            /*
            else {
				mp[sum]=i;
			}*/
            
        }
        return false;
    }
};

反思: 其实我们细看,这个题目与上一道题目是十分类似的,但上一道题目判断取余为负的情况就全部ak了,而这道题却没有判断,这样一想好像不对劲,就比如输入是[-1,2,4] k=2,这种情况上面的解法就不能通过,(其实是别人题意种说明了nums中的所有值都是>=0,所以肯定不存在取余为负数的情况)。如果存在负数的情况我们可以改进为如下:

class Solution {
public:
    typedef long long ll;
    bool checkSubarraySum(vector<int>& nums, int k) {
        unordered_map<int, int> mp;
        if (mp.size()<2) return false;
        ll sum=0;
        
        mp.insert(make_pair(0, -1));
        for (int i=0;i<nums.size(); i++) {
            sum += nums[i];
            ll mod = (sum%k+k) %k;

            auto iter = mp.find(mod);
            if (iter!=mp.end()) {
                if (i-iter->second >= 2) return true;
            }
            else {
                mp[mod] = i;
            }
            
        }
        return false;
    }
};
713. 乘积小于K的子数组

给你一个整数数组 nums 和一个整数 k ,请你统计并返回该数组中和为 k 的连续子数组的个数。
输入: nums = [10,5,2,6], k = 100
输出: 8
解释: 8个乘积小于100的子数组分别为: [10], [5], [2], [6], [10,5], [5,2], [2,6], [5,2,6]。
需要注意的是 [10,5,2] 并不是乘积小于100的子数组。
提示:
1 <= nums.length <= 3 * 104
1 <= nums[i] <= 1000
0 <= k <= 106

class Solution {
public:
    int numSubarrayProductLessThanK(vector<int>& nums, int k) {
        if (k==0||k==1) return 0;
        unordered_map<int, int> mp;
        int product = 1; // 前缀乘积和
        int res = 0;
        int l = 0; // 左指针
        for (int i=0; i<nums.size(); i++) {
            product *= nums[i];

            while (product >= k) {
                product /= nums[l++];
            }
            res += i-l+1;
        }
        return res;
    }
};

你可能感兴趣的:(C++知识圈,c++,leetcode,算法)