【代码随想录】数组篇

一、二分查找

给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target  ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1

二分查找的前提

有序数组,无重复元素

二分法的写法

1. 左闭右闭,即[ left, right ]

而(左 <= 右)

如果 nums[mid] > trg : 右 = mid-1

def  binary_search(nums,target):
    left=0 
    right=len(nums)-1 #定义target在左闭右闭的区间里,[left, right]
    while left <= right:
        mid= left + (right-left)//2 #防止溢出
        if nums[mid] > target:
            right = mid-1  #target在左区间
        elif nums[mid] < target:
            left = mid+1 #target在右区间
        else:
            return mid
    return -1

2. 左闭右开,即[left, right)

而左<右

如果 nums[mid] > 目标:右=mid

def binary_search(nums, target):
    left=0
    right=len(nums)
    while left < right:
        mid = left + (right-left)//2
        if nums[mid] > target:
            right = mid
        elif nums[mid] < target:
            left = mid+1
        else:
            return mid
    return -1

题目与代码

(1)在排序数组中查找元素的第一个和最后一个位置

给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。

如果数组中不存在目标值 target,返回 [-1, -1]

你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题

思路:target在数组里的左右边界存在三种情况

情况一:target在数组范围的左边或右边,如{3,4,5},target为2或者{3,4,5},target为6,应该返回[-1,-1]

情况二:target在数组范围内,且数组中不存在target,如{3,5,6},target为4,应该返回[-1,-1]

情况三:target在 数组范围内,且数组中存在target,如{3,4,5},target为4,应该返回[1,1]

class Solution:
    def searchRange(self, nums: List[int], target: int) -> List[int]:
        def getRightBorder(nums:List[int], target:int) -> int:
            left, right = 0, len(nums)-1
            rightBoder = -2 # 记录一下rightBorder没有被赋值的情况
            while left <= right:
                middle = left + (right-left) // 2
                if nums[middle] > target:
                    right = middle - 1
                else: # 寻找右边界,nums[middle] == target的时候更新left
                    left = middle + 1
                    rightBoder = left
    
            return rightBoder
        
        def getLeftBorder(nums:List[int], target:int) -> int:
            left, right = 0, len(nums)-1 
            leftBoder = -2 # 记录一下leftBorder没有被赋值的情况
            while left <= right:
                middle = left + (right-left) // 2
                if nums[middle] >= target: #  寻找左边界,nums[middle] == target的时候更新right
                    right = middle - 1
                    leftBoder = right
                else:
                    left = middle + 1
            return leftBoder
        leftBoder = getLeftBorder(nums, target)
        rightBoder = getRightBorder(nums, target)
        # 情况一
        if leftBoder == -2 or rightBoder == -2: return [-1, -1]
        # 情况三
        if rightBoder -leftBoder >1: return [leftBoder + 1, rightBoder - 1]
        # 情况二
        return [-1, -1]
(2)搜索旋转排序数组

 整数数组 nums 按升序排列,数组中的值 互不相同 。

在传递给函数之前,nums 在预先未知的某个下标 k0 <= k < nums.length)上进行了 旋转,使数组变为 [nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]](下标 从 0 开始 计数)。例如, [0,1,2,4,5,6,7] 在下标 3 处经旋转后可能变为 [4,5,6,7,0,1,2] 。

给你 旋转后 的数组 nums 和一个整数 target ,如果 nums 中存在这个目标值 target ,则返回它的下标,否则返回 -1 。

你必须设计一个时间复杂度为 O(log n) 的算法解决此问题。

解题思路:

原始数组是有序的,旋转后只有一边(局部)有序。判断当前 mid 为分割位置分割出来的两个部分 [l, mid] 和 [mid + 1, r] 哪个部分是有序的,并根据有序的那个部分确定我们该如何改变二分查找的上下界:

  • 如果 [l, mid - 1] 是有序数组,且 target 的大小满足 [nums[l],nums[mid]),则我们应该将搜索范围缩小至 [l, mid - 1],否则在 [mid + 1, r] 中寻找。
  • 如果 [mid, r] 是有序数组,且 target 的大小满足 (nums[mid+1],nums[r]],则我们应该将搜索范围缩小至 [mid + 1, r],否则在 [l, mid - 1] 中寻找。

代码:

class Solution:
    def search(self, nums: List[int], target: int) -> int:
        #局部有序,找出有序的部分
        if not nums:
            return -1
        l,r=0,len(nums)-1
        while l<=r:
            mid=l+(r-l)//2
            if nums[mid]==target:
                return mid
            if nums[0]<=nums[mid]: #左边有序
                if nums[0]<=target<nums[mid]:
                    r=mid-1
                else:
                    l=mid+1
            else: #右边有序
                if nums[mid]<target<=nums[len(nums)-1]:
                    l=mid+1
                else:
                    r=mid-1
        return -1

二、移除元素

方法:快慢指针法

快指针:寻找新数组的元素,即不含目标值的元素

慢指针:指向更新新数组下标的位置

**双指针常见于数组、链表和字符串的操作

题目与代码

给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。

不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并原地修改输入数组。

元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素

class Solution:
    def removeElement(self, nums: List[int], val: int) -> int:
        # 快慢指针
        fast = 0  # 快指针
        slow = 0  # 慢指针
        size = len(nums)
        while fast < size:  # 不加等于是因为,a = size 时,nums[a] 会越界
            # slow 用来收集不等于 val 的值,如果 fast 对应值不等于 val,则把它与 slow 替换
            if nums[fast] != val:
                nums[slow] = nums[fast]
                slow += 1
            fast += 1
        return slow

三、有序数组的平方

方法:双指针法

∵ 数组平方的最大值就在数组的两端,不是最左边就是最右边,不可能是中间

题目与代码

给你一个按 非递减顺序 排序的整数数组 nums,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。

class Solution:
    def sortedSquares(self, nums: List[int]) -> List[int]:
        #双指针
        result=[0]*len(nums)
        l,r,i = 0,len(nums)-1,len(nums)-1
        while l <= r:
            if nums[r]**2 > nums[l]**2:
                result[i]=nums[r]**2
                r-=1
            else:
                result[i]=nums[l]**2
                l+=1
            i-=1
        return result

四、长度最小的子数组

方法:滑动窗口

不断的调节子序列的起始位置和终止位置,从而得出我们要想的结果

  • 窗口内是什么?

满足和 ≥ s 的长度最小的连续 子数组

  • 如何移动窗口的起始位置?

若当前窗口的值 ≥ s,窗口就要向前移动,即缩小窗口的值

  • 如何移动窗口的结束位置?

for循环的索引

题目与代码

class Solution:
    def minSubArrayLen(self, target: int, nums: List[int]) -> int:
        #滑动窗口法
        min_len=float('inf') #用于更新最小长度
        l,r=0,0
        cur_sum=0 #用于累加
        while r < len(nums):
            cur_sum+=nums[r] #累加
            #判断当前值是否超过目标值
            while cur_sum >= target:
                min_len =min(min_len,r-l+1) #更新长度
                #缩小窗口
                cur_sum-=nums[l]
                l+=1
            r+=1
        
        return min_len if min_len != float('inf') else 0

五、螺旋矩阵Ⅱ

方法:循环不变量原则

模拟顺时针画矩阵的过程:上行从左到右-右列从上到下-下行从右到左-左列从下到上

每一条边保持相同的左闭右开或者左开右闭的原则

题目与代码

class Solution:
    def generateMatrix(self, n: int) -> List[List[int]]:
        # 初始化 n x n 矩阵
        matrix = [[0]*n for _ in range(n)]

        # 初始化边界和起始值
        top, bottom, left, right = 0, n-1, 0, n-1
        num = 1

        while top <= bottom and left <= right:
            # 从左到右填充上边界
            for i in range(left, right + 1):
                matrix[top][i] = num
                num += 1
            top += 1

            # 从上到下填充右边界
            for i in range(top, bottom + 1):
                matrix[i][right] = num
                num += 1
            right -= 1

            # 从右到左填充下边界

            for i in range(right, left - 1, -1):
                matrix[bottom][i] = num
                num += 1
            bottom -= 1

            # 从下到上填充左边界

            for i in range(bottom, top - 1, -1):
                matrix[i][left] = num
                num += 1
            left += 1

        return matrix

六、区间和

方法:前缀和

前缀和的思想是重复利用计算过的子数组之和,从而降低区间查询需要累加计算的次数

前缀和在涉及计算区间和的问题时非常有用

题目与代码

def main():
    n=int(input())
    cursum=[]
    sum=0
    for _ in range(n):
        num=int(input())
        sum+=num
        cursum.append(sum)
        
    while True:
        try:
            a,b=map(int,input().split())
        except:
            break
        if a == 0:
            print(cursum[b])
        else:
            print(cursum[b]-cursum[a-1])

if __name__ == '__main__':
    main()

七、开发商购买土地

方法:前缀和

本题要求任何两个行(或者列)之间的数值总和,也就是前缀和的思路。分别统计前n行或前n列的和q[n],a行和b行之间的总和,即q[b]-q[a-1]

题目与代码

​​​​​​​

n,m=map(int,input().split())
array=[[0]*m for _ in range(n)]
sum=0 #所有元素总和
#存储元素
for i in range(n):
    lines=list(map(int,input().split()))
    for j in range(len(lines)):
        array[i][j]=lines[j]
        sum+=lines[j]
        
#统计横向和
horizon=[0]*n
for i in range(n):
    for j in range(m):
        horizon[i]+=array[i][j]
    
#统计纵向和
vertical=[0]*m
for j in range(m):
    for k in range(n):
        vertical[j]+=array[k][j]
        
#横向切割
res=float('inf')
horiticalcut=0
for i in range(n):
    horiticalcut+=horizon[i]
    res=min(res,abs(sum-2*horiticalcut))

#纵向切割
verticalcut=0
for i in range(m):
    verticalcut+=vertical[i]
    res=min(res,abs(sum-2*verticalcut))
    
print(res)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值