数据结构与算法--数字在排序数组中出现次数

数字在排序数组中出现次数
  • 题目:统计一个数字在一个排序数组中出现的次数。例如,输入数组{1,2,3,3,3,3,3,4,5} 和数字3,由于3 在数组中出现的次数是5,因此返回5
简单方案一
  • 既然输入的数组是有序的,那么最简单的方式是一次遍历,统计出需要的数字出现的个数,时间复杂度是O(n),实现方法如下:
**
 * 查询有序数组中数字k 出现的次数
 *
 * @author liaojiamin
 * @Date:Created in 16:08 2021/6/24
 */
public class GetNumberOfK {

    public static void main(String[] args) {
        int[] array = new int[]{1, 2, 3, 4, 5, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 9, 33, 44, 55, 56, 56, 56, 56, 56, 57};
        System.out.println(countK(array, 0));
    }

    /**
     * 方法一,遍历统计 O(n)
     */
    public static Integer countK(int[] array, int k) {
        if (array == null || array.length <= 0) {
            return -1;
        }
        int count = 0;
        for (int i = 0; i < array.length; i++) {
            if (array[i] == k) {
                count++;
            }
        }
        return count;
    }
 }
方法二二分查找
  • 有序数组,而且是查询,那么时间效率最高的就是二分查找了,分析流程如下:
    • 如上案例中二分查找找出其中一个3 的位置,标记为position
    • 此时可能前后都还存在3,那么从position向前遍历,查找之前的3,得到firstK
    • 从position向后遍历,查找之后的3,得到lastK
    • 那么k出现的次数n = lastK - firstK + 1
  • 如上分析有如下代码:
/**
 * 查询有序数组中数字k 出现的次数
 *
 * @author liaojiamin
 * @Date:Created in 16:08 2021/6/24
 */
public class GetNumberOfK {

    public static void main(String[] args) {
        int[] array = new int[]{1, 2, 3, 4, 5, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 9, 33, 44, 55, 56, 56, 56, 56, 56, 57};
        System.out.println(binarySearchK(array, 0));
    }
 

    /**
     * 方法二:二分查找找出k,得到k后,左右遍历k,直到找到,firstK和lastK
     * O(n)
     */
    public static Integer binarySearchK(int[] array, int k) {
        Integer positionK = binarySearch(array, 0, array.length - 1, k);
        if (positionK < 0) {
            return -1;
        }
        Integer firstK = positionK;
        Integer lastK = positionK;
        for (int i = positionK; i < array.length; i++) {
            if (array[i] == k) {
                lastK = i;
            }
        }
        for (int i = positionK; i >= 0; i--) {
            if (array[i] == k) {
                firstK = i;
            }
        }
        return lastK - firstK + 1;
    }

    public static Integer binarySearch(int[] array, int left, int right, int k) {
        if (array == null || left < 0 || right > array.length - 1 || left > right) {
            return -1;
        }
        int middle = (left + right) / 2;
        if (array[middle] == k) {
            return middle;
        }
        if (array[middle] > k) {
            return binarySearch(array, left, middle - 1, k);
        }
        if (array[middle] < k) {
            return binarySearch(array, middle + 1, right, k);
        }
        return -1;
    }
}
  • 如上算法因为k出现的次数可能是n次,那么我们向前向后遍历的次数可能就是n, 那么时间复杂度还是O(n),并没有达到优化的目的。此算法中实际主要消耗在如何确定重复数字的第一个k与最后一个k的位置上。
方案三纯二分查找
  • 方案二中既然可以通过二分查找找到其中一个3 ,那么我们是否能通过二分查找找到firstK和lastK

  • 分析如下:

    • 先查找第一个k, 标记位置为position
    • 当地position-1 个位置的值也是k的时候,我们认为k之前还有重复的数字k存在,依然用二分查找继续找 left ~ k-1 位置中的k
    • 持续以上步骤,直到position-1 位置不是k为止,我们得到firstK = position
    • 同理,如果position+1位置也是k,那么我们递归position+1 ~ right的数组中二分查找k,得到lastK
    • 如上我们用纯二分查找的方式找出了lastK,firstK
  • 经如上分析有如下代码:

/**
 * 查询有序数组中数字k 出现的次数
 *
 * @author liaojiamin
 * @Date:Created in 16:08 2021/6/24
 */
public class GetNumberOfK {

    public static void main(String[] args) {
        int[] array = new int[]{1, 2, 3, 4, 5, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 9, 33, 44, 55, 56, 56, 56, 56, 56, 57};
        System.out.println(binarySearchAllK(array, 0));
    }
 /**
     * 方法三,还是二分查找,分别找出firstK, lastK
     */
    public static Integer binarySearchAllK(int[] array, int k) {
        if (array == null || array.length <= 0) {
            return -1;
        }
        int firstK = binarySearchFirstK(array, 0, array.length - 1, k);
        int lastK = binarySearchLastK(array, 0, array.length - 1, k);
        if (firstK < 0 && lastK < 0) {
            return -1;
        }
        return lastK - firstK + 1;
    }

    /**
     * 二分查找第一个k
     */
    public static Integer binarySearchFirstK(int[] array, int left, int right, int k) {
        if (array == null || left < 0 || right > array.length - 1 || left > right) {
            return -1;
        }
        int middle = (left + right) / 2;
        if (array[middle] == k) {
            if (middle - 1 >= left && array[middle - 1] == k) {
                return binarySearchFirstK(array, left, middle - 1, k);
            } else {
                return middle;
            }
        }
        if (array[middle] < k) {
            return binarySearchFirstK(array, middle + 1, right, k);
        }
        if (array[middle] > k) {
            return binarySearchFirstK(array, left, middle - 1, k);
        }
        return -1;
    }

    /**
     * 二分查找最后一个k
     */
    public static Integer binarySearchLastK(int[] array, int left, int right, int k) {
        if (array == null || left < 0 || right > array.length - 1 || left > right) {
            return -1;
        }
        int middle = (left + right) / 2;
        if (array[middle] == k) {
            if (middle + 1 <= right && array[middle + 1] == k) {
                return binarySearchLastK(array, middle + 1, right, k);
            } else {
                return middle;
            }
        }
        if (array[middle] > k) {
            return binarySearchLastK(array, left, middle - 1, k);
        }
        if (array[middle] < k) {
            return binarySearchLastK(array, middle + 1, right, k);
        }
        return -1;
    }
}
  • 如上实现中firstK,lastK都是使用二分查找在数组中查找,并且二分查找的总量不会大于整个数组的量n,他们两次查询的时间复杂度都是O(logn),因此总的时间复杂度就是O(logn),达到了我们优化的目的。

上一篇:数据结构与算法–最小的k个数
下一篇:数据结构与算法–二叉树的深度问题

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值