【问题标题】:Given an array, find out the next smaller element for each element给定一个数组,找出每个元素的下一个较小的元素
【发布时间】:2012-03-18 15:04:18
【问题描述】:

给定一个数组,在不改变元素原始顺序的情况下,找到数组中每个元素的下一个较小元素。

例如,假设给定的数组是 4,2,1,5,3。

结果数组将是 2,1,-1,3,-1。

我在一次采访中被问到这个问题,但我想不出比琐碎的 O(n^2) 解决方案更好的解决方案。 我能想到的任何方法,即创建二叉搜索树或对数组进行排序,都会扭曲元素的原始顺序,从而导致错误的结果。

任何帮助将不胜感激。

【问题讨论】:

  • 你的意思是第一个比当前元素低的下一个元素? For i X[j] such that min_j j>i and X[j]<X[i] ?

标签: arrays algorithm


【解决方案1】:

具有 O(n) 时间复杂度和 O(1) 空间复杂度的解决方案。这个解决方案在没有堆栈的情况下理解和实现并不复杂。

def min_secMin(a,n):
    min = a[0]
    sec_min = a[1]
    for i in range(1,n):
        if(a[i]<min):
              sec_min = min
              min = a[i]
        if(a[i]>min and a[i]<sec_min):
              sec_min = a[i]

return min,sec_min

【讨论】:

    【解决方案2】:

    时间复杂度O(N),空间复杂度O(N)

    java保持数组顺序的干净解决方案:

    public static int[] getNGE(int[] a) {
        var s = new Stack<Pair<Integer, Integer>>();
        int n = a.length;
        var result = new int[n];
        s.push(Pair.of(0, a[0]));
    
        for (int i = 1; i < n; i++) {
            while (!s.isEmpty() && s.peek().v2 > a[i]) {
                var top = s.pop();
                result[top.v1] = a[i];
            }
            s.push(Pair.of(i, a[i]));
        }
    
        while (!s.isEmpty()) {
            var top = s.pop();
            result[top.v1] = -1;
        }
    
        return result;
    }
    
    static class Pair<K, V> {
        K v1;
        V v2;
    
        public static  <K, V> Pair<K, V> of (K v1, V v2) {
            Pair p = new Pair();
            p.v1 = v1;
            p.v2 = v2;
            return p;
        }
    }
    

    【讨论】:

      【解决方案3】:

      您可以在 O(n) 运行时以 O(n) 空间复杂度解决这个问题。 从 Stack 开始并继续推送元素,直到找到 arr[i] 使得 arr[i]

      代码片段:

      vector<int> findNext(vector<int> values) {
      
          stack<int> st;
          vector<int> nextSmall(values.size(), -1);
          st.push(0);
      
          for (int i = 1; i < values.size(); i++) {
              while (!st.empty() && values[i] < values[st.top()]) {  
            // change values[i] < values[st.top()] to values[i] > values[st.top()] to find the next greater element.
                  nextSmall[st.top()] = i;
                  st.pop();
              }
              st.push(i);
          }
           return nextSmall;
      }
      

      【讨论】:

        【解决方案4】:

        这里是 javascript 代码。这个video 更好地解释了算法

        function findNextSmallerElem(source){
            let length = source.length;
            let outPut = [...Array(length)].map(() => -1);
            let stack = [];
            for(let i = 0 ; i < length ; i++){
                let stackTopVal = stack[ stack.length - 1] && stack[ stack.length - 1].val;
                // If stack is empty or current elem is greater than stack top
                if(!stack.length || source[i] > stackTopVal ){
                    stack.push({ val: source[i], ind: i} );
                } else {
                    // While stacktop is greater than current elem , keep popping
                    while( source[i] < (stack[ stack.length - 1] && stack[ stack.length - 1].val) ){
                        outPut[stack.pop().ind] = source[i];
                    }
                    stack.push({ val: source[i], ind: i} );
                }
            }
            return outPut;
        }
        

        输出 -

        findNextSmallerElem([98,23,54,12,20,7,27])
        
        [23, 12, 12, 7, 7, -1, -1]
        

        【讨论】:

          【解决方案5】:

          出于某些原因,我发现更容易推理“先前的较小元素”,即"all nearest smaller elements"。因此向后应用会给出“下一个更小的”。

          作为记录,在 O(n) 时间,O(1) 空间(即没有堆栈)中的 Python 实现,支持数组中的负值:

          def next_smaller(l):
              """ Return positions of next smaller items """
              res = [None] * len(l)
              for i in range(len(l)-2,-1,-1):
                  j=i+1
                  while j is not None and (l[j] > l[i]):
                      j = res[j]
                  res[i] = j
              return res
          
          def next_smaller_elements(l):
              """ Return next smaller items themselves """
              res = next_smaller(l)
              return [l[i] if i is not None else None for i in res]
          

          【讨论】:

          • 这是我要找的那个。但是给定res = [None] * len(l),怎么可能不是O(N)?
          • 我的意思是没有额外的空间要求(临时堆栈)。
          【解决方案6】:

          具有 O(1) 空间复杂度和 O(n) 时间复杂度的解决方案。

          void replace_next_smallest(int a[], int n)                                                          
              {                                                                                                   
                  int ns = a[n - 1];                                                                              
                  for (int i = n - 1; i >= 0; i--) {                                                              
                      if (i == n - 1) {                                                                           
                          a[i] = -1;                                                                              
                      }                                                                                           
                      else if (a[i] > ns) {                                                                       
                          int t = ns;                                                                             
                          ns = a[i];                                                                
                          a[i] = t;                                                                               
                      }                                                                                           
                      else if (a[i] == ns) {                                                                      
                          a[i] = a[i + 1];                                                                        
                      }                                                                                           
                      else {                                                                                      
                          ns = a[i];                                                                              
                          a[i] = -1;                                                                              
                      }                                                                                           
                  }                                                                                               
              }
          

          【讨论】:

          • 这不起作用。输出必须提供 NEXT 较小的元素。您的代码所做的是在当前元素的 RHS 上找到 SMALLEST 元素,而不管 RHS 上可能已经存在一个较小的元素。例如。如果 a = {4,3,3,2,5} 预期的输出是 {3,3,2,-1,-1} 但是您的代码将输出 {3,2,2,-1,-1} 。看出区别了吗?
          • 我愿意,但我认为您对需求的理解与 OP 的要求不符。检查接受的答案 - 它产生与我上面的解决方案相同的结果 - 不是你认为“有效”的结果。
          【解决方案7】:
                  All that is actually not required i think
          
              case 1: a,b
              answer : -a+b
          
              case 2: a,b,c
              answer : a-2b+c
          
              case 3: a,b,c,d
              answer : -a+3b-3c+d
          
              case 4 :a,b,c,d,e
              answer : a-4b+6c-4d+e
          
              .
              .
              .
              recognize the pattern in it?
              it is the pascal's triangle!
                                             1
                                          1     1
                                        1    2     1
                                     1    3     3     1
                                  1    4     6     4     1
              so it can be calculated using Nth row of pascal's triangle!
              with alternate + ans - for odd even levels!
              it is O(1)
          

          【讨论】:

            【解决方案8】:

            这是一个使用 DP 的 O(n) 算法(实际上是 O(2n) ):

            int n = array.length();
            

            数组 min[] 记录从索引 i 到数组末尾的最小数。

            int[] min = new int[n];
            min[n-1] = array[n-1];
            for(int i=n-2; i>=0; i--)
               min[i] = Math.min(min[i+1],array[i]);
            

            通过原始数组和min[]搜索比较。

            int[] result = new int[n];
            result[n-1] = -1;
            for(int i=0; i<n-1; i++)
               result[i] = min[i+1]<array[i]?min[i+1]:-1;
            

            这是寻找“下一个较小元素”的新解决方案:

            int n = array.length();
            int[] answer = new int[n];
            answer[n-1] = -1;
            for(int i=0; i<n-1; i++)
               answer[i] = array[i+1]<array[i]?array[i+1]:-1;
            

            【讨论】:

            • 不,这不起作用..在 [6 4 2] 上尝试您的算法,您的算法将返回 [2 2 -1] 这是不正确的
            • 对不起,我误解了“下一个较小的元素”的问题,我的解决方案是试图找到最小的元素。
            • 我刚刚又看了看,从给定的例子中,“下一个较小的元素”要求查看元素[i+1],如果它小于元素[i],则输出它,否则输出 -1。
            【解决方案9】:

            O(N) 算法

            1. 将输出数组初始化为全 -1。
            2. 为我们在输入数组中访问过但在输出数组中不知道答案的项目创建一个空的索引堆栈。
            3. 遍历输入数组中的每个元素:
              1. 它是否小于堆栈顶部索引的项目?
                1. 是的。这是第一个这样的元素。在我们的输出数组中填入对应的元素,从栈中取出该项,然后重试,直到栈为空或者答案是否定的。
                2. 没有。继续 3.2。
              2. 将此索引添加到堆栈中。从 3 继续迭代。

            Python 实现

            def find_next_smaller_elements(xs):
                ys=[-1 for x in xs]
                stack=[]
                for i,x in enumerate(xs):
                    while len(stack)>0 and x<xs[stack[-1]]:
                       ys[stack.pop()]=x
                    stack.append(i)
                return ys
            
            >>> find_next_smaller_elements([4,2,1,5,3])
            [2, 1, -1, 3, -1]
            >>> find_next_smaller_elements([1,2,3,4,5])
            [-1, -1, -1, -1, -1]
            >>> find_next_smaller_elements([5,4,3,2,1])
            [4, 3, 2, 1, -1]
            >>> find_next_smaller_elements([1,3,5,4,2])
            [-1, 2, 4, 2, -1]
            >>> find_next_smaller_elements([6,4,2])
            [4, 2, -1]
            

            说明

            工作原理

            这是有效的,因为每当我们向堆栈中添加一个项目时,我们都知道它的值大于或等于堆栈中的每个元素。当我们访问数组中的一个元素时,我们知道如果它低于堆栈中的any项,那么它一定低于堆栈中的last项,因为最后一项必须是最大的。所以我们不需要在堆栈上进行任何类型的搜索,我们可以只考虑最后一项。

            注意:只要添加最后一步清空堆栈并使用每个剩余索引将相应的输出数组元素设置为-1,就可以跳过初始化步骤。在 Python 中创建它时将其初始化为 -1 更容易。

            时间复杂度

            这是 O(N)。主循环清楚地访问每个索引一次。每个索引只添加到堆栈一次,最多删除一次。

            作为面试题解决

            这种问题在面试中可能会非常令人生畏,但我想指出,(希望如此)面试官不会期望你的脑海中会出现完整的解决方案。通过您的思考过程与他们交谈。我的是这样的:

            • 数字的位置与其在数组中的下一个较小数字之间是否存在某种关系?了解其中一些是否会限制其他可能是什么?
            • 如果我在白板前,我可能会画出示例数组并在元素之间画线。我也可以将它们绘制为 2D 条形图 - 水平轴是输入数组中的位置,垂直轴是值。
            • 我有一种预感,这会显示一个图案,但手头没有纸。我认为图表会很明显。仔细想来,线条不会随意重叠,只会嵌套。
            • 在这一点上,我突然想到,这与 Python 内部用于将缩进转换为 INDENT 和 DEDENT 虚拟标记的算法非常相似,这是我之前读过的。请参阅“编译器如何解析缩进?”在此页面上:http://www.secnetix.de/olli/Python/block_indentation.hawk 但是,直到我真正制定出一个算法,我才跟进这个想法并确定它实际上是相同的,所以我认为它没有太大帮助。不过,如果您发现与您知道的其他问题有相似之处,最好提及它,并说明它的相似之处和不同之处。
            • 从这里开始,基于堆栈的算法的一般形式变得明显,但我仍然需要多考虑一下,以确保它适用于那些没有后续较小元素的元素。

            即使您没有提出可行的算法,也请尝试让面试官了解您的想法。通常,他们感兴趣的是思考过程而不是答案。对于一个棘手的问题,未能找到最佳解决方案但表现出对问题的洞察力可能比知道固定答案但无法给出太多答案要好分析。

            【讨论】:

              【解决方案10】:

              开始制作 BST,从数组末尾开始。对于每个值“v”,答案将是您在插入“v”的过程中采用的最后一个节点“右”,您可以轻松地在递归或迭代版本中跟踪它。

              更新:

              根据您的要求,您可以以线性方式进行处理:

              如果每个下一个元素都小于当前元素(例如 6 5 4 3 2 1),您可以线性处理它而无需任何额外的内存。当你开始得到混乱的元素(例如 4 2 1 5 3)时会出现有趣的情况,在这种情况下,只要你没有得到它们的“较小的对应物”,你就需要记住它们的顺序。 一个简单的基于堆栈的方法如下所示:

              将第一个元素 (a[0]) 压入堆栈。

              对于每个下一个元素 a[i],您查看堆栈,如果值 (peek()) 大于现有的 a[i],则您将获得该堆栈元素的下一个较小数字 (peek( )) { 并继续弹出元素,只要 peek() > a[i] }。将它们弹出并打印/存储相应的值。 否则,只需将您的 a[i] 推回堆栈即可。

              在最后的堆栈中将包含那些从未有过小于它们的值的元素(在它们的右边)。您可以在输出中为它们填写 -1。

              例如A=[4, 2, 1, 5, 3];

              stack: 4
              a[i] = 2, Pop 4, Push 2 (you got result for 4)
              stack: 2
              a[i] = 1, Pop 2, Push 1 (you got result for 2)
              stack: 1
              a[i] = 5
              stack: 1 5
              a[i] = 3, Pop 5, Push 3 (you got result for 5)
              stack: 1 3
              1,3 don't have any counterparts for them. so store -1 for them.
              

              【讨论】:

              • 为 [4 2 1 5 3] 运行你的算法它会产生 [3 1 -1 3 -1] 因为由于最后一个元素,即 3 是根,它从不检查左子树,它有2,即实际较小的元素,因此算法失败
              • 哦!是的,我在那里误读了您的要求。给定的方法适用于右侧的“下一个较小的元素”。因此,使用这种方法满足您的要求,您必须搜索以最后一个“右”节点为根的整个左子树,这使得复杂性不超过 O(N^2)!
              • 我认为您的基于堆栈的算法在 - [4 8 3] 等情况下会失败。但是,如果我们尽可能长时间地比较顶部元素(直到当前元素变大),它可能会起作用,而不是仅仅与顶部元素进行比较并采取行动。
              • 我发现该方法存在一些问题..假设数组是 [4 5 1 2 3] 那么最后,堆栈有 [4 1 2 3] 现在,可以做的是从顶部开始,维护一个具有观察到该点的最小值的变量 Ex,最初最小值为 3,然后逐个弹出堆栈如果遇到的元素的值大于最小值,则下一个较小的元素将是 min 持有的那个,否则,将 min 更新为最近弹出元素的值,并为该元素存储 -1 但这是在 O(n^2) 方法中最坏的情况
              • @RamanBhatia - 我错过了这样一个事实:只要 peek() > a[i],我们就需要弹出元素,因为元素 a[i] 可以是 req.一种以上元素的解决方案。在这种情况下,[4 5 1 2 3] 将在最后的堆栈中有 [1 2 3] 并且所有这些都必须有 -1。
              【解决方案11】:

              假设您的意思是第一个低于当前元素的下一个元素,这里有 2 个解决方案 -

              1. 使用sqrt(N) 分段。将数组划分为sqrt(N) 段,每个段的长度为sqrt(N)。对于每个段,使用循环计算其最小元素。这样,您已经预先计算了O(N) 中每个段的最小元素。现在,对于每个元素,下一个较低的元素可以与那个元素在同一段中,也可以在任何后续段中。因此,首先检查当前段中的所有下一个元素。如果都更大,则遍历所有后续段以找出哪个元素低于当前元素。如果找不到,结果将是-1。否则,检查该段的每个元素以找出低于当前元素的第一个元素。总体而言,算法复杂度为O(N*sqrt(N))O(N^1.5)

              您可以使用类似方法的分段树来实现O(NlgN)

              1. 首先对数组进行升序排序(将元素的原始位置保留为卫星数据)。现在,假设数组的每个元素都是不同的,对于每个元素,我们需要找到该元素左侧的最低原始位置。这是一个经典的 RMQ(范围最小查询)问题,可以通过多种方式解决,包括 O(N) 一种。因为我们需要先排序,所以总体复杂度为O(NlogN)。您可以通过in a TopCoder tutorial了解更多关于 RMQ 的信息。

              【讨论】:

                【解决方案12】:

                这是一个我认为可以做成 O(n log n) 解决方案的观察结果。假设你有数组最后 k 个元素的答案。为了在此之前找出元素的值,您需要什么?您可以将最后 k 个元素视为被拆分为一系列范围,每个范围都从某个元素开始并继续向前直到遇到较小的元素。这些范围必须按降序排列,因此您可以考虑对它们进行二进制搜索以找到小于该元素的第一个间隔。然后,您可以更新范围以考虑这个新元素。

                现在,如何最好地表示这一点?我想到的最好的方法是使用一个展开树,它的键是定义这些范围的元素,其值是它们开始的索引。然后,您可以及时 O(log n) amortized 进行前驱搜索以找到当前元素的前驱。这会找到小于当前值的最早值。然后,在平均 O(log n) 时间内,将当前元素插入到树中。这表示从该元素向前定义一个新范围。要丢弃此取代的所有范围,然后从树中剪切新节点的右子节点,因为这是一个展开树,它位于根节点。

                总的来说,这对 O(log n) 过程进行了 O(n) 次迭代,总时间为 O(n lg n)。

                【讨论】:

                  猜你喜欢
                  • 2017-09-12
                  • 1970-01-01
                  • 2016-10-09
                  • 2015-08-15
                  • 1970-01-01
                  • 1970-01-01
                  • 2012-03-18
                  • 1970-01-01
                  • 2014-07-28
                  相关资源
                  最近更新 更多