代码随想录算法训练营Day2 | 209. 长度最小的子数组,59. 螺旋矩阵II,Kama58. 区间和,Kama44. 开发商购买土地

目录

209. 长度最小的子数组

59. 螺旋矩阵II

Kama58. 区间和

Kama44. 开发商购买土地


209. 长度最小的子数组

题目链接:. - 力扣(LeetCode)

文章讲解:代码随想录

解题卡点: 无

        双指针法&滑动窗口:左右指针的窗口内即为连续数组。窗口数组的和大于等于目标值,左指针向前移动,窗口缩小,窗口数组的值缩小;窗口数组的和小于目标值,右指针向前移动,窗口增大,窗口数组的值增大。不断更新窗口数组和大于等于目标值的数组长度,遍历结束后得到最小数组长度。

       做题时可以先固定移动右指针,判断条件收缩左指针。这里使用for循环控制右指针,循环结束一次右指针+1,因此在循环内专心控制左指针就行。当窗口数组和大于等于目标值时,记录窗口长度,减去该元素,左指针+1.

        总结: 涉及连续子数组的问题,通常有两种思路:一是滑动窗口、二是前缀和。滑动窗口得益于元素非负,右指针向右一定增加总和,左指针向右一定减少总和。若数组含有负元素,不可使用该方法,因为有负数的话无论是收缩还是扩张窗口,窗口内值的总和可能增加也可能减少,不像之前收缩一定变小,扩张一定变大。

        Tips:

        ①窗口的数字和使用累积和cum_sum来计算,比sum(list)快。cum_sum的右指针+1数组和就加该元素值,左指针+1就减该元素值,而sum会遍历整个子数组。

        ②时间复杂度O(n)。时间复杂度看每一个元素被操作的次数,每个元素在滑动窗后进来操作一次,出去操作一次,每个元素都是被操作两次,故时间复杂度是2×n,也就是O(n)。

class Solution:
    def minSubArrayLen(self, target: int, nums: List[int]) -> int:
        sub_len = float('inf') # 数组长度初始值,设为无穷大
        sub_sum = 0 # 数组和
        left = 0 # 左指针
        for right in range(len(nums)): # 右指针使用for循环自动进行
            sub_sum += nums[right] # 累加右指针元素
            while sub_sum >= target:
                sub_len = min(right-left+1, sub_len) # 不断更新满足条件窗口数组的最小长度
                sub_sum -= nums[left] # 累减左指针元素
                left += 1
        if sub_len == float('inf'): # 没有满足条件的窗口数组时特殊处理
            sub_len = 0
        return sub_len

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

59. 螺旋矩阵II

题目链接:. - 力扣(LeetCode)

文章讲解:代码随想录

解题卡点:循环规律是什么

        过程模拟题,考察对代码的掌控能力。

        本题重点在于坚持循环不变量。我一开始的想法是,从外围顺时针按3、2、2、1的个数填充。然而个数不一样,写循环的代码困难重重。因此应该坚持个数不变,如上图,从外围顺时针按2、2、2、2的个数填充,保证每个循环都相同。另外最外层填充完,填充倒数第二圈时,每圈起始索引list[0][0]应该变成list[1][1],同时每个方向填充的个数也要少2个。这些变化使用offest控制,代表每一圈上述变化的偏移量。

        总结:坚持循环不变量,找到规律,注意偏移量,由外向内一圈一圈填充。

        Tips:

        ①对于n为奇数的矩阵来说,正中心是一个数而不是一个圈。可以对这种情况单独处理,使矩阵中心为n^2。也可以在创建矩阵时,使所有元素均为n^2,不用再单独处理。

        ②关于初始数组的创建,以n=3为例,对初始数组matrix_1的中心赋值,数组全部元素均发生变化。

matrix_1 = [[0] * 3] * 3
matrix_2 = [[0] * 3 for _ in range(3)]
matrix_3 = [[0, 0, 0], [0, 0, 0], [0, 0, 0]]

print('', matrix_1, '\n', matrix_2, '\n', matrix_3)
# [[0, 0, 0], [0, 0, 0], [0, 0, 0]] 
# [[0, 0, 0], [0, 0, 0], [0, 0, 0]] 
# [[0, 0, 0], [0, 0, 0], [0, 0, 0]]

matrix_1[1][1] = 9
matrix_2[1][1] = 9
matrix_3[1][1] = 9

print('', matrix_1, '\n', matrix_2, '\n', matrix_3)
# [[0, 9, 0], [0, 9, 0], [0, 9, 0]] 
# [[0, 0, 0], [0, 9, 0], [0, 0, 0]] 
# [[0, 0, 0], [0, 9, 0], [0, 0, 0]]

        原因如下:

         参考来源:Python数据结构与算法分析(第三版),p9

class Solution:
    def generateMatrix(self, n: int) -> List[List[int]]:
        res = [[n**2]*n for _ in range(n)] # 初始数组
        offest = 0 # 每条边遍历长度的偏移量
        val = 1 # 待填充的数值
        loop = n//2 # 循环圈数
        col = 0 # 填充列索引
        row = 0 # 填充行索引
        while loop: # 只要循环圈数没归0就继续下去
            boundary = n-offest-2 # 每条边本圈可填充的数量,以3为例这里是1
            while col <= boundary: # 向右填充,列索引取值0、1
                res[row][col] = val
                val += 1
                col += 1
            while row <= boundary: # 向下填充
                res[row][col] = val
                val += 1
                row += 1
            while n-col-1 <= boundary: # 向左填充
                res[row][col] = val
                val += 1
                col -= 1
            while n-row-1 <= boundary: # 向上填充
                res[row][col] = val
                val += 1
                row -= 1
            col += 1 # 每圈起始值向右下角移动
            row += 1 # 每圈起始值向右下角移动
            offest += 1
            loop -= 1 # 本圈填充结束,圈数-1
        return res

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

Kama58. 区间和

题目链接:58. 区间和(第九期模拟笔试)

文章讲解:58. 区间和 | 代码随想录

解题卡点:不熟悉ACM模式

         前缀和:重复利用计算过的子数组之和,降低区间查询需要累加计算的次数。前缀和在涉及区间和的问题时非常有用。

        计算区间2~5的和时,只需用5的前缀和减去1的前缀和。在解题时注意顺序,先构造前缀和初始化列表,再通过对应下标值相减得到区间和。

        Tips:

        sys.stdin.read()会将所有输入内容一次性读取,包括换行符等内容。input()会按行读取输入内容,不会包括换行符。

#以下面输入为例
#5
#1
#2
#3
#4
#5
#0 1
#1 3

import sys
input = sys.stdin.read() # str'5\n1\n2\n3\n4\n5\n0 1\n1 3\n'

def main():
    data = input.split() # list['5','1','2','3','4','5','0','1','1','3']
    data = list(map(int, data)) # list[5, 1, 2, 3, 4, 5, 0, 1, 1, 3]
    array_len = data[0]

    p = [] # 前缀和数组初始化
    cum_sum = 0
    for i in range(1, array_len+1):
        cum_sum += data[i]
        p.append(cum_sum) # p = [1, 3, 6, 10, 15]

    idx = array_len + 1 # 按批次将输入数组后面的待求区间信息传入a和b
    while idx <= len(data)-1:
        a = data[idx]
        b = data[idx+1]
        if a == 0:
            sub_sum = p[b]
        else:
            sub_sum = p[b] - p[a-1]
        print(sub_sum) # ACM模式使用print输出
        idx += 2

if __name__ == '__main__':
    main()

Kama44. 开发商购买土地

题目链接:44. 开发商购买土地(第五期模拟笔试)

文章讲解:44. 开发商购买土地 | 代码随想录

解题卡点:从一维前缀和转为二维有点懵

        首先要理解题目。本题为创建一个矩阵,对矩阵进行一次横向分割,或者一次纵向分割,得到两个小矩阵,计算两个小矩阵的内部和,并得到它们的差值。遍历每一种切法,输出最小的差值。

        二维区间和计算→二维前缀和。二维前缀和初始化与求值公式如下:

class MatrixSum:
    def __init__(self, matrix: List[List[int]]):
        m, n = len(matrix), len(matrix[0])
        s = [[0] * (n + 1) for _ in range(m + 1)]
        for i, row in enumerate(matrix):
            for j, x in enumerate(row):
                s[i + 1][j + 1] = s[i + 1][j] + s[i][j + 1] - s[i][j] + x
        self.s = s

    # 返回左上角在 (r1,c1) 右下角在 (r2-1,c2-1) 的子矩阵元素和(类似前缀和的左闭右开)
    def query(self, r1: int, c1: int, r2: int, c2: int) -> int:
        return self.s[r2][c2] - self.s[r2][c1] - self.s[r1][c2] + self.s[r1][c1]

    # 如果你不习惯左闭右开,也可以这样写
    # 返回左上角在 (r1,c1) 右下角在 (r2,c2) 的子矩阵元素和
    def query2(self, r1: int, c1: int, r2: int, c2: int) -> int:
        return self.s[r2 + 1][c2 + 1] - self.s[r2 + 1][c1] - self.s[r1][c2 + 1] + self.s[r1][c1]

#作者:灵茶山艾府
#链接:https://leetcode.cn/circle/discuss/UUuRex/

        在本题中,由于直上直下分割,待求和小矩阵的左上角点始终为(0, 0)。

#以下面输入为例
#3 4
#1 2 3 8
#2 1 3 12
#1 2 3 3

import sys
input = sys.stdin.read()

def main():
    data = input.split()
    data = list(map(int, data)) # [3,4,1,2,3,8,2,1,3,12,1,2,3,3]
    m, n = data[0], data[1] # 得到矩阵的行列数
    res = float('inf')

    matrix = [] # 将输入转为标准的矩阵形式
    val_idx = 2
    while val_idx <= len(data)-1:
        row = []
        col_idx = 1
        while col_idx <= n:
            row.append(data[val_idx])
            col_idx += 1
            val_idx += 1
        matrix.append(row) # matrix[[1,2,3,8], [2,1,3,12], [1,2,3,3]]

    p = [[0]*(n+1) for _ in range(m+1)] # 二维前缀和矩阵
    for i, row in enumerate(matrix):
        for j, x in enumerate(row):
            p[i+1][j+1] = p[i+1][j] + p[i][j+1] - p[i][j] + x

#前缀和初始化矩阵p
#0  0  0  0  0
#0  1  3  6  14
#0  3  6  12 32
#0  4  9  18 41

    total_sum = p[-1][-1] # 输入矩阵的总和
    for i in range(0, n-1): # 按列遍历(竖着切矩阵,第一次切完为1,2,1)
        sub_sum = p[m][i+1] # 对应在p上就是最下面一行第二列的4
        res = min(res, abs(sub_sum - (total_sum - sub_sum)))
    for i in range(0, m-1): # 按行遍历
        sub_sum = p[i+1][n]
        res = min(res, abs(sub_sum - (total_sum - sub_sum)))
    print(res)
    
if __name__ == '__main__':
    main()
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值