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

news2024/9/20 15:31:35


一个认为一切根源都是“自己不够强”的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 = left
        for i in range(left, right):
            if arr[i] < pivot:
                arr[i], arr[store_index] = arr[store_index], arr[i]
                store_index += 1
        arr[store_index], arr[right] = arr[right], arr[store_index]
        return store_index

    unique_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 n
    else:
        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) - 1
    while left <= right:
        mid = (left + right) // 2
        if nums[mid] == target:
            return mid
        if nums[left] <= nums[mid]:
            if nums[left] <= target < nums[mid]:
                right = mid - 1
            else:
                left = mid + 1
        else:
            if nums[mid] < target <= nums[right]:
                left = mid + 1
            else:
                right = mid - 1
    return -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 arr

    mid = len(arr) // 2
    left_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]

总结

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

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

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


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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1703270.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Text2SQL 论文】IncSQL:通过增量式生成 action 序列来得到 SQL

论文&#xff1a;IncSQL: Training Incremental Text-to-SQL Parsers with Non-Deterministic Oracles ⭐⭐⭐ ICLR 2019&#xff0c;arXiv:1809.05054, Microsoft Research 一、论文速读 本文提出了 IncSQL&#xff0c;一个使用 Non-Deterministic Oracles 思路的增量式 Text…

从零开始打造教育APP:在线教育系统源码与开发流程

很多人疑问&#xff0c;应该如何从零开始打造一个在线教育APP&#xff1f;今天&#xff0c;小编将详细为大家讲解在线教育系统的源码与开发流程。 一、需求分析 对于在线教育APP&#xff0c;需要要明确以下几点&#xff1a; 1.目标用户&#xff1a;明确APP的用户群体&#xf…

字符串和字符串函数(1)

前言&#xff1a; 字符串在C语言中比较特别&#xff0c;没有单另的字符串类型&#xff0c;想要初始化字符串必须用字符变量的数组初始化&#xff0c;但是在C语言标准库函数中提供了大量能对字符串进行修改的函数&#xff0c;比如说可以实现字符串的的拷贝&#xff0c;字符串的追…

判断dom元素是否滚动到底、是否在可视区域

概览 我们日常开发中&#xff0c;在面对懒加载、虚拟列表需求时&#xff0c;经常需要判断dom元素是否滚动到底、是否在可视区域。但是由于涉及的属性太多了&#xff0c;比如scrollTop、clientHeight、scrollHeight、getBoundingClientRect()等属性&#xff0c;现根据这两个场景…

Docker环境安装并使用Elasticsearch

1、拉取es docker pull elasticsearch:7.10.12、查看镜像 docker images3、启动es docker run -d --name esearch -p 9200:9200 -p 9300:9300 elasticsearch:7.10.14、如果启动ES时出现一下问题 Unable to find image docker.elastic.co/elasticsearch/elasticsearch:7.10.…

基于springboot+vue的班级综合测评管理系统

开发语言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;…

台灯的功能作用有哪些?看完就知道台灯怎么选!

作为一位家长&#xff0c;我深刻意识到保护孩子眼部健康的重要性。随着科技的飞速发展&#xff0c;孩子们越来越多地使用电子设备&#xff0c;如平板电脑、手机和电视&#xff0c;长时间盯着屏幕成为了他们日常生活的一部分。然而&#xff0c;这些屏幕发出的蓝光和闪烁的光线&a…

C++的第一道门坎:类与对象(一)

1.面向过程与面向对象 1.1面向过程 我们之前学习的C语言就是一种面向过程的语言&#xff0c;面向过程的语言强调的是具体实现的过程&#xff0c;一般用函数来具体实现。我们用面向过程的思想&#xff0c;就可以把炒菜分为以下几个步骤: 1.2面向对象 而对于面向对象的语言而言…

duckdb 插件机制研究

本文研究 duckdb 内置的 extensions 工作机制。 插件架构 在 duckdb 源码内&#xff0c;内置了一组原生插件&#xff0c;位于顶层 extension 目录下&#xff1a; 除此之外&#xff0c;还支持 Out-of-Tree Extension&#xff0c;简单说就是独立的插件&#xff0c;不是集成在源…

零代码创建属于自己的情伤治愈者

前言 在这个社会物质文明生活发展迅速的年代&#xff0c;很多人都有心底里难以说出的痛楚&#xff0c;他们往往都与情伤相关&#xff0c;面对这样的情况&#xff0c;我们结合文心智能体设计出一款适合所有人的情伤治愈工具 体验智能体 文心智能体平台是一款基于自然语言处理和…

【鱼眼镜头10】等距Equidistant模型的Kannala-Brandt模型,opencv的鱼眼标定使用的模型。kalibr中的 pinhole + equidistant 都是指该模型。

Kannala Brandt 模型 / opencv中的fisheye / kalibr中的 pinhole equidistant 都是指该模型。 opencv https://docs.opencv.org/4.x/db/d58/group__calib3d__fisheye.html kalibr https://github.com/ethz-asl/kalibr/wiki/supported-models 在之前的博客【鱼眼镜头1】鱼眼…

linux centos磁盘清理相关

清理磁盘流程 1、查看磁盘挂载路径及使用率 df -h2、查看当前文件下文件大小 du -sh *3、制空文件内容 > 文件名 ###制空当前文件内容&#xff0c;直接清0 列子 >access.loglinux操作系统中&#xff0c;经常会遇到磁盘空间满的问题。遇到这样的问题&#xff0c;先查下…

实战 | 使用YoloV8实例分割识别猪的姿态(含数据集)

导 读 本文主要介绍如何使用YoloV8实例分割识别猪的姿态&#xff08;含数据集&#xff09;。 背景介绍 在本文中&#xff0c;我将介绍如何使用YoloV8在猪的自定义数据集上进行实例分割&#xff0c;以识别和跟踪它们的不同姿态。 数据集 使用的数据集来源于Kokkenborg Aps&…

【智能算法】青蒿素优化算法(AO)原理及实现

目录 1.背景2.算法原理2.1算法思想2.2算法过程 3.结果展示4.参考文献5.代码获取 1.背景 2024年&#xff0c;C Yuan受到青蒿素药物治疗疟疾过程启发&#xff0c;提出了青蒿素优化算法&#xff08;Artemisinin Optimization, AO&#xff09;。 2.算法原理 2.1算法思想 AO灵感来…

在C++中自定义命名空间,在命名空间中定义string变量,同时定义一个函数实现单词逆置

代码 #include <iostream> #include <cstring> using namespace std; namespace my_space {string s;void reverse(string s);//定义逆置函数 } using namespace my_space; void my_space::reverse(string s){int lens.size();int i0;int jlen-1;while(i<j){//…

MongoDB 和 AI 赋能行业应用:电信和媒体

欢迎阅读“MongoDB 和 AI 赋能行业应用”系列的第二篇。 本系列重点介绍 AI 应用于不同行业的关键用例&#xff0c;涵盖制造业和汽车行业、金融服务、零售、电信和媒体、保险以及医疗保健行业。 电信行业的经营环境以利润空间狭小为特点&#xff0c;尤其是在差异化极小的商品…

markdown画时序图的时候,如何自动显示每一条时序的序号

1: 现象描述 今天画时序图的时候&#xff0c;发现时序上面没有显示序号&#xff0c;看起来不够清晰&#xff0c;只有单纯的说明; 如下图所示 刚测试CSDN的时序图&#xff0c;默认是带序号的&#xff0c;看起来和实际使用的markdown工具有关系&#xff1b; 2&#xff1a;解决办…

MIT6.828 Lab2-1 Using gdb

Using gdb gdb使用&#xff1a; xv6 gdb调试方法 问题1&#xff1a; Looking at the backtrace output, which function called syscall? 按照提示开启gdb后键入&#xff1a; b syscall c layout src backtrace输出结果&#xff1a; (gdb) backtrace #0 syscall () at k…

nodejs开发入门01启动服务器

目录 1 创建项目2 初始化项目3 创建app.js4 服务器启动代码5 浏览器里访问6 部署到云服务器总结 nodejs是一个服务器运行环境&#xff0c;可以让我们搭建我们自己的服务器&#xff0c;接收客户端的请求&#xff0c;并给出响应。第一篇我们介绍一下服务器的搭建以及启动过程。 1…

Compose学习记录(1)

Compose学习记录(1) 简易使用HelloWorld。 新建一个工程&#xff0c;它已经默认启用了compose特性。MainActivity继承自 ComponentActivity&#xff0c;可以用compose来编写UI界面。 // sample 1: simple VersionsetContent {Text("Hello World.")}一个函数&#xf…