【chap3-数组】用Python3刷《代码随想录》

文章介绍了数组的基础知识,包括其内存特性和元素访问方式,并详细阐述了二分查找算法的实现,包括不同边界条件的处理。同时,文章通过多个实例展示了如何运用双指针和滑动窗口解决数组问题,如查找元素范围、删除元素、移动零、比较含退格的字符串等。此外,还涉及了有序数组的平方和长度最小的子数组等问题的解决方案。
摘要由CSDN通过智能技术生成

数组是存储在连续内存空间上的、相同类型数据的集合(在删除或者增添元素时难免要移动其他元素的地址)

在数组中,可以通过下标索引获取对应数据(数组下标都是从0开始)

数组的元素是不能删的,只能覆盖


目录

704. 二分查找

(一)定义target在 [left, right] 里

(二)定义target在 [left, right) 里

相关题目推荐

35. 搜索插入位置

34. 在排序数组中查找元素的第一个和最后一个位置【middle】

69. x 的平方根 

367. 有效的完全平方数

27. 移除元素

(一)暴力解法【两个for循环】

(二)双指针法/快慢指针法【一个for循环即可】

相关题目推荐

26. 删除有序数组中的重复项

283. 移动零

844. 比较含退格的字符串

977. 有序数组的平方

209. 长度最小的子数组【middle】

(一)暴力解法

(二)滑动窗口

相关题目推荐

904. 水果成篮【middle】

3. 无重复字符的最长子串【middle】

76. 最小覆盖子串【hard】

59. 螺旋矩阵 II

相关题目推荐

54. 螺旋矩阵

剑指 Offer 29. 顺时针打印矩阵


704. 二分查找

使用二分法的前提条件:数组有序+数组中无重复元素

两种写法的时空间复杂度均:

  • 时间复杂度:O(logn)
  • 空间复杂度:O(1)

(一)定义target在 [left, right] 里

while (left <= right) : 可以带等号

如果nums[middle] > target,则更新搜索范围右下标right为middle-1


class Solution:
    def search(self, nums: List[int], target: int) -> int:
        # 定义target在左闭右闭的区间
        left = 0
        right = len(nums)-1
        while left <= right:
            middle = (right + left) // 2
            if nums[middle] > target:
                right = middle-1
            elif  nums[middle] < target:
                left = middle+1
            else:
                return middle
        # 未找到目标值
        return -1

(二)定义target在 [left, right) 里

跟(一)左闭右闭的代码相比,有以下几处不同:

  • right = len(nums),不需要-1

  • while (left < right) ,不带等号

  • 如果nums[middle] > target,则更新搜索范围右下标right为middle


class Solution:
    def search(self, nums: List[int], target: int) -> int:
        left = 0
        right = len(nums)
        while left < right:
            middle = (left + right) // 2
            if nums[middle] > target:
                right = middle
            elif nums[middle] < target:
                left = middle+1
            else:
                return middle
        return -1

相关题目推荐

35. 搜索插入位置

暴力解法时间复杂度为O(n),而本题要求必须使用时间复杂度为 O(logn) 的算法 —— 二分查找法

重点:如果目标值不存在于数组中,返回它将会被按顺序插入的位置,为此时的left指针


class Solution:
    def searchInsert(self, nums: List[int], target: int) -> int:
        left = 0
        right = len(nums)-1
        while left <= right:
            middle = (left+right)//2
            if nums[middle] > target:
                right = middle -1
            elif nums[middle] < target:
                left = middle +1
            else:
                return middle
        return left

34. 在排序数组中查找元素的第一个和最后一个位置【middle】

题目要求时间复杂度为O(logn)

考虑 target 开始和结束位置,其实我们要找的就是数组中「第一个等于 target 的位置」(记为 leftIdx)和「第一个大于 target 的位置减一」(记为 rightIdx)

三种情况:

思路参考: 官方题解
  • 在nums数组中二分查找得到第一个 ≥ target 的下标 leftidx

  • 在nums数组中二分查找得到第一个 ≥ target+1 的下标,减1为 rightidx

  • 如果第一个 ≥ target 的下标 leftidx 在数组右边,或者数组里不存在target,则返回 [-1, -1],否则返回 [leftidx, rightidx]


class Solution:
    def searchRange(self, nums: List[int], target: int) -> List[int]:
        def binarySearch(nums, target):
            left = 0
            right = len(nums)-1
            while left <= right:
                middle = (left + right)//2
                if nums[middle] >= target:
                    right = middle-1
                else:
                    left = middle+1
            return left   # 若存在target,则返回第一个=target的值
  
        # 第一个位置:返回第一个 >= target 元素的下标
        leftidx = binarySearch(nums, target)
        # 第二个位置:返回第一个 > target 元素的下标,再减1
        rightidx = binarySearch(nums, target+1)-1

        if leftidx == len(nums) or nums[leftidx] != target:
            return [-1,-1]
        else:
            return [leftidx, rightidx]

69. x 的平方根 

注意,例如 8 的算术平方根是 2.82842..., 由于返回类型是整数,小数部分将被舍去,最后输出结果是2。因此,最后跳出 while循环时,返回的是 right指针,而不是left指针(跳出循环后,left指针在right指针的右侧) 


class Solution:
    def mySqrt(self, x: int) -> int:
        left = 0
        right = x
        while left <= right:
            middle = (left + right) // 2
            if middle*middle > x:
                right = middle-1
            elif middle*middle < x:
                left = middle+1
            else:
                return middle
        return right

367. 有效的完全平方数


class Solution:
    def isPerfectSquare(self, num: int) -> bool:
        left = 0
        right = num
        while left<=right:
            middle = (left+right)//2
            if middle*middle > num:
                right = middle-1
            elif middle*middle < num:
                left = middle+1
            else:
                return True
        return False

27. 移除元素

不需要考虑数组中超出新长度后面的元素

数组的元素在内存地址中是连续的,不能单独删除数组中的某个元素,只能覆盖

(一)暴力解法【两个for循环】

一个for循环遍历数组元素【注意实际写的是while】,一个for循环更新数组

  • 时间复杂度O(n^2)
  • 空间复杂度O(1)

class Solution:
    def removeElement(self, nums: List[int], val: int) -> int:
        i = 0
        l = len(nums)
        while i < l:   # 遍历数组元素
            if nums[i] == val:
                for j in range(i+1, l):   # 更新数组
                    nums[j-1] = nums[j]
                i = i-1   # 因为下标i以后的数值都向前移动了一位,所以i也向前移动一位
                l = l-1   # 此时数组长度-1
            i = i+1
        return l

(二)双指针法/快慢指针法【一个for循环即可】

时间复杂度O(n),空间复杂度O(1)

  • 快指针:寻找新数组里所需要的元素,也即删除目标值之后的元素

  • 慢指针:为新数组的下标值

把快指针所获得的值赋给慢指针所在的位置


class Solution:
    def removeElement(self, nums: List[int], val: int) -> int:
        fast = 0
        slow = 0
        for fast in range(len(nums)):
            if nums[fast] != val:
                nums[slow] = nums[fast]
                slow = slow +1
        return slow  

注意该方法并没有改变元素的相对位置

相关题目推荐

26. 删除有序数组中的重复项


class Solution:
    def removeDuplicates(self, nums: List[int]) -> int:
        if len(nums) == 0:
            return 0
        else:
            # 删除重复元素后,也至少有1个元素,所以下标都从1开始
            fast = 1
            slow = 1
            for fast in range(1,len(nums)):
                if nums[fast] != nums[fast-1]:
                    nums[slow] = nums[fast]
                    slow += 1
            return slow

283. 移动零

这题的重点在于交换,设置一个左指针left和一个右指针right,让right从左依次遍历每一个元素,当遇到非0元素时就和left指向的元素交换


class Solution:
    def moveZeroes(self, nums: List[int]) -> None:
        """
        Do not return anything, modify nums in-place instead.
        """
        left = 0
        right = 0
        for right in range(len(nums)):
            if nums[right] != 0:
                nums[left], nums[right] = nums[right], nums[left]
                left += 1
            right += 1

844. 比较含退格的字符串

思路是看Leetcode刷题 844. 比较含退格的字符串 Backspace String Compare_哔哩哔哩_bilibili看懂的

从后往前读字符串,backspaceS和backspaceT是用来记录退格的

三种情况:

  • 当前指针遇到退格键,backspaceS变量+1,指针i继续向左走

  • 没有遇到退格,但backspaceS变量不为0,此时需要将backspaceS变量-1,指针i继续向左走

  • 没有遇到退格,且backspaceS和backspaceT变量都为0,此时将两个字符串的指针所指字符进行比较,若不相等,则不是一个字符串;若相等,两个指针都分别向左走一位,继续比较


class Solution:
    def backspaceCompare(self, s: str, t: str) -> bool:
        # 记录两个字符串#的数量
        backspaceS = 0
        backspaceT = 0
        # 两个指针
        i = len(s)-1
        j = len(t)-1
        while i>=0 or j>=0:
            while i>=0:
                if s[i] == '#':  # 第一种情况
                    backspaceS += 1
                    i -= 1
                elif backspaceS > 0:  # 第二种情况
                    backspaceS -= 1
                    i -= 1
                else:
                    break   # i处理完了,j还没处理。此时i在非退格,且退格数为0的情况下

            while j>=0:
                if t[j] == '#':
                    backspaceT += 1
                    j -=1
                elif backspaceT > 0:
                    backspaceT -= 1
                    j -=1
                else:
                    break

            # 第三种情况,比较两者
            if i>=0 and j>=0:    # 都指向字符
                if s[i] != t[j]:
                    return False
            elif i>=0 or j>=0:   # 字符和空比较
                return False
            i -= 1
            j -= 1 
        return True

977. 有序数组的平方

数组是有序的,只不过负数平方之后可能成为最大数了。那么数组平方的最大值就在数组两端,要么是最左边,要么是最右边,不可能是中间

  • 双指针,i指向起始位置,j指向终止位置,逐步向中间合拢

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

时间复杂度为O(n)


class Solution:
    def sortedSquares(self, nums: List[int]) -> List[int]:
        ans = [0] * len(nums)
        pos = len(nums) - 1   # 由大到小更新 

        i = 0
        j = len(nums) - 1
        while i<=j:
            if nums[i]*nums[i] > nums[j]*nums[j]:
                ans[pos] = nums[i]*nums[i]
                i += 1
            else:
                ans[pos] = nums[j]*nums[j]
                j -= 1
            pos -= 1
        return ans

209. 长度最小的子数组【middle】

(一)暴力解法

两个for循环,一个控制起始位置,一个控制终止位置,时间复杂度O(n^2)

超时了,略

(二)滑动窗口

重点:若只用一个for循环,那么这个循环的索引,一定是表示滑动窗口的终止位置 

  • 窗口内的元素:保持窗口内数值总和 ≥s 的长度最小的连续子数组

  • 移动窗口的起始位置:如果当前窗口的值≥s,则窗口向前移动(即缩小窗口,是一个持续向后移动的过程,而不是单次移动)

  • 移动窗口的结束位置:for循环遍历数组的指针

时间复杂度O(n),空间复杂度O(1)

  • 不要以为for里放一个while时间复杂度就是O(n^2),主要是看每一个元素被操作的次数:每个元素在滑动窗口进来操作一次,出去操作一次,即每个元素都是被操作两次,时间复杂度是 2 × n 也就是O(n)

class Solution:
    def minSubArrayLen(self, target: int, nums: List[int]) -> int:
        result = float("inf")   # 定义一个无限大的数
        i = 0  # 起始位置
        sum = 0
        for j in range(len(nums)):   # j为终止位置
            sum += nums[j]
            while sum >= target:   # 是while,不是if,是一个持续向后移动的过程
                sublength = j-i+1  # 此时子数组的长度
                result = min(result, sublength)
                sum -= nums[i]   # 移动起始位置,缩小窗口
                i += 1
        return 0 if result == float("inf") else result

相关题目推荐

904. 水果成篮【middle】

翻译一下题目:给定一个数组,要求我们从这个数组中选取一个子数组,条件是这个子数组的长度最长,并且这个子数组中元素种类不能超过2

思路:滑动窗口 Leetcode(Sliding windows)-Python-904-水果成篮

  • 用两个指针,一个是left,一个是right,初始都为0

  • right++,如果没有违背题目要求的性质(子数组中元素种类不能超过2),就计算一下目前子数组的长度right-left+1

  • 直到违背性质,left++,直到不违背性质为止

  • 整个过程中记录子数组的最大长度max_length

这一题与209. 长度最小的子数组的差别在于:(1)209是求长度最小,而本题是求长度最长,所以初始化时,长度最小的话初始化应是float("inf"),长度最长的话初始化是float("-inf");(2)209的要求就是长度,而本题的要求是窗口里数字的种类,通过一个dict来记录数字的种类(key为数字,value为该数字出现次数)


class Solution:
    def totalFruit(self, fruits: List[int]) -> int:
        max_length = float("-inf")
        left = 0
        dic = {}

        for right in range(len(fruits)):
            if fruits[right] not in dic:
                dic[fruits[right]] = 1
            else:
                dic[fruits[right]]+=1

            while len(dic)>2:   # 超过2类
                dic[fruits[left]] -= 1
                if dic[fruits[left]]==0:
                    del dic[fruits[left]]  # 同时删掉key和value
                left+=1

            max_length = max(max_length, right-left+1)
        return 0 if max_length == float("-inf") else max_length
求窗口长度min,那么一开始初始化为正无穷大float("inf")
求窗口长度max,那么一开始初始化为负无穷大float("-inf")

3. 无重复字符的最长子串【middle】

这题跟904的区别在于:904是要统计每个数字及对应的出现次数,用的是dict类型;而本题是要求字符无重复,用的是set类型

当滑动窗口的右边right移动时,若它指向的元素已经在集合里,则将滑动窗口左边left指向的元素从集合里remove掉,直到右边right指向的元素不在集合里了为止(是一个持续remove的过程,故为while,而不是if)。此时比较一下长度,并将right指向的元素add进集合里


class Solution:
    def lengthOfLongestSubstring(self, s: str) -> int:
        left = 0
        max_length = float("-inf")
        mark = set()   # 集合不能有重复元素
        for right in range(len(s)):
            while s[right] in mark:
                mark.remove(s[left])
                left+=1
            max_length = max(max_length, right-left+1)
            mark.add(s[right])
        return 0 if max_length == float("-inf") else max_length

76. 最小覆盖子串【hard】

这题好难,明白了思路但是实际写的时候还是无从下手,还是得多写几遍才能熟...

要求:两个字符串,一个s,一个t,要求返回s中涵盖t所有字符的最小子串

思路:

  • 通过collections库里的Counter类,生成一个字典template_dict,统计t里的字符及出现次数

  • 生成一个字典window_dict,该字典的key和template_dict的key是一样的,value通过后续遍历s字符串的时候再统计

  • 定义一个函数isContains,该函数的目的是看window_dict里每个key的值是不是≥template_dict里每个key的值,若不是就直接返回False

  • 最后就是滑动窗口的老步骤,end指针在s字符串中逐步往右遍历,若遍历到的字符在字典template_dict里(即是t字符串里的字符),则对应value加1;直到window_dict里每个key的值都≥template_dict里每个key的值;再移动左指针start


class Solution:
    def minWindow(self, s: str, t: str) -> str:
        from collections import Counter   
        template_dict = Counter(t)  # 统计一个字符串里的字符及出现次数

        # 初始化一个窗口,key和template_dict的key是一样的
        window_dict = {}
        for each_key in template_dict:
            if each_key not in window_dict:
                window_dict[each_key] = 0

        def isContains(cur_dict, tmp_dict):
            for each_key in tmp_dict:
                if cur_dict[each_key] < tmp_dict[each_key]:
                    return False
            return True

        start = 0
        min_len = float('inf')
        res = ""
        for end in range(len(s)):
            if s[end] in template_dict:
                window_dict[s[end]] += 1
            # 判断window_dict是否包含template_dict
            while isContains(window_dict, template_dict):
                if min_len > end-start+1:
                    min_len = end-start+1
                    res=s[start:end+1]
                if s[start] in window_dict:
                    window_dict[s[start]]-=1
                start+=1
        return res 

59. 螺旋矩阵 II

看这个视频才看懂思路:每日一题——leecode59( 螺旋矩阵 II)讲解最详细零基础——python_哔哩哔哩_bilibili,对应文章:每日一题——leecode59( 螺旋矩阵 II)


class Solution:
    def generateMatrix(self, n: int) -> List[List[int]]:
        matrix = [[0]*n for _ in range(n)]   # 构造正方形矩阵
        right,left,up,down = n-1,0,0,n-1   # 定义四个指针
        number = 1  # 要填充的数,从1开始填充

        while right>left and down>up:
            for x in range(left,right):
                matrix[up][x] = number
                number += 1
            for y in range(up,down):
                matrix[y][right] = number
                number += 1
            for x in range(right,left,-1):
                matrix[down][x] = number
                number += 1
            for y in range(down,up,-1):
                matrix[y][left] = number
                number += 1
            # 缩圈
            up += 1
            right -= 1
            left += 1
            down -= 1

        if n%2 != 0:   # 奇数的话,中间的数没填充上,是0
            matrix[n//2][n//2] = n**2  # 若是3的话,中间的位置是(1,1)

        return matrix

相关题目推荐

54. 螺旋矩阵


class Solution:
    def spiralOrder(self, matrix: List[List[int]]) -> List[int]:
        m = len(matrix)
        n = len(matrix[0])
        left, right, up, down = 0, n-1, 0, m-1

        nums = []
        while right>=left and down>=up:
            for j in range(left,right+1):   # left到right
                nums.append(matrix[up][j])
            for i in range(up+1,down+1):   # up+1到down
                nums.append(matrix[i][right])
            if left<right and up<down:
                for j in range(right-1,left,-1):   # right-1到left+1
                    nums.append(matrix[down][j])
                for i in range(down,up,-1):    # down到up+1 
                    nums.append(matrix[i][left])
            left+=1
            right-=1
            up+=1
            down-=1   
        return nums

剑指 Offer 29. 顺时针打印矩阵

跟上题54基本一模一样,有个小差别:


class Solution:
    def spiralOrder(self, matrix: List[List[int]]) -> List[int]:
        if not matrix:
            return []
            
        m = len(matrix)
        n = len(matrix[0])
        left, right, up, down = 0, n-1, 0, m-1

        nums = []
        while right>=left and down>=up:
            for j in range(left,right+1):
                nums.append(matrix[up][j])
            for i in range(up+1,down+1):
                nums.append(matrix[i][right])
            if left<right and up<down:
                for j in range(right-1,left,-1):
                    nums.append(matrix[down][j])
                for i in range(down,up,-1):
                    nums.append(matrix[i][left])
            left+=1
            right-=1
            up+=1
            down-=1
                
        return nums
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Cheer-ego

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值