文章目录
1、题目描述2、解题思路2.1 暴力遍历2.2 二分查找2.3 插值查找2.4 斐波那契查找
3、解题代码3.1 暴力遍历3.2 二分查找3.3 插值查找3.4 斐波那契查找
4、解题心得
1、题目描述
【JZ37】统计一个数字在排序数组中出现的次数。 知识点:数组,查找算法 难度:☆
2、解题思路
本题其实就是考察查找算法,常用的查找算法有:暴力遍历、二分查找、插值查找和斐波那契查找。 设数组为array,目标数字为k。
2.1 暴力遍历
因为数组是有序的,所以我只需要一个个去遍历即可,比较简单。需要注意的是,遍历找到相同的数字时,因为是有序数组,后面可能跟着若干个一样的数字,因此,统计好相同数字的个数后要结束算法,后面再遍历下去没有意义。
2.2 二分查找
二分查找的思路就是不断缩减待查询的数组长度。假设数组长度为10,那么,一开始的左边界就是0,右边界是9。中间索引为(0+9)/2,为4,若array[4]<k,说明k在数组的右半截,我们就把右半部分当成新数组,就不去考虑左边的部分,节省了一半的计算量。 以此类推,不断缩减数组范围,直到array[4]==k,就找到了。如果缩减到数组长度为0都找不到,则不存在目标数字。
2.3 插值查找
插值查找是在二分查找的基础上优化的,与二分查找的不同之处在于中间值的计算方式: 相比较于二分查找,插值查找能够更快速接近目标值。
2.4 斐波那契查找
斐波那契查找和上面两个查找的不同之处也在于mid值的计算,斐波那契数列的特点是,第一个元素和第二个元素都为1,以后每个元素都等于前两个元素之和,比如:{1,1,2,3,5,8,13,21,...}。 那如何根据斐波那契数列来计算mid呢? 假设array的长度为10,那么,我们要把中间某个位置设为mid,然后分为mid前部分和mid后部分。 首先把array长度扩充为最接近的斐波那契数列元素,现在长度为10,斐波那契数列中,比它大且最接近的,只能是13。array的索引是0到9,那么,拓展的10-12的值从索引9的值复制。 根据斐波那契数列的特点,13=8+5,所以把array分成前8个和后5个两部分,而这个mid就是前部分的最后一个元素(或者后部分的第一个元素),即所谓的黄金分割点。 找到mid后,其他计算和前面的二分和插值是一样的。 因为索引是从0开始的,所以长度为F[k]的数组,起索引范围为0到F[k]-1。 由图可知,mid = low + F[k-1]-1。 也就是说,mid的取值,和两个值有关,low和F[k-1],low也就是左边界,F[k-1]为F[k]的前一个值。 在计算mid之前,我们需要把要用到的斐波那契数列计算出来。 为了方便起见,我们生成的斐波那契数列元素个数和array元素个数一致即可。
3、解题代码
3.1 暴力遍历
package pers
.klb
.jzoffer
.medium
;
public class NumberOfK {
public int GetNumberOfK(int[] array
, int k
) {
int count
= 0;
for (int i
= 0; i
< array
.length
; i
++) {
if (array
[i
] == k
) {
int j
= i
;
while (j
< array
.length
&& array
[j
] == k
) {
count
++;
j
++;
}
break;
}
}
return count
;
}
}
时间复杂度:O(N) 空间复杂度:O(1)
3.2 二分查找
package pers
.klb
.jzoffer
.medium
;
public class NumberOfK {
public int GetNumberOfK(int[] array
, int k
) {
int count
= 0;
int leftIndex
= 0;
int rightIndex
= array
.length
- 1;
int mid
;
int index
= -1;
while (leftIndex
<= rightIndex
) {
mid
= midOf(leftIndex
, rightIndex
);
if (array
[mid
] < k
) {
leftIndex
= mid
+ 1;
} else if (k
< array
[mid
]) {
rightIndex
= mid
- 1;
} else {
index
= mid
;
break;
}
}
if (index
!= -1) {
count
= 1;
for (int i
= index
- 1; i
>= 0; i
--) {
if (array
[i
] == k
) {
count
++;
}
}
for (int j
= index
+ 1; j
< array
.length
; j
++) {
if (array
[j
] == k
) {
count
++;
}
}
}
return count
;
}
private int midOf(int left
, int right
) {
return (left
+ right
) / 2;
}
}
时间复杂度:O(logN) 空间复杂度:O(1)
3.3 插值查找
package pers
.klb
.jzoffer
.medium
;
public class NumberOfK {
public int GetNumberOfK(int[] array
, int k
) {
int count
= 0;
int leftIndex
= 0;
int rightIndex
= array
.length
- 1;
int mid
;
int index
= -1;
while (leftIndex
<= rightIndex
) {
mid
= midOf(array
, leftIndex
, rightIndex
, k
);
if (array
[mid
] < k
) {
leftIndex
= mid
+ 1;
} else if (k
< array
[mid
]) {
rightIndex
= mid
- 1;
} else {
index
= mid
;
break;
}
}
if (index
!= -1) {
count
= 1;
for (int i
= index
- 1; i
>= 0; i
--) {
if (array
[i
] == k
) {
count
++;
}
}
for (int j
= index
+ 1; j
< array
.length
; j
++) {
if (array
[j
] == k
) {
count
++;
}
}
}
return count
;
}
private int midOf(int[] arr
, int left
, int right
, int key
) {
if (arr
[left
] == arr
[right
]) {
return left
;
} else {
return left
+ ((key
- arr
[left
]) / (arr
[right
] - arr
[left
])) * (right
- left
);
}
}
}
时间复杂度:O(loglogN) 空间复杂度:O(1)
3.4 斐波那契查找
package pers
.klb
.jzoffer
.medium
;
import java
.util
.ArrayList
;
import java
.util
.Arrays
;
import java
.util
.List
;
public class NumberOfK {
public int GetNumberOfK(int[] array
, int k
) {
int count
= 0;
if (array
.length
== 1) {
if (array
[0] == k
) count
= 1;
} else {
int low
= 0;
int high
= array
.length
- 1;
int mid
;
int index
= -1;
List
<Integer> f
= fib(array
.length
);
int s
= f
.size() - 1;
int[] arr1
= Arrays
.copyOf(array
, f
.get(s
));
for (int i
= array
.length
; i
< s
; i
++) {
arr1
[i
] = array
[array
.length
- 1];
}
while (low
<= high
) {
mid
= midOf(low
, f
.get(s
- 1));
if (arr1
[mid
] < k
) {
s
= s
- 2;
low
= mid
+ 1;
} else if (k
< arr1
[mid
]) {
s
= s
- 1;
high
= mid
- 1;
} else {
if (mid
<= high
) {
index
= mid
;
} else {
index
= high
;
}
break;
}
}
if (index
!= -1) {
count
= 1;
for (int i
= index
- 1; i
>= 0; i
--) {
if (array
[i
] == k
) {
count
++;
}
}
for (int j
= index
+ 1; j
< array
.length
; j
++) {
if (array
[j
] == k
) {
count
++;
}
}
}
}
return count
;
}
private int midOf(int low
, int f
) {
return low
+ f
- 1;
}
private List
<Integer> fib(int arrLength
) {
List
<Integer> f
= new ArrayList<Integer>();
f
.add(0, 1);
for (int i
= 1; f
.get(f
.size() - 1) < arrLength
; i
++) {
if (i
== 1) {
f
.add(i
, 1);
} else {
f
.add(i
, f
.get(i
- 1) + f
.get(i
- 2));
}
}
return f
;
}
}
4、解题心得
暴力遍历是每个人都能想得到的,如果在笔试的时候,使用二分查找既可以快速求解,难度也不会太高。如果是面试,能实现插值查找就最好了,斐波那契查找能说出原理也是OK的。但是不建议在笔试的时候用斐波那契查找,第一是比较复杂,第二点性能相比插值查找相差不远。