【问题标题】:Intuition behind storing sum and frequency in the HashMap在 HashMap 中存储总和和频率背后的直觉
【发布时间】:2017-12-19 12:36:35
【问题描述】:

我们得到了一个整数数组和另一个数字k,我们需要找到总和等于k 的连续子数组的总数。我在 LeetCode 上发现了以下有趣的代码 sn-p:

public class Solution {
    public int subarraySum(int[] nums, int k) {
        int count = 0, sum = 0;
        HashMap < Integer, Integer > map = new HashMap < > ();
        map.put(0, 1);
        for (int i = 0; i < nums.length; i++) {
            sum += nums[i];
            if (map.containsKey(sum - k))
                count += map.get(sum - k);
            map.put(sum, map.getOrDefault(sum, 0) + 1);
        }
        return count;
    }
}

我喜欢高效的解决方案,因此我正在努力理解它;但是我有两个问题:

  1. 在HashMap中存储当前sum及其frequency背后的直觉是什么?
  2. 什么是我们检测到的子阵列是连续的保证?

示例输入:[1,1,1] 和 k=2;
输出:2

【问题讨论】:

  • 更新 - 问题是here

标签: java algorithm hashmap


【解决方案1】:

当我们扫描nums[] 数组时,map 将包含我们看到特定sum 的次数(sum 表示从开始到当前点的数字相加)。

现在,在任何给定点,if,如果我们看到,map 包含 sum-k X 次,并且当前总和是 sum,我们知道,我们找到了 X 个不同的子数组总和k。这是因为sum 包含从开始到当前点的总和,而map 由 sum-from-the-beginning-to-a-certain-point 索引。如果map 包含大于一的值,则意味着某个总和发生多次(如果num[] 具有零或负数,则可能发生)。找到的子数组就是从这个“某个”点到我们当前位置的,所以它一定是连续的。

【讨论】:

  • 您能否详细说明一下 - 如果我们看到,地图包含 sum-k X 次,并且当前总和是 sum,我们知道,我们发现 X 个不同的子数组具有总和k。在我看来,如果我们看到地图包含 X 次总和,并且当前总和再次为 sum,那么我们可以说我们找到了 X+1 个不同的子数组,总和为 sum
  • @UmedhSinghBundela:它是“地图包含 sum-k X 次”和sum-(sum-k)=k
  • 你是怎么得到sum-(sum-k)的?
  • @UmedhSinghBundela:这意味着,目前我们的总和是sum。我们正在搜索子数组 sum k。因此,我们需要检查map 的总和sum-k,因为如果我们从当前的sum 中减去sum-k,我们将得到k,即我们搜索的数量
  • 但我们没有做减法,是吗?抱歉,我觉得有点难以理解。感谢您的耐心等待。
【解决方案2】:

不错的算法。

让我们从简单的事实开始:sum(1, i) = sum(1, j) + sum(j + 1, i)(我在这里不使用 Java,它是通常的数学符号) 任何ij 都是如此。

我们需要找到所有sum(j+1, i) 等于k

查找sum(1, i) = sum(1, j) + ksum(1, i) -k = sum(1, j)是一样的

在您的程序中,sum(1, i)sum 变量。所以我们需要检查是否有任何jsum -k = sum(1, j) 为真。希望我们在map 中拥有所有sum(1, j) 作为键。

我们检查map.containsKey(sum - k),如果它是真的,那么就有这样的j 给我们所需的总和。

需要 map 中的值来计算有多少种不同的方式来获得这样的总和。

PS:顺便说一句,如果所有值都是非负数,那么会有更好的算法。它不需要额外的内存。

PPS:我还对你的代码做了一些改进,以防你使用 Java 8

    for (int num : nums) {
        sum += num;
        count += map.getOrDefault(sum - k, 0);
        map.compute(sum, (key, value) -> (value == null) ? 1 : value + 1);
    }

【讨论】:

  • 您能否解释一下为什么我们使用sum 作为地图中的键;但随后使用sum-k? 查询它
  • 我试图重申我的答案。但是用笔和纸会更容易。但是我懒得画了。
  • 也许您可以用笔和纸来做,然后将您的计算作为图像上传?您可以稍后更新您的答案。我不确定这是否符合 SO 政策。
  • 我再次重申了我的答案。希望能给sum - k 的来源带来光明。
  • 我问了这个问题的后续,here
【解决方案3】:

3 个关键点:

  • 第一个键是 0
  • 仅当键 sum - k 已在地图中时才增加 count
  • 用它的频率映射每个总和

我将它分为3种情况:

第一个:大于零的数字

每个键的频率等于 1。
那么计数是如何增加的呢?只有当键已经在地图中时,它才能增加。 键是所有以前的总和。所以条件if (map.containsKey(sum - k)) 的当前键是sum - k。如果sum - k 是一个键,这意味着在前一个总和(sum - k)和当前总和之间是总和等于k 的元素(因为k + (sum - k) = sum - 我们的当前值)。所以我们可以增加count - 我们找到了子数组。

第二个:大于或等于零的数字。
所以现在我们可以在两者之间放置零。差别不大,但是您可以想象如果我们增加 count 会发生什么,在下一步中,我们的 nums 数组中将有 0。
在这种情况下,我们将正常再次增加我们的count

如果包含零,我们的频率将会改变。想象一下这个例子: subarraySum({0, 0, 0, 7, 0}, 7); 结果是 8。 请记住,第一个键是 0。所以当我们迭代时,我们是 7,我们有一个映射 (0: 4)。现在sum = 77 - 7 = 0,所以,我们有3,所以这个时间计数增加了这个频率。现在我们从数组中挑选最后一个元素 - 0。这个键的值仍然是 4。sum - k 仍然是一样的,所以我们的 count 再次增加了 4。

第三个:整数
我想你明白了;)这次负数也可能增加一些频率。 如果我们在 map 中有键 sum - key,那么这意味着在之前的总和 (sum - k) 和现在我们有整数之间,总和为 k(因为 k + (sum - k) = sum - 我们的当前值)。如果是这样,让我们​​按频率增加计数。

【讨论】:

  • 你说所以当前密钥是sum - k。如何?不就是sum吗?
  • sumsum,我们在这里使用:if (map.containsKey(sum - k)) key sum - k
  • 所以我的确切问题是:如果我们在插入过程中使用sum 作为映射中的键,那么为什么我们在查找时使用sum-k 作为键?
  • 假设,我们有第一种情况。这样我们就处于迭代的某个点。现在我们知道每个键的频率为 1。你看到了吗?
  • 我们在进行查找之前使用 sum 插入。我们正在使用密钥sum - k 来检查我们之前是否有这个和sum - k
猜你喜欢
  • 1970-01-01
  • 2015-07-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-01-11
  • 1970-01-01
  • 1970-01-01
  • 2023-03-11
相关资源
最近更新 更多