问题是在数组(包含至少一个数字)中找到具有最大产品的连续子阵列。
例如,给定数组[2,3]
,
连续的子阵列6
具有最大的乘积if(nums == null || nums.Length == 0)
{
throw new ArgumentException("Invalid input");
}
int max = nums[0];
int min = nums[0];
int result = nums[0];
for(int i = 1; i < nums.Length; i++)
{
int prev_max = max;
int prev_min = min;
max = Math.Max(nums[i],Math.Max(prev_max*nums[i], prev_min*nums[i]));
min = Math.Min(nums[i],Math.Min(prev_max*nums[i], prev_min*nums[i]));
result = Math.Max(result, max);
}
return result;
。
为什么以下工作?任何人都可以提供有关如何证明其正确性的任何见解吗?
AUG
答案 0 :(得分:2)
从逻辑方面开始,了解如何解决问题。每个子阵列有两个相关特征需要考虑:
现在我们可以从算法本身开始:
由于0将子阵列的乘积归零,因此解决方案的子阵列不能包含0,除非输入中仅包含负值和0。这可以非常简单地实现,因为只要在数组中遇到0,max
和min
都会重置为0:
max = Math.Max(0 , Math.Max(prev_max * 0 , prev_min * 0));
min = Math.Min(0 , Math.Min(prev_max * 0 , prev_min * 0));
无论到目前为止的输入是什么,逻辑上都会评估为0。
arr: 1 1 1 1 0 1 1 1 0 1 1 1 0 1 1 0
result: 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
min: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
max: 1 1 1 1 0 1 1 1 0 1 1 1 0 1 1 0
//non-zero values don't matter for Rule 1, so I just used 1
使用规则1,我们已经隐式地将数组拆分为子数组,这样子数组可以包含单个0或多个非零值。现在的任务是找到子阵列中最大的可能产品(我将从这里开始称之为数组)。
如果数组中的负值数是偶数,则整个问题变得非常简单:只需乘以数组中的所有值,结果就是数组的最大乘积。对于奇数个负值,有两种可能的情况:
现在让我们来看看代码:
max = Math.Max(nums[i] , Math.Max(prev_max * nums[i] , prev_min * nums[i]));
min = Math.Min(nums[i] , Math.Min(prev_max * nums[i] , prev_min * nums[i]));
案例1:min
的评估实际上是无关紧要的,因为对于负值,数组乘积的符号只会翻转一次。一旦遇到负数(= nums[i]
),max
将为nums[i]
,因为max
和min
都至少为1,因此乘以nums[i]
会生成<= nums[i]
个数字。对于负数nums[i + 1]
之后的第一个数字,max
将再次为nums[i + 1]
。由于到目前为止发现的最大值在每个步骤后在result
(result = Math.Max(result, max);
)中保持不变,因此这将自动导致该数组的正确结果。
arr: 2 3 2 -4 4 5
result: 2 6 12 12 12 20
max: 2 6 12 -4 4 20
//Omitted min, since it's irrelevant here.
案例2:此处min
也变得相关。在我们遇到第一个负值之前,min
是到目前为止在数组中遇到的最小数字。在我们遇到数组中的第一个正元素后,该值变为负数。我们会继续构建这两种产品(min
和max
),并在每次遇到负值时进行交换,并不断更新result
。当遇到数组的最后一个负值时,result
将保存子阵列的值,该值消除了最后一个负值及其后继值。在最后一个负值之后,max
将是子阵列的产物,它消除了第一个负值并且它的前辈和min
变得无关紧要。现在我们继续将max
与数组中的剩余值相乘并更新result
,直到到达数组的末尾。
arr: 2 3 -4 3 -2 5 -6 3
result: 2 6 6 6 144 770 770 770
min: 2 6 -24 -72 -6 -30 -4620 ...
max: 2 6 -4 3 144 770 180 540
//min becomes irrelevant after the last negative value
由于每次遇到0都会重置min
和max
,因此我们可以轻松地为每个不包含0的子数组重用它们。因此,隐式应用规则1而不会干扰使用规则2.由于每次检查新子数组时result
都不会重置,因此该值将在所有运行中保持不变。因此这个算法有效。
希望这是可以理解的(说实话,我对此表示怀疑,如果出现任何问题,我会尝试改进答案)。为那个可怕的答案而烦恼。
答案 1 :(得分:1)
让我们假设生成最大乘积的连续子阵列为a[i], a[i+1], ..., a[j]
。由于它是具有最大产品的数组,因此它也是a[0], a[1], ..., a[j]
的后缀,可生成最大的产品。
您给定算法的想法如下:对于每个前缀数组a[0], ..., a[j]
,找到最大的后缀数组。在这些后缀数组中,取最大值。
最初,最小和最大的后缀产品只是nums[0]
。然后它迭代数组中的所有其他数字。最大的后缀数组总是以三种方式之一构建。它只是最后一个数字nums[i]
,它是缩短列表的最大后缀乘以乘以最后一个数字(如果nums[i] > 0
),或者它是最小的(< 0
)后缀 - 乘积乘以按最后一个数字(如果nums[i] < 0
)。 (*)
使用辅助变量result
,存储到目前为止找到的最大此类后缀产品。
(*)这个事实很容易证明。如果你有不同的情况,例如存在一个产生更大数字的不同后缀产品,而不是最后一个数字nums[i]
,你会创建一个更大的后缀,这将是一个矛盾。