每日一题——Python实现PAT乙级1050 螺旋矩阵(举一反三+思想解读+逐步优化)6千字好文


一个认为一切根源都是“自己不够强”的INTJ

个人主页:用哲学编程-CSDN博客
专栏:每日一题——举一反三
Python编程学习
Python内置函数

Python-3.12.0文档解读

目录

我的写法

时间复杂度分析

空间复杂度分析

总结

我要更强

代码解释

时间复杂度

空间复杂度

总结

哲学和编程思想

哲学思想

编程思想

总结

举一反三

1. 模块化设计

2. 抽象化

3. 迭代与递归

4. 算法优化

5. 数据结构选择

6. 边界条件处理

7. 简约主义

8. 秩序与结构


题目链接:https://pintia.cn/problem-sets/994805260223102976/exam/problems/type/7?problemSetProblemId=994805275146436608&page=0

我的写法

import math

# 读取矩阵元素的数量
N = int(input())
# 读取数字列表并按降序排序
nums = list(map(int, input().split()))
nums.sort(reverse=True)

def find_dimensions(N):
    # 找到最接近N的平方根的整数
    middle = math.isqrt(N)
    # 从middle开始向上找,直到找到两个数乘积为N
    for i in range(middle, N+1):
        for j in range(middle, 0, -1):
            if i * j == N:
                return i, j

def create_spiral_matrix(N, nums, m, n, seq_paces):
    # 初始化矩阵
    matrix = [[0 for i in range(n)] for j in range(m)]
    index_matrix = [0, -1]  # 矩阵索引初始位置
    index_nums = 0  # 数字列表索引
    for i in range(len(seq_paces)):
        if i % 4 == 0:  # 向右移动seq_paces[i]步
            index_matrix[1] += 1
            for j in range(seq_paces[i]):
                matrix[index_matrix[0]][index_matrix[1] + j] = nums[index_nums]
                if j == seq_paces[i] - 1:
                    index_matrix[1] = index_matrix[1] + j
                index_nums += 1
        elif i % 4 == 1:  # 向下移动seq_paces[i]步
            index_matrix[0] += 1
            for j in range(seq_paces[i]):
                matrix[index_matrix[0] + j][index_matrix[1]] = nums[index_nums]
                if j == seq_paces[i] - 1:
                    index_matrix[0] = index_matrix[0] + j
                index_nums += 1
        elif i % 4 == 2:  # 向左移动seq_paces[i]步
            index_matrix[1] -= 1
            for j in range(seq_paces[i]):
                matrix[index_matrix[0]][index_matrix[1] - j] = nums[index_nums]
                if j == seq_paces[i] - 1:
                    index_matrix[1] = index_matrix[1] - j
                index_nums += 1
        elif i % 4 == 3:  # 向上移动seq_paces[i]步
            index_matrix[0] -= 1
            for j in range(seq_paces[i]):
                matrix[index_matrix[0] - j][index_matrix[1]] = nums[index_nums]
                if j == seq_paces[i] - 1:
                    index_matrix[0] = index_matrix[0] - j
                index_nums += 1
    return matrix

def calculate_sequence_paces(N, m, n):
    # 计算螺旋填充的步数序列
    if m == 1:
        return [n]
    elif n == 1:
        return [1, m - 1]
    seq_paces = []
    dif = 0
    for i in range(N):
        if i == 0:
            seq_paces.append(n)
            dif += 1
            continue
        if i % 2 == 1:
            seq_paces.append(m - dif)
        elif i % 2 == 0:
            seq_paces.append(n - dif)
            dif += 1
        if seq_paces[-1] == 0:
            return seq_paces[:-1]
    return seq_paces

# 找到矩阵的维度
m, n = find_dimensions(N)
# 计算螺旋填充的步数序列
seq_paces = calculate_sequence_paces(N, m, n)
# 创建螺旋矩阵
matrix = create_spiral_matrix(N, nums, m, n, seq_paces)
# 输出矩阵
for row in matrix:
    print(*row)

时间复杂度分析

  • 排序:对 N 个数字进行排序的时间复杂度是 O(N log N)。
  • find_dimensions:这个函数在最坏情况下需要检查大约 sqrt(N) 个可能的行和列组合,因此时间复杂度大约是 O(N)。
  • calculate_sequence_paces:这个函数在最坏情况下需要遍历 N 次,时间复杂度也是 O(N)。
  • create_spiral_matrix:这个函数需要遍历所有步数序列,步数序列的长度大约是 sqrt(N),每次填充矩阵的时间复杂度是 O(N),所以总时间复杂度也是 O(N)。

综合来看,主要的时间消耗来自于排序和矩阵填充,因此总时间复杂度是 O(N log N)。

空间复杂度分析

  • 排序:排序需要的额外空间复杂度是 O(N)。
  • 矩阵:矩阵的大小是 O(N)。
  • 步数序列:步数序列的大小大约是 sqrt(N)。

综合来看,主要的空间消耗来自于矩阵和排序,因此总空间复杂度是 O(N)。

总结

这段代码在功能上实现了将一组数字按螺旋顺序填充到矩阵中,并且在时间和空间复杂度上都是高效的。代码结构清晰,函数职责明确,易于理解和维护。主要的时间消耗来自于排序和矩阵填充,而空间消耗主要由矩阵和排序决定。总体来说,这是一段设计良好且高效的代码。

我要更强

为了解决这个问题,我们需要完成以下步骤:

  1. 读取输入的数字数量 N 和数字列表。
  2. 对数字列表进行降序排序。
  3. 找到满足 m * n = N 且 m >= n 的矩阵维度 m 和 n,并且使得 m - n 最小。
  4. 创建一个空的螺旋矩阵,并按照顺时针方向填充数字。
  5. 输出填充好的螺旋矩阵。

以下是实现上述步骤的Python代码:

import math

# 读取输入
N = int(input())
nums = list(map(int, input().split()))

# 对数字列表进行降序排序
nums.sort(reverse=True)

# 找到合适的矩阵维度
def find_dimensions(N):
    min_diff = float('inf')
    m, n = 0, 0
    for i in range(1, int(math.sqrt(N)) + 1):
        if N % i == 0:
            j = N // i
            if j >= i and j - i < min_diff:
                min_diff = j - i
                m, n = j, i
    return m, n

m, n = find_dimensions(N)

# 创建并填充螺旋矩阵
matrix = [[0] * n for _ in range(m)]
top, bottom, left, right = 0, m - 1, 0, n - 1
index = 0

while top <= bottom and left <= right:
    # 向右填充
    for i in range(left, right + 1):
        matrix[top][i] = nums[index]
        index += 1
    top += 1

    # 向下填充
    for i in range(top, bottom + 1):
        matrix[i][right] = nums[index]
        index += 1
    right -= 1

    # 向左填充
    if top <= bottom:
        for i in range(right, left - 1, -1):
            matrix[bottom][i] = nums[index]
            index += 1
        bottom -= 1

    # 向上填充
    if left <= right:
        for i in range(bottom, top - 1, -1):
            matrix[i][left] = nums[index]
            index += 1
        left += 1

# 输出螺旋矩阵
for row in matrix:
    print(' '.join(map(str, row)))

代码解释

  1. 输入读取:首先读取输入的数字数量 N 和数字列表 nums。
  2. 排序:对数字列表 nums 进行降序排序。
  3. 找到矩阵维度:定义 find_dimensions 函数来找到合适的矩阵维度 m 和 n,使得 m * n = N 且 m >= n,并且 m - n 最小。
  4. 填充矩阵:创建一个空的矩阵 matrix,并使用边界指针(top, bottom, left, right)来控制填充方向,按照顺时针方向填充数字。
  5. 输出矩阵:最后,输出填充好的螺旋矩阵。

这个代码在时间和空间复杂度上都是高效的,并且能够正确地解决给定的问题。

代码结构和逻辑

  1. 输入处理:代码首先读取输入的数字数量 N 和数字列表 nums,这一部分的时间复杂度是 O(N),因为需要读取和存储 N 个数字。
  2. 排序:对数字列表 nums 进行降序排序,使用的是 Python 内置的 sort 方法,其时间复杂度是 O(N log N)。
  3. 找到矩阵维度:定义 find_dimensions 函数来找到合适的矩阵维度 m 和 n。这个函数的时间复杂度是 O(sqrt(N)),因为它只需要遍历到 sqrt(N) 的整数。
  4. 填充矩阵:创建一个空的矩阵 matrix,并使用边界指针(top, bottom, left, right)来控制填充方向,按照顺时针方向填充数字。这一部分的时间复杂度是 O(N),因为每个数字只被处理一次。
  5. 输出矩阵:最后,输出填充好的螺旋矩阵。这一部分的时间复杂度也是 O(N),因为需要遍历并输出每个元素。

时间复杂度

综合以上分析,主要的时间消耗来自于排序和矩阵填充。因此,总的时间复杂度是 O(N log N)。

空间复杂度

  1. 输入存储:需要存储 N 个数字,空间复杂度是 O(N)。
  2. 排序:排序过程中需要额外的空间,但由于 Python 的 sort 方法是原地排序,所以这部分的空间复杂度可以忽略不计。
  3. 矩阵存储:需要存储一个 m * n 的矩阵,空间复杂度是 O(N)。

因此,总的空间复杂度是 O(N)。

总结

这段代码在功能上实现了将一组数字按螺旋顺序填充到矩阵中,并且在时间和空间复杂度上都是高效的。代码结构清晰,函数职责明确,易于理解和维护。主要的时间消耗来自于排序和矩阵填充,而空间消耗主要由矩阵和输入数据决定。总体来说,这是一段设计良好且高效的代码。


哲学和编程思想

这段代码体现了多个哲学和编程思想,以下是一些主要的思想:

哲学思想

  1. 秩序与结构:哲学上,秩序和结构是宇宙的基本原则。这段代码通过将无序的数字列表转换为有序的螺旋矩阵,体现了对秩序和结构的追求。
  2. 简约主义:代码尽量保持简洁和高效,避免不必要的复杂性。这种简约主义的思想在代码的结构和逻辑中得到了体现。

编程思想

  1. 模块化:代码被划分为多个函数,每个函数负责一个特定的任务(如输入处理、排序、找到矩阵维度、填充矩阵和输出矩阵)。这种模块化的设计使得代码更易于理解和维护。
  2. 抽象化:通过定义 find_dimensions 函数来抽象出找到矩阵维度的逻辑,使得主逻辑更加简洁。这种抽象化的思想有助于隐藏复杂性,提高代码的可读性。
  3. 迭代与递归:虽然代码中没有显式的递归,但通过边界指针的迭代方式来填充矩阵,体现了迭代思想。迭代是一种逐步推进的解决问题的方法,适用于处理有序数据。
  4. 算法优化:在找到矩阵维度的过程中,代码通过遍历到 sqrt(N) 的整数来减少计算量,体现了算法优化的思想。这种优化减少了不必要的计算,提高了效率。
  5. 数据结构选择:代码选择了列表和二维列表(矩阵)作为主要的数据结构,这种选择是基于问题的需求和数据结构的特性。合理选择数据结构是编程中的重要思想。
  6. 边界条件处理:在填充矩阵的过程中,代码通过边界指针来控制填充方向,并处理边界条件。这种对边界条件的细致处理是编程中的重要思想,有助于避免错误和提高代码的健壮性。

总结

这段代码体现了秩序与结构、简约主义等哲学思想,以及模块化、抽象化、迭代与递归、算法优化、数据结构选择和边界条件处理等编程思想。这些思想的综合运用使得代码既高效又易于理解和维护。


举一反三

结合上述的哲学和编程思想,以下是一些技巧,可以帮助你举一反三,将这些思想应用到其他编程问题中:

1. 模块化设计

  • 技巧:将复杂问题分解为更小的、可管理的部分。每个部分都应该有一个清晰的目的和接口。
  • 应用:在处理大型项目或复杂算法时,尝试将功能分解为独立的函数或类,每个部分负责一个具体的任务。

2. 抽象化

  • 技巧:识别和抽象出问题中的通用模式或逻辑,将其封装为可重用的组件。
  • 应用:在编写代码时,思考哪些部分是通用的,可以被抽象出来,以便在其他地方重用。

3. 迭代与递归

  • 技巧:根据问题的性质选择合适的迭代或递归方法。迭代通常更直观,而递归在处理树状或嵌套结构时更自然。
  • 应用:在解决需要逐步推进或处理层次结构的问题时,考虑使用迭代或递归方法。

4. 算法优化

  • 技巧:始终寻找减少计算量和提高效率的方法。这可能包括使用更高效的数据结构、减少不必要的计算或利用问题的特性。
  • 应用:在设计算法时,思考如何利用问题的特性来优化性能,例如通过预处理、缓存或选择合适的算法策略。

5. 数据结构选择

  • 技巧:根据问题的需求选择合适的数据结构。理解不同数据结构的优缺点,并根据访问模式和操作需求做出选择。
  • 应用:在解决需要频繁查找、插入或删除操作的问题时,考虑使用哈希表、树或链表等数据结构。

6. 边界条件处理

  • 技巧:在编写代码时,始终考虑边界条件和异常情况。确保代码在这些情况下也能正确运行。
  • 应用:在编写循环、递归或处理输入数据时,特别注意边界条件,确保代码的健壮性。

7. 简约主义

  • 技巧:保持代码简洁和清晰。避免过度工程化和不必要的复杂性。
  • 应用:在编写代码时,尽量使用简单直接的解决方案,避免引入不必要的抽象或复杂逻辑。

8. 秩序与结构

  • 技巧:在解决问题时,考虑如何引入秩序和结构,使问题更易于理解和处理。
  • 应用:在处理无序数据或复杂逻辑时,思考如何通过排序、分组或层次化来引入秩序和结构。

通过将这些技巧应用到你的编程实践中,你将能够更有效地解决问题,并提高代码的质量和可维护性。记住,编程不仅仅是编写代码,更是关于如何思考和解决问题。

  • 8
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

用哲学编程

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

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

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

打赏作者

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

抵扣说明:

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

余额充值