代码随想录刷题记录(一)——数组

数组

数组理论基础

概念:数组是存放在连续内存空间上的相同类型数据的集合,如:

在这里插入图片描述
数组特点:数组的内存空间的地址连续,元素是不能删的,只能覆盖。如果要添加或删除元素,就要移动别的元素。
内存地址特点:通常以“0x”开头,表示随后的数字是十六进制格式,如0x7ffee4065820。计算机中字节(byte)是最基本的数据单位,每个字节包含8位(bit)。一般int类型的大小是4个字节,意味着相邻的int数组元素在内存中相隔4个字节。

二分查找

特点:有序元素数组是二分查找的基础(无重复更好)。
原题
题目描述:给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
思路:采用二分法,在初始区间中取中间索引的值,将其与目标值比较,并根据大小关系不断缩短搜索区间。
注意:为了防止溢出,在计算区间的中点时,我们采用middle = left + (right - left)// 2 (//为floor division,返回商的整数部分)
代码

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

注意:上面更新方式为right = middle-1left = middle+1这样可以确保每次迭代都会缩小搜索区间,并且避免无限循环的可能性。

相关题目

35.搜索插入位置
题目表述:给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
代码

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

解析为什么返回right+1:不满足while left <= right:的上一轮必然是left==right==middle,而由于nums[middle]>target,使得left = middle+1 >right。又因为在缩为一个值之前,target值必然包含在[left,right]的区间内的。所以可以判断,right 指向了 target 应该被插入的位置的前一个索引。

34. 在排序数组中查找元素的第一个和最后一个位置
题目表述:给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。如果数组中不存在目标值 target,返回 [-1, -1]。
思路:寻找target在数组的左右边界。采用两个二分法分别查找左边界和右边界。值得注意的是,题目说升序,但没说无重复元素,

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
                    rightBoder = middle
                    left = middle + 1
            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往左走
                    leftBoder =  middle
                    right = middle - 1
                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 >=0: return [leftBoder, rightBoder]
        # 情况二
        return [-1, -1]

注意:在寻找右边界时,当nums[middle]==target时,左边界会往右移动,所以可能会出现,区间收敛到目标区间的右侧一位(因为left = middle+1)。下一步由于target在左侧,right会更新导致区间长度<0而结束循环。所以在left更新前更新右边界rightBoder = middle。左边界同理

69. x的平方根
题目表述:给你一个非负整数 x ,计算并返回 x 的 算术平方根 。
由于返回类型是整数,结果只保留 整数部分 ,小数部分将被 舍去 。
注意:不允许使用任何内置指数函数和算符,例如 pow(x, 0.5) 或者 x ** 0.5。
思路

在这里插入代码片

367. 有效的完全平方数
题目表述:给你一个正整数 num 。如果 num 是一个完全平方数,则返回 true ,否则返回 false 。
完全平方数 是一个可以写成某个整数的平方的整数。换句话说,它可以写成某个整数和自身的乘积。
不能使用任何内置的库函数,如 sqrt 。

思路

移除元素

题目描述:给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并原地修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
思路:由于数组不能单独删除某个元素,只能覆盖。可以采用快慢指针法,快指针遍历数组,遇到不等于目标值的元素,将其赋值给慢指针,慢指针移动一位;否则快指针一位,但慢指针不移动。

def removeElement(nums, val):
	fast, slow = 0, 0
	# 遍历数组
	while i<len(nums):
		# 如果当前元素不等于val,加其赋值到索引j,并向右移动
		if num[fast] != val:
			num[slow] = num[i]
			slow +=1
		fast += 1
	return slow

相关题目

283.移动零
题目描述:给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。
请注意 ,必须在不复制数组的情况下原地对数组进行操作。
思路:注意要原地操作。采用快慢指针都从索引为0开始往右走,当快指针遇到不为0的元素,则赋值给慢指针。最后将慢指针往后的元素赋零。

class Solution:
    def moveZeroes(self, nums: List[int]) -> None:
        slow,fast = 0,0
        while fast <= len(nums)-1:
            if nums[fast] != 0:
                nums[slow] = nums[fast]
                slow += 1
            fast += 1
        while slow < len(nums):
            nums[slow] = 0
            slow += 1
            
        # for i in range(slow, len(nums)):
        #     nums[i] = 0

        # nums[slow:] = [0]*(len(nums)-slow)
        return nums

26.删除排序数组中的重复项
题目描述:给你一个 非严格递增排列 的数组 nums ,请你原地删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回 nums 中唯一元素的个数。
思路:注意要原地操作。采用快慢指针,从索引为1开始往右走,若快指针指向的元素与上一个元素值不相等,则将该元素赋值给slow,并且不重复元素计数+1(注意从1开始计数)。

class Solution:
    def removeDuplicates(self, nums: List[int]) -> int:
        count = 1 # 至少有一个元素是正确的
        slow, fast = 1, 1
        while fast < len(nums):
            if nums[fast] != nums[fast-1]:
                nums[slow] = nums[fast]
                slow += 1
                count += 1
            fast += 1
        return count   

977. 有序数组的平方

题目描述:给你一个按 非递减顺序 排序的整数数组 nums,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。
思路一:暴力解法(用python的sort函数)

class Solution:
    def sortedSquares(self, nums):
        squared_nums = [x**2 for x in nums]
        # 对平方后的数组进行降序排序
        squared_nums.sort()
        return squared_nums

上面如果采用squared_nums.sort(reverse=True),则倒序

思路二:双指针解法。由于nums是非递减排序的数组,平方以后的最大值肯定出现在两侧,不是左边就是右边(负数的平方为正数)。

class Solution:
    def sortedSquares(self, nums):
        left, right = 0, len(nums)-1
        # 存储结果数组,从数组结尾开始
        res = [-1]*len(nums)
        site = len(nums)-1
        while left <= right: # 相等的时候也能运行,
            if nums[left]*nums[left] < nums[right]*nums[right]:
                res[site] = nums[right]*nums[right]
                right -= 1
            else:
                res[site] = nums[left]*nums[left]
                left += 1
            site -= 1
        return res

209.长度最小的子数组

题目描述:给定一个含有 n 个正整数的数组和一个正整数 target 。
找出该数组中满足其总和大于等于 target 的长度最小的子数组[numsl, numsl+1, …, numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0 。
思路一:暴力法。

思路二:采用滑动窗口法,实质是用快慢两个指针构造一个区间,通过不断调节子序列起始位置和终止位置,得出我们想要的结果。假设最小长度为inf,快指针先移动,且计算累加值。若累加值大于target,进入循环,移动slow指针以减小区间。注意的是,先在窗口中减去了慢指针的值cel -= nums[slow],再移动慢指针slow += 1

class Solution:
    def minSubArrayLen(self, target: int, nums: List[int]) -> int:
        minlen = float('inf')  # 初始化为无穷大
        cel = 0  # 当前窗口的和
        slow, fast = 0, 0  # 初始化两个指针
     
        while fast < len(nums):  # 移动 fast 指针扩展窗口
            cel += nums[fast]  # 将 fast 指针的值加到当前窗口的和上
            while cel >= target:  # 当前窗口的和大于等于 target 时
                minlen = min(minlen, fast - slow + 1)  # 更新最小长度
                cel -= nums[slow]  # 从当前窗口的和中减去 slow 指针的值
                slow += 1  # 移动 slow 指针向前,缩小窗口
            fast += 1  # 移动 fast 指针继续扩展窗口
        
        # 如果 minlen 仍然是无穷大,说明没有找到满足条件的子数组
        return 0 if minlen == float('inf') else minlen

59.螺旋矩阵二

题目描述:给定一个正整数 n,生成一个包含 1 到 n^2 所有元素,且元素按顺时针顺序螺旋排列的正方形矩阵。
思路:首先判断有多少轮(圈),用n//2求取,注意处理n=1的特殊情况。对于每一圈,需要按照四条边填补数据,在python中,x in range(a, b)是取不到右边界b的,所以需要对右边界+1处理。对于n=奇数,还需要天目中间元素。

class Solution:
    def generateMatrix(self, n: int) -> List[List[int]]:  
        count  = 1
        loop = n//2 # 如果是偶数,则为n/2,如果是奇数,则为(n-1)/2
        # 注意,2和1在这里得到的结果都会是1,所以需要做出区分
        matrix = [[0 for _ in range(n)] for _ in range(n)]
        if n == 1:
            matrix  = [[1]]
        else:
            for i in range(loop): # 索引从0开始
                for j in range(i,(n-1-i)):
                    matrix[i][j] = count
                    count += 1
                for j in range(i,(n-1-i)):
                    matrix[j][n-1-i] = count
                    count += 1
                for j in range((n-1-i),i, -1):
                    matrix[n-1-i][j] = count
                    count += 1
                for j in range(n-1-i,i,-1):
                    matrix[j][i] = count
                    count += 1
        if n%2 != 0: # 说明是奇数
            matrix[n//2][n//2] = n**2
        return matrix

58. 区间和

题目描述:给定一个整数数组 Array,请计算该数组在每个指定区间内元素的总和。
输入描述:第一行输入为整数数组 Array 的长度 n,接下来 n 行,每行一个整数,表示数组的元素。随后的输入为需要计算总和的区间,直至文件结束。
思路:采用前缀和的方式,创建一个数组cel,其中第i个位置用来记录前i个元素的累积和。要求某段区间[a,b]的和,只需要求cel[b]-cel[a-1],注意a==0的情况。

import sys
input=sys.stdin.read
data = input().split()
index = 0
n = int(data[index])# 行数
index += 1
vec = []

for i in range(n):
    vec.append(int(data[index+i]))

cel = [0]*n
cel_num = 0
for i in range(n):
    cel_num += vec[i]
    cel[i] = cel_num

results = []
index += n
while index < len(data):
    a = int(data[index])
    b = int(data[index+1])

    index += 2
    if a == 0:
        sum = cel[b]
    else:
        sum = cel[b]-cel[a-1]

    results.append(sum)

for result in results:
    print(result)

44. 开发商购买土地

题目描述:在一个城市区域内,被划分成了n * m个连续的区块,每个区块都拥有不同的权值,代表着其土地价值。目前,有两家开发公司,A 公司和 B 公司,希望购买这个城市区域的土地。现在,需要将这个城市区域的所有区块分配给 A 公司和 B 公司。然而,由于城市规划的限制,只允许将区域按横向或纵向划分成两个子区域,而且每个子区域都必须包含一个或多个区块。为了确保公平竞争,你需要找到一种分配方式,使得 A 公司和 B 公司各自的子区域内的土地总价值之差最小。
注意:区块不可再分。
思路:采用前缀和的方式。首先记录每个输入元素并构成数组,然后分别按行和按列求出行累加和以及列累加和。然后再分行和列找到使两个子块相差最小的分割方式。

import sys
input =  sys.stdin.read
data = input().split()
index = 0
n = int(data[index])
index += 1
m = int(data[index])
sum = 0
vec = []
index += 1
for i in range(n):
    row = []
    for j in range(m):
        num = int(data[index])
        index +=  1
        row.append(num)
        sum += num # 用来求整个矩阵所有元素之和
    vec.append(row)

# 统计横向
h = [0]*n
for i in range(n):
    for j in range(m):
        h[i] += vec[i][j]

# 统计纵向
v = [0]*m
for j in range(m):
    for i in range(n):
        v[j] += vec[i][j]

result = float('inf')

# 看看是否要横向切割
hCut = 0
for i in range(n):
    hCut += h[i]
    result = min(result,abs((sum-hCut)-hCut))

# 纵向切割
vCut = 0
for i in range(m):
    vCut += v[i]
    result = min(result,abs((sum-vCut)-vCut))

print(result)

# 统计纵向

参考资料

[1] https://programmercarl.com/%E6%95%B0%E7%BB%84%E7%90%86%E8%AE%BA%E5%9F%BA%E7%A1%80.html

  • 24
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值