【问题标题】:What is the min buffer value such that an array of ints will be sorted in increasing order?使整数数组按升序排序的最小缓冲区值是多少?
【发布时间】:2014-10-29 23:37:14
【问题描述】:

问题:给定一个整数数组,找到最小的缓冲区值,使得数组可以严格按递增顺序排序。数组中的每个元素都可以加减缓冲区的值。

Ex: [4, 0, 9, -2]

最小缓冲区值为6,因为你可以把数组改成:

[4 - (6 or 5 or 4 or 3), 0 + (-1 or 0 or 1 or 2), 9 - (6) = 3, -2 + (6) = 4]

我相信我有一个 O(N^2) 有效的解决方案,但我想知道我是否可以做得更好。

编辑:没关系,我的解决方案不起作用。

我的解决方案:

对于每个元素,绘制一条经过该元素的斜率为 1 的线。找到允许每个元素移动到行上的最小缓冲区值。跟踪获得的缓冲区值并在最后返回最小的值。

谢谢

【问题讨论】:

  • 你能解释清楚一点吗?也许与您的 N^2 算法有关。
  • 所以基本上你不想对数组进行排序,而是通过在 [0,n] 范围内添加/减去一个值将其转换为排序序列,然后调用 n 缓冲区?在那种情况下 8 不是最小值,你可以用 6 => [0 (4-4),1 (0+1),3 (9-6),4 (-2+6)] 来做到这一点。
  • 你也只需要找到值或你可以得到的序列吗?如果我的两个 cmets 都是正确的,那么使用一些观察和动态编程在线性时间内可能是可行的。
  • 请更改问题标题max buffer或min buffer?
  • @MateuszDymczyk 哦,你是对的。那我的解决方案是错误的。我们不需要序列,我们只需要值。

标签: arrays performance algorithm list sorting


【解决方案1】:

O(n) 可以通过将数组一一循环并保持不变量正确并跟踪当前缓冲区大小来实现:

  1. 我们从左边开始,一个接一个地往右走
  2. 根据定义对单个元素子数组进行排序
  3. 使用我们的当前缓冲区将当前元素调整为可能的最低值(因此不变量仍然成立)
  4. 如果当前元素(调整后)比前一个元素大,那么我们不需要做任何事情(排序)
  5. 否则(未排序)我们需要更新缓冲区(增加它) - 这可以通过检查当前元素的差异和已排序数组的最大值来轻松完成,这只是前一个元素(因此我们将abs((curr-prev)/2) + 1 添加到当前缓冲区)和先前/当前值(尽可能的最小值)。在这一点上,我们不需要关心之前的条目,因为我们正在增加缓冲区以减少之前/当前的值,我们可以简单地从之前的每个值中减去 abs((curr-prev)/2) + 1 并且不变量将保持不变。

一些例子让一切更清楚(粗体 - 当前,斜体 - 以前):

I) 输入:[4, 0, 9, -2]

  • [4] - 按定义排序
  • [4,0] - 未排序,用缓冲区 [4,0] 更新当前一个, diff = (4-0)/2+1 = 3 => [1,2], buffer = 3 // 更新如下:将前一个值减去整个 diff,对于当前值,检查是否 newPrevious ( 1 这里)加 1 在范围 current+-diff 内,如果是则设置 newPrevious+1,否则设置 current+diff
  • [1,2,9] - 已排序,用缓冲区更新当前一个 [1,2,6] // 这里的更新与之前的更新类似,但我们不做 current+diff 我们做 current-diff
  • [1,2,6,-2] - 未排序,用缓冲区 [1,2,6 更新当前一个,1],仍然未排序,所以 diff = (6-1)/2+1 = 3, buffer = 6, [1,2,3,4 em>]

完成,缓冲区 = 6

II) 输入:[40, 31, 140, 131]

  • [40] - 按定义排序
  • [40,31] - 未排序,用缓冲区 [40,31] 更新当前一个,diff = (40-31)/2+1=5 = > [35,36],缓冲区 = 5
  • [35,36,140] - 已排序,更新当前的 [35,36,135]
  • [35,36,135,131] - 未排序,更新当前一个 [35,36,135,136]

完成,缓冲区 = 5

III) 输入:[1,1,1,1]

  • [1] - 按定义排序
  • [1,1] - 未排序,更新当前一个 [1,1],diff = (1-1)/2+1=1 => [0,1](因为 0 +1 没问题),缓冲区 = 1
  • [0,1,1] - 未排序,更新当前 [0,1,2],已排序
  • [0,1,2,1] - 未排序,更新当前一个 [0,1,2,2], diff = (2-2)/ 2+1=1 => [0,1,2,3],缓冲区 = 2

完成,缓冲区 = 2

IV) 输入:[7,11,1,2,3]

  • [7] - 按定义排序
  • [7,11] - 排序,调整 [7,11]
  • [7,11,1] - 未排序,调整[7,11,1],diff = (11-1)/2+1 = 6 = > [7,5,6],缓冲区 = 6
    • 这里我们可以看到我们不需要检查 11 之前的任何内容,因为之前的所有数字都小于 11,所以如果我们执行 11 - X 我们可以简单地执行 PREVIOUS - X 并且不变量仍然成立
  • [7,5,6,2] - 未排序,调整 [7,5,6,7]
  • [7,5,6,7,3] - 未排序,调整 [7,5,6,7,8]

完成,缓冲区 = 6

V) 输入:[0,1,3,-15]

直到 [0,1,3] 没有任何变化

  • [0,1,3,-15] diff = 10, 调整 prev 和 current [0,1,-7,-6] - 这里我们再次可以看到我们将缓冲区从 0 增加到 10,因此 3 左侧的所有数字和 3 本身都可以减少 10 并且不变量将保持不变,但对于算法的其余部分,我们不需要这样做

完成,缓冲区 = 10

VI) 输入:[1,2,3,4]

  • [1] - 按定义排序
  • [1,2] - 排序,按缓冲区 0 调整
  • [1,2,3] - 排序,按缓冲区 0 调整
  • [1,2,3,4] - 排序,按缓冲区 0 调整

完成,缓冲区 = 0

【讨论】:

    【解决方案2】:

    原答案

    我最初的答案包括以下三个步骤。如果规范化数组中的 max 值位于规范化数组中的 min 值之前,该算法就可以工作。我考虑的样本数组是 [4, 0, 9, -2] 和 [0, 1, 3, -15]。请注意,在这两个示例数组中,max 位于 min 之前。但是,如果数组中的绝对值min 出现在绝对值max 之前,则此算法将失败。算法失败的两个例子是 [-15, 1] 和 [40, 31, 140, 131]。

    第 1 步:从该索引处的值中减去数组索引

    array:              4    0   9   -2
    index:             -0   -1  -2   -3
                       ----------------
    normalized array:   4   -1   7   -5
    

    第 2 步:扫描归一化数组以找到最小值和最大值

    max =  7
    min = -5
    

    第 3 步:从最大值中减去最小值并除以 2(必要时四舍五入),这就是您的答案

    (7 - (-5)) / 2 = 6
    

    原始答案的理由和缺陷

    我最初的答案背后的想法是max 和min 之间的中点提供了一个target 值,每个 规范化数组中的条目都可以调整到命中。 max 和 min 距离 target 最远,需要最大的调整,因此提供了答案。但是,在看到 hk6279 的评论后,我意识到标准化数组可以有多个 targets。例如,考虑数组 [40, 31, 140, 131]。第一步是

    array:         40  31   140  131
    index:         -0  -1    -2   -3
                   -----------------
    normalized:    40  30   138  128
    

    前两个数字的target 是35(通过+-5 的调整可以达到)。后两个数字的target 是133(也可以通过+-5 的调整达到)。所以答案是5,对数组的调整是

    array:         40  31   140  131
    adjustment:    -5  +5    -5   +5
                   -----------------
    sorted:        35  36   135  136
    

    (旁注:数组[-15,1]也有两个目标。-15的目标是-15,调整为0。0的目标(1的归一化值)是0,调整0。所以答案是 0)。

    更复杂(但仍为 O(n)) 的答案

    第 1 步:通过从该索引处的值中减去数组索引来规范化数组。这一步与原来的答案没有变化。

    第 2 步:定义一个数据结构来保存有关数组条目的信息。

    struct Section
    {
       int max;           // the maximum value seen in this section of the array
       int min;           // the minimum value seen in this section of the array
       int startIndex;    // the starting index for this section of the array
       int endIndex;      // the ending index for this section of the array
    }
    

    第 3 步:扫描规范化数组,同时创建 Section 结构数组。 (sectionsArray 可能和normalized 数组一样长。)sectionsArray 的创建规则是

    initialize the first section as { normalized[0], normalized[0], 0, 0 }
    
    for each subsequent entry in the normalized array
    {
       if ( normalized[i] > currentSection.max )        // found a larger value than the current max
       {
          newSection = { normalized[i], normalized[i], i, i }   // create a new section and add it to the sectionsArray
          currentSection = newSection                           // the new section is now our current section
       }
       else if ( normalized[i] < currentSection.min )           // found a new minimum for the current section
       {
          currentSection.min = normalized[i]                    // update the min and end of the current section
          currentSection.endIndex = i;
       }
       else                                                     // normalized[i] is within the current range of values
       {  
          currentSection.endIndex = i;              // update the end of the current section
       }
    }
    

    请注意,在这一步中,sectionsArray 中的最大值严格按照升序排列。这对下一步很重要。

    第 4 步:从 sectionsArray 的末尾向后工作,尽可能合并各个部分。合并两个部分的规则是

    if ( sectionsArray[i].min <= sectionsArray[i-1].min )       // bigger max and smaller min allows preceding section to be absorbed into the current section  
    { 
       sectionsArray[i-1].max = sectionsArray[i].max            
       sectionsArray[i-1].min = sectionsArray[i].min            
       sectionsArray[i-1].endIndex = sectionsArray[i].endIndex
    
       discard sectionsArray[i]
    }
    

    第 5 步:扫描sectionsArray,找出max 和min 之间的最大差异。最大的差异,除以二并在必要时四舍五入,就是问题的答案。此外,min 和 max 之间的中点是该数组部分的目标值(可以截断目标值)。

    一个例子

    考虑数组 [8,5,8,6,14,12,18,13]。首先对数组进行归一化:

    Input array:    8   5   8   6  14  12  18  13
    index:         -0  -1  -2  -3  -4  -5  -6  -7
                   ------------------------------
    Normalized:     8   4   6   3  10   7  12   6
    

    然后创建sections数组:

    {  8,  3,   0, 3 }   // started with 8, 4 became the min, 6 was in range, 3 replaced 4 as the min.  10 ended the section since it was higher than 8
    { 10,  7,   4, 5 }   // started with 10, 7 became the min.  12 ended the section.
    { 12,  6,   6, 7 }
    

    向后工作:10,7 部分可以吸收到 12,6 部分中,导致

    {  8,  3,   0, 3 }
    { 12,  6,   4, 7 }
    

    最大差是6,所以答案是3。

    第一部分的目标是 (8+3)/2 = 5(截断后) 第二部分的目标是 (12+6)/2 = 9

    调整如下:

    Normalized:     8   4   6   3   10   7  12   6
    Adjustments:   -3   1  -1   2   -1   2  -3   3
                   -------------------------------
    Targets:        5   5   5   5    9   9   9   9
    

    对输入数组应用同样的调整:

    Input array:    8   5   8   6   14  12  18  13
    Adjustments:   -3   1  -1   2   -1   2  -3   3
                   -------------------------------
    Sorted:         5   6   7   8   13  14  15  16
    

    将此技术应用于此线程中的其他数组

    input:         4   0  9  -2
    normalized:    4  -1  7  -5
    sections:      { 4, -1, 0, 1 }  { 7, -5, 2, 3 }
    combined:      { 7, -5, 0, 3 }
    answer:        (7 - (-5)) / 2 = 6
    targets:       one target for the entire normalized array => (7 + (-5)) / 2 = 1
    
    input:         0  1  3  -15
    normalized:    0  0  1  -18
    sections:      { 0, 0, 0, 1 }  { 1, -18, 2, 3 }
    combined:      { 1, -18, 0, 3 }
    answer:        (1 - (-18)) / 2 = 10
    targets:       one target for the entire normalized array => (1 + (-18)) / 2 = -8
    
    input:         -15 1
    normalized:    -15 0
    sections:      { -15, -15, 0, 0 }  { 0, 0, 1, 1 }
    combined:      same as above
    answer:        0 (same answer for both sections)
    targets:       targets are -15 and 0
    
    input:         40  31  140  131
    normalized:    40  30  138  128
    sections:      { 40, 30, 0, 1 }  { 138, 128, 2, 3 }
    combined:      same as above
    answer:        5 (same answer for both sections)
    targets:       targets are 35 and 133
    

    挑战

    找到一个破坏这个算法的反例并将其发布在 cmets 中:)

    【讨论】:

    • 对于 [-15 , 1] 失败。需要事先检查数组是否严格递增顺序。
    • @hk6279 感谢您指出这一点!这么简单的例子,却迫使我彻底重新设计算法。期待看到您对更新答案的反例:)
    【解决方案3】:

    您可以使用二分搜索解决此问题。

    • 所以假设缓冲区的大小是 x = (low + high)/2,你可以做的是从 0 到 n - 1 的每个索引(其中 n 是数字元素),你只需需要计算最小值是多少 它可以使用缓冲区 x (条件是它应该大于最后一个元素)。这个贪心将帮助你验证是否 x 可以是一个有效的解决方案。

    • 例如,如果 x = 6,则数组为 [4,0,9,-2]

      index 0, min is 4 - 6 = -2
      index 1, min is 0 - 1 = -1 (as we need to make this greater than -2)
      index 2, min is 9 - 6 = 3
      index 3, min is -2 + 6 = 4
      

      所以,6 是有效的。

    伪代码:

     int low = 0;
     int high = //n times Max absolute value in the array
     while(low <- high){
       int x = (low + high)/2
       if(x make the array become sorted)
          update min result;  
          high = x - 1;
       else
          low = x + 1;
     } 
    

    【讨论】:

    • 如何设置//... something?还有当你有x 时,你如何轻松检查它是否可以使数组排序?
    • @MateuszDymczyk 对于数组中的每个元素,我们可以使用 x 使每个元素尽可能小,并满足它需要大于前一个元素的条件,从第一个元素。所以通过这个贪心,我们总能知道x是否是一个有效元素。 (因为如果前一个元素是最小的,那么下一个元素就有很大的机会比前一个元素大,这使得这个贪心在x有效的情况下总是可以产生一个解)
    • 我会给你半分,因为 1) 它是 O(nlogn) 而不是线性的,并且 2) //Max value in the array 对 [0,1,3,-15] 失败 :-)
    • @MateuszDymczyk 哈哈,好吧,所以我只需要一个上限,所以也许(n* 所有元素的最大绝对值)
    • 评论正确地指出我的答案有缺陷,但比评论指出的要糟糕得多。调整数组后,您必须找到数组中跟随数组中较小数字的较大数字之间的最大差异。较小的数字后跟较大的数字可能会或可能不会影响结果,这是我不确定的部分。我现在意识到调整后的数组可能有多个目标值。例如 [40, 31, 140, 131]。答案显然是 5,导致 [35, 36, 135, 136] 但有多个目标。
    【解决方案4】:

    这是O(N) 解决方案。

    对于相邻的一对,比如a 和b,如果a &gt; b 发生,我们需要调整它们。为了将它们调整为升序,我们需要找到一个X,使得a - X &lt; b + X,相当于找到满足a - b &lt; 2X的X。因此我们只需要扫描一次数组,找到a &gt; b的那些对,并计算ceil((a - b) / 2),并回答最大值。

    一个可行的实现可以写成如下:

    temp = a[0];
    ans = 0
    for (i = 1; i < N; i ++)
        temp = max(temp + 1, a[i])
        ans = max(ans, temp - a[i]);
    return ceil(ans / 2)
    

    【讨论】:

    • 嗯,[7,11,1,2,3] 怎么样,你的算法是 5 对,但它是错误的!
    • 是的,在我阅读了您的解决方案@PhamTrung 下的 cmets 后,我意识到了这一点。我的解决方案返回不严格增加的数组。我需要做更多的思考......
    • 实际上,更糟糕的情况是数组具有所有相同的数字,例如 [1, 1, 1, 1, 1]。我们的算法都返回 0 但实际上不是。
    • 嗯,快速解决方法是 ceil((a - b + 1)/2)
    • 这似乎与第一轮调整后共享相同值的数字的总数有关。
    猜你喜欢
    • 2011-03-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多