【子数组/子串】相关类型题目整理

一、力扣560.和为k的子数组

  • 给定一个整数数组和一个整数 k,你需要找到该数组中和为 k 的连续的子数组的个数。
  • 输入:nums = [1,1,1], k = 2
  • 输出: 2 , [1,1] 与 [1,1] 为两种不同的情况。

(一)暴力枚举(双重循环)

  • 枚举数组中所有的子数组,统计出和为k的子数组的个数。
  • 双重循环的思想比较简单,但是会超出题目限制。代码如下:
int subarrySum(vector<int>& nums, int k)
    {
        int sum = 0, count = 0;
        for (int i = 0; i < nums.size(); i++)
        {
            for (int j = i; j < nums.size(); j++)
            {
                sum += nums[j];
                if (sum == k)
                {
                    count++;
                }
            }
            sum = 0;
        }
        return count;
    }
  • 解析:

在这里插入图片描述

(二)前缀和思想+哈希优化

  • 所谓前缀和,就是对于一个给定的数列A,它的前缀和数列S是通过递推能求出来的部分和。
  • 举个例子如下:

在这里插入图片描述

  • 假设我们要获取nums[2]到nums[4]这个区间的和,可以用下图所示方法得到:

在这里插入图片描述

  • 思路:

在这里插入图片描述

  • 代码如下:
int subarrySum(vector<int>& nums, int k)
{
    unordered_map<int, int> mp;//哈希表
    int count = 0;//统计前缀和出现的次数
    int pre = 0;//计算前缀和
    mp[0] = 1;
    for (auto& x : nums)
    {
        pre += x;
        if (mp.find(pre - k) != mp.end())//寻找出现过的前缀和,如果找到了就给count加上它出现过的次数。
        {
            count += mp[pre - k];
        }
        mp[pre]++;
    }
    return count;
}

二、力扣974.和可被k整除的子数组

  • 给定一个整数数组 A,返回其中元素之和可被 K 整除的(连续、非空)子数组的数目。
  • 输入:A = [4,5,0,-2,-3,1], K = 5
  • 输出:7
  • 解释:有 7 个子数组满足其元素之和可被 K = 5 整除:
    [4, 5, 0, -2, -3, 1], [5], [5, 0], [5, 0, -2, -3], [0], [0, -2, -3], [-2, -3]

(一)哈希表+逐一统计

  • 思路和上题基本相同:

在这里插入图片描述

  • 代码如下:
int subarraysDivByK(vector<int>& nums, int k) 
{
    unordered_map<int, int> mp;
    mp[0] = 1;
    int count = 0;
    int sum = 0;
    for (auto& x : nums)
    {
        sum += x;
        int tmp = (sum % k + k) % k;//处理取模之后为负数的情况
        count += mp[tmp];
        mp[tmp]++;
    }
    return count;
}

三、力扣523.连续的子数组和

  • 给定一个包含 非负数 的数组和一个目标 整数 k ,编写一个函数来判断该数组是否含有连续的子数组,其大小至少为 2,且总和为 k的倍数,即总和为 n * k ,其中 n 也是一个整数。
  • 输入:nums = [2,7,11,15], target = 9
  • 输出:[0,1]
  • 解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。
  • 输入:nums = [3,2,4], target = 6
  • 输出:[1,2]

(一)暴力枚举(O(N^2))

  • 暴力枚举直接用双重循环解决,思想比较简单,直接写代码:
vector<int> twoSum(vector<int>& nums, int target)
{
    vector<int> vec(2, 0);
    for (int i = 0; i < nums.size(); i++)
    {
        for (int j = i + 1; j < nums.size(); j++)
        {
            if (nums[i] + nums[j] == target)
            {
                vec[0] = i;
                vec[1] = j;
            }
        }
    }
    return vec;
}

(二)哈希表

  • 思路:

在这里插入图片描述

  • 代码如下:
vector<int> twoSum(vector<int>& nums, int target)
{
    unordered_map<int, int> hash;
    vector<int> vec(2, 0);
    for (int i = 0; i < nums.size(); i++)
    {
        if (hash.find(target - nums[i]) != hash.end())
        {
            vec[0] = hash[target - nums[i]];
            vec[1] = i;
            break;
        }
        else
        {
            hash[nums[i]] = i;
        }
    }
    return vec;
}

四、拼多多笔试题.多多的求和计算

  • 多多路上从左到右有N棵树(编号1~N),其中第i个颗树有和谐值Ai。
  • 多多鸡认为,如果一段连续的树,它们的和谐值之和可以被M整除,那么这个区间整体看起来就是和谐的。
  • 现在多多鸡想请你帮忙计算一下,满足和谐条件的区间的数量。
  • 输入描述:
  • 第一行,有2个整数N和M,表示树的数量以及计算和谐值的参数。
  • ( 1 <= N <= 100,000, 1 <= M <= 100 )
  • 第二行,有N个整数Ai, 分别表示第i个颗树的和谐值。
  • ( 0 <= Ai <= 1,000,000,000 )

👀👀

  • 在这里要注意的是树的数量和和谐之Ai的取值都已经超过了int(0~65535)所表示的范围,因此在这里我们将数据类型设置为long long类型。

👀👀

  • 解题思路和上面力扣974完全相同,需要注意的就是数据的类型。代码如下:
#include <iostream>
#include <unordered_map>
#include <vector>
using namespace std;

long long Sum(vector<long long>& vec,int target)
{
    long long sum=0;
    long long count=0;
    unordered_map<long long,int> hash;
    hash[0]=1;
    for(auto& x:vec)
    {
        sum+=x;
        long long tmp=(sum%target + target) % target;
        count += hash[tmp];
        hash[tmp]++;
    }
    return count;
}

int main()
{
    vector<long long> vec;
    long long m;
    int n;
    cin>>m;
    cin>>n;
    long long x;
    for(long long i=0;i<m;i++)
    {
        cin>>x;
        vec.push_back(x);
    }
    long long sum=Sum(vec,n);
    cout<<sum<<endl;
    return 0;
}

五、力扣724.寻找数组的中心下标

  • 给你一个整数数组 nums,请编写一个能够返回数组 “中心下标” 的方法。
  • 数组 中心下标 是数组的一个下标,其左侧所有元素相加的和等于右侧所有元素相加的和。
  • 如果数组不存在中心下标,返回 -1 。如果数组有多个中心下标,应该返回最靠近左边的那一个。
  • 注意:中心下标可能出现在数组的两端。
  • 输入输出示例1:
  • 输入:nums = [1, 7, 3, 6, 5, 6]
    输出:3
    解释:
    中心下标是 3 。
    左侧数之和 (1 + 7 + 3 = 11),
    右侧数之和 (5 + 6 = 11) ,二者相等。
  • 输入输出示例2:
  • 输入:nums = [1, 2, 3]
    输出:-1
    解释:
    数组中不存在满足此条件的中心下标。
  • 输入输出示例3:
  • 输入:nums = [2, 1, -1]
    输出:0
    解释:
    中心下标是 0 。
    下标 0 左侧不存在元素,视作和为 0 ;
    右侧数之和为 1 + (-1) = 0 ,二者相等。

(一)前缀和+遍历vector(以一个vector为代价)

  • 首先我们可以利用前面几道题的前缀和思想,将数组中的每一位上的前缀和存放到一个vector中;
  • 在这里要注意的是由于原数组的头端和尾端都有可能是数组的中心下标,因此为了解决这个问题我们将前缀和vector的第一个元素push_back(0)一个0进去;
  • 然后遍历一遍vector找到符合条件的下标即可。
  • 如下图所示:

在这里插入图片描述

  • 代码如下:
int pivotIndex(vector<int>& nums) 
{
    vector<int> vec;
    int sum = 0;
    vec.push_back(sum);
    for (auto& x : nums)
    {
        sum += x;
        vec.push_back(sum);
    }
    for (int i = 1; i < vec.size(); i++)
    {
        if (vec[vec.size() - 1] - vec[i] == vec[i - 1])
        {
            return i - 1;
        }
    }
    return -1;
}

(二)前缀和+空间复杂度O(1)

  • 思路如下:

在这里插入图片描述

  • 代码如下:
int pivotIndex(vector<int>& nums)
{
    int total = accumulate(nums.begin(), nums.end(),0);
    int sum = 0;
    for (int i = 0; i < nums.size(); i++)
    {
        if (2 * sum + nums[i] == total)
        {
            return i;
        }
        sum += nums[i];
    }
    return -1;
}

(三)天平平衡解法

  • 思路:有一个天平,开始时把数全放在右边,右边肯定更沉,然后就一个数一个数的向天平左边搬移,直至天平平衡。

在这里插入图片描述

  • 代码:

int pivotIndex(vector<int>& nums) 
{
    int sumr = 0;//天平右边
    int suml = 0;//天平左边
    int n = nums.size();
    for (auto& num : nums)  sumr += num;//求总和
    for (int i = 0; i < n; i++)
    {
        if (suml == sumr - nums[i])
        {
            return i;//天平平衡则返回
        }
        suml += nums[i];//左边加nums[i]
        sumr -= nums[i];//右边减去nums[i]
    }
    return -1;//没找到返回-1
}

旺财加油!✨

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值