剑指offer打卡Day22:数字在升序数组中出现的次数<从 BinarySearch 到 util.Arrays>

题目描述

统计一个数字在升序数组中出现的次数。

示例1

输入

[1,2,3,3,3,3,4,5],3

返回值

4

解析:

乍一看题目,哟吼老套路了,直接上个计数器不就完事了?但是突然想到上次做题出现的数组越界(详见 Day14中的解析)😧 得冷静一下

  • 暴力解题

    最简单粗暴的解决方法,遍历+计数

    public int GetNumberOfK(int [] array , int k) {
        int count = 0;
        for(int i = 0; i<=array.length-1; i++){
            if(array[i] == k){
                count ++;
            }
        }
        return count;
    }
    

    结果提交牛客后显示

    运行时间:12ms
    超过80.67%用Java提交的代码
    
    占用内存:9592KB
    超过4.72%用Java提交的代码
    

    😂😂没想到这都可以过?但是如果面试这样写的话怕不是被轰出去喔哈哈。

    不过也可以从中发现解题的思路:就是找准元素

    • 如题:在升序(即有序)的数组中,
    • 因此只要找到其中一个元素,并且根据其角标左右遍历便能快速找到剩下的同类元素,可以快速计算出次数。
  • 二分查找:

    除了上述的依次遍历匹配进行搜索,在数组中还有个二分查找

    二分查找又称折半查找:

    • 优点是比较次数少,查找速度快,平均性能好;

    • 其缺点是要求待查表为有序表,且插入删除困难。

      • 因此,折半查找方法适用于不经常变动而查找频繁的有序列表(数组表示:我可以)
    • 算法描述:

      • 它的基本思想是,将n个元素分成个数大致相同的两半,取a[n/2]与欲查找的x作比较:
        • 如果x=a[n/2]则找到x,算法终止。
        • 如果x<a[n/2],则我们只要在数组a的左半部继续搜索x(这里假设数组元素呈升序排列)。
        • 如果x>a[n/2],则我们只要在数组a的右半部继续搜索x, 在最坏的情况下用 O(log n)。

      类似聚会中经常玩的猜数字的游戏:

      写下一个数字,给定范围后,参与者不断提供范围内的数字用来不断缩小范围,直至缩小至到达临界点游戏结束。

    二分查找的代码实现:(假设在升序的数组里查找)

    /**  递归写法  
         *  
         * @param array  
         * @param low  
         * @param high  
         * @param value  
         * @return int  
         */    
    public static int search1(int[] array, int low, int high, int value) {    
        int mid = (low + high) / 2;    
        if (array[mid] == value) {    
            return mid;    
        } else if (array[mid] < value) {    
            return search1(array, mid + 1, high, value);    
        } else {    
            return search1(array, 0, mid - 1, value);    
        }    
    }    
    
    /**  
         * 非递归写法  
         *  
         * @param array  
         * @param value  
         * @return int  
         */    
    public static int search2(int[] array, int value) {    
        int low = 0;    
        int high = array.length - 1;    
    
        while (low <= high) {    
            int mid = (low + high) / 2;
            //情况1:所找的元素直接在array[mid]
            if (array[mid] == value) {    
                return mid;    
            } else if (array[mid] < value) {    
                //情况2:折半值小于value,目标区域缩小至数组右侧
                low = mid + 1;    
            } else {    
    			//情况3:折半值大于value,目标区域缩小至数组左侧
                high = mid - 1;    
            }    
        }    
        return -1;    
    } 
    
  • Arrays工具类

    • Java Arrays工具类用法详解

      1. 返回数组:asList()

        public static <T> List<T> asList(T... a) {
            return new ArrayList<>(a);
        }
        
      2. 填充数组:fill()

        public static void fill(int[] a, int val) {
            for (int i = 0, len = a.length; i < len; i++)
                a[i] = val;
        }
        
      3. 复制数组:

        • copyOf():将原始数组的元素,复制到新的数组中,可以设置复制的长度(即需要被复制的元素个数)。

          public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
              @SuppressWarnings("unchecked")
              T[] copy = ((Object)newType == (Object)Object[].class)
                  ? (T[]) new Object[newLength]
                  : (T[]) Array.newInstance(newType.getComponentType(), newLength);
              System.arraycopy(original, 0, copy, 0,
                               Math.min(original.length, newLength));
              return copy;
          }
          
        • copyOfRange():将某个范围内的元素复制到新的数组中。

          public static <T,U> T[] copyOfRange(U[] original, int from, int to, Class<? extends T[]> newType) {
              int newLength = to - from;
              if (newLength < 0)
                  throw new IllegalArgumentException(from + " > " + to);
              @SuppressWarnings("unchecked")
              T[] copy = ((Object)newType == (Object)Object[].class)
                  ? (T[]) new Object[newLength]
                  : (T[]) Array.newInstance(newType.getComponentType(), newLength);
              System.arraycopy(original, from, copy, 0,
                               Math.min(original.length - from, newLength));
              return copy;
          }
          
      4. 判断相等:equals()

        • 判断两个数组中的元素是否一一对应相等

          public static boolean equals(int[] a, int[] a2) {
              if (a==a2)
                  return true;
              if (a==null || a2==null)
                  return false;
          
              int length = a.length;
              if (a2.length != length)
                  return false;
          
              for (int i=0; i<length; i++)
                  if (a[i] != a2[i])
                      return false;
          
              return true;
          }
          
      5. 排序:sort()

        • 对数组进行升序排序,排序后 ,数组中存放的是排序后的结果

          public static void sort(int[] a) {
              DualPivotQuicksort.sort(a, 0, a.length - 1, null, 0, 0);
          }
          

          这个排序就厉害了,双基准快排算法,挖个坑,下次填哈哈

      6. 二分搜索:binarySearch()

        对排序好的数组,采用二分查找的方式查找某个元素,可以在整个数组中查找,也可以在某个范围内查找:下面详解

    • 在Arrays中有二分搜索的现成调用

    public static int binarySearch(int[] a, int key) {
        return binarySearch0(a, 0, a.length, key);
    }
    // Like public version, but without range checks.
    private static int binarySearch0(int[] a, int fromIndex, int toIndex,
                                     int key) {
        int low = fromIndex;
        int high = toIndex - 1;
    
        while (low <= high) {
            int mid = (low + high) >>> 1;
            int midVal = a[mid];
    
            if (midVal < key)
                low = mid + 1;
            else if (midVal > key)
                high = mid - 1;
            else
                return mid; // key found
        }
        return -(low + 1);  // key not found.
    }
    
  • 左移、右移的运算

    • java中的移位运算符:<<,>>,>>>总结

      java中有三种移位运算符

      << : 左移运算符,num << 1,相当于num乘以2

      >> : 右移运算符,num >> 1,相当于num除以2

      >>> : 无符号右移,忽略符号位,空位都以0补齐

解答:

  • 个人解法:

    • 除了最原始的遍历求解外,此次增加了用二分搜索定位进行遍历
    • 实现思路:
      • 用二分查找找出目标元素出现的索引–> i
      • 并将i赋值给forwardbackward对数组进行前后遍历
      • 存在则count++,最后返回count即可。
    public int myGetNumberOfK(int[] nums, int K) {
        //寻找元素k时出现的索引(不唯一)
        int i = myBinarysearch(nums, K);
        //将索引赋值给forward、backward两个指针进行遍历
        int forward = i;
        int backward = i;
        //如果没找到则返回0
        if (i == -1) {
            return 0;
        }
        //默认已经有一个了
        int count = 1;
        //forward不是首位时(防止数组越界)
        if (forward != 0) {
            while (nums[forward - 1] == K) {
                forward--;
                count++;
                if (forward - 1 < 0) {
                    break;
                }
            }
        }
        //backward不是末尾时(防止数组越界)
        if (backward != nums.length - 1){
            while (nums[backward + 1] == K) {
                backward++;
                count++;
                if (backward + 1 > nums.length - 1) {
                    break;
                }
            }
        }
        return count;
    }
    private int myBinarysearch(int[] array, int value) {
        int low = 0;
        int high = array.length - 1;
    
        while (low <= high) {
            int mid = (low + high) / 2;
            //情况1:所找的元素直接在array[mid]
            if (array[mid] == value) {
                return mid;
            } else if (array[mid] < value) {
                //情况2:折半值小于value,目标区域缩小至数组右侧
                low = mid + 1;
            } else {
                //情况3:折半值大于value,目标区域缩小至数组左侧
                high = mid - 1;
            }
        }
        return -1;
    }
    
    • 通过是通过了,不过这么多代码感觉确实有点复杂啊😂

      • 自己写的二分搜索有个最大的局限:

        对于重复的元素,其会随机定位至某一个索引

      • 这就造成了必须遍历多次的尴尬

      于是又默默看了牛客的高赞回答,不由赞叹妙啊👍


  • 牛客Java高赞回答:

    public int GetNumberOfK(int[] nums, int K) {
        if (nums.length <= 0) {
            return 0;
        }
        int first = binarySearch(nums, K);
        int last = binarySearch(nums, K + 1);
        return ((first == nums.length) || (nums[first] != K)) ? 0 : (last - first);
    }
    
    private int binarySearch(int[] nums, int K) {
        int low = 0, high = nums.length;
        while (low < high) {
            int mid = low + (high - low) / 2;
            if (nums[mid] >= K)
                high = mid;
            else
                low = mid + 1;
        }
        return low;
    }
    
    • 实现思路:

      • int[] arr = {1, 2, 3, 6, 6, 6, 6, 6, 11, 23, 34};6为例
        在这里插入图片描述

      • 此种二分搜索可以搜索出 元素首次出现时的索引

        • int mid = low + (high - low) / 2; 赋值mid
        • 最后返回 low索引
      • 运用题目给的升序重要条件

        • 找到元素k以及k+1确定元素首次与最后出现的索引,相减可得出现次数👍
  • 吐槽:

    • 讲了那么多最后一对比各种解法的运行速度:

      • 牛客高赞
        在这里插入图片描述

      • 二分+遍历

在这里插入图片描述

  • 最后用Arrays自带的binarySearch()方法
  import java.util.Arrays;
      public class Solution {
          public int GetNumberOfK(int [] array , int k) {
              int index = Arrays.binarySearch(array, k);
              if(index<0)return 0;
              int cnt = 1;
              for(int i=index+1; i < array.length && array[i]==k;i++)
                  cnt++;
              for(int i=index-1; i >= 0 && array[i]==k;i--)
                  cnt++;
              return cnt;
          }
      }

嗯😂真香哈哈

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值