【问题标题】:Smallest subarray with sum equal to k和等于 k ​​的最小子数组
【发布时间】:2020-10-18 20:48:32
【问题描述】:

我想找到总和等于k的长度最小的子数组。

Input: arr[] = {2, 4, 6, 10, 2, 1}, K = 12 
Output: 2

说明: 总和为 12 的所有可能子数组是 {2, 4, 6}{10, 2}

Input: arr[] = { 1, 2, 4, 3, 2, 4, 1 }, K = 7
Output: 2

【问题讨论】:

  • 到目前为止你尝试了什么,得到了什么结果?
  • 连续子数组?
  • 假设连续,使用两个指针,跟踪指针之间的子数组总和,并在获得满足目标的新最小子数组大小时记录距离。这是线性时间。根据您在目标的哪一侧决定要前进的指针。

标签: arrays algorithm


【解决方案1】:

这是一个使用 JavaScript 的解决方案。
当然,它可以提高效率,但我已经对其进行了编码。

function lengthOfShortestSubArrayOfSumK(array, k) {
  var combos=[];
  for(var i=0; i<Math.pow(2, array.length); i++) {
    var bin=("0".repeat(array.length)+i.toString(2)).slice(-array.length).split("");
    var ones=bin.reduce((count, digit)=>{count+=digit=="1";return count;},0);
    var sum=bin.reduce((sum, digit, index)=>{sum+=digit=="1"?array[index]:0;return sum;},0);
    combos.push([bin, ones, sum]);
  };
  return combos.filter(combo=>combo[2]==k).sort((a, b)=>a[1]-b[1])[0][1];
}

var arraysAndKs=[
  {array:[2, 4, 6, 10, 2, 1], k:12},
  {array:[1, 2, 4, 3, 2, 4, 1], k:7}
];

for(arrayAndK of arraysAndKs)
  console.log("Length of shortest sub array of ["+arrayAndK.array.join(", ")+"] with sum "+arrayAndK.k+" is : "+lengthOfShortestSubArrayOfSumK(arrayAndK.array, arrayAndK.k));

0array.length 之间的二进制数的平方将为我们提供总和中包含的数组项的表示。
我们计算该二进制数中有多少个“1”。
我们将被那些“一个”屏蔽的数组项求和。
我们将二进制数、“一个”的计数和总和的数组保存到 combos 数组中。
我们过滤 combos 的总和 k,按 "one" 的计数排序,然后返回第一个的 "one" 计数。

我确信这可以翻译成任何编程语言。

【讨论】:

  • 这是一种非常昂贵的计算答案的方法;对于 10 个元素的数组,它需要 2^10 次循环迭代 (O(n^2))。鉴于存在线性时间答案,除了某个“酷”因素之外,没有任何理由可以支付如此高的成本。
  • "对于 10 个元素的数组,它需要 2^10 次循环迭代" - 不,10^2。这是你的意思/错字吗?
  • “鉴于存在线性时间答案”-“给定”?!? “线性时间” == O(n)?!?
  • “非常昂贵” - 请量化...“更复杂” = 好的,“非常昂贵” = 没有意义...
  • 是的,这是一个错字——我的意思是 O(2^n)。是的,如果正在寻找一个连续的区间,则存在线性时间 O(n) 答案(请参阅stackoverflow.com/a/64419079/15472)。是的,即使间隔不需要是连续的,你的答案也太贵了(=指数时间太可怕了),并且可以使用回溯和最小修剪来显着改善(例如,一旦超过 K 就不需要继续添加元素;或者如果找到了小集合的答案,则无需探索大集合)
【解决方案2】:

您可以使用一种算法来查找大小为 K 的子集,并保存另一个变量来存储构成此类子数组的成员数。

求K子数组的算法是:

  1. 初始化一个大小为K的数组,每个位置(idx)表示是否有一个子数组等于idx(我用的是字典)
  2. 遍历数组中的任何数字 (i),以及我们在上一次迭代中可以达到的任何总和 (j),现在我们可以达到 j + i。
  3. 如果在 K 位被标记为 TRUE,则存在一个等于 K 的子数组。

这是 Python 中的解决方案

def foo(arr,k):
    dynamic = {0:0}
    for i in arr:
        temp = {}
        for j, l in dynamic.items():
            if i + j <= k: # if not it's not interesting us
                # choose the smallest subarray
                temp[i+j] = min(l+1,dynamic.get(i+j,len(arr)))
        dynamic.update(temp)
    return dynamic.get(k,-1)

复杂度为 O(N*K)。

我假设子数组是指原始数组的任何可能组合。


这是解决问题的 Python 代码在子集必须是连续的条件下

O(N) 复杂度


def shortest_contiguous_subarray(arr,k):
    if k in arr:
        return 1
    n = len(arr)
    sub_length = float('inf')
    sub = arr[(i:=0)]
    j = 1
    while j < n:
        while sub < k and j < n:
            sub += arr[j]
            j += 1
        while sub > k:
            sub -= arr[i]
            i += 1
        if sub == k:
            # print(arr[i:j],j-i)
            sub_length = min(sub_length,j-i)
            sub -= arr[i]
            i += 1
    return sub_length if sub_length <= n else -1

【讨论】:

  • 据我所知,没有“O(N*K)”这样的东西。如果 K 是一个独立的标量(它不在您的代码中),那么它是 O(n),它取决于“N”,所以它是 O(n^2)。第二个代码相同:O(n^2).
  • 你错了:对于第一个代码,请参见此处:[geeksforgeeks.org/subset-sum-problem-dp-25/?ref=lbp] 该算法将数组中的每个元素传递到从 0 到 K 的所有位置。所以:O (N * K)同样关于第二个代码,该算法基于 Caterpillar 方法,并且每个元素的迭代最多两次
  • 我不是在争论 - 我不确切知道如何确定复杂性,但是,我从未见过 O(nk) => kO(n ) => O(n) for k 不取决于 n 或 O(n^2)... 链接页面上写着“给定一组非负整数”,这不一定适用于这个问题 - 然而,我不知道这是否重要...
  • 你是对的,在负数的情况下,代码需要稍微改变一下。您可以删除条件if i + j &lt;= k:,然后在我看来就可以了。但是关于复杂度很简单:如果输入长度(N)线性增加,K不变,计算的次数也会线性增加。
【解决方案3】:

此答案适用于任何正数数组,如果执行 O(n) 预处理传递,则可以修改为使用具有零或负元素的数组(1. 找到最小元素 m, m &lt;= 0, 2.通过将-m+1添加到所有元素使整个数组为正,3.求解sum + n*(1-m))

function search(input, goal) {    
    let queue = [ { avail: input.slice(), used: [], sum: 0 } ]; // initial state
    for (let qi = 0; qi < queue.length; qi ++) {
          let s = queue[qi]; // like a pop, but without using O(n) shift
        for (let i = 0; i < s.avail.length; i++) {        
            let e = s.avail[i];
            if (s.sum + e > goal) continue;               // dead end
            if (s.sum + e == goal) return [...s.used, e]; // eureka!!
            queue.push({                              // keep digging
                avail: [...s.avail.slice(0, i), ...s.avail.slice(i+1)], 
                used: [...s.used, e], 
                sum: s.sum + e 
            });
        }
    }
    return undefined; // no subset of input adds up to goal
}

console.log(search([2, 4, 6, 10, 2, 1], 12))

这是一个经典的广度优先搜索,当它检测到我们已经超过目标总和时会进行一些修剪。它可以进一步优化以避免多次探索同一个分支(例如,[4,2] 等同于[2,4])——但这需要额外的内存来保持一组“已访问”状态。此外,您可以先添加启发式方法来探索更有希望的分支。

【讨论】:

    猜你喜欢
    • 2017-03-16
    • 2019-09-26
    • 2021-06-04
    • 2016-12-20
    • 2018-02-15
    • 1970-01-01
    • 2021-12-29
    • 2020-11-11
    • 2012-11-08
    相关资源
    最近更新 更多