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

示例 1:

输入: nums = [5,7,7,8,8,10], target = 8
输出:

示例 2:

输入: nums = [5,7,7,8,8,10], target = 6
输出:

限制:

0 <= 数组长度 <= 50000

排序数组中的搜索问题,首先想到 二分法 解决。

使用遍历数组再 count++ 的话,时间复杂度是 O(n)

但是使用二分法,使时间复杂度降低到 O(log n)

自己第一次提交时的代码:

class Solution {
public int search(int[] nums, int target) {
int count = 0;
for (int i = 0; i < nums.length; i++) {
if (nums[i] == target) {
count++;
}
}
return count;
}
}

使用非常粗暴的循环求个数,时间非常慢。看题解知道:

排序数组中的搜索问题,首先想到 二分法 解决。

二分查找法(时间复杂度:O(log n)):

二分查找要求线性表必须顺序存储,并且元素是有序排列

二分查找(折半查找)的基本思想:减少查找序列的长度,分而治之地进行关键字的查找。他的查找过程是:先确定待查找记录的所在的范围,然后逐渐缩小查找的范围,直至找到该记录为止(也可能查找失败)。

int binarySerch(vector<int> & nums,int low,int hi,int target)
{
int len = nums.size();
if(low<0 || low > len-1 || hi < 0 || hi > len-1 || 0 == len)
{
return -1;
} while(low <= hi) //注意这里一定要加上=
{
int mid = low + (hi -low)/2;
if(target == nums[mid])
return mid;
else if(target > nums[mid])
low = mid+1;
else
hi = mid-1;
} return -1; }

Notes:

线性表与非线性表的区别:

(1) 非线性表:二维数组、多维数组、广义表、树、图

(2) 线性表:

① 顺序存储结构(顺序表):数组、队列、栈

② 链式存储结构(链表):链表

顺序存储结构和链式存储结构的区别:

(1) 链式存储结构的内存地址不一定连续,但顺序存储结构的内存地址一定连续

(2)链式存储适用于在较频繁的插入、删除、更新元素时,而顺序存储结构适用于频繁查询时使用

顺序存储结构和链式存储结构的优缺点:

(1)空间上:顺序比链式节约空间。因为链式结构每一个节点都有一个指针存储域。

(2)存储操作上:顺序支持随机存取,方便操作

(3)插入和删除上:链式比顺序方便。因为顺序表的插入要执行更大的空间复杂度,包括一个从表头索引以及索引后的元素后移。

总结:

查询频繁 + 存储量固定 = 顺序结构

插入删除频繁 + 存储量不固定 = 链式结构

二分法解法1:

将已排序的数组记左右边界的索引为 left / right。本题要求统计数字 target 的出现次数,可以转化为:使用二分法找出有序数组中出现该 target 的左边界和右边界,可以得出 target 的数量为 right - left - 1

算法解析:

初始化:左边界 i = 0,右边界 j = nums.length - 1。

循环二分:

  1. 计算中点: mid = i + (j - i) / 2;     ----> 防止数值溢出
  2. 若 nums[mid] < target,则 target 在闭区间 [mid + 1, right]中,执行 i = m + 1;
  3. 若 nums[mid] > target,则 target 在闭区间 [left, mid - 1]中,执行 j = m - 1;
  4. 若 nums[mid] = target,则 left(左边界) 在闭区间 [i, mid - 1] 中,right(右边界) 在闭区间  [mid + 1, j] 中。需要分一下两种情况:
    1. 若查找 left,执行 i = m - 1,当 nums[mid] != target 时跳出,此时 j 指向 left;
    2. 若查找 right,执行 j = m + 1,当 nums[mid] != target 时跳出,此时 i 指向 right;

返回值:找出 left 和 right 之后,最终返回 right - left - 1

代码

class Solution {
public int search(int[] nums, int target) {
int i = 0;
int j = nums.length - 1; while (i <= j) {
int mid = i + (j - i) / 2;
if (nums[mid] <= target) {
i = mid + 1;
} else if (nums[mid] > target) {
j = mid - 1;
}
} int right = i;
if (j >= 0 && nums[j] != target) {
return 0;
}
i = 0;
j = nums.length - 1; while(i <= j) {
int m = (i + j) / 2;
if(nums[m] < target) {
i = m + 1;
} else {
j = m - 1;
}
} int left = j; return right - left - 1;
}
}

提交结果

效率优化:

看上面代码可以看出运用了两次二分法,故可以将二分法封装成一个方法进行调用,此时应该考虑的就是左边界和右边界的查找怎么通过一个循环来找出。

此时有两种方法:

① 将二分查找右边界 right 的代码封装起来,找左边界的时候只需找数组中比 target 小且最邻近的数字的右边界,最后将两结果相减并返回即可

② 将二分查找左边界 left 的代码封装起来,找右边界的时候只需找数组中比 target 大且最邻近的数字的左边界,最后将两结果相减并返回即可

代码:

class Solution {
private int rightLoc(int[] nums, int tar) {
int i = 0;
int j = nums.length - 1;
while(i <= j) {
int m = i + (j - i) / 2;
if(nums[m] <= tar) {
i = m + 1;
} else {
j = m - 1;
}
}
return i;
}
public int search(int[] nums, int target) {
return rightLoc(nums, target) - rightLoc(nums, target - 1);
}
}

本质上看, helper() 函数旨在查找数字 tar 在数组 nums 中的 插入点 ,且若数组中存在值相同的元素,则插入到这些元素的右边。

提交结果:

二分法解法2:

因为数组是已经排序好的数组,故如果直接找出来 nums[mid] = target,则通过左右边界点的值和 target 做判断,当:

左边界点 < target 时,左边界点右移

右边界点 > target 时,右边界点左移

移动左右边界点,直到左右边界点对应的值相等,最后返回 right - left + 1 即可。

代码:

class Solution {
public int search(int[] nums, int target) {
int low = 0;
int high = nums.length - 1; while (low <= high) {
int mid = low + (high - low) / 2;
if(nums[mid] < target) {
low = mid + 1;
} else if (nums[mid] > target) {
high = mid - 1;
} else {
if (nums[low] == nums[high]) {
return high - low + 1;
}
if (nums[low] < target) {
low++;
}
if (nums[high] > target) {
high--;
}
} }
return 0;
}
}

提交结果:

 

4 剑指Offer53-在排序数组中查找数字的更多相关文章

  1. [剑指Offer]53-在排序数组中查找数字(二分查找)

    题目一 数字在排序数组中出现的个数 题目描述 统计一个数字在排序数组中出现的次数. 解决思路 写两个二分查找分别找第一个和最后一个该数字,然后可直接出计算有几个该数字.时间复杂度为O(logn). 这 ...

  2. [简单-剑指 Offer 53 - I. 在排序数组中查找数字 I]

    [简单-剑指 Offer 53 - I. 在排序数组中查找数字 I] 统计一个数字在排序数组中出现的次数. 示例 1: 输入: nums = [5,7,7,8,8,10], target = 8 输出 ...

  3. 剑指 Offer 53 - I. 在排序数组中查找数字 I + 二分法

    剑指 Offer 53 - I. 在排序数组中查找数字 I Offer_53_1 题目描述 方法一:使用HashMap package com.walegarrett.offer; /** * @Au ...

  4. 力扣 - 剑指 Offer 53 - I. 在排序数组中查找数字 I

    题目 剑指 Offer 53 - I. 在排序数组中查找数字 I 思路1 一般来说,首先想到的是使用一个变量,从头开始遍历整个数组,记录target数组出现的次数,但是这样的时间复杂度是O(n),还是 ...

  5. 每日一题 - 剑指 Offer 53 - I. 在排序数组中查找数字 I

    题目信息 时间: 2019-07-04 题目链接:Leetcode tag:二分查找 哈希表 难易程度:简单 题目描述: 统计一个数字在排序数组中出现的次数. 示例1: 输入: nums = [5,7 ...

  6. [LeetCode]面试题53 - I. 在排序数组中查找数字 I(二分);面试题53 - II. 0~n-1中缺失的数字(二分)

    ##面试题53 - I. 在排序数组中查找数字 I ###题目 统计一个数字在排序数组中出现的次数. 示例 1: 输入: nums = [5,7,7,8,8,10], target = 8 输出: 2 ...

  7. 【Java实现】剑指offer53.1——在排序数组中查找数字(LeetCode34:在排序数组中查找元素的起始位置)

    序数组中查找元素的起始位置):思路分享 <剑指offer>题目和LeetCode主站本质是一样的,想要找到target数目,也需要找到左右边界 题目解析: 在一个排序数组中,找到targe ...

  8. 剑指offer——56在排序数组中查找数字

    题目描述 统计一个数字在排序数组中出现的次数.   题解: 使用二分法找到数k然后向前找到第一个k,向后找到最后一个k,即可知道有几个k了 但一旦n个数都是k时,这个方法跟从头遍历没区别,都是O(N) ...

  9. 剑指offer-面试题53_1-在排序数组中查找数字-二分查找

    /* 题目: 统计一个数字在排序数组中出现的次数. */ /* 思路: 1.从前往后遍历,时间复杂度O(n). 2.二分查找到目标数字target,向前向后遍历,时间复杂度O(n). 3.利用二分法, ...

  10. 剑指 Offer 53 - I. 在排序数组中查找数字 I

    题目描述 统计一个数字在排序数组中出现的次数. 示例1: 输入: nums = [5,7,7,8,8,10], target = 8 输出: 2 示例2: 输入: nums = [5,7,7,8,8, ...

随机推荐

  1. 由ASP.NET Core WebApi添加Swagger报错引发的探究

    缘起 在使用ASP.NET Core进行WebApi项目开发的时候,相信很多人都会使用Swagger作为接口文档呈现工具.相信大家也用过或者了解过Swagger,这里咱们就不过多的介绍了.本篇文章记录 ...

  2. Docker:docker部署PXC-5.7.21(mysql5.7.21)集群搭建负载均衡实现双机热部署方案

    单节点数据库弊端 大型互联网程序用户群体庞大,所以架构必须要特殊设计 单节点的数据库无法满足性能上的要求 单节点的数据库没有冗余设计,无法满足高可用 推荐Mysql集群部署方案 PXC (Percon ...

  3. Python管道进行数据的吞吐处理

    import multiprocessing import random import time import datetime import struct import os import getF ...

  4. 在Xshell中文件内容显示乱码

    1.修改系统语言 支持中文 echo $LANG    查看系统语言  默认 en_US.UFT_8 vim /etc/locale.conf    修改配置文件 将LANG的值改为 zh_CN.UT ...

  5. 在Java中如何高效判断数组中是否包含某个元素

    如何检查一个数组(无序)是否包含一个特定的值?这是一个在Java中经常用到的并且非常有用的操作.同时,这个问题在Stack Overflow中也是一个非常热门的问题.在投票比较高的几个答案中给出了几种 ...

  6. Linux alias 或者 unalias 设置别名

    设置别名 查看别名:alias 设置别名: 临时设置: alias show='ls -al' 上述设置方法存在一个问题,即设置的命令别名只针对当前回话有效,一旦连接断开并重连之前设置的别名别不在有效 ...

  7. vim下出现^M怎么解决

    将window下的文本文件上传到linux上,在读取数据文件时,在每一行数据后会出现^M字符.   为什么会出现这种情况呢: 因为windows.linux.os系统的换行符标准不同: 先了解下概念, ...

  8. 第12次抽考(GUI)

    1. package week4; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JTextFiel ...

  9. Kotlin Coroutine(协程): 三、了解协程

    @ 目录 前言 一.协程上下文 1.调度器 2.给协程起名 3.局部变量 二.启动模式 CoroutineStart 三.异常处理 1.异常测试 2.CoroutineExceptionHandler ...

  10. git常用命令自己梳理总结

    一.新建代码库 # git-init - 创建一个空的 Git 存储库或重新初始化一个现有的存储库 $ git init # 在本地新建一个repo,进入一个项目目录,执行git init,会初始化一 ...