代码随想录算法训练营day2(python版)|数组part02

209.长度最小的子数组

题目链接:209. 长度最小的子数组 - 力扣(LeetCode)

文章讲解:代码随想录

视频讲解:拿下滑动窗口! | LeetCode 209 长度最小的子数组_哔哩哔哩_bilibili

题目

给定一个含有 n 个正整数的数组和一个正整数 target 。

找出该数组中满足其总和大于等于 target 的长度最小的 子数组 [numsl, numsl+1, ..., numsr-1, numsr] ,并返回其长度如果不存在符合条件的子数组,返回 0 。

示例 1:

输入:target = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组 [4,3] 是该条件下的长度最小的子数组。

示例 2:

输入:target = 4, nums = [1,4,4]
输出:1

示例 3:

输入:target = 11, nums = [1,1,1,1,1,1,1,1]
输出:0

提示:

  • 1 <= target <= 109
  • 1 <= nums.length <= 105
  • 1 <= nums[i] <= 104

进阶:

如果你已经实现 O(n) 时间复杂度的解法, 请尝试设计一个 O(n log(n)) 时间复杂度的解法。

暴力解法

两个指针指向起点和终点,两层循环,求两个位置中间的和,如果超过给的值就更新结果,然后和上一个结果比较谁的长度更小谁就是下一个结果。时间复杂度O(n^2),力扣警告超出时间限制。

from typing import List
# 力扣部分开始
class Solution:
    def minSubArrayLen(self, target: int, nums: List[int]) -> int:
      rel=float('inf')#定义子序列长度为无穷大
      sum=0#子序列之和
      sumlen=0#子序列长度
      for i in range(len(nums)):
         sum=0
         for j in range(i,len(nums)):#j可以=i到len(nums)-1,j=i时,子序列长度为1,自己不会和自己相加
            sum+=nums[j]#子序列之和
            sumlen=j-i+1#子序列长度
            if sum>=target:#如果子序列之和大于等于目标值
               rel=min(rel,sumlen)#更新子序列长度
               break#跳出内层循环
      return rel if rel!=float('inf') else 0#如果没有满足条件的子序列,返回0
# 力扣部分结束
# 测试代码
if __name__ == "__main__":
    solution = Solution()
    target = 7
    nums = [2,3,1,2,4,3]
    result = solution.minSubArrayLen(target, nums)
    print(result)  # 输出: 2
    target1 = 2
    nums = [1,1,1,1,1]
    result = solution.minSubArrayLen(target1, nums)
    print(result)  # 输出: 2
滑动窗口

动态调节子序列初始位置和最终位置,得到结果

滑动窗口可以理解为双指针的一种

fast遍历整个数组,一个for循环

slow跟在后面,一边计算fast和slow之间的元素总和sum以及序列长度sumlen

在总和>目标的情况下,sum>=target,slow就移动,初始位置前移,子序列长度减小,求最小长度

sum+=nums[fast]#fast一边往前走一边把元素扔到sum中
while sum>=target:
    sumlen=fast-slow+1#计算窗口长度
    result=min(result,sumlen)#更新最小长度
    sum-=nums[slow]#slow一边前移一边把前面的元素踢出sum,减小sum的长度以求出最小符合条件的
    slow+=1

每个元素进入滑动窗口再出滑动窗口,被操作两次,2*n,时间复杂度为O(n)

不是循环套循环就是O(n^2),暴力解法两个for循环,一个i一个j,都遍历n个,i中每个j都会被操作n次,n个n所以是n*n,时间复杂度是O(n^2)

from typing import List
# 力扣部分开始
class Solution:
    def minSubArrayLen(self, target: int, nums: List[int]) -> int:
      rel= float('inf')  # 初始化结果为正无穷
      sum=0
      slow=0  # 滑动窗口的左边界
      # 遍历数组,使用滑动窗口
      for fast in range(len(nums)):
         sum+=nums[fast]  # 扩大窗口
         while sum >= target:
            rel = min(rel, fast - slow + 1)
            sum -= nums[slow]  # 缩小窗口
            slow += 1
      return rel if rel != float('inf') else 0
    
# 力扣部分结束
# 测试代码
if __name__ == "__main__":
    solution = Solution()
    target = 7
    nums = [2,3,1,2,4,3]
    result = solution.minSubArrayLen(target, nums)
    print(result)  # 输出: 2
    target1 = 2
    nums = [1,1,1,1,1]
    result = solution.minSubArrayLen(target1, nums)
    print(result)  # 输出: 2

59.螺旋矩阵II

题目建议: 本题关键还是在转圈的逻辑,在二分搜索中提到的区间定义,在这里又用上了。

题目链接:59. 螺旋矩阵 II - 力扣(LeetCode)

文章讲解:代码随想录

视频讲解:一入循环深似海 | LeetCode:59.螺旋矩阵II_哔哩哔哩_bilibili

题目

给你一个正整数 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

思路

键还是在转圈的逻辑模拟画矩阵的过程,遵循循环不变量原则【我用的是四个边界的方法,两个的没太懂】

top,left=0

bottom,right=n-1

  1. 填充上行从左到右:【left到right+1】,矩阵元素是【top】【num】行不变;top+1上边界下移
  2. 填充右列从上到下:【top到bottom+1】矩阵元素是【num】【right】列不变;right-1,右边界左移
  3. 填充下行从右到左:【right到left-1】(反向)矩阵元素是【bottom】【num】行不变;bottom-1下边界上移
  4. 填充左列从下到上:【bottom到top-1】(反向)矩阵元素是【num】【left】列不变;left+1左边界右移

上面的边界,因为是range是左闭右开,所以又边界正向要+1,反向要-1

循环条件是left<=right&top<=bottom

遍历二维矩阵,时间复杂度为 O(n^2)

这里的图每层都是左闭右开,但我上面的思路是取到最后一个,然后上边界直接下移,代码也是这样,所以是左闭右闭

  1. 先填充123,上边界下移到第二行,
  2. 填充45,右边界移动到前一列,
  3. 填充67,下边界移动到上一行,
  4. 填充89

from typing import List
class Solution:
    def generateMatrix(self, n: int) -> List[List[int]]:
        if n<=0: return []#n小于等于0时返回空列表
        matrix=[[0]*n for _ in range(n)]#创建n*n的矩阵,下划线_表示不考虑变量只考虑循环次数
        top,bottom=0,n-1#top表示上边界,bottom表示下边界
        left,right=0,n-1#left表示左边界,right表示右边界
        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#填充右边界,下标从0开始所以最右边不能是[right+1]
                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
    
# 测试代码
if __name__ == "__main__":
    solution = Solution()
    n = 3
    result = solution.generateMatrix(n)
    for row in result:
        print(row)  # 输出: [[1, 2, 3], [8, 9, 4], [7, 6, 5]]
    
    n1 = 4
    result1 = solution.generateMatrix(n1)
    for row in result1:
        print(row)  # 输出: [[1, 2, 3, 4], [12, 13, 14, 5], [11, 16, 15, 6], [10, 9, 8, 7]]

58.区间和

前缀和是一种思维巧妙很实用 而且 很有容易理解的一种算法思想,大家可以体会一下

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

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

题目

58. 区间和(第九期模拟笔试)

题目描述

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

输入描述

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

输出描述

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

输入示例

5
1
2
3
4
5
0 1
1 3

输出示例

3
9

提示信息

数据范围:
0 < n <= 100000

思路:前缀和

统计数组的区间和,从0到i,因此:

【0,1】p[1] = vec[0] + vec[1];

【0,5】p[5] = vec[0] + vec[1] + vec[2] + vec[3] + vec[4] + vec[5];

【2,5】p[5] - p[1] = vec[2] + vec[3] + vec[4] + vec[5];

注意:如果要求 区间下标 [2, 5] 的区间和,那么应该是 p[5] - p[1],而不是 p[5] - p[2]

步骤:

1.输入:读取键盘上输入的数组长度n和数组元素data[index]

2.计算前缀和:

        前缀和数组p中,p[i]表示原数组前i+1个元素的和

        一次遍历,时间复杂度为 O (n)

3.区间和查询:

        对于查询 [a, b](索引从 0 开始),区间和为 p[b] - p[a-1]

        若 a=0,则直接返回 p[b]

        每个查询的时间复杂度为 O (1)

4.输出每个查询的结果

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的值不变,比如第一次一直是1,index表示输入的每一行的第一个数
        #是data[index+i]而不是index+=i,index本身不变
    index+=n#更新索引,跳过数组元素,这句index才变化

    #计算前缀和
    p=[0]*n
    presum=0#前缀和累加器
    for i in range(n):
        presum+=vec[i]
        p[i]=presum
    #p[i]表示vec[0]到vec[i]的和
    #p[i]的值是vec[0]到vec[i]的和,p[0]=vec[0],p[1]=vec[0]+vec[1],p[2]=vec[0]+vec[1]+vec[2]...

    #处理查询
    results=[]#存储查询结果
    while index < len(data):#还有数据未处理
        l=int(data[index])#读取左边界,比如查询边界为【1,3】
        r=int(data[index+1])#读取右边界 
        index+=2#更新索引,跳过左边界和右边界

        #计算区间和
        if l == 0:
            results.append(p[r])#如果左边界是0,直接返回p[r]
        else:
            results.append(p[r] - p[l-1])#否则返回p[r] - p[l-1]
        
    #输出结果
    for result in results:
        print(result)
if __name__ == "__main__":
    main()#调用主函数
    

输入后在空行敲ctrl+Z,然后enter

44. 开发商购买土地

44. 开发商购买土地 | 代码随想录

题目

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。

思路
  1. 读取矩阵的行数 n 和列数 m
  2. 逐行读取矩阵元素,并计算所有元素的总和 sum
  3. 横向切割:计算前 i 行的元素和 horizontalCut,剩余部分的和为 sum - horizontalCut,差值为 |sum - 2*horizontalCut|
  4. 纵向切割:同理,计算前 j 列的元素和 verticalCut,差值为 |sum - 2*verticalCut|
  5. 原理:设矩阵总和为 S,切割后的一部分和为 T,另一部分和为 S - T,则差值为 |(S - T) - T| = |S - 2T|。通过遍历所有可能的切割位置,计算每个位置对应的 |S - 2T|,取最小值即可。
def main():
    import sys
    input = sys.stdin.read  # 读取键盘上输入的内容
    data = input().split()  # 将输入的内容按空格分割成列表
    index = 0  # 索引
    n = int(data[index])# 读取行数
    index+=1
    m=int(data[index])# 读取列数
    index+=1

    #计算总合
    vec=[]
    sum=0
    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)#将每行数据添加到vec中
        #vec[i]表示第i行的数据,vec[i][j]表示第i行第j列的数据
    # 计算每行和和每列和
    # h[i]表示第i行的和,v[j]表示第j列的和
    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')# 初始化结果为正无穷大
    # 计算最小差值

    hsum=0
    for i in range(n):
        hsum+=h[i]# 累加每行的和
        # 计算当前行和的差值
        result=min(result,abs((sum-hsum)-hsum))#注意绝对值
        
    vsum=0
    for j in range(m):
        vsum+=v[j]
        result=min(result,abs((sum-vsum)-vsum))

    print(result)
if __name__ == "__main__":
    main()  # 调用主函数

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

清水泛轻舟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值