题目描述
统计一个数字在升序数组中出现的次数。
示例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)。
类似聚会中经常玩的猜数字的游戏:
写下一个数字,给定范围后,参与者不断提供范围内的数字用来不断缩小范围,直至缩小至到达临界点游戏结束。
- 它的基本思想是,将n个元素分成个数大致相同的两半,取a[n/2]与欲查找的x作比较:
二分查找的代码实现:(假设在升序的数组里查找)
/** 递归写法 * * @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工具类
-
-
返回数组:
asList()
public static <T> List<T> asList(T... a) { return new ArrayList<>(a); }
-
填充数组:
fill()
public static void fill(int[] a, int val) { for (int i = 0, len = a.length; i < len; i++) a[i] = val; }
-
复制数组:
-
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; }
-
-
判断相等:
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; }
-
-
排序:
sort()
-
对数组进行升序排序,排序后 ,数组中存放的是排序后的结果
public static void sort(int[] a) { DualPivotQuicksort.sort(a, 0, a.length - 1, null, 0, 0); }
这个排序就厉害了,双基准快排算法,挖个坑,下次填哈哈
-
-
二分搜索:
binarySearch()
对排序好的数组,采用二分查找的方式查找某个元素,可以在整个数组中查找,也可以在某个范围内查找:下面详解
-
-
在Arrays中有二分搜索的现成调用
- 在Arrays中有对二分搜索的许多重载,详见
- 此处看int类型的源码
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中有三种移位运算符
<< : 左移运算符,num << 1,相当于num乘以2
>> : 右移运算符,num >> 1,相当于num除以2
>>> : 无符号右移,忽略符号位,空位都以0补齐
-
解答:
-
个人解法:
- 除了最原始的遍历求解外,此次增加了用二分搜索定位进行遍历
- 实现思路:
- 用二分查找找出目标元素出现的索引–>
i
- 并将
i
赋值给forward
和backward
对数组进行前后遍历 - 存在则
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;
}
}
嗯😂真香哈哈