剑指offer——旋转数组的最小数字

目录

  • 1. 题目描述
  • 2. 分析思路
    • 2.1 示例分析
  • 3. 更完美的做法

1. 题目描述

  • 把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如数组{3.4,5,1.2}为{1.2,3,4,5}的一个旋转,该数组的最小值为 1。

2. 分析思路

  • 这道题最直观的解法并不难,从头到尾遍历数组一次,我们就能找出最小的元素。
  • 这种思路的时间复杂度显然是O(n)。但是这个思路没有利用输入的旋转数组的特性,肯定达不到面试官的要求。
  • 我们注意到旋转之后的数组实际上可以划分为两个排序的子数组,而且前面的子数组的元素都大于或者等于后面子数组的元素。
  • 我们还注意到最小的元素刚好是这两个子数组的分界线。
  • 在排序的数组中我们可以用二分查找法实现 O(logn)的查找。
  • 本题给出的数组在一定程度上是排序的,因此我们可以试着用二分查找法的思路来寻找这个最小的元素。
  • 和二分查找法一样,我们用两个指针分别指向数组的第一个元素和最后一个元素。
  • 按照题目中旋转的规则,第一个元素应该是大于或者等于最后一个元素的(这其实不完全对,还有特例,后面再加以讨论)。
  • 接着我们可以找到数组中间的元素。
  • 如果该中间元素位于前面的递增子数组,那么它应该大于或者等于第一个指针指向的元素。
  • 此时数组中最小的元素应该位于该中间元素的后面。
  • 我们可以把第一个指针指向该中间元素,这样可以缩小寻找的范围。移动之后的第一个指针仍然位于前面的递增子数组之中。
  • 同样,如果中间元素位于后面的递增子数组,那么它应该小于或者等于第二个指针指向的元素。
  • 此时该数组中最小的元素应该位于该中间元素的前面。
  • 我们可以把第二个指针指向该中间元素,这样也可以缩小寻找的范围。
  • 移动之后的第二个指针仍然位于后面的递增子数组之中。
  • 不管是移动第一个指针还是第二个指针,查找范围都会缩小到原来的一半。接下来我们再用更新之后的两个指针,重复做新一轮的查找
  • 按照上述的思路,第一个指针总是指向前面递增数组的元素,而第一个指针总是指向后面递增数组的元素。
  • 最终第一个指针将指向前面子数组的最后一个元素,而第二个指针会指向后面子数组的第一个元素。
  • 也就是它们最终会指向两个相邻的元素,而第二个指针指向的刚好是最小的元素。这就是循环结束的条件。

2.1 示例分析

  • 以前面的数组{3.4,5.1.2}为例,我们先把第一个指针指向第0个元素,把第二个指针指向第4个元素(如图2.10(a)所示)。
  • 位于两个指针中间(在数组中的下标是2)的数字是5,它大于第一个指针指向的数字。
  • 因此中间数字5一定位于第一个递增子数组中,并且最小的数字一定位于它的后面。
  • 因此我们可以移动第一个指针让它指向数组的中间(图2.10(b)所示)。

在这里插入图片描述

  • :旋转数组中包含两个递增排序的子数组,有阴影背景的是第二个子数组。(a)把P1指向数组的第一个数字,P2指向数组的最后一个数字。由于P1和P2中间的数字5大于P1指向的数字,中间的数字在第一个子数组中。下一步把P1指向中间的数字。(b)P1和P2中间的数字1小于 P2指向的数字,中间的数字在第二个子数组中。下一步把P2指向中间的数字。©P1和P2指向两个相邻的数字,则P2指向的是数组中的最小数字。
  • 此时两个指针的距离是1,表明第一个指针已经指向了第一个递增子数组的末尾,而第二个指针指向第二个递增子数组的开头。
  • 第二个子数组的第一个数字就是最小的数字,因此第二个指针指向的数字就是我们查找的结果。
  • 基于这个思路我们可以写出如下代码:
#define ROW 5
#include <stdio.h>int search(int arr[ROW], int len)
{if (arr == NULL || len <= 0){return;}int p1 = 0;int p2 = len - 1;int mid = p1;while (arr[p1] >= arr[p2]){if (p2 - p1 == 1){mid = p2;break;}mid = (p1 + p2) / 2;if (arr[mid] >= arr[p1]){p1 = mid;}else if (arr[mid] <= arr[p2]){p2 = mid;}}return arr[mid];}int main()
{int arr[ROW] = { 3, 4, 5, 1, 2 };printf("%d\n", search(arr, ROW));return 0;
}
  • 运行结果为:

在这里插入图片描述

  • 前面我们提到在旋转数组中,由于是把递增排序数组前面的若干个数字搬到数组的后面,因此第一个数字总是大于或者等于最后一个数字。
  • 但按照定义还有一个特例:如果把排序数组的前面的0个元素搬到最后面,即排序数组本身,这仍然是数组的一个旋转,我们的代码需要支持这种情况。
  • 此时,数组中的第一个数字就是最小的数字,可以直接返回。
  • 这就是在上面的代码中,把 indexMid 初始化为 index1的原因。一旦发现数组中第一个数字小于最后一个数字,表明该数组是排序的,就可以直接返回第一个数字了。

3. 更完美的做法

  • 上述代码是否就完美了呢?面试官会告诉我们其实不然。
  • 他将提示我们再仔细分析下标为indexl和 index2(indexl和index2 分别和图中 P1和P2 相对应)的两个数相同的情况。
  • 在前面的代码中,当这两个数相同,并且它们中间的数字(即 indexMid 指向的数字)也相同时,我们把 indexMid赋值给了 index1,也就是认为此时最小的数字位于中间数字的后面。
  • 是不是一定这样?

在这里插入图片描述

  • :在这两个数组中,第一个数字、最后一个数字和中间数字都是1,我们无法确定中间的数字 1 属于第一个递增子数组还是属于第二个递增子数组。第二个子数组用灰色背景表示。
  • 在这两种情况中,第一个指针和第二个指针指向的数字都是1,并且两个指针中间的数字也是1,这3个数字相同。
  • 在第一种情况中,中间数字(下标为 2)位于后面的子数组;在第二种情况中,中间数字(下标为2)位于
  • 前面的子数组中。因此,当两个指针指向的数字及它们中间的数字三者相同的时候,我们无法判断中间的数字是位于前面的子数组中还是后面的子数组中,也就无法移动两个指针来缩小查找的范围。
  • 此时,我们不得不采用顺序查找的方法。
  • 把问题分析清楚了之后,我们就可以把代码修改为:
#define ROW 5
#include <stdio.h>int search_(int arr[ROW], int p1, int p2)
{int min = arr[p1];for (int i = p1 + 1; i <= p2; i++){if (arr[i] < min){min = arr[i];}}return min;
}int search(int arr[ROW], int len)
{if (arr == NULL || len <= 0){return;}int p1 = 0;int p2 = len - 1;int mid = p1;while (arr[p1] >= arr[p2]){if (p2 - p1 == 1){mid = p2;break;}mid = (p1 + p2) / 2;if (arr[p1] == arr[mid] && arr[mid] == arr[p2]){return search_(arr, p1, p2);}if (arr[mid] >= arr[p1]){p1 = mid;}else if (arr[mid] <= arr[p2]){p2 = mid;}}return arr[mid];
}int main()
{int arr[ROW] = { 1, 0, 1, 1, 1 };printf("%d\n", search(arr, ROW));return 0;
}

最后,
恭喜你又遥遥领先了别人!

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/682089.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

神经网络:卷积神经网络中的BatchNorm

一、BN介绍 1.原理 在机器学习中让输入的数据之间相关性越少越好&#xff0c;最好输入的每个样本都是均值为0方差为1。在输入神经网络之前可以对数据进行处理让数据消除共线性&#xff0c;但是这样的话输入层的激活层看到的是一个分布良好的数据&#xff0c;但是较深的激活层…

揭秘某电商公司最新面试流程

&#x1f3c3;‍♂️ 微信公众号: 朕在debugger© 版权: 本文由【朕在debugger】原创、需要转载请联系博主&#x1f4d5; 如果文章对您有所帮助&#xff0c;欢迎关注、点赞、转发和订阅专栏&#xff01; 记录近期某电商公司面试流程及问题&#xff0c;分为三面&#xff1a;…

Hive的相关概念——分区表、分桶表

目录 一、Hive分区表 1.1 分区表的概念 1.2 分区表的创建 1.3 分区表数据加载及查询 1.3.1 静态分区 1.3.2 动态分区 1.4 分区表的本质及使用 1.5 分区表的注意事项 1.6 多重分区表 二、Hive分桶表 2.1 分桶表的概念 2.2 分桶表的创建 2.3 分桶表的数据加载 2.4 …

【计算机网络】网际协议——互联网中的转发和编址

编址和转发是IP协议的重要组件 就像这个图所示&#xff0c;网络层有三个主要组件&#xff1a;IP协议&#xff0c;ICMP协议&#xff0c;路由选择协议IPV4 没有选项的时候是20字节 版本&#xff08;号&#xff09;&#xff1a;4比特&#xff1a;规定了IP协议是4还是6首部长度&am…

作业2.14

指针练习 1、选择题 1.1、若有下面的变量定义&#xff0c;以下语句中合法的是&#xff08;A&#xff09;。 int i&#xff0c;a[10]&#xff0c;*p&#xff1b; A&#xff09; pa2; B&#xff09; pa[5]; C&#xff09; pa[2]2; D&#xff09; p&(i2); 1.2、…

Servlet JSP-Eclipse安装配置Maven插件

Maven 是一款比较常用的 Java 开发拓展包&#xff0c;它相当于一个全自动 jar 包管理器&#xff0c;会导入用户开发时需要使用的相应 jar 包。使用 Maven 开发 Java 程序&#xff0c;可以极大提升开发者的开发效率。下面我就跟大家介绍一下如何在 Eclipse 里安装和配置 Maven 插…

医疗相关名词,医疗名词整理

1.系统类&#xff1a; HIS Hospital Information System&#xff0c;医院信息系统&#xff0c;在国际学术界已公认为新兴的医学信息学(Medical Informatics)的重要分支。美国该领域的著名教授Morris.Collen于1988年曾著文为医院信息系统下了如下定义&#xff1a;利用电子计算…

【安装指南】markdown神器之Typora下载、安装与无限使用详细教程

&#x1f33c;一、概述 Typora是一款轻量级的Markdown编辑器&#xff0c;它提供了简洁的界面和直观的操作方式&#xff0c;专注于让用户更加专注于写作。Typora支持实时预览功能&#xff0c;用户在编辑Markdown文档时可以即时看到最终的样式效果&#xff0c;这有助于提高写作效…

Golang快速入门到实践学习笔记

Go学习笔记 1.基础 Go程序设计的一些规则 Go之所以会那么简洁&#xff0c;是因为它有一些默认的行为&#xff1a; 大写字母开头的变量是可导出的&#xff0c;也就是其它包可以读取 的&#xff0c;是公用变量&#xff1b;小写字母开头的就是不可导出的&#xff0c;是私有变量…

寒假学习记录11:grid布局

1. display:grid 2. grid-template-columns: 100px 100px 100px //指定每列的宽度 grid-template-rows: 100px 100px 100px //指定每行的宽度 3. column-gap: 24px //列间距 row-gap: 24px //行间距 gap: 24px //都设置 4.grid-template-areas用法 <!DO…

计算机组成原理 2 数据表示

机器数 研究机器内的数据表示&#xff0c;目的在于组织数据&#xff0c;方便计算机硬件直接使用。 需要考虑&#xff1a; 支持的数据类型&#xff1b; 能表示的数据精度&#xff1b; 是否有利于软件的移植 能表示的数据范围&#xff1b; 存储和处理的代价&#xff1b; ... 真值…

PHP开发日志 ━━ 深入理解三元操作与一般条件语句的不同

概况 三元运算符的功能与“if…else”流程语句一致。 在一般情况下&#xff0c;三元操作替换if条件语句可以精简代码&#xff0c;并且更为直观&#xff0c;但是在下面的情况中使用三元操作将会返回警告。 借图&#xff1a; 案例 比如原代码&#xff1a; class classA{publ…

DS:树及二叉树的相关概念

创作不易&#xff0c;兄弟们来波三连吧&#xff01;&#xff01; 一、树的概念及结构 1.1 树的概念 树是一种非线性的数据结构&#xff0c;它是由n&#xff08;n>0&#xff09;个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树&#xff0c…

Java并发基础:ConcurrentLinkedDeque全面解析!

内容概要 ConcurrentLinkedDeque类提供了线程安全的双端队列操作&#xff0c;支持高效的并发访问&#xff0c;因此在多线程环境下&#xff0c;可以放心地在队列的两端添加或移除元素&#xff0c;而不用担心数据的一致性问题。同时&#xff0c;它的内部实现采用了无锁算法&…

概率论-随机变量

更多AI技术入门知识与工具使用请看下面链接&#xff1a; https://student-api.iyincaishijiao.com/t/iNSVmUE8/

二叉树-------前,中,后序遍历 + 前,中,后序查找+删除节点 (java详解)

目录 提要&#xff1a; 创建一个简单的二叉树&#xff1a; 二叉树的前中后序遍历&#xff1a; 二叉树的前序遍历&#xff1a; 二叉树的中序遍历&#xff1a; 二叉树的后续遍历&#xff1a; 小结&#xff1a; 二叉树的前中后续查找&#xff1a; 二叉树的前序查找&#…

MySQL表的增删查改(基础)

新增&#xff08;Create) 1.全列插入 全列单行插入 insert into 表名 values(值&#xff0c;值……)&#xff1b; 也可以全列且多行插入 insert into 表名 values (值&#xff0c;值……)&#xff0c;(值&#xff0c;值……)……&#xff1b; 2.指定列插入 insert into 表…

【JAVA WEB】JavaScript--函数 作用域 对象

目录 函数 语法格式 示例 定义没有参数列表&#xff0c;也没有返回值的一个函数 定义一个有参数列表 &#xff0c;有返回值的函数 关于参数个数 函数表达式 作用域 作用域链 对象 基本概念 创建对象 1.使用 字面量 创建对象 2.使用new Object()创建对象 3.使…

【教程】MySQL数据库学习笔记(二)——数据类型(持续更新)

写在前面&#xff1a; 如果文章对你有帮助&#xff0c;记得点赞关注加收藏一波&#xff0c;利于以后需要的时候复习&#xff0c;多谢支持&#xff01; 【MySQL数据库学习】系列文章 第一章 《认识与环境搭建》 第二章 《数据类型》 文章目录 【MySQL数据库学习】系列文章一、整…

Ps:创建联系表

Ps菜单&#xff1a;文件/自动/联系表 II Automate/Contact sheet II Photoshop 的“联系表 II” Contact Sheet II命令为快速生成图像集合的预览和打印目录提供了一种高效的方法。 此命令可以通过自动化过程读取指定的图像文件&#xff0c;然后根据用户定义的参数&#xff08;如…