每日一题——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

相关文章

Python如何实现PDF隐私信息检测

《Python如何实现PDF隐私信息检测》随着越来越多的个人信息以电子形式存储和传输,确保这些信息的安全至关重要,本文将介绍如何使用Python检测PDF文件中的隐私信息,需要的可以参考下... 目录项目背景技术栈代码解析功能说明运行结php果在当今,数据隐私保护变得尤为重要。随着越来越多的个人信息以电子形

使用 sql-research-assistant进行 SQL 数据库研究的实战指南(代码实现演示)

《使用sql-research-assistant进行SQL数据库研究的实战指南(代码实现演示)》本文介绍了sql-research-assistant工具,该工具基于LangChain框架,集... 目录技术背景介绍核心原理解析代码实现演示安装和配置项目集成LangSmith 配置(可选)启动服务应用场景

使用Python快速实现链接转word文档

《使用Python快速实现链接转word文档》这篇文章主要为大家详细介绍了如何使用Python快速实现链接转word文档功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 演示代码展示from newspaper import Articlefrom docx import

前端原生js实现拖拽排课效果实例

《前端原生js实现拖拽排课效果实例》:本文主要介绍如何实现一个简单的课程表拖拽功能,通过HTML、CSS和JavaScript的配合,我们实现了课程项的拖拽、放置和显示功能,文中通过实例代码介绍的... 目录1. 效果展示2. 效果分析2.1 关键点2.2 实现方法3. 代码实现3.1 html部分3.2

Python Jupyter Notebook导包报错问题及解决

《PythonJupyterNotebook导包报错问题及解决》在conda环境中安装包后,JupyterNotebook导入时出现ImportError,可能是由于包版本不对应或版本太高,解决方... 目录问题解决方法重新安装Jupyter NoteBook 更改Kernel总结问题在conda上安装了

Python如何计算两个不同类型列表的相似度

《Python如何计算两个不同类型列表的相似度》在编程中,经常需要比较两个列表的相似度,尤其是当这两个列表包含不同类型的元素时,下面小编就来讲讲如何使用Python计算两个不同类型列表的相似度吧... 目录摘要引言数字类型相似度欧几里得距离曼哈顿距离字符串类型相似度Levenshtein距离Jaccard相

Python安装时常见报错以及解决方案

《Python安装时常见报错以及解决方案》:本文主要介绍在安装Python、配置环境变量、使用pip以及运行Python脚本时常见的错误及其解决方案,文中介绍的非常详细,需要的朋友可以参考下... 目录一、安装 python 时常见报错及解决方案(一)安装包下载失败(二)权限不足二、配置环境变量时常见报错及

Python中顺序结构和循环结构示例代码

《Python中顺序结构和循环结构示例代码》:本文主要介绍Python中的条件语句和循环语句,条件语句用于根据条件执行不同的代码块,循环语句用于重复执行一段代码,文章还详细说明了range函数的使... 目录一、条件语句(1)条件语句的定义(2)条件语句的语法(a)单分支 if(b)双分支 if-else(

Python itertools中accumulate函数用法及使用运用详细讲解

《Pythonitertools中accumulate函数用法及使用运用详细讲解》:本文主要介绍Python的itertools库中的accumulate函数,该函数可以计算累积和或通过指定函数... 目录1.1前言:1.2定义:1.3衍生用法:1.3Leetcode的实际运用:总结 1.1前言:本文将详

Java深度学习库DJL实现Python的NumPy方式

《Java深度学习库DJL实现Python的NumPy方式》本文介绍了DJL库的背景和基本功能,包括NDArray的创建、数学运算、数据获取和设置等,同时,还展示了如何使用NDArray进行数据预处理... 目录1 NDArray 的背景介绍1.1 架构2 JavaDJL使用2.1 安装DJL2.2 基本操