【问题标题】:Efficient way to count occurrences of a key in a sorted array计算排序数组中键出现次数的有效方法
【发布时间】:2010-12-01 08:08:50
【问题描述】:

这是在微软现场采访中提出的。

计算给定键在数组中出现的次数。

我回答线性搜索是因为元素可能分散在 大批。假设在开头和结尾都找到了密钥。所以我们 需要扫描整个数组。

接下来他问如果数组排序了怎么办?

想了一会儿,说我会再次使用线性搜索。因为 键的重复(如果存在)可以在数组中的任何位置。作为一个 优化我还说如果第一个和最后一个数组元素相同 可以把数组长度作为答案。

我的分析在这两种情况下都正确吗?

例子:

Input = [0 0 1 1 1 2 2 3 3], key = 1, Answer = 3
Input = [0 0 2 2 3 3],       key = 1, Answer = 0

【问题讨论】:

    标签: arrays algorithm


    【解决方案1】:

    对于未排序的数组,除了线性搜索我们无能为力。

    对于排序数组,您可以在O(logN) 中使用稍微修改的二分搜索:

    • 查找第一次出现的索引 key,就叫它f
    • 查找最后一次出现的索引 key,就叫它l
    • 如果key存在于数组l-f+1中 就是答案。

    找到第一个匹配项:

    arr[i]key iff 的第一次出现

    • arr[i] == key
      • i == 0 ( 它是第一个元素 数组)
      • arr[i-1] != key(不是第一个 数组的元素和元素 左边不一样)

    您可以稍微修改二进制搜索以找到第一个匹配项。
    在二进制搜索中,当您找到 arr[mid] == key 时终止搜索。
    修改条件,以便在找到第一个匹配项时终止搜索第一次次出现,而不是任何次出现。

    算法:

    low = 0
    high = arrSize - 1 
    
    while low <=  high
    
      mid = (low + high) / 2
    
      //if arr[mid] == key         // CHANGE
      if arr[mid] == key AND ( mid == 0 OR arr[mid-1] != key )
        return mid
      //else if ( key < arr[mid] ) // CHANGE
      else if ( key <= arr[mid] ) 
        high = mid - 1
      else
        low = mid + 1        
      end-if
    
    end-while
    
    return -1
    

    同样你可以找到最后一次出现。

    【讨论】:

    • +1,但 mid = (low + high) / 2 应该在循环内。另外,我认为您可以摆脱相当复杂的第一个if,但是如果摆脱​​它是不那么复杂还是更复杂,这是有争议的。
    • @IVlad:感谢您的指点。对于第一个 if,您有什么更简单的替代方案?
    • 您可以将二分搜索功能合并为一个,特别是如果您删除了可能最终节省几个周期的break-early条件。
    • 我正在考虑用low &lt; high替换while条件,删除第一个if,将下一个更改为if (arr[mid] &lt;= key) high = mid,然后最后检查是否arr[low] == key
    • @marcog:可以做到。我将它们分开以使其更清晰。 @IVlad:这也可以。我想对标准 BS 做尽可能少的更改。
    【解决方案2】:

    这一次,我将提出一个 C++ 实现。

    size_t count(std::vector<int> const& vec, int key)
    {
      auto p = std::equal_range(vec.begin(), vec.end(), key);
      return std::distance(p.first, p.second);
    }
    

    equal_range使用二分查找,结果等价于:

    std::make_pair(std::lower_bound(vec.begin(), vec.end(), key),
                   std::upper_bound(vec.begin(), vec.end(), key));
    

    但实现应该稍微快一点,即使所有都在 O(log N) 中(就比较次数而言)。

    【讨论】:

    • @T33C: 那么两个迭代器都指向同一个元素,因此(因为它们划定了一个半开的范围)从一个到另一个的距离是 0 :)
    • +1。不错的镜头,但他应该知道 C++ 才能理解这一点:-) 无论如何,为什么是 std::vector?如果我决定使用 boost::array 怎么办?使用模板:-)
    • @Stas:是的,我通常用 Python 演示,但这是展示 C++ 的好机会。在 C++ 中找到不那么冗长的替代方案并不常见:p 至于模板……我考虑过,但担心它会让非 C++ 用户更加困惑。至少可以说语法是特殊的 :)
    • 在 C++ 中很好地使用了 lower_bound() 和 upper_bound(),但我认为面试官会期望在 OP 的编码面试中使用手工解决方案。
    • @AlanEvangelista:也许,也许不是。我发现在面试中最好从高层次开始讨论到低层次:这可以让面试官决定到多低层次,这让我可以展示我没有患上 NIH 综合症的情况。
    【解决方案3】:
    #include<stdio.h>
    int binarysearch(int a[],int n,int k,bool searchfirst){
        int result=-1;
        int low=0,high=n-1;
        while(low<=high){
            int mid=(low+high)/2;
            if(a[mid]==k)  {
                  result=mid; 
               if(searchfirst)
                  high=mid-1; 
                else
                  low=mid+1;
        }
        else if(k<a[mid])  high=mid-1;
        else low=mid+1;
        }
        return result;
    }
    
    int main(){
        int a[]={1,1,1,2,2,3,3,3,6,6,6,6,6,7,7};
        int n=sizeof(a)/sizeof(a[0]);
        int x=6;
        int firstindex=binarysearch(a,n,x,true);
        printf("%d\n",firstindex);
        if(firstindex==-1){
            printf("elment not found in the array:\n ");
        }
        else {
            int lastindex=binarysearch(a,n,x,false);
            printf("%d\n",lastindex);
            printf("count is = %d", lastindex-firstindex+1);
        }
    
    }
    

    【讨论】:

    • 对为什么这样做更好的一些解释将有助于人们在未来研究这个问题。
    【解决方案4】:

    你可以使用递归版本的二分搜索

    int modifiedbinsearch_low(int* arr, int low, int high , int key)
    {   
        if(low==high) return high ; 
    
        int mid = low + (high-low) /2;
    
        if(key >  arr[mid] ) { modifiedbinsearch_low(arr,mid + 1 , high,key);  } 
        else  { modifiedbinsearch_low(arr,low,mid,key);  }  
    }
    int modifiedbinsearch_high(int* arr, int low, int high , int key)
    {   
        if(low==high) return high ; 
    
        int mid = low + (high-low) /2;
    
        if(key <  arr[mid] ) { modifiedbinsearch_high(arr,low,mid,key);  } 
        else  { modifiedbinsearch_high(arr,mid+1,high,key);  } 
    
    } 
    

    .

    int low = modifiedbinsearch_low( ...)
    
    int high = modifiedbinsearch_high( ...)
    

    (high - low) 给出键的数量

    【讨论】:

      【解决方案5】:

      ** 时间复杂度 = O ( lg N ) 其中N是数组的大小

      ** binarySearchXXXXX 的参数:**

      1. int[] 数组是长度 >= 1 的排序数组
      2. int k:搜索键

      **

      package array;
      
       public class BinarySearchQuestion {
      
      public static int binarySearchFirst(int[] array, int k) {
          int begin = 0;
          int end = array.length-1;
          int mid = -1;
          while (begin <= end) {
              mid = begin + (end - begin) / 2;
              if (array[mid] < k) {
                  begin = mid + 1;
              } else {
                  end = mid - 1;
              }
          }
          //System.out.println("Begin index :: " + begin + " ,  array[begin] " + array[begin]);
          return (begin <= array.length - 1  && begin >= 0 && array[begin] != k) ? -1 : begin;
          //      return begin;
      }
      
      public static int binarySearchLast(int[] array, int k) {
          int begin = 0;
          int end = array.length - 1;
          int mid = -1;
          while (begin <= end) {
              mid = begin + (end - begin) / 2;
              if (array[mid] > k) {
                  end = mid - 1;
              } else {
                  begin = mid + 1;
              }
          }
          //System.out.println("Last index end :: " + end + " ,  array[mid] " + array[end]);
          return (end <= array.length - 1  && end >= 0 &&  array[end] != k) ? -1 : end;
          //return end;
      }
      
      /**
       * @param args
       */
      public static void main(String[] args) {
                   //     int[] array = { 0, 1,1,1, 2, 3, 4,4,4,5, 5, 5, 5, 5, 5, 5, 5, 5, 5,5,6,6,6,6, 6, 7, 7, 7,
                   //             7, 8, 9 };
                  //      int[] array = {-1, 0,1, 1,1,2,3};
          int[] array = {1,1,1};
      
          int low = binarySearchFirst(array, 1);
          int high = binarySearchLast(array, 1);
          int total = (high >= low && low != -1 && high != -1) ? ( high - low + 1 ): 0;
          System.out.println("Total Frequency " + total);
      }
      
         }
      

      【讨论】:

        【解决方案6】:

        对于具有 O(logN) 时间复杂度的已排序部分如何?

        int count(int a[], int k, int l, int h) {
          if (l>h) {
            return 0;
          }
          int mid = (l+h)/2;
          if (k > a[mid]) {
             return count(a, k, mid+1, h);
          }
          else if (k < a[mid]) {
             return count(a, k, l, mid-1);
          }
          else {
             return count(a, k, mid+1, h) + count(a, k, l, mid-1) + 1;
          }
        }
        

        【讨论】:

          【解决方案7】:

          封装数组;

          /* * 给定一个有序数组,求一个元素出现的次数。 * 二分查找 O(lgn) * */

          公共类 NumberOfN {

          static int bSearchLeft(int[] arr, int start, int end, int n){
          
              while(start < end){
          
                  int mid = (start + end)>>1;
                  if(arr[mid] < n){
                      start = mid + 1;
                  }else{
                      end = mid;
                  }
          
              }
          
              return end;
          }
          
          static int bSearchRight(int[] arr, int start, int end, int n){
          
              while(start < end){
          
                  int mid = (start + end)>>1;
                  if(arr[mid] <= n){
                      start = mid + 1;
                  }else{
                      end = mid;
                  }
          
              }
          
              return end;
          }
          
          /**
           * @param args
           */
          public static void main(String[] args) {
          
              int[] arr = new int[]{3,3,3,3};
              int n = 3;
              int indexLeft = bSearchLeft(arr, 0, arr.length, n);
              int indexRight = bSearchRight(arr, 0, arr.length, n);
              System.out.println(indexLeft + " " +indexRight);
              System.out.println("Number of occurences: " + (indexRight - indexLeft));
          }
          

          }

          【讨论】:

            【解决方案8】:

            我们可以使用线性搜索和二分搜索来解决这个问题。 但是线性搜索将是 O(n)。二进制搜索将给出 O(Logn)。 因此最好使用二分搜索。 完整的程序是:

            public class Test4 {
            public static void main(String[] args) {
                 int a[] = {1, 2, 2, 3, 3, 3, 6,6,6,6,6,66,7}; 
                 int x =  6; 
            
                     System.out.println(fix(a,x));
            }
            
            private static int fix(int[] a, int x) {
                int res = 0 ;
            
                for (int i = 0; i < a.length; i++) {
                    int ch = a[i];
                    if(x == ch) {res++ ;}
                }
                return res;
            }
            }
            

            还有一个后续问题是:给定数字在排序数组中的第一次和最后一次出现。

            class Occurence1 {
            
                public static void findFirstAndLast(int a[], int x) {
            
                    int first = -1, last = -1;
                    for (int i = 0; i < a.length; i++) {
                        if (x == a[i]) {
                            if (first == -1) {
                                first = i;
                            }
                            // update last
                            last = i;
                        } // if
            
                    } // for                                                                           
                    if (first != -1) {
                        System.out.println("First Occurrence = " + first);
                        System.out.println("Last Occurrence = " + last);
                    } 
                }// end1
            
                public static void main(String[] args) {
                    int arr[] = { 1, 2, 2, 2, 2, 3, 4, 7, 8, 8 };
                    int x = 8;
                    findFirstAndLast(arr, x);
                }
            }
            

            在 Python 中:

            def findFirstAndLast(a, x):
                first = -1 ; last = -1
                for i in range(len(a)) :
                    if(x == a[i]): 
                        if(first == -1):first = i 
            
                     # update last if the first contains oter value than -1    
                    last = i
            
                if(first != -1):
                    print("first => ",first)
                    print("last =>", last)       
            
            
            a = [1, 2, 3,4, 5, 6, 7, 8, 1, 10, 10]
            x = 10
            findFirstAndLast(a, x)
            

            【讨论】:

              【解决方案9】:

              如果数组未排序,是的,从一端到另一端的线性搜索是最好的。

              但是,如果对数组进行了排序,则可以通过应用二进制或插值搜索技术获得比线性时间更好的结果。

              将问题视为与“在排序列表中查找数字 X”相同的问题,并添加“然后左右扫描以确定 X 出现多少次”的附加细节。第一部分,搜索,在大多数情况下最好使用二分或插值搜索。

              http://en.wikipedia.org/wiki/Interpolation_search

              http://en.wikipedia.org/wiki/Binary_search

              【讨论】:

              • 如果列表中的所有数字都是 X 怎么办?然后这将退化为 O(N)。解决方案是使用稍微修改的二进制搜索,当您在查找的值上时,总是将右边界向左移动。这将在对数时间内找到您的第一次出现。然后做同样的事情,但在最后一次出现时将左边界向右移动。
              • marcog - 你的方法和 codaddict 的一样吗?
              【解决方案10】:

              是的,对于未排序的数组,您是正确的,但是对于已排序的数组,您可以使用二进制搜索来定位元素的一次出现,一旦发现一次,只扫描相邻的元素,直到找到不匹配然后停止。

              【讨论】:

              • @codaddict:嗯,是的,如果有很多这些值,它就是线性的。我猜可以使用二分查找来查找范围,但这会很复杂。
              • 这与您在第一步中所做的完全相同。这有什么复杂的?
              猜你喜欢
              • 2011-01-23
              • 2023-03-27
              • 1970-01-01
              • 2015-09-01
              • 2021-12-17
              • 1970-01-01
              • 2016-10-29
              • 2013-12-03
              相关资源
              最近更新 更多