剑指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;但是较深的激活层…

C语言——oj刷题——字符串左旋

问题&#xff1a; 实现一个函数&#xff0c;可以左旋字符串中的k个字符。 例如&#xff1a; ABCD左旋一个字符得到BCDA ABCD左旋两个字符得到CDAB 实现&#xff1a; 当我们谈到字符串左旋时&#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 …

Android 11.0 framework中禁止某个无源码app使用分屏功能

1.前言 在11.0的系统rom定制化开发中,在app中多窗口模式中,分屏模式也是其中的一种模式,可以通过app内部设置分屏的属性,然后实现 app启动的时候分屏功能,在无源码的app中就没办法更改。就需要在安装的时候修改这个属性,接下来实现这个功能 2.framework中禁止某个无源码…

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

编址和转发是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;这有助于提高写作效…

P1506 拯救oibh总部---洪水填充问题(bfs解)

思路&#xff1a;因为中间部分如果是没被覆盖的&#xff0c;就是周围一定有****&#xff0c;这说明这个点一定与边界连不上&#xff0c;反过来想&#xff0c;从两边向中间延申&#xff08;如果是0的话&#xff09;&#xff0c;这样被填充的部分就是被淹没的部分 // Problem: …

ACP科普:敏捷开发之kanban

Q1: Kanban是什么&#xff1f; A1:敏捷开发中的Kanban是一种项目管理方法&#xff0c;其核心理念是通过可视化管理来提高生产效率和任务交付速度。Kanban来自日本&#xff0c;意为“看板”&#xff0c;最初是由丰田汽车公司引入生产线上的生产控制系统&#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…

git错误整理

remote: Support for password authentication was removed on August 13, 2021. 参考&#xff1a;这篇即可 GnuTLS recv error (-110): The TLS connection was non-properly terminated. 执行下面的指令&#xff1a; git config --global http.sslVerify false

14.如果new一个箭头函数的会怎么样

箭头函数是ES6中的提出来的&#xff0c;它没有prototype&#xff0c;也没有自己的this指向&#xff0c;更不可以使用arguments参数&#xff0c;所以不能New一个箭头函数。 new操作符的实现步骤如下&#xff1a; 创建一个对象将构造函数的作用域赋给新对象&#xff08;也就是将…

蓝桥杯:日期统计讲解(C++)

日期统计 本题来自于&#xff1a;2023年十四届省赛大学B组真题 主要考察&#xff1a;暴力。 代码放在下面&#xff0c;代码中重要的细节全都写了注释&#xff0c;非常清晰明了&#xff1a; #include <bits/stdc.h> //万能头文件 using namespace std;int main() {…

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

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

【广度优先遍历】310. 最小高度树

310. 最小高度树 解题思路 首先&#xff0c;在方法的开头&#xff0c;创建了一个空的列表res&#xff0c;用于存储最小高度树的根节点。 接着&#xff0c;通过条件判断&#xff0c;检查如果图中只有一个节点&#xff08;即n 1&#xff09;&#xff0c;则将该节点添加到结果列…

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

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