每日一题——Python实现PAT甲级1029 Median(举一反三+思想解读+逐步优化)

本文主要是介绍每日一题——Python实现PAT甲级1029 Median(举一反三+思想解读+逐步优化),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!


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

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

Python-3.12.0文档解读

目录

我的方法

代码功能和结构点评

时间复杂度分析

空间复杂度分析

优化建议

我要更强!

代码详解:

时间和空间复杂度

示例解释

示例输入

合并后的数组

中位数位置

详细步骤

哲学和编程思想

编程思想

哲学思想

示例的哲学与编程思想

举一反三

技巧1:化繁为简

技巧2:递归的自相似性

技巧3:二分查找

技巧4:抽象

技巧5:分治法和递归结合(归并排序)

总结


题目链接


我的方法

nums1=list(map(int,input().split()))
N1=nums1[0]
nums1=nums1[1:]
nums2=list(map(int,input().split()))
N2=nums2[0]
nums2=nums2[1:]nums1+=nums2
nums1.sort()if (N1+N2)%2==0:print(nums1[(N1+N2)//2-1])
else:print(nums1[(N1+N2)//2])

这段代码的功能是读取两个列表(nums1 和 nums2),合并它们,排序,然后根据合并后的列表长度的奇偶性,输出中位数。

代码功能和结构点评

  1. 输入处理:
    • 两次调用 input().split() 读取输入,将其转换为整型列表。
    • nums1 和 nums2 的第一个元素被分别赋值给 N1 和 N2,表示两个列表的长度。
    • 余下的元素分别保存在 nums1 和 nums2 中。
  2. 合并与排序:
    • 将 nums2 合并到 nums1 中,然后对合并后的列表进行排序。
  3. 中位数计算:
  • 根据合并后列表长度的奇偶性,计算并输出中位数。

时间复杂度分析

  1. 读取输入:
    • map(int, input().split()) 的时间复杂度是 O(N),这里 N 是输入的元素总数。
  2. 合并列表:
    • nums1 += nums2 是 O(N2),其中 N2 是 nums2 的长度。
  3. 排序:
    • 使用 sort() 方法对列表排序,时间复杂度是 O((N1 + N2) log(N1 + N2)),因为采用的是 Timsort 算法。
  4. 中位数查找:
  • 访问列表元素的时间复杂度是 O(1)。

综上所述,总的时间复杂度是: [ O(N1 + N2 + (N1 + N2) \log(N1 + N2)) ]

空间复杂度分析

  • 需要额外的空间来存储输入的整数列表,空间复杂度是 O(N1 + N2)。
  • sort() 方法在最差情况下使用 O(N1 + N2) 的额外空间(Timsort 的空间复杂度)。

综上所述,总的空间复杂度是: [ O(N1 + N2) ]

优化建议

合并和排序的时间复杂度是这段代码的主要瓶颈。如果只是为了找中位数,可以采用更高效的算法(如归并排序中的选择算法),其时间复杂度为 O(N1 + N2),无需对整个列表排序。


我要更强!

要优化这段代码的时间复杂度和空间复杂度,可以使用一种称为“二分查找”的方法来找到两个有序数组的中位数,而无需将它们合并和排序。这个方法的时间复杂度是 O(log(min(N1, N2))),空间复杂度是 O(1)。

以下是实现这个方法的完整代码和注释:

def findMedianSortedArrays(nums1, nums2):def find_kth_element(arr1, arr2, k):# 如果 arr1 比 arr2 长,交换它们if len(arr1) > len(arr2):arr1, arr2 = arr2, arr1# 如果 arr1 为空,直接返回 arr2 中的第 k 个元素if len(arr1) == 0:return arr2[k - 1]# 如果 k == 1,返回两个数组第一个元素中较小的一个if k == 1:return min(arr1[0], arr2[0])# 取两个数组的第 k//2 个元素进行比较i = min(len(arr1), k // 2)j = min(len(arr2), k // 2)if arr1[i - 1] > arr2[j - 1]:return find_kth_element(arr1, arr2[j:], k - j)else:return find_kth_element(arr1[i:], arr2, k - i)total_len = len(nums1) + len(nums2)if total_len % 2 == 1:# 如果总长度是奇数,返回第 (total_len // 2 + 1) 个元素return find_kth_element(nums1, nums2, total_len // 2 + 1)else:# 如果总长度是偶数,返回第 (total_len // 2) 个元素return find_kth_element(nums1, nums2, total_len // 2)# 读取输入
import sys
input = sys.stdin.read
data = input().split()# 解析输入
n1 = int(data[0])
nums1 = list(map(int, data[1:n1+1]))n2 = int(data[n1+1])
nums2 = list(map(int, data[n1+2:]))# 调用函数并输出结果
print(findMedianSortedArrays(nums1, nums2))
  • 代码详解:

  1. 定义 find_kth_element 函数:
    • 该函数用于查找两个有序数组中的第 k 个元素。
    • 如果数组 arr1 比 arr2 长,则交换它们,以确保 arr1 是较短的数组。
    • 如果 arr1 为空,则直接返回 arr2 中的第 k 个元素。
    • 如果 k == 1,返回两个数组第一个元素中较小的一个。
    • 通过比较 arr1 和 arr2 的第 k//2 个元素来缩小查找范围。
  2. 计算总长度 total_len:
    • 如果总长度是奇数,则返回第 (total_len // 2 + 1) 个元素。
    • 如果总长度是偶数,则返回第 (total_len // 2) 个元素(即中间两个元素偏左的那个)。
  3. 读取和解析输入:
    • 使用 sys.stdin.read 读取输入,并根据输入格式解析成两个数组。
  4. 调用函数并输出结果:
  • 调用 findMedianSortedArrays 函数计算中位数,并输出结果。

这样,通过二分查找,我们能以 O(log(min(N1, N2))) 的时间复杂度找到两个有序数组的中位数。

时间和空间复杂度

  • 时间复杂度:O(log(min(N1, N2))),因为我们对较短的数组进行二分查找。
  • 空间复杂度:O(1),只使用了常数级别的额外空间。

这段代码高效地找到了两个有序数组的中位数,避免了合并和排序的高时间复杂度,并且只使用了常数级别的额外空间。

示例解释

示例输入

数组1:[11, 12, 13, 14]
数组2:[9, 10, 15, 16, 17]

合并后的数组

合并并排序后,我们得到一个新的排序数组:[9, 10, 11, 12, 13, 14, 15, 16, 17]

中位数位置

由于合并后的数组长度为 9(奇数),中位数是第 5 个元素(偏左的那个):13

详细步骤

因为 arr1[1] > arr2[1],所以排除 arr2 的前 j = 2 个元素,并递归查找剩下的第 k - j = 3 个元素。

因为 arr1[0] <= arr2[0],所以排除 arr1 的前 i = 1 个元素,并递归查找剩下的第 k - i = 2 个元素。

因为 arr1[0] <= arr2[0],所以排除 arr1 的前 i = 1 个元素,并递归查找剩下的第 k - i = 1 个元素。

  1. 调用 findMedianSortedArrays(nums1, nums2):
    • nums1 = [11, 12, 13, 14]
    • nums2 = [9, 10, 15, 16, 17]
    • total_len = 9(奇数)。
  2. 查找第 (9 // 2 + 1) = 5 个元素。
  3. 调用 find_kth_element(nums1, nums2, 5):
    • k = 5,初始时 arr1 = [11, 12, 13, 14] 和 arr2 = [9, 10, 15, 16, 17]。
  4. 比较两个数组的第 k // 2 = 2 个元素:
    • i = min(len(arr1), k // 2) = 2
    • j = min(len(arr2), k // 2) = 2
    • arr1[1] = 12
    • arr2[1] = 10
  5. 调用 find_kth_element(nums1, nums2[2:], 3),即:
    • arr1 = [11, 12, 13, 14]
    • arr2 = [15, 16, 17]
    • k = 3。
  6. 比较两个数组的第 k // 2 = 1 个元素:
    • i = min(len(arr1), k // 2) = 1
    • j = min(len(arr2), k // 2) = 1
    • arr1[0] = 11
    • arr2[0] = 15
  7. 调用 find_kth_element(nums1[1:], nums2, 2),即:
    • arr1 = [12, 13, 14]
    • arr2 = [15, 16, 17]
    • k = 2。
  8. 比较两个数组的第 k // 2 = 1 个元素:
    • i = min(len(arr1), k // 2) = 1
    • j = min(len(arr2), k // 2) = 1
    • arr1[0] = 12
    • arr2[0] = 15
  9. 调用 find_kth_element(nums1[1:], nums2, 1),即:
    • arr1 = [13, 14]
    • arr2 = [15, 16, 17]
    • k = 1。
  10. 由于 k == 1,直接返回两个数组的第一个元素中较小的一个,即:
  • min(arr1[0], arr2[0]) = min(13, 15) = 13

所以,合并后数组的中位数为 13。


哲学和编程思想

编程思想

  1. 分治法(Divide and Conquer):
    • 该方法通过将问题分成更小的子问题,然后递归地解决这些子问题。具体来说,二分查找的方法将两个数组的中位数问题分解为对较短数组的一部分和较长数组的一部分进行递归查找。
    • 分治法的核心在于将一个复杂问题分解成更小、更容易解决的部分,然后组合这些部分的解来解决整个问题。
  2. 递归(Recursion):
    • 递归是一种直接或间接调用自身的编程技术。这种方法通过不断地缩小问题规模,最终解决最小规模的问题来达到解决整个问题的目的。
    • 递归的核心思想在于找到基准情况(base case)和递归步骤(recursive step),基准情况是问题的最小实例,它可以直接解答,而递归步骤则是将问题缩小并继续递归求解。
  3. 二分查找(Binary Search):
  • 二分查找是一种在有序数组中查找元素的高效算法,其时间复杂度为 O(log n)。在这个方法中,二分查找用于确定数组中第 k 个元素,从而显著减少了查找的时间复杂度。
  • 二分查找的核心思想在于每次比较时将搜索范围缩小一半,从而快速定位目标元素。

哲学思想

  1. 化繁为简(Reductionism):
    • 这种思想主张将复杂的问题分解为更小、更简单的问题来解决。在这个算法中,通过将两个数组的中位数问题分解为查找第 k 个元素的问题,我们可以更容易地处理问题。
    • 化繁为简的哲学在于相信任何复杂的问题都可以通过适当的分解和简化来解决。
  2. 递归的自相似性(Self-Similarity in Recursion):
    • 递归过程中的每一层调用看起来都与其他层次类似,只是处理的规模不同。这种自相似性是许多自然界和数学现象的共同特征。
    • 递归的自相似性在编程中的应用体现了问题的结构和解决方案之间的一致性。
  3. 抽象(Abstraction):
  • 抽象是一种只关注问题的高层次视角,而忽略具体实现细节的方法。在这个算法中,通过定义 find_kth_element 函数,我们把查找第 k 个元素的具体实现细节封装起来,使得主函数的逻辑更加清晰。
  • 抽象的核心在于从复杂的现实中提取出关键的本质部分,从而简化问题的解决过程。

示例的哲学与编程思想

通过运用上述思想,能够高效地解决合并两个有序数组并找到中位数的问题:

  1. 分治法使我们能够递归地缩小问题规模,避免了直接合并两个数组的高昂时间复杂度。
  2. 递归允许我们自然地处理分治法分解出的子问题。
  3. 二分查找提供了一种高效的方式来确定数组的中位数位置。
  4. 化繁为简的哲学思想帮助我们将复杂问题分解,使其更易于解决。
  5. 递归的自相似性和抽象使得代码结构清晰,逻辑简洁。

通过结合这些编程和哲学思想,不仅能够高效地解决问题,还能够提高代码的可读性和可维护性。


举一反三

理解这些编程和哲学思想后,您可以应用这些思想解决其他复杂问题。以下是一些技巧和示例代码,帮助您举一反三:

技巧1:化繁为简

问题:给定一个整数数组,找到数组中的第 k 小的元素(不包括重复元素)。

技巧:可以利用分治法和递归来解决这个问题。

def findKthSmallest(arr, k):def quickselect(left, right, k_smallest):if left == right:return arr[left]pivot_index = partition(left, right)if k_smallest == pivot_index:return arr[k_smallest]elif k_smallest < pivot_index:return quickselect(left, pivot_index - 1, k_smallest)else:return quickselect(pivot_index + 1, right, k_smallest)def partition(left, right):pivot = arr[right]store_index = leftfor i in range(left, right):if arr[i] < pivot:arr[i], arr[store_index] = arr[store_index], arr[i]store_index += 1arr[store_index], arr[right] = arr[right], arr[store_index]return store_indexunique_arr = list(set(arr))return quickselect(0, len(unique_arr) - 1, k - 1)# 示例使用
arr = [3, 2, 1, 5, 6, 4, 3, 2]
k = 2
print(findKthSmallest(arr, k))  # 输出 3

技巧2:递归的自相似性

问题:计算斐波那契数列的第 n 个数。

技巧:递归的自相似性可以自然地解决这种问题。

def fibonacci(n):if n <= 1:return nelse:return fibonacci(n - 1) + fibonacci(n - 2)# 示例使用
n = 10
print(fibonacci(n))  # 输出 55

技巧3:二分查找

问题:在一个旋转排序数组中找到一个目标值。

技巧:二分查找可以高效地解决这个问题。

def search_rotated_array(nums, target):left, right = 0, len(nums) - 1while left <= right:mid = (left + right) // 2if nums[mid] == target:return midif nums[left] <= nums[mid]:if nums[left] <= target < nums[mid]:right = mid - 1else:left = mid + 1else:if nums[mid] < target <= nums[right]:left = mid + 1else:right = mid - 1return -1# 示例使用
nums = [4, 5, 6, 7, 0, 1, 2]
target = 0
print(search_rotated_array(nums, target))  # 输出 4

技巧4:抽象

问题:计算字符串的所有可能子集。

技巧:抽象出递归的核心部分,使得代码更清晰。

def subsets(s):def backtrack(start, path):result.append(path[:])for i in range(start, len(s)):path.append(s[i])backtrack(i + 1, path)path.pop()result = []backtrack(0, [])return result# 示例使用
s = "abc"
print(subsets(s))  # 输出 [[''], ['a'], ['a', 'b'], ['a', 'b', 'c'], ['a', 'c'], ['b'], ['b', 'c'], ['c']]

技巧5:分治法和递归结合(归并排序)

问题:对一个整数数组进行排序。

技巧:利用分治法和递归实现归并排序。

def merge_sort(arr):if len(arr) <= 1:return arrmid = len(arr) // 2left_half = merge_sort(arr[:mid])right_half = merge_sort(arr[mid:])return merge(left_half, right_half)def merge(left, right):sorted_array = []while left and right:if left[0] < right[0]:sorted_array.append(left.pop(0))else:sorted_array.append(right.pop(0))sorted_array.extend(left or right)return sorted_array# 示例使用
arr = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]
print(merge_sort(arr))  # 输出 [1, 1, 2, 3, 3, 4, 5, 5, 5, 6, 9]

总结

通过理解这些技巧,可以在解决各种问题时应用相应的编程和哲学思想:

  • 化繁为简:将复杂问题分解为简单子问题。
  • 递归的自相似性:利用递归解决具有自相似性的复杂问题。
  • 二分查找:在有序或部分有序的数据结构中快速查找元素。
  • 抽象:将复杂的逻辑封装在函数内,使代码更简洁清晰。

分治法和递归结合:通过分治法和递归解决需要多步骤处理的问题。


感谢阅读,关注我每日一题提升自己。

这篇关于每日一题——Python实现PAT甲级1029 Median(举一反三+思想解读+逐步优化)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1009378

相关文章

SpringBoot集成redisson实现延时队列教程

《SpringBoot集成redisson实现延时队列教程》文章介绍了使用Redisson实现延迟队列的完整步骤,包括依赖导入、Redis配置、工具类封装、业务枚举定义、执行器实现、Bean创建、消费... 目录1、先给项目导入Redisson依赖2、配置redis3、创建 RedissonConfig 配

Python的Darts库实现时间序列预测

《Python的Darts库实现时间序列预测》Darts一个集统计、机器学习与深度学习模型于一体的Python时间序列预测库,本文主要介绍了Python的Darts库实现时间序列预测,感兴趣的可以了解... 目录目录一、什么是 Darts?二、安装与基本配置安装 Darts导入基础模块三、时间序列数据结构与

Python正则表达式匹配和替换的操作指南

《Python正则表达式匹配和替换的操作指南》正则表达式是处理文本的强大工具,Python通过re模块提供了完整的正则表达式功能,本文将通过代码示例详细介绍Python中的正则匹配和替换操作,需要的朋... 目录基础语法导入re模块基本元字符常用匹配方法1. re.match() - 从字符串开头匹配2.

Python使用FastAPI实现大文件分片上传与断点续传功能

《Python使用FastAPI实现大文件分片上传与断点续传功能》大文件直传常遇到超时、网络抖动失败、失败后只能重传的问题,分片上传+断点续传可以把大文件拆成若干小块逐个上传,并在中断后从已完成分片继... 目录一、接口设计二、服务端实现(FastAPI)2.1 运行环境2.2 目录结构建议2.3 serv

C#实现千万数据秒级导入的代码

《C#实现千万数据秒级导入的代码》在实际开发中excel导入很常见,现代社会中很容易遇到大数据处理业务,所以本文我就给大家分享一下千万数据秒级导入怎么实现,文中有详细的代码示例供大家参考,需要的朋友可... 目录前言一、数据存储二、处理逻辑优化前代码处理逻辑优化后的代码总结前言在实际开发中excel导入很

通过Docker容器部署Python环境的全流程

《通过Docker容器部署Python环境的全流程》在现代化开发流程中,Docker因其轻量化、环境隔离和跨平台一致性的特性,已成为部署Python应用的标准工具,本文将详细演示如何通过Docker容... 目录引言一、docker与python的协同优势二、核心步骤详解三、进阶配置技巧四、生产环境最佳实践

Python一次性将指定版本所有包上传PyPI镜像解决方案

《Python一次性将指定版本所有包上传PyPI镜像解决方案》本文主要介绍了一个安全、完整、可离线部署的解决方案,用于一次性准备指定Python版本的所有包,然后导出到内网环境,感兴趣的小伙伴可以跟随... 目录为什么需要这个方案完整解决方案1. 项目目录结构2. 创建智能下载脚本3. 创建包清单生成脚本4

SpringBoot+RustFS 实现文件切片极速上传的实例代码

《SpringBoot+RustFS实现文件切片极速上传的实例代码》本文介绍利用SpringBoot和RustFS构建高性能文件切片上传系统,实现大文件秒传、断点续传和分片上传等功能,具有一定的参考... 目录一、为什么选择 RustFS + SpringBoot?二、环境准备与部署2.1 安装 RustF

Nginx部署HTTP/3的实现步骤

《Nginx部署HTTP/3的实现步骤》本文介绍了在Nginx中部署HTTP/3的详细步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学... 目录前提条件第一步:安装必要的依赖库第二步:获取并构建 BoringSSL第三步:获取 Nginx

MyBatis Plus实现时间字段自动填充的完整方案

《MyBatisPlus实现时间字段自动填充的完整方案》在日常开发中,我们经常需要记录数据的创建时间和更新时间,传统的做法是在每次插入或更新操作时手动设置这些时间字段,这种方式不仅繁琐,还容易遗漏,... 目录前言解决目标技术栈实现步骤1. 实体类注解配置2. 创建元数据处理器3. 服务层代码优化填充机制详