破解大厂算法面试最难动态规划题:将数组分割成元素和相等的两部分
Posted tyler_download
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了破解大厂算法面试最难动态规划题:将数组分割成元素和相等的两部分相关的知识,希望对你有一定的参考价值。
我们继续研究算法面试题型中最复杂的动态规划类型。题目如下:给定一个含有正整数的数组,请给出算法将其分成两个子数组,使得他们的元素和相等。例如给定数组[2,3, 5, 6],它可以分割成两个数组[2,6],[3,5],两个字数组的和都是8.
在面试中遇到问题时,我们首先需要对其中一些模糊之处进行澄清,这样我们才能给面试官思维周全和谨慎的好印象。对于这个问题,我们需要搞清楚的问题又,数组是否为空?数组最多包含多少元素等。清除掉这些模糊点再下手,这样成功率才会更大。
根据前面说过的动态规划解决套路,问题的思考模式为:首先缩小问题规模,看看解决更小规模的问题后,能不能通过其结果来解决更大规模的问题。例如给定的问题中输入数组包含n个元素,那么我们先思考能不能解决n-1个元素情况下的问题,如果能解决,如何利用其结果来解决n个元素的问题。其次一定要利用缓存来存储中间问题的答案,在递归的解决更小规模问题时,先从缓存中查找,如果没有结果在进行下一步的处理。
我们把题目里面的元素增加一些有利于讨论,假设数组为[14, 6, 7, 2, 3, 5, 7],我们将其分成两部分,使得两部分元素之和相等。首先从肉眼上不难看出两部分子数组为:
part1 : 14, 3, 5
part2: 6, 7, 2, 7
现在我们看看如何将问题的规模进行缩小。假设我们去掉数组最后一个元素7,那么两部分数组变成:
part1: 14, 3, 5
part2: 6, 7, 2,
不难发现此时两个数组的元素和的差值为7,这意味着当数组元素为n时,我们要找到两个子数组使得他们元素和的差值为0,如果拿掉最后一个元素,我们用last_element表示,那么问题变成从包含n-1个元素的数组中找到两子数组,使得他们的差值为last_element,如果我们从包含n-1个元素的数值中找到给定分组,那么我们把拿掉的元素放入到元素和较小的那个分组中,这样我们就得到在n个元素下的两个子数组,使得他们元素和相等。
我们再往下看,如果我们再上面问题基础上再进行规模缩小,拿掉最后一个元素5,那么分组变成:
part1: 14, 3
part2: 6,7,2
于是问题变成当数组包含n-2个元素时,我们需要找到两个分组,使得他们元素和的差值为|5 - 7| = 2。我们再看一种情况,那就是元素2排在5的后面,那么在规模为n-2时,拿掉最后一个元素也就是2,于是分组变成:
part: 14,3,5
part2: 6,7
于是问题变成当问题规模为n-2时,我们需要找到两个子数组,使得两个数组元素和的差值为7+2=9。这样我们可以看到一个规律,如果连续两次递归中拿掉的元素属于同一个子数组,那么分组的差值要变成所拿掉元素的和,如果前后两次递归中,拿掉的元素分属与不同子数组,那么差值要变成元素的差。这样一来问题就出现了,我们怎么知道拿掉的元素属于哪个子数组呢。
实际情况是我们不知道,因此两种情况都得考虑,我们分别假设本次递归和上次递归,两次拿走的元素属于同一集合和不同集合,看看这两种情况哪一种能返回有效结果,由此我们给出实现代码如下:
class ArrayPartition:
def __init__(self):
self.element_sum = 0
self.elements = []
def add(self, elem):
self.element_sum += elem
self.elements.append(elem)
def copy(self):
array_partition = ArrayPartition()
array_partition.element_sum = self.element_sum
array_partition.elements = self.elements.copy()
return array_partition
def __str__(self):
return f"partition:self.elements with sum:self.element_sum"
class PartitionByTarget:
def __init__(self, elements, target):
self.elements = elements
self.target = target
self.hash_table =
def __append_element_to_partition(self, array_partition, last_element, target):
'''
看看将最后一个元素放到哪一个分组能使得其值等于target
'''
if abs(array_partition[0].element_sum + last_element - array_partition[1].element_sum) == abs(target):
array_partition[0].add(last_element)
else:
array_partition[1].add(last_element)
def __partition_elements(self, index, target):
if index == 0:
return None
if len(self.elements[0: index + 1]) == 2:
if abs(self.elements[0] - self.elements[1]) == abs(target):
partition1 = ArrayPartition()
partition2 = ArrayPartition()
partition1.add(self.elements[0])
partition2.add(self.elements[1])
self.hash_table[(index, target)] = (partition1, partition2)
return self.hash_table[(index, target)]
if (index, target) in self.hash_table: # 先查表看看结果是否已经存在
return self.hash_table[(index, target)]
'''
将问题进行递归处理,要看当前数值[0:index]是否能分成两部分,使得他们和的差值等于target,假设数组能够分成两部分,使得他们的差值为target,
'''
last_element = self.elements[index]
next_target1 = last_element + target
next_target2 = last_element - target
array_partition1 = self.__partition_elements(index - 1, next_target1)
array_partition2 = self.__partition_elements(index - 1, next_target2)
if array_partition1 is None and array_partition2 is None:
self.hash_table[(index, target)] = None
return None
if array_partition1 is not None:
'''
这里的copy()很重要,在python中所有对象都以引用的方式存在。由于array_partition1和array_partition2
有可能是从哈希表中获取,由于__append_element_to_partion函数会修改他们的内容,因此如果我们不传入他们的拷贝,
那么这里一旦修改其内容,原来哈希表指向的对象也会被修改,这样就会引入错误
'''
copy_partition = (array_partition1[0].copy(), array_partition1[1].copy())
self.__append_element_to_partition(copy_partition, last_element, target)
self.hash_table[(index, target)] = copy_partition
else:
copy_partition = (array_partition2[0].copy(), array_partition2[1].copy())
self.__append_element_to_partition(copy_partition, last_element, target)
self.hash_table[(index, target)] = copy_partition
return self.hash_table[(index, target)]
def find_partition(self):
return self.__partition_elements(len(self.elements) -1 , self.target)
partion_target = PartitionByTarget([14, 6, 7, 2, 3, 5, 7, 15, 7, 8, 47, 30, 17, 12, 16, 6, 6, 8, 8], 0)
partions = partion_target.find_partition()
if partions is not None:
print(f"first part:partions[0]")
print(f"second part:partions[1]")
上面代码运行后所得结果如下:
first part:partition:[14, 7, 2, 5, 7, 15, 7, 8, 47] with sum:112
second part:partition:[6, 3, 30, 17, 12, 16, 6, 6, 8, 8] with sum:112
在代码中有值得注意的地方。我把会把ArrayPartition类存储在哈希表中,当递归时会进行查询,一旦条件满足,对应的ArrayPartion类会返回给上层用于组合出新的结果。但由于python所有对象都以引用的方式存在,因此为了防止缓存的对象其内容被更改,我们从哈希表中获得ArrayPartition的实例时,需要返回它的拷贝,这样上层进行操作时才不会影响原有实例内容。
我们注意到,哈希表的键值形式为(index, target),因此哈希表存在的对象数量为index的取值范围乘以target的可能取值范围,由于index最大值为n, target最大值为所有元素之和,我们用s来表示,因此缓存中可能存储的对象数量为n * s, 由此代码的时间和空间复杂度都是O(n * s)。
以上是关于破解大厂算法面试最难动态规划题:将数组分割成元素和相等的两部分的主要内容,如果未能解决你的问题,请参考以下文章