【优选算法必刷100题】第023~24题(二分查找算法):寻找/搜索旋转排序数组中的最小值、点名(缺失的数字)

【优选算法必刷100题】第023~24题(二分查找算法):寻找/搜索旋转排序数组中的最小值、点名(缺失的数字)

🔥艾莉丝努力练剑:个人主页

专栏传送门:《C语言》《数据结构与算法》C/C++干货分享&学习过程记录Linux操作系统编程详解笔试/面试常见算法:从基础到进阶

⭐️为天地立心,为生民立命,为往圣继绝学,为万世开太平


🎬艾莉丝的简介:


🎬艾莉丝的算法专栏简介:


目录

​023  寻找/搜索旋转排序数组中的最小值

1.1  解法一:暴力查找

1.2  解法二:二分查找

1.2.1  算法思路

1.2.2  算法实现

1.2.3  尝试:选择A点呢?

1.3  博主手记

​024  点名(缺失的数字)

2.1  解法一:暴力遍历找结果

2.2  解法二:用哈希表解决

2.3  解法三:位运算

2.4  解法四:用(数学)高斯求和公式解决

2.5  解法五:二分查找(最优解)

2.5.1  算法思路

2.5.2  算法实现

2.6  五种方法总结

2.7  博主手记

结尾


​023  寻找/搜索旋转排序数组中的最小值

力扣链接:153. 寻找旋转排序数组中的最小值

力扣题解链接:二分查找模版解决【寻找旋转排序数组中的最小值】

题目描述:

1.1  解法一:暴力查找

关于暴力查找,只需遍历一遍数组,这里不再赘述。

1.2  解法二:二分查找

1.2.1  算法思路

题目中的数组规则如下图所示——

这个C点就是我们要求的点。

二分的本质:找到一个判断标准,使得查找区间能够一分为二——二段性。

通过图像我们可以发现,[A , B]区间内的点都是严格大于点的值的,C点的值是严格小于D点的值的。但是当[C , D]区间只有一个元素的时候,C点的值是可能等于D点的值的。

因此,初始化左右两个指针left,right。

然后根据mid的落点,我们可以这样划分下一次查询的区间——

(1)当mid在[A , B]区间的时候,也就是mid位置的值严格大于D点的值,下一次查询区间在[mid + 1 , right]上;
(2)当mid在[C , D]区间的时候,也就是mid位置的值严格小于等于D点的值,下次查询区间在[left , mid]上。
当区间长度变成1的时候,就是我们要找的结果。

1.2.2  算法实现

代码演示如下——

// D点 class Solution { public: int findMin(vector<int>& nums) { int left = 0,right = nums.size() - 1; int x = nums[right]; while(left < right) { int mid = left + (right - left) / 2; if(nums[mid] > x) left = mid + 1; else right = mid; } return nums[left]; // 返回数组中的最小元素 } };
时间复杂度:O(logn),空间复杂度:O(1)。

1.2.3  尝试:选择A点呢?

A点也可以有二段性,我们可以尝试一下——

int y = nums[left]; // 选择第一个元素作为参考点 while(left < right) { int mid = left + (right - left + 1) / 2; if(nums[mid] >= y) right = mid - 1; // 还在左半部分,往右找 else left = mid; // 已经在右半部分,往左找 }
问题:当数组没有旋转时(如[1 , 2 , 3 , 4 , 5]),所有元素都 >= nums[0],会导致一直执行 right = mid - 1,最终返回错误的结果。

1.3  博主手记

本题整个的思路、算法原理、解题过程博主在纸上推导了一遍,大家可以参考一下手记的推导过程!最好做题的过程中自己也推导一遍!!!自己能够推导很重要!


​024  点名(缺失的数字)

力扣链接:LCR 173. 点名

力扣题解链接:二分查找模版解决【点名】

题目描述:

2.1  解法一:暴力遍历找结果

代码演示如下——

class Solution { public: int takeAttendance(vector<int>& records) { int n = records.size(); for (int i = 0; i < n; i++) { if (records[i] != i) { return i; } } return n; // 如果前面都连续,缺失的就是最后一个 } };
时间复杂度:O(n),空间复杂度:O(1)。

2.2  解法二:用哈希表解决

代码演示如下——

class Solution { public: int takeAttendance(vector<int>& records) { unordered_set<int> recordSet(records.begin(), records.end()); int n = records.size(); for (int i = 0; i <= n; i++) { if (recordSet.find(i) == recordSet.end()) { return i; } } return -1; } };
时间复杂度:O(n),空间复杂度:O(n)。

2.3  解法三:位运算

代码演示如下——

class Solution { public: int takeAttendance(vector<int>& records) { int result = 0; int n = records.size(); // 将 0~n 的所有数与数组中的数进行异或 for (int i = 0; i <= n; i++) { result ^= i; } for (int num : records) { result ^= num; } return result; } };
时间复杂度:O(n),空间复杂度:O(1)。

2.4  解法四:用(数学)高斯求和公式解决

代码演示如下——

class Solution { public: int takeAttendance(vector<int>& records) { int n = records.size(); // 计算 0~n 的完整和 int totalSum = n * (n + 1) / 2; // 计算实际数组的和 int actualSum = 0; for (int num : records) { actualSum += num; } // 缺失的数 = 完整和 - 实际和 return totalSum - actualSum; } };
时间复杂度:O(n),空间复杂度:O(1)。

2.5  解法五:二分查找(最优解)

2.5.1  算法思路

关于这道题中,时间复杂度为O(N) 的解法有很多种,而且也是比较好想的,就是上面我们已经展示过的四种方法,已经介绍过了,这里就不再赘述。

在这个升序的数组中,我们发现——

(1)在第一个缺失位置的左边,数组内的元素都是与数组的下标相等的;

(2)在第一个缺失位置的右边,数组内的元素与数组下标是不相等的。

因此,我们可以利用这个【二段性】,来使用【二分查找】算法。

2.5.2  算法实现

代码演示如下——

class Solution { public: int takeAttendance(vector<int>& records) { int left = 0,right = records.size() - 1; while(left < right) { int mid = left + (right - left) / 2; if(records[mid] == mid) left = mid + 1; // 左边找不到,到右边去找 else right = mid; } // 处理细节问题 return records[left] == left ? left + 1 : left; } };
时间复杂度:O(logn),空间复杂度:O(1)。

2.6  五种方法总结

方法时间复杂度空间复杂度适用场景
哈希表O(n)O(n)通用解法
直接遍历O(n)O(1)简单直观
位运算O(n)O(1)无溢出风险
高斯求和O(n)O(1)数学思维
二分查找O(log n)O(1)最优解

推荐使用二分查找,因为它的时间复杂度最优,且能充分利用数组有序的特性。

2.7  博主手记

本题整个的思路、算法原理、解题过程博主在纸上推导了一遍,大家可以参考一下手记的推导过程!最好做题的过程中自己也推导一遍!!!自己能够推导很重要!


结尾

往期回顾:

【优选算法必刷100题】第021~22题(二分查找算法):山脉数组的峰顶索引、寻找峰值

结语:都看到这里啦!请大佬不要忘记给博主来个“一键四连”哦!

🗡博主在这里放了一只小狗,大家看完了摸摸小狗放松一下吧!🗡

૮₍ ˶ ˊ ᴥ ˋ˶₎ა


Read more

《算法题讲解指南:递归,搜索与回溯算法--递归》--1.汉诺塔,2.合并两个有序链表

《算法题讲解指南:递归,搜索与回溯算法--递归》--1.汉诺塔,2.合并两个有序链表

🔥小叶-duck:个人主页 ❄️个人专栏:《Data-Structure-Learning》 《C++入门到进阶&自我学习过程记录》 《算法题讲解指南》--优选算法 《算法题讲解指南》--递归、搜索与回溯算法 ✨未择之路,不须回头 已择之路,纵是荆棘遍野,亦作花海遨游 目录 前言 递归,搜索与回溯算法前置知识(极其重要) 1.汉诺塔 题目链接: 题目描述: 题目示例: 解法(递归): 算法思路: C++算法代码: 算法总结及流程解析: 2.合并两个有序链表 题目链接: 题目描述: 题目示例: 解法(递归): 算法思路: C++算法代码: 算法总结及流程解析: 结束语 前言       从本篇文章开始我们就要讲解很多人一开始学习就感到非常不解以及迷茫,不清楚代码到底怎么执行的算法:递归、

By Ne0inhk
算法基础篇:(二十一)数据结构之单调栈:从原理到实战,玩转高效解题

算法基础篇:(二十一)数据结构之单调栈:从原理到实战,玩转高效解题

目录 前言 一、什么是单调栈?先打破 “栈” 的常规认知 1.1 单调栈的核心特性 1.2 如何实现一个单调栈? 实现单调递增栈 实现单调递减栈 1.3 核心操作解析:为什么要 “弹出元素”? 二、单调栈能解决什么问题?四大核心场景全覆盖 2.1 场景 1:找左侧最近的 “更大元素” 问题描述 解题思路 代码实现 测试用例验证 2.2 场景 2:找左侧最近的 “更小元素” 问题描述 解题思路 代码实现 测试用例验证 2.3 场景 3:找右侧最近的 “更大元素” 问题描述

By Ne0inhk
【数据结构初阶】排序算法(中)快速排序专题

【数据结构初阶】排序算法(中)快速排序专题

文章目录 * 1. 快排主框架 * 2. 快排的不同实现 * 2. 1 hoare版本 * 2. 2 挖坑法 * 2. 3 lomuto前后指针法 * 2. 4 快排的非递归版本 * 3. 快排优化 * 3. 1 快排性能的关键点分析: * 3. 1 三路划分 * 3. 2 introsort自省排序 1. 快排主框架 快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法。 其基本思想为:任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。 简单地说,就是将数组分成左右两个部分,左部分都大于(或小于)中间的基准值,右部分都小于(或大于)中间的基准值,然后不断重复上述过程,直到数组完全有序。 //快排主框架voidQuickSort(int*

By Ne0inhk
动态规划 线性 DP 经典四题一遍吃透

动态规划 线性 DP 经典四题一遍吃透

文章目录 * 台阶问题 * 最大子段和 * 传球游戏 * 乌龟棋 线性dp 是动态规划问题中最基础、最常⻅的⼀类问题。它的特点是状态转移只依赖于前⼀个或前⼏个状态,状态之间的关系是线性的,通常可以⽤⼀维或者⼆维数组来存储状态。 我们在⼊⻔阶段解决的《下楼梯》以及《数字三⻆形》其实都是线性dp,⼀个是⼀维的,另⼀个是⼆ 维的。 台阶问题 题目描述 题目解析 本题就是上一节下楼梯的问题的加强版,总体思路不变,下面我们还是按照动规5板斧来分析一下这道题。 1、状态表示 dp[i]表示走到第i个台阶的所有方案数 2、状态转移方程 第i个台阶的方案数等于从i-1阶到i-k阶的所有方案数之和,因为本题数据比较大,用long long都无法保证数据不越界,所以题目规定方案数还需要模100003,第i个台阶的方案数等于从i-1阶到i-k阶的所有方案数之和再模上100003,所以但是注意是可能越界访问的,比如i为3,

By Ne0inhk