最基础的数组你真的掌握了吗?

这篇具有很好参考价值的文章主要介绍了最基础的数组你真的掌握了吗?。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

最基础的数组你真的掌握了吗?

最基础的数组你真的掌握了吗?

🐱‍🐉作者简介:大家好,我是黑洞晓威,一名大二学生,希望和大家一起进步。
👿本文收录于 算法,本专栏是针对大学生、初学算法的人准备,解析常见的数据结构与算法,同时备战蓝桥杯。

一:数组理论基础

首先要知道数组在内存中的存储方式,这样才能真正理解数组相关的题

数组是存放在连续内存空间上的相同类型数据的集合。

数组可以方便的通过下标索引的方式获取到下标下对应的数据。

举一个字符数组的例子,如图所示:

最基础的数组你真的掌握了吗?

需要两点注意的是

  • 数组下标都是从0开始的。
  • 数组内存空间的地址是连续的

正是因为数组的在内存空间的地址是连续的,所以我们在删除或者增添元素的时候,就难免要移动其他元素的地址。

例如删除下标为3的元素,需要对下标为3的元素后面的所有元素都要做移动操作,如图所示:

最基础的数组你真的掌握了吗?

二:数组这种数据结构的优点和缺点是什么?

最基础的数组你真的掌握了吗?

优点:查询/查找/检索某个下标上的元素时效率极高。
原因:

第一:每一个元素的内存地址在空间存储上是连续的。

第二:每一个元素类型相同,所以占用空间大小一样。

第三:知道第一个元素内存地址,知道每一个元素占用空间的大小,又知道下标,所以通过一个数学表达式就可以计算出某个下标上元素的内存地址。直接通过内存地址定位元素,所以数组的检索效率是最高的。
注意:

缺点:
第一:由于为了保证数组中每个元素的内存地址连续,所以在数组上随机删除或者增加元素的时候,效率较低,因为随机增删元素会涉及到后面元素统一向前或者向后位移的操作。
第二:数组不能存储大数据量。
因为很难在内存空间上找到一块特别大的连续的内存空间。
注意:
对于数组中最后一个元素的增删,是没有效率影响的。

三:数组是如何实现随机访问的呢?

数组(Array)是一种线性表数据结构。它用一组连续的内存空间,来存储一组具有相同类型的数据。

这个定义里有几个关键词,理解了这几个关键词,我想你就能彻底掌握数组的概念了。下面就从我的角度分别给你“点拨”一下。

第一是 线性表(Linear List)。顾名思义,线性表就是数据排成像一条线一样的结构。每个线性表上的数据最多只有前和后两个方向。其实除了数组,链表、队列、栈等也是线性表结构。

最基础的数组你真的掌握了吗?

而与它相对立的概念是 非线性表,比如二叉树、堆、图等。之所以叫非线性,是因为,在非线性表中,数据之间并不是简单的前后关系。

最基础的数组你真的掌握了吗?

第二个是 连续的内存空间和相同类型的数据。正是因为这两个限制,它才有了一个堪称“杀手锏”的特性:“随机访问”。但有利就有弊,这两个限制也让数组的很多操作变得非常低效,比如要想在数组中删除、插入一个数据,为了保证连续性,就需要做大量的数据搬移工作。

说到数据的访问,那你知道数组是如何实现根据下标随机访问数组元素的吗?

我们拿一个长度为10的int类型的数组int[] a = new int[10]来举例。在我画的这个图中,计算机给数组a[10],分配了一块连续内存空间1000~1039,其中,内存块的首地址为base_address = 1000。

最基础的数组你真的掌握了吗?

我们知道,计算机会给每个内存单元分配一个地址,计算机通过地址来访问内存中的数据。当计算机需要随机访问数组中的某个元素时,它会首先通过下面的寻址公式,计算出该元素存储的内存地址:

a[i]_address = base_address + i * data_type_size

其中data_type_size表示数组中每个元素的大小。我们举的这个例子里,数组中存储的是int类型数据,所以data_type_size就为4个字节。这个公式非常简单,我就不多做解释了。

这里我要特别纠正一个“错误”。问到数组和链表的区别,很多人都回答说,“链表适合插入、删除,时间复杂度O(1);数组适合查找,查找时间复杂度为O(1)”。

实际上,这种表述是不准确的。数组是适合查找操作,但是查找的时间复杂度并不为O(1)。即便是排好序的数组,你用二分查找,时间复杂度也是O(logn)。所以,正确的表述应该是,数组支持随机访问,根据下标随机访问的时间复杂度为O(1)。

四:低效的“插入”和“删除”原因在哪里?

前面概念部分我们提到,数组为了保持内存数据的连续性,会导致插入、删除这两个操作比较低效。现在我们就来详细说一下,究竟为什么会导致低效?又有哪些改进方法呢?

我们先来看 插入操作

假设数组的长度为n,现在,如果我们需要将一个数据插入到数组中的第k个位置。为了把第k个位置腾出来,给新来的数据,我们需要将第k~n这部分的元素都顺序地往后挪一位。那插入操作的时间复杂度是多少呢?你可以自己先试着分析一下。

如果在数组的末尾插入元素,那就不需要移动数据了,这时的时间复杂度为O(1)。但如果在数组的开头插入元素,那所有的数据都需要依次往后移动一位,所以最坏时间复杂度是O(n)。 因为我们在每个位置插入元素的概率是一样的,所以平均情况时间复杂度为(1+2+…n)/n=O(n)。

如果数组中的数据是有序的,我们在某个位置插入一个新的元素时,就必须按照刚才的方法搬移k之后的数据。但是,如果数组中存储的数据并没有任何规律,数组只是被当作一个存储数据的集合。在这种情况下,如果要将某个数据插入到第k个位置,为了避免大规模的数据搬移,我们还有一个简单的办法就是,直接将第k位的数据搬移到数组元素的最后,把新的元素直接放入第k个位置。

为了更好地理解,我们举一个例子。假设数组a[10]中存储了如下5个元素:a,b,c,d,e。

我们现在需要将元素x插入到第3个位置。我们只需要将c放入到a[5],将a[2]赋值为x即可。最后,数组中的元素如下: a,b,x,d,e,c。

最基础的数组你真的掌握了吗?

利用这种处理技巧,在特定场景下,在第k个位置插入一个元素的时间复杂度就会降为O(1)。这个处理思想在快排中也会用到,我会在排序那一节具体来讲,这里就说到这儿。

我们再来看 删除操作

跟插入数据类似,如果我们要删除第k个位置的数据,为了内存的连续性,也需要搬移数据,不然中间就会出现空洞,内存就不连续了。

和插入类似,如果删除数组末尾的数据,则最好情况时间复杂度为O(1);如果删除开头的数据,则最坏情况时间复杂度为O(n);平均情况时间复杂度也为O(n)。

实际上,在某些特殊场景下,我们并不一定非得追求数组中数据的连续性。如果我们将多次删除操作集中在一起执行,删除的效率是不是会提高很多呢?

我们继续来看例子。数组a[10]中存储了8个元素:a,b,c,d,e,f,g,h。现在,我们要依次删除a,b,c三个元素。

最基础的数组你真的掌握了吗?

为了避免d,e,f,g,h这几个数据会被搬移三次,我们可以先记录下已经删除的数据。每次的删除操作并不是真正地搬移数据,只是记录数据已经被删除。当数组没有更多空间存储数据时,我们再触发执行一次真正的删除操作,这样就大大减少了删除操作导致的数据搬移。

五:实战解题

1. 移除元素

力扣链接

给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。

不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。

元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。

示例 1:

输入:nums = [3,2,2,3], val = 3
输出:2, nums = [2,2]
解释:函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。你不需要考虑数组中超出新长度后面的元素。例如,函数返回的新长度为 2 ,而 nums = [2,2,3,3] 或 nums = [2,2,0,0],也会被视作正确答案。

暴力解法

这个题目暴力的解法就是两层for循环,一个for循环遍历数组元素 ,第二个for循环更新数组。

很明显暴力解法的时间复杂度是O(n^2),这道题目暴力解法在leetcode上是可以过的。

代码如下:

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^2)
  • 空间复杂度:O(1)

双指针法

思路及算法
由于题目要求删除数组中等于val的元素,因此输出数组的长度一定小于等于输入数组的长度,我们可以把输出的数组直接写在输入数组上。可以使用双指针:右指针right指向当前将要处理的元素,左指针left指向下一个将要赋值的位置。

·如果右指针指向的元素不等于val,它一定是输出数组的一个元素,我们就将右指针指向的元素复制到左指针位置,然后将左右指针同时右移;

·如果右指针指向的元素等于val,它不能在输出数组里,此时左指针不动,右指针右移一位。

整个过程保持不变的性质是:区间[0, left)中的元素都不等于value。当左右指针遍历完输入数组以后,left的值就是输出数组的长度。
这样的算法在最坏情况下(输入数组中没有元素等于value),左右指针各遍历了数组一次。

class Solution {
    public int removeElement(int[] nums, int val) {
        // 快慢指针
        int slowIndex = 0;
        for (int fastIndex = 0; fastIndex < nums.length; fastIndex++) {
            if (nums[fastIndex] != val) {
                nums[slowIndex] = nums[fastIndex];
                slowIndex++;
            }
        }
        return slowIndex;
    }
}

最基础的数组你真的掌握了吗?

2.有序数组的平方

力扣链接

给你一个按 非递减顺序 排序的整数数组 nums,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。

示例 1:

输入:nums = [-4,-1,0,3,10] 输出:[0,1,9,16,100] 解释:平方后,数组变为 [16,1,0,9,100],排序后,数组变为 [0,1,9,16,100]

示例 2:

输入:nums = [-7,-3,2,3,11] 输出:[4,9,9,49,121]

最基础的数组你真的掌握了吗?

暴力解法

最直观的想法,莫过于:每个数平方之后,排个序,美滋滋,代码如下:

class Solution {
public:
    vector<int> sortedSquares(vector<int>& A) {
        for (int i = 0; i < A.size(); i++) {
            A[i] *= A[i];
        }
        sort(A.begin(), A.end()); // 快速排序
        return A;
    }
};

这个时间复杂度是 O(n + nlogn), 可以说是O(nlogn)的时间复杂度,但为了和下面双指针法算法时间复杂度有鲜明对比,我记为 O(n + nlog n)。

双指针法

数组其实是有序的, 只不过负数平方之后可能成为最大数了。

那么数组平方的最大值就在数组的两端,不是最左边就是最右边,不可能是中间。

此时可以考虑双指针法了,i指向起始位置,j指向终止位置。

定义一个新数组result,和A数组一样的大小,让k指向result数组终止位置。

class Solution {
    public int[] sortedSquares(int[] nums) {
        int right = nums.length - 1;
        int left = 0;
        int[] result = new int[nums.length];
        int index = result.length - 1;
        while (left <= right) {
            if (nums[left] * nums[left] > nums[right] * nums[right]) {
                // 正数的相对位置是不变的, 需要调整的是负数平方后的相对位置
                result[index--] = nums[left] * nums[left];
                ++left;
            } else {
                result[index--] = nums[right] * nums[right];
                --right;
            }
        }
        return result;
    }
}

最后说一句

感谢大家的阅读,文章通过网络资源与自己的学习过程整理出来,希望能帮助到大家。

才疏学浅,难免会有纰漏,如果你发现了错误的地方,可以提出来,我会对其加以修改。

最基础的数组你真的掌握了吗?文章来源地址https://www.toymoban.com/news/detail-410974.html

到了这里,关于最基础的数组你真的掌握了吗?的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【Python基础知识】在VSCode中怎么配置Python开发环境?真的超简单!小白也能掌握

    前言:VS Code 里是不包括 Python 的,所以你首先得安装一个 Python。 安装完 python 之后,我们可以用任何一个文本编辑工具开始写 python 代码,然后在 cmd 中运行代码。 在 VS Code 中,在不安装任何插件的情况下,也可以运行 python 代码。 新建一个 test.py 文件,输入 print(\\\'Hello Wor

    2024年02月03日
    浏览(53)
  • 【JAVA真的没出路了吗?】

    2023年了,转行IT学习Java是不是已经听过看过很多次了。随之而来的类似学Java没出路、Java不行了、对Java感到绝望等等一系列的制造焦虑的话题也在网上层出不穷,席卷了一大片的对行业不了解的吃瓜群众或是正在学习中的人。如果是行外人真的会被这种言论轻易的欺骗,对行

    2023年04月09日
    浏览(108)
  • 互联网行业真的不行了吗?

    英雄算法联盟 - 七月集训 已经开始 16 天,八月算法集训 将于 08月01日 正式开始,目前已经提前开始报名,报名方式参见(八月算法集训报名),想要参加的同学,建议提早报名,因为对于算法零基础的同学,会有一些提前的准备工作,比如需要 1 - 5 天的时间完成预训练 和

    2024年02月16日
    浏览(58)
  • Fil真的要归零了吗?

    最近听见有人说有fil的内部消息,意思就是fil要清零了,关于这个消息今天就大家好好聊一聊fil到底会不会归零。 首先市场上面没有这种确定性的说法,一定会到多少多少,我也不知道你们是从哪里听来的消息,如果这个市场有确定性,那你在得到这个内部消息之后,完全可

    2024年02月13日
    浏览(65)
  • 【Spring】IOC,你真的懂了吗?

    作者:狮子也疯狂 专栏:《spring开发》 坚持做好每一步,幸运之神自然会驾凌在你的身上 Spring框架是狮子入坑Java的第一个开源框架。当我们接触到它时,总会发现老师或者书本介绍这两个词汇—— IOC和AOP ,它们分别是 控制反转和面向切面 ,是Spring的思想内核,提供了控制

    2024年02月20日
    浏览(62)
  • 最长递增子序列问题(你真的会了吗)

    目录 一.最长递增子序列问题I 二.最长递增子序列问题II 三. 最长递增子序列问题III 1.对应牛客网链接 最长上升子序列(一)_牛客题霸_牛客网 (nowcoder.com) 2.题目描述:  3.解题思路 1.首先我们分析题意:最长递增子序列拆:要递增的,还是序列,不一定连续 ,要长度最长的。

    2024年02月15日
    浏览(53)
  • AI人工智能时代真的到来了吗?

    近一个月来,关于AI人工智能的话题此起彼伏,先有OpenAI发布GPT-4,后有百度推出文心一言,再有微软把GPT-4接入Office全家桶并命名为“Microsoft 365 Copilot”,除此之外,微软Bing还上线了AI绘图功能、谷歌开放聊天机器人Bard等等,各大巨头们这一波操作使得AI又一次火爆全网,人

    2024年02月02日
    浏览(58)
  • 关于低代码开发,你是真的了解了吗?

    在低代码开发已是大势所趋的今天,不少企业都切身感受到了低代码开发带来的便利。低代码开发平台的优势在当下数字化浪潮中,为企业提供了定制专属的数字化解决方案。 低代码本身没有太强的行业属性,这也让低代码开发平台能够更加灵活地适应不同行业。目前低代码

    2024年02月05日
    浏览(54)
  • 制造业的寒冬真的要来了吗?

    制造业的寒冬真的要来了吗?其实当前,我国制造业发展水平是处于全球第三阵列,排名第四的: 但能处第三序列靠前,还是因为“规模发展”起了重要支撑——依靠规模拉动发展。所以如果从“质量效益”、“结构优化”、“持续发展”三项来评估,我们仅排名第六,就与

    2023年04月08日
    浏览(102)
  • 二分查找算法 | 你真的搞懂二分了吗?

    我身边的人都认为二分查找很简单,但事实真是如此吗?不,并不简单。二分算法有着许多的 边界问题 ,当你写着代码一不小心就会陷入死循环。本篇文章会深入细节详细介绍 整数二分算法 以及使用 二分算法步骤 和 力扣题目练习 ,并且还会给出 二分查找算法模板 ,下面

    2023年04月10日
    浏览(46)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包