数组例题整理
-
参考:题目+知识点整理
-
概念
我们知道常用的数据存储方式有两种:顺序存储和非顺序存储。顺序存储就是把数据存储在一块连续的空间内。数组(array)就是典型的顺序存储,而链表就是典型的非顺序存储。
数组通常用于存储一系列相同类型的数据。当我们在创建数组时,会在内存中划分出一块连续的内存用于存储数据,插入数据时,会将数据按顺序存储在这块连续的内存中,读取时通过访问数组的索引迅速取出。数组名就是一个指针,指向这段内存的起始地址。通过数组的类型,编译器知道在访问下一个元素的时候需要在内存中后移多少个字节。由于数组在存储时是顺序存储的,存储数据的内存也是连续的,所以数组在读取数据时比较容易,随机访问速度快,但是插入和删除就比较费劲了。读取可以直接根据索引,插入和删除则比较耗时,插一个数据需要移动大量元素,在内存中空出一个元素的空间,然后将要增加的元素放在其中,如果想删除一个元素,同样需要移动大量元素去掉被移动的元素。所以如果需求是快速访问数据,很少或者几乎不插入和删除元素,数组是一个不错的选择。
最常见的有一维数组和二维数组,稍微复杂一点的是多维数组和动态数组。在c++中,STL提供了Vector,在Java中,Collection集合中提供了ArrayList和Vector,对于Python而言,内置的List就是一个动态指针数组。当列表中没有空间存储新的元素时,列表会动态地改变大小以容纳新的元素,每次改变大小时,会预留一部分空间以降低改变大小的频率。
第一部分:K-SUM
思路:
这类题目通常会给定一个数组和一个值,让求出这个数组中两个/三个/K个值的和等于这个给定的值target。leetcode第一题就是two-sum,对于这类题目,首先看题目要求的时间复杂度和空间复杂度是什么,其次看有没有限制条件,如要求不能有重复的子数组或者要求按照升序/降序排列等。解法如下:
- 暴力解法:最常见,但是通常会超时,只能作为备选,
- hash-map:建立一个hash-map循环遍历一次即可
- two-pointers:定位两个指针根据和的大小来移动另外一个。这里设定的指针个数根据题目中K的个数来定。3Sum中可以设定3个指针,固定两个,移动另一个
4.27:
1.两数和:
#两数之和-hash-map
def twoSum(self, nums, target) :
dic = {}
for i,num in enumerate(nums) :
if num in dic.keys():
return [ dic[num],i]
else:
dic[target - num ] = i
#时间复杂 O(n)
#空间复杂O(n)
#用双指针的话需要有序,才能记录位置,可以对数组的索引进行排序
sorted_id_lst = sorted(range(len(nums)), key=lambda x: nums[x]) # 记录排序后的位置索引(不会真的对原nums排序)
def twoSum(self, nums: List[int], target: int) -> List[int]:
sorted_id_lst = sorted(range(len(nums)), key=lambda x: nums[x]) # 记录排序后的位置索引(不会真的对原nums排序)
left = 0
right = len(nums) -1
nums.sort()
while left <= right:
res = nums[left] + nums[right]
if res == target:
return [sorted_id_lst[left],sorted_id_lst[right]]
elif res > target:
right -= 1
else :
left += 1
return None
#时间复杂:O(nlogn+n)
#空间复杂O(n)要创造额外一个索引的数组
两种方法,这里值得学习是可以利用索引排序记录原数组的位置
15.三数和
# 三数和
def threesum(nums,target):
nums = sorted(nums)
n = len(nums)
result = []
if n < 3:
return []
for i in range(n):
L = i + 1
R = n - 1
if i > 0 and nums[i] == nums[i-1] :
continue
while(L < R):
if nums[i] + nums[L] +nums[R] == target:
result.append([nums[i],nums[L],nums[R]])
while L < R and nums[L] == nums[L+1]:
L = L + 1
while L < R and nums[R] == nums[R-1]:
R = R - 1
L = L + 1
R = R - 1
elif nums[i] + nums[L] +nums[R] > target:
R = R - 1
else:
L = L + 1
return result
#时间复杂 O(nlogn) + O(n)*O(n) = O(n2)[遍历+双指针]
关键是注意边界条件,以及如何去重,在判断相等后里面加上while。
第二部分-区间
这类题目通常会给一个包含多个子数组的数组,然后针对区间是否重合来判断true or false。
- 解题技巧:
1.按start排序【若有按第二个排序,用sorted( ,key= lambda x: x[1])】
2.在前一个区间的end和后一个区间的start找交集
252. 会议室 I
def canAttendMeetings(self, intervals: List[List[int]]) -> bool:
nums = sorted(intervals, key = lambda x: x[0])
if len(nums) <= 1:
return True
low = nums[0][0]
high = nums[0][1]
for i,num in enumerate(nums):
if i > 0:
if num[0] < high :
return False
high = nums[i][1]
return True
最简单的判断吧,区间重叠即不行
56.区间合并
def merge(self, intervals: List[List[int]]) -> List[List[int]]:
if len(intervals) == 0:
return []
res = []
intervals = list(sorted(intervals))
low = intervals[0][0]
high = intervals[0][1]
for i in range(1,len(intervals)):
if high >= intervals[i][0]:
if high < intervals[i][1]:
high = intervals[i][1]
else:
res.append([low,high]) #先添加再赋值
low = intervals[i][0]
high = intervals[i][1]
res.append([low,high])
#这样最后一次也可以添加,而且,如果是一个区间全覆盖的话,也可以保证最后有东西添加
return res
需要考虑:区间部分重叠,区间包含,还是以一个区间的尾和下一个区间的头来判断。
时间复杂度:O(nlogn) 【用到了排序】
空间复杂度:O(n)
253. 会议室 II
最少安排多少间会议室不冲突
def minMeetingRooms(self, intervals: List[List[int]]) -> int:
merge = sorted([(k,i) for num in intervals for i,k in enumerate(num)],
key = lambda x: (x[0],-x[1]))
cnt = 0
ans = 0
for i,(time,state) in enumerate(merge):
if state == 0:
cnt += 1
else:
cnt -= 1
ans = max(ans,cnt)
return ans
时间复杂度 :两个排序也是O(nlogn)?不太确定
解决思路:将时间排序,寻找最大的重合数目。
做法:用一个元组(time,state)state是开始or结束。对元组time升序,state降序。降序的目的是如果一个结束,一个开始。应该先扣除结束(-1),在计算开始(+1)。
57.插入区间
def insert(self, intervals: List[List[int]], newInterval: List[int]) -> List[List[int]]:
n = len(intervals)
l = newInterval[0]
h = newInterval[1]
ans = []
i,j = 0,0
if intervals == []:
return [newInterval]
while i < n and intervals[i][0] <= h:
i += 1
while j < n and intervals[j][1] < l:
j+= 1
for k in range(j):
ans.append(intervals[k])
l = min(l,intervals[j][0]) if j < n else l
h = max(h,intervals[i-1][1]) if i >= 1 else h
ans.append([l,h])
for k in range(i,n):
ans.append(intervals[k])
return ans
这道题,麻烦的做法,插入,再合并区间;插入的时候只要找到对应的位置就好,因为原先是有序的。
自己的做法,找到需要合并区间的头与尾,合并。相当三部分,合并头区间之前的区间,合并的区间,合并尾区间之后的区间。
需要注意:
- 边界条件:这边自己加的i,j是最容易报错的地方,主要是不能超过索引的范围。
- 极端情况: 当区间头尾重叠时,注意,<=h与<l。当尾区间起始与新区间结尾重叠时,要比较的是尾区间结尾与新区间结尾的大小(因为后面用的是i-1,所以i也要+1);而头区间结尾与新区间起始重叠时,就不需要。可以理解为一个向上看,一个向下看。
执行用时 :36 ms, 在所有 Python3 提交中击败了98.67%的用户
内存消耗 :15.3 MB, 在所有 Python3 提交中击败了100.00%的用户
难得难得,又改进了下,i从j的位置开始走,就更快一点,但时间复杂度也还是O(n)
第三部分—子数组
这类题目通常会在一个包含多个子数组的数组中,求和/积,最大最小等。
53. 最大子序和
求连续子数组的最大和
有三种方法:暴力、分治、动态规划
暴力法
def maxSubArray(self, nums: List[int]) -> int:
arr = nums[0]
max_ = nums[0]
if len(nums) == 1:
return max_
for i in range(1,len(nums)):
if arr + nums[i] > nums[i]:
max_ = max(max_,arr+nums[i])
arr = arr + nums[i]
else:
max_ = max(arr,nums[i],max_)
arr = nums[i]
return max_
时间点复杂度O(n)
思路:遍历数组,两个变量,一个记录当前和(arr),一个记录最大和。
209. 长度最小的子数组
给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的连续子数组,并返回其长度。如果不存在符合条件的连续子数组,返回 0。
def minSubArrayLen(self, s: int, nums: List[int]) -> int:
l = 0
r = 0
n = len(nums)
ans = 0 if sum(nums) < s else n
while l <= r and r < n:
arr = sum(nums[l:r+1])
if arr < s:
r += 1
else:
ans = min(ans,r-l+1)
l += 1
return ans
这是自己的做法,可以说极慢无比。想到了双指针,但是做法比较僵硬。
为什么会慢?这样用双指针是无序的,不是线性的
改进做法:
def minSubArrayLen(self, s: int, nums: List[int]) -> int:
l = 0
r = 0
n = len(nums)
ans = 0 if sum(nums) < s else n
arr = nums[0] if nums else 0
ans = 1 if arr >= s else ans
for i in range(1,len(nums)):
arr = arr + nums[i]
while arr >= s:
arr = arr - nums[l]
l += 1
ans = min(ans,i - l + 2)
return ans
执行用时 :48 ms, 在所有 Python3 提交中击败了92.04%的用户
内存消耗 :15.3 MB, 在所有 Python3 提交中击败了83.33%的用户
明显快了很多,第一个指针用for循环。对于第二个,考虑极端情况,对于每一次for循环(也就是每个元素都大于等于S),我都需要移动l,所以总共有2n次动作,还是线性的。
第四部分—旋转
189. 旋转数组
def rotate(self, nums: List[int], k: int) -> None:
n = len(nums)
k = k % n
nums[:] = nums[n-k:] + nums[:n-k]
nums = nums[:]
def rotate(self, nums: List[int], k: int) -> None:
for i in range(k):
nums.insert(0,nums.pop())
注意:K是可以超过长度的,所以要取余
48.旋转图像
def rotate(self, matrix: List[List[int]]) -> None:
"""
Do not return anything, modify matrix in-place instead.
"""
l1 = l2 = l3 = 0
n = len(matrix)
for i in range(n//2):
for j in range(i,n-i-1):
l1 = matrix[j][n-i-1]
matrix[j][n-i-1] = matrix[i][j]
l2 = matrix[n-i-1][n-j-1]
matrix[n-i-1][n-j-1] = l1
l3 = matrix[n-j-1][i]
matrix[n-j-1][i] = l2
matrix[i][j] = l3
要求是原地旋转,就不能创造一个新的0矩阵来操作。所以只能逐层旋转,需要三个中间变量来存储。