总和等于0的最大子阵列

时间:2014-12-03 06:39:33

标签: python algorithm arrays

这是一个典型的面试问题。给定一个包含正负元素而不包含0的数组,找到最大的子数组,其总和等于0.我试图解决这个问题。这就是我提出的。

def sub_array_sum(array,k=0):
    start_index = -1
    hash_sum = {}
    current_sum = 0
    keys = set()
    best_index_hash = {}
    for i in array:
        start_index += 1
        current_sum += i
        if current_sum in hash_sum:
            hash_sum[current_sum].append(start_index)
            keys.add(current_sum)
        else:
            if current_sum == 0:
                best_index_hash[start_index] = [(0,start_index)]
            else:
                hash_sum[current_sum] = [start_index]
    if keys:
        for k_1 in keys:
            best_start = hash_sum.get(k_1)[0]
            best_end_list = hash_sum.get(k_1)[1:]
            for best_end in best_end_list:
                if abs(best_start-best_end) in best_index_hash:
                    best_index_hash[abs(best_start-best_end)].append((best_start+1,best_end))
                else:
                    best_index_hash[abs(best_start-best_end)] = [(best_start+1,best_end)]

    if best_index_hash:
        (bs,be) = best_index_hash[max(best_index_hash.keys(),key=int)].pop()
        return array[bs:be+1]
    else:
        print "No sub array with sum equal to 0"


def Main():
    a = [6,-2,8,5,4,-9,8,-2,1,2]
    b = [-8,8]
    c = [-7,8,-1]
    d = [2200,300,-6,6,5,-9]
    e = [-9,9,-6,-3]
    print sub_array_sum(a)
    print sub_array_sum(b)
    print sub_array_sum(c)
    print sub_array_sum(d)
    print sub_array_sum(e)

if __name__ == '__main__':
    Main()

我不确定这是否能满足所有边缘情况。如果有人可以对此发表评论,那就太棒了。我也希望将其扩展到等于任何K的总和,而不仅仅是0.我应该怎么做呢。任何进一步优化这一点的指针也很有帮助。

4 个答案:

答案 0 :(得分:2)

这是我自己的答案,只是为了好玩。

子序列的数量是二次的,并且子序列求和的时间是线性的,所以最天真的解是立方。

这种方法只是对子序列的详尽搜索,但是一点点诡计避免了线性求和因子,所以它只是二次方。

from collections import namedtuple
from itertools import chain


class Element(namedtuple('Element', ('index', 'value'))):
    """
    An element in the input sequence. ``index`` is the position
    of the element, and ``value`` is the element itself.
    """
    pass


class Node(namedtuple('Node', ('a', 'b', 'sum'))):
    """
    A node in the search graph, which looks like this:

         0      1       2      3
          \    /  \    /  \   /
           0-1     1-2     2-3
              \   /   \   /
               0-2     1-3
                  \   /
                   0-3

    ``a`` is the start Element, ``b`` is the end Element, and
    ``sum`` is the sum of elements ``a`` through ``b``.
    """

    @classmethod
    def from_element(cls, e):
        """Construct a Node from a single Element."""
        return Node(a=e, b=e, sum=e.value)

    def __add__(self, other):
        """The combining operation depicted by the graph above."""
        assert self.a.index == other.a.index - 1
        assert self.b.index == other.b.index - 1
        return Node(a=self.a, b=other.b, sum=(self.sum + other.b.value))

    def __len__(self):
        """The number of elements represented by this node."""
        return self.b.index - self.a.index + 1


def get_longest_k_sum_subsequence(ints, k):
    """The longest subsequence of ``ints`` that sums to ``k``."""
    n = get_longest_node(n for n in generate_nodes(ints) if n.sum == k)
    if n:
        return ints[n.a.index:(n.b.index + 1)]
    if k == 0:
        return []


def get_longest_zero_sum_subsequence(ints):
    """The longest subsequence of ``ints`` that sums to zero."""
    return get_longest_k_sum_subsequence(ints, k=0)


def generate_nodes(ints):
    """Generates all Nodes in the graph."""
    nodes = [Node.from_element(Element(i, v)) for i, v in enumerate(ints)]
    while len(nodes) > 0:
        for n in nodes:
            yield n
        nodes = [x + y for x, y in zip(nodes, nodes[1:])]


def get_longest_node(nodes):
    """The longest Node in ``nodes``, or None if there are no Nodes."""
    return max(chain([()], nodes), key=len) or None


if __name__ == '__main__':

    def f(*ints):
        return get_longest_zero_sum_subsequence(list(ints))

    assert f() == []
    assert f(1) == []
    assert f(0) == [0]
    assert f(0, 0) == [0, 0]
    assert f(-1, 1) == [-1, 1]
    assert f(-1, 2, 1) == []
    assert f(1, -1, 1, -1) == [1, -1, 1, -1]
    assert f(1, -1, 8) == [1, -1]
    assert f(0, 1, -1, 8) == [0, 1, -1]
    assert f(5, 6, -2, 1, 1, 7, -2, 2, 8) == [-2, 1, 1]
    assert f(5, 6, -2, 2, 7, -2, 1, 1, 8) == [-2, 1, 1]

答案 1 :(得分:2)

你已经给出了一个很好的线性时间解决方案(比其他两个答案更好,这是二次时间),基于这样的想法,即每当sum(i ... j)= 0时,它必须是sum(0 .. i-1)= sum(0 .. j),反之亦然。基本上你计算所有i的前缀和sum(0 ... i),建立一个哈希表hash_sum,其中hash_sum[x]是所有位置的列表,我有总和(0 ... i)= x 。然后你经历这个哈希表,一次一个总和,寻找由多个前缀产生的任何总和。在所有这些多于一次的金额中,你选择的是由一对相距最远的前缀 - 这是最长的。

由于你已经注意到使这个算法成为线性时间所需的关键洞察力,我有点疑惑为什么你在第二个循环中在best_index_hash中积累了这么多不必要的东西。对于给定的和x,构成该总和的最远的前缀对将始终是hash_sum[x]中最小和最大的条目,这必然是第一个和最后一个条目(因为这是顺序它们被附加了),所以不需要在它们之间循环元素。事实上,您甚至根本不需要第二个循环:通过将start_index视为最右端点,您可以在第一个循环期间保持运行最大值。

处理任意差异k:我们需要找到前缀最左边的前缀,而不是找到最后一个出现的前缀current_sum。{{1} }。但那只是current_sum - k

以下代码未经过测试,但应该可以使用:

first_with_sum{current_sum - k}

在开始时设置def sub_array_sum(array,k=0): start_index = -1 first_with_sum = {} first_with_sum{0} = -1 best_start = -1 best_len = 0 current_sum = 0 for i in array: start_index += 1 current_sum += i if current_sum - k in first_with_sum: if start_index - first_with_sum{current_sum - k} > best_len: best_start = first_with_sum{current_sum - k} + 1 best_len = start_index - first_with_sum{current_sum - k} else: first_with_sum{current_sum} = start_index if best_len > 0: return array[best_start:best_start+best_len-1] else: print "No subarray found" 意味着我们不必将从索引0开始的范围视为特殊情况。请注意,此算法不会改进原始算法的渐近时间或空间复杂度,但实现起来更简单,并且在包含零和子数组的任何输入上将使用少量空间。

答案 2 :(得分:1)

我同意sundar nataraj,他说这必须发布到代码审查论坛。

虽然我看了你的代码,但很有趣。虽然我能够理解您的方法,但我无法理解使用Counter的必要性。

  1. best_index_hash[start_index] = [(0,start_index)] - 此处best_index_hash的类型为Counter。你为什么要给它分配一个清单?

  2. for key_1, value_1 in best_index_hash.most_common(1) - 您尝试获取largest子序列,并且您正在使用most_common作为答案。这在语义上并不直观。

  3. 我很想发布解决方案,但我会等你编辑代码片段并改进它。


    <强>附录

    为了好玩,我抓住了这个难题,并在下面展示我的努力。我不保证正确/完整。

    from collections import defaultdict
    
    def max_sub_array_sum(a, s):
        if a:
            span = defaultdict(lambda : (0,0))
            current_total = 0
            for i in xrange(len(a)):
                current_total = a[i]
                for j in xrange (i + 1, len(a)):
                    current_total +=  a[j]
                    x,y = span[current_total]
                    if j - i > y - x:
                        span[current_total] = i,j
    
            if s in span:
                i, j = span[s]
                print "sum=%d,span_length=%d,indices=(%d,%d),sequence=%s" %\
                        (s, j-i + 1, i, j, str(a[i:j + 1]))
                return
        print "Could not find a subsequence of sum %d in sequence %s" % \
                (s, str(a))
    
    max_sub_array_sum(range(-6, -1), 0)
    max_sub_array_sum(None, 0)
    max_sub_array_sum([], 0)
    max_sub_array_sum(range(6), 15)
    max_sub_array_sum(range(6), 14)
    max_sub_array_sum(range(6), 13)
    max_sub_array_sum(range(6), 0)
    

答案 3 :(得分:0)

这里是the solution taken from LeetCode

  extension ViewController: WKNavigationDelegate{
    func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
        print(navigationAction.request.url ?? "")
        if let url = navigationAction.request.url?.absoluteString{

        }
        decisionHandler(.allow)
    }
}