【数据结构与算法】前缀和+哈希表算法

这篇具有很好参考价值的文章主要介绍了【数据结构与算法】前缀和+哈希表算法。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、引入

关于前缀和和哈希这两个概念大家都不陌生,在之前的文章中也有过介绍:前缀和与差分算法详解

而哈希表最经典的一题莫过于两数之和
题目链接

题目描述:

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

示例 1:

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

示例 2:

输入:nums = [3,2,4], target = 6
输出:[1,2]

示例 3:

输入:nums = [3,3], target = 6
输出:[0,1]

只会存在一个有效答案

思路分析:
我们在遍历这个数组要做两件事:
假设现在遍历到下标为idx的位置。
1️⃣ 查看target - nums[idx]是否在哈希表中,如果在,说明这两个数加起来就是目标和,那么就找到了两个下标,一个是hash[target - nums[idx]],一个是当前位置idx。
2️⃣ 用哈希表记录两个数据,first记录当前位置的值,second记录当前位置的下标。

代码:

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        unordered_map<int, int> hash;
        int n = nums.size();
        for(int i = 0; i < n; i++)
        {
            if(hash.find(target - nums[i]) != hash.end())
            {
                return {hash[target - nums[i]], i};
            }
            hash[nums[i]] = i;
        }
        return {};
    }
};

二、前缀和与哈希表的结合

用一个例子来说明以下:
【数据结构与算法】前缀和+哈希表算法
假设我们要寻和为5连续子数组的个数,那么只要前缀和中任意两个数的差值为5,那么就找到了子数组。

那么我们就可以直接用哈希表把前缀和的数据存储起来,first存前缀和的值,second用来标识有多少个子数组。
这里首先要注意初始化哈希表把0的位置先设置成1:hash[0] = 1,因为当我们计算前缀和为5的位置的时候,就标识了从0 ~ 5存在和为5的连续子数组。

假设目标和为k,遍历到i位置。
所以现在我们在计算前缀和的同时看看是否存在hash[k - nums[i]],这个的数值大小就代表有多少个连续的子数组和。那么为什么会存在多个呢?

因为可能数组存在负数,这样就会导致出现这种情况:
【数据结构与算法】前缀和+哈希表算法
那么省略号这段区间的前缀总和就为0,所以就会存在两段子数组和为5的区间。
【数据结构与算法】前缀和+哈希表算法

三、例题

3.1 和为 K 的子数组

题目链接

题目描述:

给你一个整数数组 nums 和一个整数 k ,请你统计并返回 该数组中和为 k 的连续子数组的个数 。

示例 1:

输入:nums = [1,1,1], k = 2
输出:2

示例 2:

输入:nums = [1,2,3], k = 3
输出:2

思路分析:
这个题如果我们只使用前缀和:先计算前缀和,然后依次遍历看是否有两个数字的差值为k。

class Solution {
public:
    int subarraySum(vector<int>& nums, int k) {
        int n = nums.size();
        // 前缀和数组
        vector<int> sums(n + 1);
        for(int i = 0; i < n; i++)
        {
            sums[i + 1] = sums[i] + nums[i];
        }
        int res = 0;
        for(int i = 0; i < n; i++)
        {
            for(int j = i; j < n; j++)
            {
                if(sums[j + 1] - sums[i] == k)
                {
                    res++;
                }
            }
        }
        return res;
    }
};

但是提交会发现运行超时。
而由于这道题只关心次数,不关注具体的解,所以我们能用哈希表来优化效率。
具体的做法在上面已经详细介绍过。

代码:

class Solution {
public:
    int subarraySum(vector<int>& nums, int k) {
        int n = nums.size();
        unordered_map<int, int> hash;
        int res = 0;
        hash[0] = 1;
        int sum = 0;
        for(int i = 0; i < n; i++)
        {
            sum += nums[i];
            res += hash[sum - k];
            hash[sum]++;
        }
        return res;
    }
};

3.2 统计「优美子数组」

题目链接

题目描述:

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

示例 1:

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

示例 2:

输入:nums = [2,4,6], k = 1
输出:0
解释:数列中不包含任何奇数,所以不存在优美子数组。

示例 3:

输入:nums = [2,2,2,1,2,2,1,2,2,2], k = 2
输出:16

思路分析:
这道题乍一看无从下手,但其实这道题跟上面一道题没什么区别,只要把偶数看成0,奇数看成1,就直接转化成了和为K的子数组问题了。

代码:

class Solution {
public:
    int numberOfSubarrays(vector<int>& nums, int k) {
        int n = nums.size();
        int res = 0;
        unordered_map<int, int> hash;
        hash[0] = 1;
        int sum = 0;
        for(int i = 0; i < n; i++)
        {
            // 偶数为0,奇数为1
            int ret = 0;
            if(nums[i] % 2)
            {
                ret = 1;
            }
            sum += ret;
            res += hash[sum - k];
            hash[sum]++;
        }
        return res;
    }
};

3.3 路径总和III

题目链接

题目描述:

给定一个二叉树的根节点 root ,和一个整数 targetSum ,求该二叉树里节点值之和等于 targetSum 的 路径 的数目。
路径 不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点)。

示例 1:

【数据结构与算法】前缀和+哈希表算法
输入:root = [10,5,-3,3,2,null,11,3,-2,null,1], targetSum = 8
输出:3
解释:和等于 8 的路径有 3 条,如图所示。

示例 2:

输入:root = [5,4,8,11,null,13,4,7,2,null,null,5,1], targetSum = 22
输出:3

方法一:
这道题可以直接用暴力遍历,每个节点都往下统计到叶子节点,看有多少个。

代码:

class Solution {
public:
    int dfs(TreeNode* root, long long targetSum)
    {
        if(root == nullptr)
        {
            return 0;
        }
        int ret = 0;
        if(targetSum - root->val == 0)
        {
            ret++;
        }
        ret += dfs(root->left, targetSum - root->val);
        ret += dfs(root->right, targetSum - root->val);
        return ret;
    }

    int pathSum(TreeNode* root, int targetSum) {
        if(root == nullptr)
        {
            return 0;
        }
        int res = dfs(root, targetSum);
        res += pathSum(root->left, targetSum);
        res += pathSum(root->right, targetSum);
        return res;
    }
};

方法二:
第二个方法当然是使用前缀和+哈希表算法。
我们边递归边求前缀和,统计的方法还是跟上面一样,这里要注意的是当回溯的时候记住要把当前的位置给去掉(没递归到当前位置的状态)。

代码:

class Solution {
public:
    unordered_map<long long, int> hash;
    int cnt;

    void dfs(TreeNode* root, long long sum, int target)
    {
        if(root == nullptr)
        {
            return;
        }
        sum += root->val;
        cnt += hash[sum - target];
        hash[sum]++;
        dfs(root->left, sum, target);
        dfs(root->right, sum, target);
        hash[sum]--;
    }

    int pathSum(TreeNode* root, int targetSum) {
        hash[0] = 1;
        cnt = 0;
        dfs(root, 0, targetSum);
        return cnt;
    }
};

四、总结

我们通过上面的问题可以总结出规律,遇到求连续的和的时候我们就应该想到用前缀和算法,而如果题目只关心次数,不关注具体的解,我们就可以使用(前缀和+哈希表)算法。文章来源地址https://www.toymoban.com/news/detail-427634.html



到了这里,关于【数据结构与算法】前缀和+哈希表算法的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • C++数据结构与算法——哈希表

    C++第二阶段——数据结构和算法,之前学过一点点数据结构,当时是基于Python来学习的,现在基于C++查漏补缺,尤其是树的部分。这一部分计划一个月,主要利用代码随想录来学习,刷题使用力扣网站,不定时更新,欢迎关注! 给定两个字符串 s 和 t ,编写一个函数来判断

    2024年02月19日
    浏览(42)
  • 数据结构算法设计——哈希表(散列表)

            哈希表 又叫 散列表 ,他们两个是同一个东西,本文全文采用“散列表”的叫法。散列表的本质其实就是一个 数组 ,他的作用就像使用数组时一样,输入下标可以得到对应元素,散列表可以实现 输入一个的时候得到这个的地址信息 。 下面是百科给出

    2024年02月03日
    浏览(46)
  • 【数据结构与算法——TypeScript】哈希表

    哈希表介绍和特性 哈希表是一种非常重要的数据结构,但是很多学习编程的人一直搞不懂哈希表到底是如何实现的。 在这一章节中,我门就一点点来实现一个自己的哈希表。 通过实现来理解哈希表背后的原理和它的优势。 几乎所有的编程语言都有直接或者间接的应用这种数

    2024年02月13日
    浏览(30)
  • 数据结构与算法 | 哈希表(Hash Table)

    在二分搜索中提到了在有序集合中查询某个特定元素的时候,通过折半的方式进行搜索是一种很高效的算法。那能否根据特征直接定位元素,而非折半去查找?哈希表(Hash Table),也称为散列表,就是一种数据结构,用于实现键-值对的映射关系。它通过将键映射到特定的值

    2024年02月06日
    浏览(33)
  • 算法数据结构基础——哈希表(Hash Table)

    哈希表(Hash Table) :也叫做散列表。是根据关键码值(Key Value)直接进行访问的数据结构。 哈希表通过「键 key 」和「映射函数 Hash(key) 」计算出对应的「值 value 」,把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做「哈希函数(散列函数

    2024年02月13日
    浏览(28)
  • 【数据结构与算法】【12】前缀表达式、中缀表达式、后缀表达式

    什么是前缀表达式、中缀表达式、后缀表达式 前缀表达式、中缀表达式、后缀表达式,是通过树来存储和计算表达式的三种不同方式 以如下公式为例 ( a + ( b − c ) ) ∗ d ( a+(b-c) )*d ( a + ( b − c ) ) ∗ d 通过树来存储该公式,可以表示为 那么问题就来了,树只是一种抽象的数据

    2024年02月08日
    浏览(35)
  • 数据结构与算法1:引入概念

    接下来系统的学一下数据结构与算法的知识,本章节是第一部分:数据结构与算法的进入与基本概述 【铁打的算法demo】 先来看到题: 如果 a + b + c = 1000,且 a 2 + b 2 = c 2 (a, b , c 为⾃然数),如何求出所有 a、b、c 可能的组合? 方法一:直接干 思路:既然 a + b + c = 1000,说明

    2024年02月03日
    浏览(33)
  • Python篇——数据结构与算法(第六部分:哈希表)

      目录 1、直接寻址表 2、直接寻址表缺点 3、哈希 4、哈希表 5、解决哈希冲突 6、拉链法 7、常见哈希函数 8、哈希表的实现 8.1迭代器iter()和__iter__ 8.2str()和repr() 8.3代码实现哈希表 8.4哈希表的应用   直接寻址表:key为k的元素放到k的位置上 改进直接寻址表:哈希(

    2024年02月10日
    浏览(32)
  • 【数据结构与算法】04 哈希表 / 散列表 (哈希函数、哈希冲突、链地址法、开放地址法、SHA256)

    一种很好用,很高效,又一学就会的数据结构,你确定不看看? 莫慌,每个概念都很好理解。 哈希表( Hash Table ),也称为 散列表 ,是一种数据结构, 用于存储键值对(key-value pairs) 。 键值对是一种数据结构,用于将键(key)与对应的值(value)相关联。在键值对中,键

    2024年02月09日
    浏览(59)
  • 【数据结构与算法】哈希表设计(C\C++)

    针对某个集体中人名设计一个哈希表,使得平均查找长度不超过R,并完成相应的建表和查找程序。 假设人名为中国人姓名的汉语拼音形式。待填入哈希表的人名共有30个,取平均查找长度的上限为2。哈希函数用除留余数法构造。用伪随机探测再散列法处理冲突。 取读者周围

    2024年02月04日
    浏览(37)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包