代码随想录第二天:(长度最小子数组,螺旋矩阵,区间和,开发商购买土地)

1.长度最小的子数组(Leetcode 209)

(1)暴力解法:利用2个for循环直接遍历寻找,时间复杂度为O(n^2)

class Solution:
    def minSubArrayLen(self, target: int, nums: List[int]) -> int:
        size, min_len = len(nums), float('inf')
        for i in range(size):
            sum = 0
            for j in range(i, size):
                sum += nums[j]
                if sum >= target:
                    min_len = min(min_len, j-i+1)
                    break
        return min_len if min_len != float('inf') else 0

!!!:此题用暴力解法会超出时间限制

(2)滑动窗口(双指针)使用两个指针 left 和 right 来表示当前窗口的左右边界,移动 right 指针来扩展窗口,累加窗口内的元素和。当窗口内的和大于或等于 s 时,尝试移动 left 指针来缩小窗口,以找到更小的子数组。在每次缩小窗口时,更新最小长度的记录。

时间复杂度:由于每个元素最多被访问两次(一次由 right 指针,一次由 left 指针),时间复杂度为 O(n)。

空间复杂度:只需要常数级别的额外空间来存储指针和累加和,空间复杂度为 O(1)。

class Solution:
    def minSubArrayLen(self, target: int, nums: List[int]) -> int:
        left, right, sum, size = 0, 0, 0, len(nums)
        min_len = float('inf')           #初始化最小长度为无穷大
        for right in range(size):        #滑动窗口右指针移动遍历
            sum += nums[right]           #计算当前窗口的和sum
            while sum >= target and left <= right:             
                min_len = min(min_len, right - left +1 )        #更新滑动窗口大小
                sum -= nums[left]                               #减去窗口最左侧值
                left += 1                                       #左指针向右移
        return min_len if min_len != float('inf') else 0 

2.螺旋矩阵(Leetcode 59)

(1):我们使用一个 while 循环来填充矩阵,循环条件是 top <= bottom and left <= right,即只要上下边界和左右边界没有交叉,就继续填充。填充顺序为顺时针螺旋:顶行 → 右列 → 底行 → 左列

class Solution:
    def generateMatrix(self, n: int) -> List[List[int]]:
        matrix = [[0] * n for _ in range(n)]        # 初始化一个 n x n 的矩阵,元素都为 0
        top, bottom, left, right, num = 0, n-1, 0, n-1, 1   # 定义上下左右边界:top,bottom, left,right
        while top <= bottom and left <= right:      # 使用循环填充矩阵,按照顺时针螺旋顺序
            for j in range(left, right + 1):        # 填充顶行,从左到右
                matrix[top][j] = num
                num += 1
            top += 1                                # 顶行填充完毕,上边界下移

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

            if top <= bottom:                       # 如果还有底行,填充底行,从右到左
                for j in range(right, left - 1, -1):#从right到left递减向左填充,不包含left-1
                    matrix[bottom][j] = num
                    num += 1
                bottom -= 1                         # 底行填充完毕,下边界上移
            
            if left <= right:                       # 如果还有左列,填充左列,从下到上
                for i in range(bottom, top - 1, -1):#从bottom向top递减填充,不包含top-1
                    matrix[i][left] = num
                    num += 1
                left += 1                           # 左列填充完毕,左边界右移
        return matrix

(2):定义一个偏移量offset, 用来控制每次循环的层数,作用是保留每次循环方向的最后一位,作为下个方向循环的起点,类似“左闭右开”的作用。

class Solution:
    def generateMatrix(self, n: int) -> List[List[int]]:
        nums = [[0]*n for _ in range(n)]
        startx, starty = 0, 0            #起始点
        loop, mid = n // 2, n // 2      #迭代次数,n为奇数时,矩阵的中心点
        sum = 1                         #矩阵的值
        for offset in range (1, loop + 1):  #每循环一层偏移量+1
            for i in range(starty, n - offset):    #从左至右,startx不变,starty变
                nums[startx][i] = sum
                sum += 1
            for i in range(startx, n - offset):    #从上至下,starty不变,startx变
                nums[i][n - offset] = sum
                sum += 1
            for i in range(n - offset, starty, -1):    #从右至左,startx不变,starty变
                nums[n - offset][i] = sum 
                sum += 1
            for i in range(n - offset, startx, -1):    #从下至上,starty不变,startx变
                nums[i][starty] = sum
                sum += 1
            startx += 1
            starty += 1
        if n % 2 !=0:
            nums[mid][mid] = sum
        return nums

3.卡码网(58. 区间和)

(1)解题思路:此题我们可以类比数列的前N项和公式,假设存在m<p<n,Sn-p = (Sn-m) - (Sp-m)。因此我们定义列表的前缀和prefix_sum,存在递推关系prefix_sum[i] = prefix_sum[i-1] + array[i],那么区间[a, b]的和就可以表示为prefix_sum[b] - prefix_sum[a-1]。

import sys
n = int(input())        # 读取数组长度 n
array = [int(input()) for _ in range(n)]    # 读取 n 个整数存入数组
prefix_sum = [0] * n        # 计算前缀和数组
prefix_sum[0] = array[0]  # 第一个元素的前缀和即自身
for i in range(1, n):
    prefix_sum[i] = prefix_sum[i-1] + array[i]  # 前缀和递推
for line in sys.stdin:              # 逐行读取直到文件结束
    a, b = map(int, line.split())   # 读取区间起点 a 和终点 b
    if a == 0:                      # 如果 a 为 0,直接输出 prefix_sum[b]
        print(prefix_sum[b])
    else:
        print(prefix_sum[b] - prefix_sum[a-1])# 否则用prefix_sum[b]-prefix_sum[a-1]计算区间和

(2)暴力求解法:把指定区间的和都累加一遍,但是容易超时

4.卡码网(44. 开发商购买土地)

1.解题思路:同样运用前缀和的思想,给定一个 n x m 的网格,将其横向或纵向划分为两个子区域,使得两个区域的土地总价值之差最小。使用前缀和数组来快速计算子区域的和,然后枚举所有可能的横向和纵向划分,找出最小差值。

def min_land_value_diff(grid):
    n, m = len(grid), len(grid[0])
    total_value = sum(sum(row) for row in grid)  # 计算整个区域的总土地价值
    min_diff = float('inf')  # 初始化最小差值为无穷大
    
    # 计算前缀和,方便快速计算子区域的总价值
    prefix_sum = [[0] * (m + 1) for _ in range(n + 1)]
    for i in range(n):
        for j in range(m):
            prefix_sum[i + 1][j + 1] = (grid[i][j] + prefix_sum[i][j + 1] + 
                                        prefix_sum[i + 1][j] - prefix_sum[i][j])
    
    # 竖直切割(按列切)
    for col in range(1, m):
        left_value = prefix_sum[n][col]  # 左侧区域的总价值
        right_value = total_value - left_value  # 右侧区域的总价值
        min_diff = min(min_diff, abs(left_value - right_value))
    
    # 水平切割(按行切)
    for row in range(1, n):
        top_value = prefix_sum[row][m]  # 上方区域的总价值
        bottom_value = total_value - top_value  # 下方区域的总价值
        min_diff = min(min_diff, abs(top_value - bottom_value))
    
    return min_diff

# 读取输入
def main():
    n, m = map(int, input().split())  # 读取n和m
    grid = [list(map(int, input().split())) for _ in range(n)]  # 读取网格数据
    
    # 计算并输出最小差值
    print(min_land_value_diff(grid))

if __name__ == "__main__":
    main()

2.代码解释

prefix_sum[i + 1][j + 1] = (grid[i][j] + prefix_sum[i][j + 1] + 
                            prefix_sum[i + 1][j] - prefix_sum[i][j])

计算逻辑:

  • grid[i][j]:当前网格单元的土地价值。
  • prefix_sum[i][j + 1]:当前单元左侧的前缀和。
  • prefix_sum[i + 1][j]:当前单元上方的前缀和。
  • prefix_sum[i][j]:左上角区域的重复累加值,需要减去一次(因为它被前两项同时包含了)。
这样,每个 prefix_sum[i+1][j+1] 存储的是从 (0,0)(i,j) 这个子矩阵的总和,从而可以在 O(1) 时间内求得任意子矩阵的总价值。
假设 grid 如下:
12
34

prefix_sum 计算如下:
000
013
0410


 

这里 prefix_sum[2][2] = 10,表示整个矩阵的总和。
for col in range(1, m):
    left_value = prefix_sum[n][col]  # 左侧区域的总价值
这个循环用于按列进行竖直切割,计算左侧区域的总土地价值。

具体逻辑:

  1. 遍历所有可能的竖直切割位置col1m-1,意味着尝试所有可能的纵向分割方案。
  2. 计算左侧区域的总价值

    • prefix_sum[n][col] 代表从最上方 (0,0) 到最底部 (n-1,col-1) 这个矩形区域的总价值
    • 由于 prefix_sum 预先计算了每个区域的累加和,这里可以 O(1) 计算左侧区域的总价值。
假设网格如下:
123
213
123

对应的 prefix_sum
0000
0136
03612
04918

如果 col = 1left_value = prefix_sum[3][1] = 4,意味着左侧部分如下:
123
213
123

然后右侧区域的总价值 right_value = total_value - left_value 计算出来后,继续比较两个区域的差值,寻找最小差值。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值