算法:数组例题-python

数组例题整理

我们知道常用的数据存储方式有两种:顺序存储和非顺序存储。顺序存储就是把数据存储在一块连续的空间内。数组(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矩阵来操作。所以只能逐层旋转,需要三个中间变量来存储。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值