提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
目录
一、介绍数组
1.什么是数组呢?
数组是最常见的数据结构,用来存储数据,只具有数据域。
2.数组的类别
我们常见的数组通常由以下几种:
一维数组:具体表现形式为 a = {1, 2, 3, 4, 5},该数组的长度为5,下标为0,1,2,3,4(一定注意数组的下标从0开始)。当我们要访问数据2的时候,通过a[1]访问。
二维数组:具体表现形式为 b = {{0, 1, 2},
{3, 4, 5},
{6, 7, 8}}
这样看的话可能更容易理解,可以把二维数组当成若干个一维数组,当我们想要访问5的时候,通过b[1][2]访问
更高维度的数组此处不在介绍,可以通过二维数组是若干个一维数组的组合来以此类推。
3.数组的特点
数组和链表我们通常放在一起对比,上一篇文章也已经提到了,数组的连续存储的,因此我们可以通过下标来进行访问,这也导致了它的一些优点和缺点。(可以和链表的对应起来看)
优点:
(1)地址连续,所以我们可以通过下标访问,也就是随机存取,比如我们想要访问数组的第n个位置元素,则可以直接通过a[n-1]访问,注意数组下标从0开始。
(2)所有的空间都用来存储数据,所以空间利用率为100%
缺点:
(1)因为地址空间要求连续,所以我们必须要找到一片连续的空间来存储数组,内存中的碎片空间利用不充分。
(2)同样因为地址空间要求连续,导致了我们对数组当中元素进行增删的时候,要移动大量元素,比如一个长度为5的数组,我们要把第二个位置的元素删除,则我们就需要依次把三位置覆盖二位置,四位置覆盖三位置,五位置覆盖四位置。想要增加元素同理,而且数组一旦创建,长度就确定了,不能增加了减少,所以当数组已满的时候,如果我们想要再增加元素,就需要从新创建一个更大的数组,然后依次把原有的元素移动过去,之后再插入新元素。
二、题目分析与心得
(1).
分析:读题时需要着重注意到一点,有序,此时我们就需要考虑二分法,如果想到了这点,则此题很容易解出,不过使用二分法的时候,一定要注意左右两边区间的开闭和循环条件中是否取等的关系。
代码实现
class Solution:
def search(self, nums: List[int], target: int) -> int:
left, right = 0, len(nums) - 1
while left <= right:
middle = (left + right) // 2
if nums[middle] == target:
return middle
elif nums[middle] > target:
right = middle - 1
else:
left = middle + 1
return -1
心得:二分法是求解数组问题中非常非常常用的方法,但要注意几个要点,数组是否有序,区间的开闭,循环条件是否取等
下面的一些题目可以作为对二分法的练习
34.在排序数组中查找元素的第一个位置和最后一个位置(此题较难,着重考察了对左右区间,循环条件的判断)
(2).
分析:此题的思路为使用快慢指针,慢指针用来指向需要保留数组位置的下一个位置,快指针从头开始遍历,指向要判断元素的位置,难点在于对if内外的代码书写,要注意,如果元素要保留的话,就让快指针指向的覆盖慢指针指向的,假如这时候快慢指针位置相同,则没有什么影响,如果不同,也没有关系,因为慢指针指向的一定是需要移除的元素,具体自己可以画图分析一下
代码实现:
class Solution:
def removeElement(self, nums: List[int], val: int) -> int:
low, quick = 0, 0
while quick <= len(nums) - 1:
if nums[quick] != val:
nums[low] = nums[quick]
low += 1
quick += 1
return low
心得:数组中快慢指针的应用!!!
数组中快慢指针相关题目练习
26. 删除有序数组中的重复项 - 力扣(LeetCode)
844. 比较含退格的字符串 - 力扣(LeetCode)此题思路比较难想
(3).
分析:最简单的想法,先对数组内每个元素平法,然后排序,不过这种方法时间复杂度较高
我们可以利用原数组有序的这个特点来寻找简单做法,平方后最大的元素,一定是最小的负数或者是最大的正数,在原数组中一定是在最左边或最右边,所以我们可以利用两个指针来分别指向原数组的头和尾,然后依次向中间靠拢并输出元素的平方,将其放在新数组的最后。有一点点归并排序的味道。
代码实现:
class Solution:
def sortedSquares(self, nums: List[int]) -> List[int]:
a = [0] * len(nums)
left, right, key = 0, len(nums) - 1, len(nums) - 1
while left <= right:
val1 = nums[left]*nums[left]
val2 = nums[right]*nums[right]
if val1 <= val2:
a[key] = val2
right -= 1
else:
a[key] = val1
left += 1
key -= 1
return a
心得:
最直观的方法往往复杂度较高,要注意用直观方法暴力解题的时候,题目中哪些条件还有用到,这往往是简单解法的关键所在
(4).
分析:此题最直观的解法,先从一个元素开始遍历,然后往后sum,看有多少个才能大于target,然后再从第二个元素数.....很慢,复杂度可以达到O(n²)
双指针法,在简单的解法中,我们是以左边的元素为基准算sum,如果我们用右边的元素作为基准呢?首先我们先从最左边开始,算一个区间和大于target,比如是区间[0, 5],此时,慢指针指向0,快指针指向5,然后我们尝试让sum-0下标的元素,也就是算下[1,5]的和,如果还是大于target,就像1+1+1+1+1+100 > 99,那我们再这样移动慢指针,算[2,5],依次类推,等到小于target了,那我们就移动快指针,再算[low,quick]的区间。这个时间复杂度我也不知道,反正肯定是n到n²之间hhh
代码实现:
class Solution:
def minSubArrayLen(self, target: int, nums: List[int]) -> int:
low, quick, summ = 0, 0, 0
lg = len(nums) + 1
while quick <= len(nums) - 1:
summ += nums[quick]
while summ >= target:
n = quick - low + 1
lg = min(lg, n)
summ = summ - nums[low]
low += 1
quick += 1
if lg == len(nums) + 1:
return 0
else:
return lg
心得:双指针的应用非常灵活,有时候也很难想,只能多做新题见思路,多复习巩固
相关题目练习(我反正俩都不会,解析都看不懂,摆烂。希望以后的我再回看的时候能看懂吧)
(5).
分析:视频讲解倒是能看懂,但是代码对我自身的水平来说很不好写,特别是对边界条件的判断
总结
数组这章目前感觉很重要的就是
二分法:一定注意题目的有序条件,再有就是对开闭区间与循环条件关系的理解(暂时掌握不好, 我自己一直用的是双闭和left <= right)
双指针:很灵活,可以和上一篇的链表一起感受
总的感觉,虽然数组的结构形式上比链表简单,只有简简单单的数据域,但是感觉题目方面比链表要灵活很多