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

209. 长度最小的子数组

太久没做题初始思路只能想到暴力破解,看了一眼提示可能会用到前缀和,能够想到只要建立一个新数组,bi = a0 + a1 + ... + ai 即数组a的前缀,这样子序列i到j就可以表示为bj - bi-1 ,由于数组元素是大于1的,所以b数组必然是递增的,那么在计算子序列的时候,当符合条件的子序列出现后记录下来,接着就可以先向右移动后下标,移动后不满足条件后,再移动左下标,直到满足条件,如此往复。但如何实现没有特别好的思路,复杂度好像也还是很大,自己的思路到此为止。

看完代码随想录,发现自己的思路没有大的问题,但是在细节方面自己没有能够想通,总感觉差了一点细节,在这里做一些补充。

滑动窗口:类似双指针

先对终止位置进行右移,此时的操作是将子序列sum += 终止下标的数;

判断此时sum是否大于等于target:

如果符合,需要判断符合的子序列长度是否小于上一次的sum(这里我自己有个纠结的思考,其实画图会清楚很多,而且这一步思考的意义可能不大,记录一下:会以为终止下标右移到符合条件之后,一定会导致【之后的子序列长度】都大于【上一次最后符合条件的长度】,因此导致【之后能得出的结果】都是大于【上一次最后符合条件的长度】的,这个想法走进了误区,是错误的,因为虽然第一次右移到符合条件的位置时,会大于等于【上一次最后符合条件的长度】,但在之后会进行起始下标的右移,使得子序列的长度不断减小,直到小于【上一次最后符合条件的长度】,也就是从上一次符合到本次符合的过程中,起始下标可能右移了多次,并且【起始下标右移次数】>【终止下标右移次数】,使得符合的子序列长度变小,这个过程自己记录的尽可能详细了,但文字表述难免混乱模糊,只作为自己理解,看到这的朋友不理解请见代码随想录讲解视频:拿下滑动窗口! | LeetCode 209 长度最小的子数组_哔哩哔哩_bilibili

如果不符合,则继续将终止下标右移。

思路过程文字表述如上

另外解答了我初始思路中时间复杂度的疑问,感觉进行了两层循环,时间复杂度好像还是很大的问题:

这里强调一下,不要以为for里放一个while就以为是O(n^2)啊, 主要是看每一个元素被操作的次数

了解上述思路后,开始用python实现代码(再次感叹上面C++精髓代码的精简):

补充:在Python中,float('inf') 表示正无穷大。这是一个特殊的浮点数值,用于表示超出浮点数表示范围的数值。当你尝试进行一个结果超出浮点数最大表示范围的计算时,Python会返回 float('inf')

class Solution:

    def minSubArrayLen(self, target: int, nums: List[int]) -> int:

        length = len(nums)

        i, result, sum = 0, float('inf'), 0         #i为起始下标, result为【当前“得到过的”最小子序列“长度”】, sum为【当前子序列】之“和”

        for j in range(length):

            sum += nums[j]

            while sum >= target:    # 当子序列之和大于target的时候进入循环,开始尝试缩小滑动窗口以获得最小值

                result = min(result, j-i+1)

                sum -= nums[i]

                i += 1

        # 退出循环后,有符合条件的返回result,否则返回0

        if result < float('inf'):

            return result

        else:

            return 0

规范代码比自己的可读性稍强点,放作对比:

class Solution:

    def minSubArrayLen(self, s: int, nums: List[int]) -> int:

        l = len(nums)

        left = 0

        right = 0

        min_len = float('inf')

        cur_sum = 0 #当前的累加值

        

        while right < l:

            cur_sum += nums[right]

            

            while cur_sum >= s: # 当前累加值大于目标值

                min_len = min(min_len, right - left + 1)

                cur_sum -= nums[left]

                left += 1

            

            right += 1

        

        return min_len if min_len != float('inf') else 0

59.螺旋矩阵II

给你一个正整数 n ,生成一个包含 1 到 n2 所有元素,且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix 。

示例 1:

输入:n = 3
输出:[[1,2,3],[8,9,4],[7,6,5]]

示例 2:

输入:n = 1
输出:[[1]]

提示:

  • 1 <= n <= 20

四个运动状态,且变化都是固定的,暂时没想到什么好办法,只能枚举硬解。

整理如下:

运动方向: 向右   下个状态变化: 向下/停止

运动方向: 向下   下个状态变化: 向左

运动方向: 向左   下个状态变化: 向上/停止

运动方向: 向上   下个状态变化: 向下

Right:向右时,首先为自己赋值,然后判断右边是否为数组内且为空,是的话向右执行right,如果右边不为空或者超出数组范围,则向下执行down,如果下边也不为空或者超出数组范围,则返回。

Down:向下时,首先为自己赋值,然后判断下边是否为数组内且为空,是的话向下执行down,如果下边不为空或者超出数组范围,则向左执行left,如果左边也不为空或者超出数组范围,则返回。

Left:向左时,首先为自己赋值,然后判断左边是否为数组内且为空,是的话向左执行left,如果左边不为空或者超出数组范围,则向上执行up,如果上边也不为空或者超出数组范围,则返回。

Up:向上时,首先为自己赋值,然后判断上边是否为数组内且为空,是的话向上执行up,如果上边不为空或者超出数组范围,则向右执行down,如果右边也不为空或者超出数组范围,则返回。

(由于要考虑n=1的情况,所以把每个方向的两种情况都考虑了,实际情况可能不需要,所以存在优化空间,自己画图模拟了一下,发现好像只有向右和向左的情况下会停下来成为终点。而且完成最外围一圈之后,剩下的部分起始是新的完整的一圈。原本考虑想用递归,发现不是很好用甚至有点忘了咋怎么用。。决定换个写法,但过程不变,写完发现实在过于暴力,可维护性非常地差,并且debug了很多次,虽然过了,但非常不推荐

class Solution:
   def generateMatrix(self, n: int) -> List[List[int]]:
       direction = 'r'
       count = 1
       x, y = 0, 0
       nums = [[0 for _ in range(n)] for _ in range(n)]
       while count <= n ** 2:
           nums[x][y] = count
           if direction == 'r': # 向右
               if (y + 1) < n and nums[x][y + 1] == 0:  # 未超出范围且数组为空
                   y += 1
                   direction = 'r'
               elif (y + 1 < n and nums[x][y + 1] != 0) or y + 1 >= n:  # 未超出但数组不为空(已填)或者超出范围,这类情况均转向
                   x += 1
                   direction = 'd'
               else: # 向右和向左存在终止的情况,可能返回
                   return nums
           elif direction == 'd': # 向下的情况,除了不会终止过程,其他过程与上述类似。
               if x + 1 < n and nums[x + 1][y] == 0:
                   x += 1
                   direction = 'd'
               elif (x + 1 < n and nums[x + 1][y] != 0) or x + 1 >= n:
                   y -= 1
                   direction = 'l'
           elif direction == 'l':
               if y - 1 >= 0 and nums[x][y - 1] == 0:  
                   y -= 1
                   direction = 'l'
               elif (y - 1 >= 0 and nums[x][y - 1] != 0) or y - 1 < 0:  
                   x -= 1
                   direction = 'u'
               else:
                   return nums
           else:
               if x - 1 >= 0 and nums[x - 1][y] == 0:
                   x -= 1
                   direction = 'u'
               elif (x - 1 >= 0 and nums[x - 1][y] != 0) or x - 1 < 0:
                   y += 1
                   direction = 'r'
           count += 1
       return nums

虽然结果过了,但这过程确实很痛苦,要足够细心和耐心,尤其代码可维护性相当差,非常不推荐。下面附上学习卡哥的题解,感觉简单很多,就是需要认真看看视频理解一下。

核心是:循环不变量原则左闭右开

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为奇数时,矩阵的中心点

        count = 1                           # 计数



        for offset in range(1, loop + 1) :      # 每循环一层偏移量加1,偏移量从1开始

            for i in range(starty, n - offset) :    # 从左至右,左闭右开

                nums[startx][i] = count

                count += 1

            for i in range(startx, n - offset) :    # 从上至下

                nums[i][n - offset] = count

                count += 1

            for i in range(n - offset, starty, -1) : # 从右至左

                nums[n - offset][i] = count

                count += 1

            for i in range(n - offset, startx, -1) : # 从下至上

                nums[i][starty] = count

                count += 1                

            startx += 1         # 更新起始点

            starty += 1



        if n % 2 != 0 : # n为奇数时,填充中心点

            nums[mid][mid] = count

        return nums

这里不多说什么了。。只要耐心总有人能A,但是理解了核心方法才有可能在短时间内做出来。

区间和

题目描述

给定一个整数数组 Array,请计算该数组在每个指定区间内元素的总和。

输入描述

第一行输入为整数数组 Array 的长度 n,接下来 n 行,每行一个整数,表示数组的元素。随后的输入为需要计算总和的区间,直至文件结束。

输出描述

输出每个指定区间内元素的总和。

输入示例

5
1
2
3
4
5
0 1
1 3

1
2
3
4
5
6
7
8

输出示例

3
9

1
2

数据范围:

0 < n <= 100000

def main():
   n = int(input())
   nums = [None] * n
   sum = [None] *n
   for i in range(n):
       val = int(input())
       nums[i] = val
   sum [0] = nums[0]
   for i in range(n-1):
       sum[i+1] = sum[i] + nums[i+1]
   while True:
       try:
           ab = input().split()
           start, end = int(ab[0]), int(ab[1])
           print(int(sum[end]) - int(sum[start]) + int(nums[start]))
'''
            #这个位置今天debug了很久,一开始的写法是 

            # print(sum[ab[1]] - sum[ab[0]] + nums[ab[0]])

            #后面发现一直不过,原因可能如下:

            #在 print(int(sum[ab[1]]) - int(sum[ab[0]]) + int(nums[ab[0]])) 这一行,如果 ab[0] 和 ab[1] 是数组的索引,那么 sum[ab[1]] 可能会超出数组的索引范围,因为 ab[1] 应该是一个数字,而不是直接用作索引。输入的 ab 应该是两个数字,代表查询的起始和结束位置。但是,程序没有对这些输入进行有效的错误处理或验证。         

'''
       except:
           break


if __name__ == "__main__":
   main()

今日另外一个纠错:

#ab=int(input().split())  这个是错误的

这样的代码是不正确的,因为它试图将一个列表转换为整数,这会导致错误。input().split() 会返回一个字符串列表,即使你只输入了一个数字。

如果你想要读取一个数字,并且确保它是一个整数,你应该这样做:

python

ab = int(input())

如果你想要读取两个数字,并将它们存储在两个变量中,你可以这样做:

python

a, b = map(int, input().split())

这里,input().split() 会读取一行输入并将其分割成多个字符串,map(int, ...) 会将这些字符串转换为整数。

今天使用的ACM模式,以前掌握了C++的写法,头回使用python,还是非常难受的,不过有收获,下一次使用应该不会再浪费那么多时间了。

提供规范代码,写的比自己好很多,需要学习

import sys
input = sys.stdin.read

def main():
    data = input().split()
    index = 0
    n = int(data[index])
    index += 1
    vec = []
    for i in range(n):
        vec.append(int(data[index + i]))
    index += n

    p = [0] * n
    presum = 0
    for i in range(n):
        presum += vec[i]
        p[i] = presum

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

        if a == 0:
            sum_value = p[b]
        else:
            sum_value = p[b] - p[a - 1]

        results.append(sum_value)

    for result in results:
        print(result)

if __name__ == "__main__":
    main()

44. 开发商购买土地

【题目描述】

在一个城市区域内,被划分成了n * m个连续的区块,每个区块都拥有不同的权值,代表着其土地价值。目前,有两家开发公司,A 公司和 B 公司,希望购买这个城市区域的土地。

现在,需要将这个城市区域的所有区块分配给 A 公司和 B 公司。

然而,由于城市规划的限制,只允许将区域按横向或纵向划分成两个子区域,而且每个子区域都必须包含一个或多个区块。

为了确保公平竞争,你需要找到一种分配方式,使得 A 公司和 B 公司各自的子区域内的土地总价值之差最小。

注意:区块不可再分。

【输入描述】

第一行输入两个正整数,代表 n 和 m。

接下来的 n 行,每行输出 m 个正整数。

输出描述

请输出一个整数,代表两个子区域内土地总价值之间的最小差距。

【输入示例】

3 3 1 2 3 2 1 3 1 2 3

【输出示例】

0

【提示信息】

如果将区域按照如下方式划分:

1 2 | 3 2 1 | 3 1 2 | 3

两个子区域内土地总价值之间的最小差距可以达到 0。

【数据范围】:

  • 1 <= n, m <= 100;
  • n 和 m 不同时为 1。

初始思路没有什么特别的,实现思路很快完成,但是这个ACM模式真的又是卡了我好久。。希望明天不要又是这个情况了。

思路:先将需要的数组数值填满,由于需要切一刀将总土地分成两份,所以在遍历数组的时候,分为两种情况,我用两个循环实现,复杂度不意外的是O(n2)。

  1. 从左向右遍历,同时记录遍历到的加数总和total_x,每到达一次右边界的时候计算一次差值,相当于到达右边界的时候进行了横切一刀的操作。表达式为|total_上-total_下| = |total - 2*total_上|
  2. 从上往下遍历,同理到达下边界的时候计算一次差值,相当于到达下边界的时候进行了竖切一刀。

def main():

    data = input().split()

    n = int(data[0])

    m = int(data[1])



    space = [[0 for _ in range(m)] for _ in range(n)]

    total = 0

    total_x, total_y = 0, 0

    result = float('inf')



    for i in range(n):

        row = input().split()

        for j in range(m):

            space[i][j] = int(row[j])

            total += space[i][j]



    for i in range(n):

        for j in range(m):

            total_x += space[i][j]

            if j == m-1:

                result = min(result, abs(total-2*total_x))



    for j in range(m):

        for i in range(n):

            total_y += space[i][j]

            if i == n-1:

                result = min(result, abs(total-2*total_y))



    print(result)









if __name__ == "__main__":

    main()

和规范代码的实现差不多,思路基本没问题。还是得吐槽,又在acm的输入上浪费了太多的时间。。。

以下是规范代码:

def main():

    import sys

    input = sys.stdin.read

    data = input().split()

    

    idx = 0

    n = int(data[idx])

    idx += 1

    m = int(data[idx])

    idx += 1

    sum = 0

    vec = []

    for i in range(n):

        row = []

        for j in range(m):

            num = int(data[idx])

            idx += 1

            row.append(num)

            sum += num

        vec.append(row)



    result = float('inf')

    

    count = 0

    # 行切分

    for i in range(n):

        

        for j in range(m):

            count += vec[i][j]

            # 遍历到行末尾时候开始统计

            if j == m - 1:

                result = min(result, abs(sum - 2 * count))



    count = 0

    # 列切分

    for j in range(m):

        

        for i in range(n):

            count += vec[i][j]

            # 遍历到列末尾时候开始统计

            if i == n - 1:

                result = min(result, abs(sum - 2 * count))



    print(result)

if __name__ == "__main__":

    main()

第二十二天算法训练营主要涵盖了Leetcode题目中的三道题目,分别是Leetcode 28 "Find the Index of the First Occurrence in a String",Leetcode 977 "有序数组的平方",和Leetcode 209 "长度最小数组"。 首先是Leetcode 28题,题目要求在给定的字符串中找到第一个出现的字符的索引。思路是使用双指针来遍历字符串,一个指向字符串的开头,另一个指向字符串的结尾。通过比较两个指针所指向的字符是否相等来判断是否找到了第一个出现的字符。具体实现的代码如下: ```python def findIndex(self, s: str) -> int: left = 0 right = len(s) - 1 while left <= right: if s[left == s[right]: return left left += 1 right -= 1 return -1 ``` 接下来是Leetcode 977题,题目要求对给定的有序数组中的元素进行平方,并按照非递减的顺序返回结果。这里由于数组已经是有序的,所以可以使用双指针的方法来解决问题。一个指针指向数组的开头,另一个指针指向数组的末尾。通过比较两个指针所指向的元素的绝对值的大小来确定哪个元素的平方应该放在结果数组的末尾。具体实现的代码如下: ```python def sortedSquares(self, nums: List[int]) -> List[int]: left = 0 right = len(nums) - 1 ans = [] while left <= right: if abs(nums[left]) >= abs(nums[right]): ans.append(nums[left ** 2) left += 1 else: ans.append(nums[right ** 2) right -= 1 return ans[::-1] ``` 最后是Leetcode 209题,题目要求在给定的数组中找到长度最小数组,使得数组的和大于等于给定的目标值。这里可以使用滑动窗口的方法来解决问题。使用两个指针来表示滑动窗口的左边界和右边界,通过移动指针来调整滑动窗口的大小,使得滑动窗口中的元素的和满足题目要求。具体实现的代码如下: ```python def minSubArrayLen(self, target: int, nums: List[int]) -> int: left = 0 right = 0 ans = float('inf') total = 0 while right < len(nums): total += nums[right] while total >= target: ans = min(ans, right - left + 1) total -= nums[left] left += 1 right += 1 return ans if ans != float('inf') else 0 ``` 以上就是第二十二天算法训练营的内容。通过这些题目的练习,可以提升对双指针和滑动窗口等算法的理解和应用能力。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值