BAT程序员手把手带你学算法-数组篇(理论知识剖析+5道经典面试题目)
文章目录
- 必须掌握的数组理论知识
- 五道数组经典面试题目
- 第一道:搜索插入位置
- 第二道:移除元素
- 第三道:删除排序数组中的重复项
- 第四道:长度最小的子数组
- 第五道:螺旋矩阵
- 总结
笔者先后在BAT中的两家工作,在我面试候选人的时候,发现很多同学简历看上去很优秀,各种框架各种经验,但是一面试发现对数据的基本操作都不太熟悉
只能说在准备面试的过程中,对最基础的数据结构都没有好好准备
这里结合自己的面试思路,从面试必备的理论知识到五道精选的面试题目,来给大家讲解一下。
数组是非常基础的数据结构,在面试中,数组的题目一般在思维上都不难,主要是考察对代码的掌控能力
也就是说,想法很简单,但实现起来 可能就不是那么回事了
这篇文章我将讲解面试中必考的数组理论知识,再给出五道精选面试题目
带大家一起分析每一道经典题目的思路,同时给出了每一道题目的暴力解法和更优解法的配有详细注释的代码
通过这篇文章可以帮助大家对算法面试中数组的相关知识有一个全面的了解
接下来先介绍面试中必考的数组理论知识
必须掌握的数组理论知识
数组是存放在连续内存空间上的相同类型数据的集合。 数组可以方便的通过下表索引的方式获取到下表下对应的数据。
举一个字符数组的例子,如图所示:
需要两点注意的是
- 数组下表都是从0开始的。
- 数组内存空间的地址是连续的
正是因为数组的在内存空间的地址是连续的,所以我们在删除或者增添元素的时候,就难免要移动其他元素的地址,
例如删除下表为3的元素,需要对下表为3的元素后面的所有元素都要做移动操作,如图所示:
那么二维数组直接上图,大家应该就知道怎么回事了
那么这里要请同学思考一下,二维数组在内存的空间地址是连续的么?
我们来举一个例子,例如: int[][] rating = new int[3][4];
, 这个二维数据在内存空间可不是一个 3*4
的连续地址空间
看了下图,就应该明白了:
这个二维数据在内存中不是 3*4
的连续地址空间,而是四条连续的地址空间组成!
接下来,我从leetcode中给大家总结了五道数组相关的经典面试题目
五道数组经典面试题目
第一道:搜索插入位置
leetcode 编号35
这道题目呢,考察的数据的基本操作,思路很简单,但是在通过率在简单题里并不高,不要轻敌
可以使用暴力解法,通过这道题目,如果准求更优的算法,建议试一试用二分法,来解决这道题目
暴力解法时间复杂度:O(n)
二分法时间复杂度:O(logn)
二分法是算法面试中的常考题,建议通过这道题目,锻炼自己手撕二分的能力。
代码详细讲解:
// 暴力解法
// 时间复杂度:O(n)
// 空间复杂度:O(1)
class Solution {
public:int searchInsert(vector<int>& nums, int target) {for (int i = 0; i < nums.size(); i++) {// 分别处理如下三种情况// 目标值在数组所有元素之前// 目标值等于数组中某一个元素// 目标值插入数组中的位置if (nums[i] >= target) { // 一旦发现大于或者等于target的num[i],那么i就是我们要的结果return i;}}// 目标值在数组所有元素之后的情况return nums.size(); // 如果target是最大的,或者 nums为空,则返回nums的长度}
};
// 二分解法
// 时间复杂度:O(logn)
// 空间复杂度:O(1)
class Solution {
public:int searchInsert(vector<int>& nums, int target) {int n = nums.size();int left = 0;int right = n - 1; // 我们定义target在左闭右闭的区间里,[left, right]while (left <= right) { // 当left==right,区间[left, right]依然有效int middle = left + ((right - left) / 2);// 防止溢出 等同于(left + right)/2if (nums[middle] > target) {right = middle - 1; // target 在左区间,所以[left, middle - 1]} else if (nums[middle] < target) {left = middle + 1; // target 在右区间,所以[middle + 1, right]} else { // nums[middle] == targetreturn middle;}}// 分别处理如下四种情况// 目标值在数组所有元素之前 [0, -1]// 目标值等于数组中某一个元素 return middle;// 目标值插入数组中的位置 [left, right],return right + 1// 目标值在数组所有元素之后的情况 [left, right], return right + 1return right + 1;}
};
第二道:移除元素
leetcode 编号27
在这道题目中,我们只要理解数组在内存中的结构,就知道数据中的元素只能被覆盖掉,而能直接删掉
所以这里题目中说的移除元素,其实是覆盖掉某一个元素
那么暴力的解法,很简单,两层for循环,一个for循环遍历数组元素 ,第二个for循环更新数组
很明显暴力解法时间复杂度是O(n), 然后尝试一个更优解,快慢指针法,时间复杂度可以做到O(n)
快慢指针法是解决数据问题中常见操作,头一个接触这个算法 还是有点懵的,
建议通过这道题目了解一下快慢指针法
代码详细讲解:
// 暴力解法
// 时间复杂度:O(n^2)
// 空间复杂度:O(1)
class Solution {
public:int removeElement(vector<int>& nums, int val) {int size = nums.size();for (int i = 0; i < size; i++) {if (nums[i] == val) { // 发现需要移除的元素,就将数组集体向前移动一位for (int j = i + 1; j < size; j++) {nums[j - 1] = nums[j];}i--; // 因为下表i以后的数值都向前移动了一位,所以i也向前移动一位size--;// 此时数组的大小-1}}return size;}
};
// 快慢指针解法
// 时间复杂度:O(n)
// 空间复杂度:O(1)
class Solution {
public:int removeElement(vector<int>& nums, int val) {int slowIndex = 0; // index为 慢指针for (int fastIndex = 0; fastIndex < nums.size(); fastIndex++) { // i 为快指针if (val != nums[fastIndex]) { //将快指针对应的数值赋值给慢指针对应的数值nums[slowIndex++] = nums[fastIndex]; 注意这里是slowIndex++ 而不是slowIndex--}}return slowIndex;}
};
第三道:删除排序数组中的重复项
leetcode 编号26
这道题目是 编号27的延伸, 做过了27题之后,再过这道题,一定会对快慢指针法有一个深刻的理解
代码详细讲解:
// 快慢指针解法
// 时间复杂度:O(n)
// 空间复杂度:O(1)
class Solution {
public:int removeDuplicates(vector<int>& nums) {if (nums.empty()) return 0; // 别忘记空数组的判断int slowIndex = 0;for (int fastIndex = 0; fastIndex < (nums.size() - 1); fastIndex++){if(nums[fastIndex] != nums[fastIndex + 1]) { // 发现和后一个不相同nums[++slowIndex] = nums[fastIndex + 1]; //slowIndex = 0 的数据一定是不重复的,所以直接 ++slowIndex}}return slowIndex + 1; //别忘了slowIndex是从0开始的,所以返回slowIndex + 1}
};
第四道:长度最小的子数组
leetcode 编号209
这道题目暴力是也可以的,时间复杂度为O(n^2)
其实也是通过一个快指针和慢指针来实现一个滑动窗口,最终得到长度最小的子数组,时间复杂度为O(n)
建议通过这道题目了解一下滑动窗口的思想
代码详细讲解:
// 暴力解法
// 时间复杂度:O(n^2)
// 空间复杂度:O(1)
class Solution {
public:int minSubArrayLen(int s, vector<int>& nums) {int result = INT32_MAX; // 最终的结果int sum = 0; // 子序列的数值之和int subLength = 0; // 子序列的长度for (int i = 0; i < nums.size(); i++) { // 设置子序列起点为isum = 0;for (int j = i; j < nums.size(); j++) { // 设置子序列终止位置为jsum += nums[j];if (sum >= s) { // 一旦发现子序列和超过了s,更新resultsubLength = j - i + 1; // 取子序列的长度// result取 result和subLength最小的那个result = result < subLength ? result : subLength;break; // 因为我们是找符合条件最短的子序列,所以一旦符合条件就break}}}// 如果result没有被赋值的话,就返回0,说明没有符合条件的子序列return result == INT32_MAX ? 0 : result;}
};
// 滑动窗口
// 时间复杂度:O(n)
// 空间复杂度:O(1)
class Solution {
public:int minSubArrayLen(int s, vector<int>& nums) {int result = INT32_MAX;int sum = 0; // 滑动窗口数值之和int i = 0; // 滑动窗口起始位置int subLength = 0; // 滑动窗口的长度for (int j = 0; j < nums.size(); j++) {sum += nums[j];// 注意这里使用while,每次更新 i(起始位置),并不断比较子序列是否符合条件while (sum >= s) {subLength = (j - i + 1); // 取子序列的长度// result取 result和subLength最小的那个result = result < subLength ? result : subLength;sum -= nums[i++]; // 这里体现出滑动窗口的精髓之处,不断变更i(子序列的起始位置)}}// 如果result没有被赋值的话,就返回0,说明没有符合条件的子序列return result == INT32_MAX ? 0 : result;}
};
第五道:螺旋矩阵
leetcode 编号59
这是一道模拟题,就是模拟螺旋矩阵
这道题绝对是面试中的常客,特别是笔试的时候
而且这道题很多同学就算做过,过一段时间,还是做这道题目 ,还是做不好。
解题的关键在于在循环遍历的时候需要定义好自己的循环不变量
这道题目是数组面试题中最常见的一个类型之一
代码详细讲解:
class Solution {
public:vector<vector<int>> generateMatrix(int n) {vector<vector<int>> res(n, vector<int>(n, 0)); // 使用vector定义一个二维数组int startx = 0, starty = 0; // 定义每循环一个圈的起始位置int loop = n / 2; // 每个圈循环几次int mid = n / 2; // 矩阵中间的位置,例如:n为3, 中间的位置就是(1,1),n为5,中间位置为(3, 3)int count = 1; // 用来计数int offset = 1; // 每一圈循环,需要偏移的位置int i,j;while (loop --) {i = startx;j = starty;// 下面开始的四个for就是模拟转了一圈// 模拟填充上行从左到右(左闭右开)for (j = starty; j < starty + n - offset; j++) {res[startx][j] = count++;}// 模拟填充右列从上到下(左闭右开)for (i = startx; i < startx + n - offset; i++) {res[i][j] = count++;}// 模拟填充下行从右到左(左闭右开)for (; j > starty; j--) {res[i][j] = count++;}// 模拟填充左列从下到上(左闭右开)for (; i > startx; i--) {res[i][j] = count++;}// 第二圈开始的时候,起始位置要各自加1, 例如:第一圈起始位置是(0, 0),第二圈起始位置是(1, 1)startx++;starty++;// offset 控制每一圈,遍历的长度offset += 2;}// 如果n为奇数的话,需要单独给矩阵最中间的位置赋值if (n % 2) {res[mid][mid] = count;}return res;}
};
总结
通过这篇文章希望可以帮助大家对算法面试中数组相关问题有更深的了解
这五道题也是数组中非常典型的题目,每一道题目都代表一个类型,一个思想
正在学习算法,或者在准备面试的同学,建议认真做好这五道算法面试题
如有问题,欢迎评论区留言。
BAT程序员手把手带你学算法-数组篇(理论知识剖析+5道经典面试题目)相关推荐
- 伍六七带你学算法 进阶篇-生命游戏
有趣的算法题–生命游戏 难度-中等 根据 百度百科 ,生命游戏,简称为生命,是英国数学家约翰·何顿·康威在 1970 年发明的细胞自动机. 想要体验生命游戏的小伙伴可以到这里-->生命游戏 进入 ...
- 伍六七带你学算法 进阶篇-排序算法
给定一个整数数组 nums,将该数组升序排列. 示例 1: 输入:[5,2,3,1] 输出:[1,2,3,5] 示例 2: 输入:[5,1,1,2,0,0] 输出:[0,0,1,1,2,5] 各排序算 ...
- 伍六七带你学算法 入门篇-卡牌分组
力扣-914. 卡牌分组 难度-简单 这是一道非常有趣的题,提交通过率令人深思 ,思考它是不是一道简单的题- 开始正题: 给定一副牌,每张牌上都写着一个整数. 此时,你需要选定一个数字 X,使我们可以 ...
- 伍六七带你学算法 入门篇-最小的k个数
java面试题-最小的k个数 难度-简单 输入整数数组 arr ,找出其中最小的 k 个数.例如,输入4.5.1.6.2.7.3.8这8个数字,则最小的4个数字是1.2.3.4. 示例 1: 输入:a ...
- 伍六七带你学算法 入门篇——最后一个单词的长度
难度 简单 给定一个仅包含大小写字母和空格 ' ' 的字符串 s,返回其最后一个单词的长度.如果字符串从左向右滚动显示,那么最后一个单词就是最后出现的单词. 如果不存在最后一个单词,请返回 0 . 说 ...
- 伍六七带你学算法 入门篇 ——最大子序和
力扣 53. 最大子序和 难度简单 给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和. 示例: 输入: [-2,1,-3,4,-1,2,1,-5,4 ...
- 伍六七带你学算法 入门篇-链表的中间节点
力扣-876链表的中间节点 难度-简单 给定一个带有头结点 head 的非空单链表,返回链表的中间结点. 如果有两个中间结点,则返回第二个中间结点. 示例 1: 输入:[1,2,3,4,5] 输出:此 ...
- 伍六七带你学算法 进阶篇-三数之和
三数之和 难度-中等 题目:给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组. 注意 ...
- 伍六七带你学算法 入门篇-最长回文串
力扣解题,每日一题:409. 最长回文串 难度- 简单 给定一个包含大写字母和小写字母的字符串,找到通过这些字母构造成的最长的回文串. 在构造过程中,请注意区分大小写.比如 "Aa" ...
- 伍六七带你学算法 入门篇-拼写单词
力扣解题,每日一题 1160. 拼写单词 难度- 简单 给你一份『词汇表』(字符串数组) words 和一张『字母表』(字符串) chars. 假如你可以用 chars 中的『字母』(字符)拼写出 w ...
最新文章
- 多级反馈队列调度算法原理
- docker fig mysql_docker管理工具 Fig 配置文件fig.yml的详解
- openshift学习_在OpenShift上将JMS与JBoss A-MQ结合使用。 学习了有关远程客户端和加密的经验。...
- numpy 随机数_TF+Numpy减少随机性的影响
- python requests verify=True vs verity=Flase
- 在一台机器设置两个listener(Oracle)
- Java 延迟队列 DelayQueue 的原理
- 小米手机只能进fastboot怎么办?
- Web前端性能优化——如何提高页面加载速度
- 【jQWidgets】jqxGrid控件在页面上重新加载的问题
- 植物大战僵尸最全最新版修改存档
- 这些数据爬虫网站,帮你工作提质增效,还不收藏?
- 举个栗子!Tableau 技巧(158):如何实现双域的服务器单点登录
- MyBatis配置数据库
- 昆仑通泰历史数据导出到u盘_MCGS配方组导出到U盘案例-专业自动化论坛-中国工控网论坛...
- 深入理解C语言中两级指针(char **pptr)的参数的用法
- 看漫画学python下载_漫画批量下载
- 谷歌浏览器 一律不翻译英语 恢复
- mysql更新等差数列求和公式_shell学习笔记(6)
- 测试护肤品好坏的软件,如何用简单工具判断护肤品的好坏