数组
1 理论
存储方式:
- 存储单元是一维的结构,数组是多维的结构
- 二维数组有两种存储方式: 以列序为主序(column major order),以行序为主序(row major order)
以0开始的索引:
- 每个数据元素都关联一个正数值,我们称之为索引,它表明数组中每个元素所在的位置。大部分语言将初始索引定义为零。
数组特点:
- 占用一段连续的内存空间
- 假设有个数组[0, 1, 2],它会以连续的空间存在在内存中;如果此时需要添加一个元素3,但是一开始连续的空间后面的位置存储了其他数据,此时就要重新开辟一段连续的空间来存储新的数组,之前的空间会被释放
数组的基本操作:
- Insert——在指定索引位置插入一个元素
- Get——返回指定索引位置的元素
- Delete——删除指定索引位置的元素
- Size——得到数组所有元素的数量
操作的时间复杂度:
- 读取元素,支持随机(索引)访问,且时间复杂度为O(1)
- 添加元素时间复杂度:O(n)
- 删除元素时间复杂度:O(n) (尾部删除时间复杂度O(1))
在中间增加的时候,要先将最后一个元素往后移,然后倒数第二个元素往后移,以此类推,不然,先移动前面的会把后面的元素覆盖掉。在中间删除的时候,要先将删除元素的后一个元素往前移,后面的依次往前移
python之Numpy
- NumPy 是一个 Python 包。 它代表 “Numeric Python”。 它是一个由多维数组对象和用于处理数组的例程集合组成的库。
- NumPy 中定义的最重要的对象是称为 ndarray 的 N 维数组类型。 它描述相同类型的元素集合。 可以使用基于零的索引访问集合中的项目。
- ndarray中的每个元素在内存中使用相同大小的块。 ndarray中的每个元素是数据类型对象的对象(称为 dtype)。
- 从ndarray对象提取的任何元素(通过切片)由一个数组标量类型的 Python 对象表示。 下图显示了ndarray,数据类型对象(dtype)和数组标量类型之间的关系。
- ndarray对象由计算机内存中的一维连续区域组成,带有将每个元素映射到内存块中某个位置的索引方案。 内存块以按行(C 风格)或按列(FORTRAN 或 MatLab 风格)的方式保存元素。
参考:https://blog.youkuaiyun.com/xiamiflying/article/details/82081036
参考:https://blog.youkuaiyun.com/Daycym/article/details/84309359
参考:https://blog.youkuaiyun.com/a373595475/article/details/79580734
2 相关编程
参考程序原博客:
https://blog.youkuaiyun.com/u013109501/article/details/88020739
https://blog.youkuaiyun.com/u013109501/article/details/88027009
1. 实现一个支持动态扩容的数组
class Arr:
def __init__(self, capacity=10):
"""
构造函数
:param capacity: 数组最大容量,不指定的话默认为10
"""
self._capacity = capacity
self._size = 0 # 数组有效元素的数目,初始化为0
self._data = [None] * self._capacity # 由于python的list是动态扩展的,而我们要实现底层具有固定容量、占用一段连续的内存空间的数组,所以用None来作为无效元素的标识
def add(self, index, elem):
"""
向数组中添加一个元素,注意数组占用的是一段连续的内存空间,所以在添加元素后,数组还是要保证这个特点的,因此需要将后面的元素都向后挪一个位置,而且要注意要先从
尾部开始挪,防止元素之间的覆盖
时间复杂度:O(n)
:param index: 添加的元素所在的索引
:param elem: 所要添加的元素
"""
if index < 0 or index > self._size: # 插入的位置无效
raise Exception('Add Filed. Require 0 <= index <= self._size')
if self._size == self._capacity: # 满了
self._resize(self._capacity * 2) # 默认扩容当前容量的二倍。容量翻倍要比容量加上一个固定值要好,这样做均摊复杂度为O(1)。具体请百度
for i in range(self._size - 1, index - 1, -1): # 从尾部开始挪动元素,在index处腾出一个空间
# 一定要注意在步长为负数的情况下,区间是左开右闭区间,即(index, self._size - 1],所以是index-1,与正常的左闭右开区间是相反的!
self._data[i + 1] = self._data[i]
self._data[index] = elem # 将该位置赋值为elem
self._size += 1 # 数组有效元素数加1
def addLast(self, elem):
"""
向数组尾部添加元素
时间复杂度:O(1)
:param elem: 所要添加的元素
"""
self.add(self._size, elem) # 直接调用add方法,注意不用再次判定合法性了,因为add函数中已经判断过了
def addFirst(self, elem):
"""
想数组头部添加元素
时间复杂度:O(n)
:param elem: 所要添加的元素
"""
self.add(0, elem) # 同理直接调用add方法
def remove(self, index):
"""
删除索引为index的元素。index后面的元素都要向前移动一个位置
时间复杂度:O(n)
:param index: 目标索引
:return: 位于该索引的元素的值
"""
if index < 0 or index >= self._size: # index合法性检查
raise Exception('Remove failed.Require 0 <= index < self._size')
ret = self._data[index] # 拷贝一下index处的元素,便于返回
for i in range(index + 1, self._size): # index后面的元素都向前挪一个位置
self._data[i - 1] = self._data[i]
self._size -= 1 # 维护self._size
self._data[self._size] = None # 最后一个元素的垃圾回收
if self._size and self._capacity // self._size == 4: # 如果当前有效元素为总容量的四分之一且还存在有效元素,则将容量缩减为原来的一半
self._resize(self._capacity // 2)
return ret
def removeFirst(self):
"""
删除数组首位置的元素
时间复杂度:O(n)
:return: 数组首位置的元素
"""
return self.remove(0) # 调用remove函数
def removeLast(self):
"""
删除数组末尾的元素
时间复杂度:O(1)
:return: 数组末尾的元素
"""
return self.remove(self._size - 1) # 调用remove函数
def _resize(self, new_capacity):
"""
数组容量放缩至new_capacity,私有成员函数
:param new_capacity: 新的容量
"""
new_arr = Arr(new_capacity) # 建立一个新的数组new_arr,容量为new_capacity
for i in range(self._size):
new_arr.addLast(self._data[i]) # 将当前数组的元素按当前顺序全部移动到new_arr中
self._capacity = new_capacity # 数组容量变为new_capacity
self._data = new_arr._data # 将new_arr._data赋值给self._data,从而完成数组的容量放缩操作
2.实现一个大小固定的有序数组,支持动态增删改操作
class Arr:
def __init__(self, capacity=10):
"""
构造函数
:param capacity: 数组最大容量,不指定的话默认为10
"""
self._capacity = capacity
self._size = 0 # 数组有效元素的数目,初始化为0
self._data = [None] * self._capacity # 由于python的list是动态扩展的,而我们要实现底层具有固定容量、占用一段连续的内存空间的数组,所以用None来作为无效元素的标识
def add(self, index, elem):
"""
向数组中添加一个元素,注意数组占用的是一段连续的内存空间,所以在添加元素后,数组还是要保证这个特点的,因此需要将后面的元素都向后挪一个位置,而且要注意要先从
尾部开始挪,防止元素之间的覆盖
时间复杂度:O(n)
:param index: 添加的元素所在的索引
:param elem: 所要添加的元素
"""
if index < 0 or index > self._size: # 插入的位置无效
raise Exception('Add Filed. Require 0 <= index <= self._size')
if self._size == self._capacity: # 满了
raise Exception('Add Filed. The array is full.')
for i in range(self._size - 1, index - 1, -1): # 从尾部开始挪动元素,在index处腾出一个空间
# 一定要注意在步长为负数的情况下,区间是左开右闭区间,即(index, self._size - 1],所以是index-1,与正常的左闭右开区间是相反的!
self._data[i + 1] = self._data[i]
self._data[index] = elem # 将该位置赋值为elem
self._size += 1 # 数组有效元素数加1
def addLast(self, elem):
"""
向数组尾部添加元素
时间复杂度:O(1)
:param elem: 所要添加的元素
"""
self.add(self._size, elem) # 直接调用add方法,注意不用再次判定合法性了,因为add函数中已经判断过了
def addFirst(self, elem):
"""
想数组头部添加元素
时间复杂度:O(n)
:param elem: 所要添加的元素
"""
self.add(0, elem) # 同理直接调用add方法
def remove(self, index):
"""
删除索引为index的元素。index后面的元素都要向前移动一个位置
时间复杂度:O(n)
:param index: 目标索引
:return: 位于该索引的元素的值
"""
if index < 0 or index >= self._size: # index合法性检查
raise Exception('Remove failed.Require 0 <= index < self._size')
ret = self._data[index] # 拷贝一下index处的元素,便于返回
for i in range(index + 1, self._size): # index后面的元素都向前挪一个位置
self._data[i - 1] = self._data[i]
self._size -= 1 # 维护self._size
self._data[self._size] = None # 最后一个元素的垃圾回收
return ret
def removeFirst(self):
"""
删除数组首位置的元素
时间复杂度:O(n)
:return: 数组首位置的元素
"""
return self.remove(0) # 调用remove函数
def removeLast(self):
"""
删除数组末尾的元素
时间复杂度:O(1)
:return: 数组末尾的元素
"""
return self.remove(self._size - 1) # 调用remove函数
3. 实现两个有序数组合并为一个有序数组
test1 = [1,3,5,7,9]
test2=[2,4,6,7,10,11,34,55]
def mergetest(test1,test2):
result=[ ]
len1=len(test1)
len2=len(test2)
i=0
j=0
while i<len1 and j<len2:
if test1[i]<=test2[j]:
result.append(test1[i])
i+=1
else:
result.append(test2[j])
j+=1
if i<len1:
for z in range(i+1,len1):
result.append(test1[z])
elif j<len2:
for z in range(j+1,len2):
result.append(test2[z])
return result
print (mergetest(test1,test2))
练习
1. Three Sum(求三数之和)
英文版:https://leetcode.com/problems/3sum/
中文版:https://leetcode-cn.com/problems/3sum/
给定一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?找出所有满足条件且不重复的三元组。
注意:答案中不可以包含重复的三元组。
- 例如, 给定数组 nums = [-1, 0, 1, 2, -1, -4],
满足要求的三元组集合为:
[
[-1, 0, 1],
[-1, -1, 2]
]
解题思路:
- 1.将数组排序 (为了解决重复的问题)
2.定义三个指针,i,j,k。遍历i,遍历j,遍历k。j从i之后开始,k从j之后开始遍历。 - 此程序是评论中的,先排序(从小到大),定义三个指针,用指针i遍历,指针l,r分别从i之后 最后 开始,向中间靠
import numpy as np
import time
class Solution(object):
def threeSum(self,nums):
nums.sort()#从小到大排序
res=[]
for i in range(len(nums)):
if i==0 or nums[i]>nums[i-1]:#在遍历过程,若有重复的值则不考虑
j=i+1
while j<(len(nums)-1) : #j要从i之后开始遍历
if j==i+1 or nums[j]>nums[j-1]:
k=j+1
while k<len(nums):
if k==j+1 or nums[k]>nums[k-1]:
s=nums[i]+nums[j]+nums[k]
if s==0: #sum=0的保存下来
res.append([nums[i],nums[j],nums[k]])
k=len(nums) #因为已经排过序,所以不用再算后面的
elif s>0:
j=len(nums)-1#后面的值比前面的大
break
else:
k=k+1
else:
k=k+1
else:
j=j+1
j=j+1
return res
#先排序(从小到大),定义三个指针,用指针i遍历,指针l,r分别从i之后 最后 开始,向中间靠
def threeSum_ref(self,nums):
nums.sort()
res =[]
i = 0
for i in range(len(nums)):
if i == 0 or nums[i]>nums[i-1]:
l = i+1
r = len(nums)-1
while l < r:
s = nums[i] + nums[l] +nums[r]
if s ==0: #s=0,l,r都往中间靠(有增大有减小可能有新的0出现)
res.append([nums[i],nums[l],nums[r]])
l +=1
r -=1
while l < r and nums[l] == nums[l-1]:
l += 1
while r > l and nums[r] == nums[r+1]:
r -= 1
elif s>0:
r -=1
else :
l +=1
return res
2. Majority Element(求众数)
英文版:https://leetcode.com/problems/majority-element/
中文版:https://leetcode-cn.com/problems/majority-element/
给定一个大小为 n 的数组,找到其中的众数。众数是指在数组中出现次数大于 ⌊ n/2 ⌋ 的元素。
你可以假设数组是非空的,并且给定的数组总是存在众数。
示例 1:
输入: [3,2,3]
输出: 3
示例 2:
输入: [2,2,1,1,1,2,2]
输出: 2
解题思路
- 如果这个数组是有序的,那么数组中间的数一定就是众数,所以一种解法是先排序,然后返回排序后数组中间位置的值,算法时间复杂度是 O(nlogn)
- 摩尔投票算法,这种算法的思想是:众数是出现次数最多的数,众数和其他数出现的次数一一抵消后,剩下的一定就是众数,所以只需遍历一遍数组,就可以找到众数,时间复杂度是 O(n)
class Solution(object):
def majorityElement(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
nums=sorted(nums)
# 找出排在中间的那个数就OK了,“//”是取整
n = len(nums)
return nums[n // 2]
def majorityElement1(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
#从第一个数开始count=1,遇到相同的就加1,遇到不同的就减1,减到0就重新换个数开始计数,总能找到最多的那个
#遍历数据,若前后相等则cnt+1,若不同cnt-1,因为众数的数量一定会超过一半,最终的众数会是最终使cnt=0时的数据
cnt, ret = 0, 0
for num in nums:
if cnt == 0:
ret = num
if num != ret:
cnt -= 1
else:
cnt += 1
return ret