当前位置: 首页 > news >正文

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


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

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

Python-3.12.0文档解读

目录

我的写法

代码点评

时间复杂度分析

空间复杂度分析

总结

我要更强

方法1:一次遍历,使用数组模拟哈希表

方法2:双哈希表优化

方法3:使用有序字典

优化分析

小结

哲学和编程思想

1. 时间和空间权衡(Time-Space Tradeoff)

2. 单一职责原则(Single Responsibility Principle)

3. 最小化遍历次数(Minimizing Passes Over Data)

4. 哈希表和数组的使用(Hash Tables and Arrays)

5. 顺序保持(Order Preservation)

6. 空间优化(Space Optimization)

7. 提前退出(Early Exit)

具体方法中的编程思想应用总结:

举一反三

1. 理解问题和数据特性

2. 选择合适的数据结构

3. 最小化遍历次数

4. 空间优化

5. 单一职责原则

6. 时空权衡

7. 算法思想

举一反三技巧:

技巧1:模式识别

技巧2:优化现有算法

技巧3:保持代码可读性和可维护性

实例应用:

分析问题:

选择数据结构:

设计算法:

优化和测试:


 题目链接


我的写法

 

import sys  # 导入sys模块,用于退出程序# 从标准输入读取一行,并将其转换为整数列表
# 首先分割输入的字符串,使用map函数将每个分割的部分转换为整数,然后再转换为列表
N_nums = list(map(int, input().split()))# 取出列表的第一个元素,作为N值
N = N_nums[0]# 取出列表中除第一个元素外的所有元素,作为新的列表
N_nums = N_nums[1:]# 初始化一个空字典,用于记录每个数字出现的次数
is_unique = {}# 遍历N_nums列表中的每个数字
for num in N_nums:# 检查当前数字是否在字典中if is_unique.get(num, None):# 如果数字已经在字典中,次数加1is_unique[num] += 1else:# 如果数字不在字典中,将其加入字典,并初始化次数为1is_unique[num] = 1# 遍历字典中的每个键值对
for k, v in is_unique.items():# 检查当前键的值是否为1(即该数字只出现了一次)if v == 1:# 如果找到一个只出现了一次的数字,打印该数字,并退出程序print(f"{k}")sys.exit(0)# 如果没有找到只出现一次的数字,打印"None"
print("None")

这段代码的功能是查找并输出一组整数中第一个只出现一次的数字。如果没有找到这样的数字,则输出"None"。以下是对这段代码的点评以及时间复杂度和空间复杂度的分析:

代码点评

  1. 输入处理:
    • N_nums = list(map(int, input().split())) 这一行实现了从标准输入读取一行并将其转换为整数列表。这种处理方式简洁有效。
    • N = N_nums[0] 取出列表的第一个元素作为N值,N_nums = N_nums[1:] 这一行将列表中除第一个元素外的所有元素提取出来。
  2. 统计数字出现次数:
    • 使用一个字典 is_unique 来记录每个数字出现的次数。这种方法非常直观且有效。
    • if is_unique.get(num, None): 这一行可以简化为 if num in is_unique:,这样更清晰一些。
  3. 查找目标数字:
    • 遍历字典 is_unique 来查找第一个只出现一次的数字,并使用 sys.exit(0) 提前退出程序。这种方法在找到目标后立即退出,避免了不必要的遍历。
  4. 输出结果:
  • 如果没有找到只出现一次的数字,程序会输出 "None"。

时间复杂度分析

  1. 输入处理:
    • list(map(int, input().split())):这行代码的时间复杂度为 O(N),其中 N 是输入数字的个数。
  2. 统计数字出现次数:
    • for num in N_nums::这一循环的时间复杂度为 O(N),因为我们需要遍历所有的数字。
    • is_unique.get(num, None) 和 is_unique[num] = 1 等操作在字典中的查找和插入操作平均时间复杂度为 O(1),所以整个循环的时间复杂度为 O(N)。
  3. 查找目标数字:
  • for k, v in is_unique.items()::这一循环的时间复杂度为 O(U),其中 U 是字典中不同数字的个数。由于最多有 N 个不同的数字,最坏情况下 U 为 N,因此最坏情况下这一部分的时间复杂度为 O(N)。

综合起来,整个程序的时间复杂度为 O(N)。

空间复杂度分析

  1. 输入处理:
    • N_nums 列表的空间复杂度为 O(N)。
  2. 统计数字出现次数:
  • is_unique 字典的空间复杂度为 O(U),其中 U 是不同数字的个数。最坏情况下 U 为 N,因此空间复杂度为 O(N)。

综合起来,整个程序的空间复杂度为 O(N)。

总结

这段代码在实现上是高效且易于理解的。时间复杂度和空间复杂度均为 O(N),很适合处理大量数据。在优化方面,可以将 if is_unique.get(num, None): 改为 if num in is_unique:,使代码更加清晰。总体来说,这段代码在性能和可读性上都表现良好。


我要更强

要优化这个问题,主要的思路是尝试减少对输入列表的多次遍历和对数据的额外存储。下面是一些可能的改进方法:

方法1:一次遍历,使用数组模拟哈希表

在已知数据范围的情况下,可以使用数组来替代字典,这样可以减少哈希开销。但是,这需要先假设数字的取值范围是已知的且不大,比如在[0, 100000]范围内。

import sysdef find_first_unique(nums):max_value = 100001  # 假设数字在 [0, 100000] 范围内count = [0] * max_valuefirst_position = [-1] * max_valuefor i, num in enumerate(nums):if count[num] == 0:first_position[num] = icount[num] += 1first_unique_position = len(nums)for num, cnt in enumerate(count):if cnt == 1:first_unique_position = min(first_unique_position, first_position[num])if first_unique_position == len(nums):print("None")else:print(nums[first_unique_position])sys.exit(0)N_nums = list(map(int, input().split()))
N = N_nums[0]
nums = N_nums[1:]
find_first_unique(nums)

方法2:双哈希表优化

使用两个哈希表,一个记录出现次数,另一个记录每个数字的第一次出现位置,最后遍历第一个哈希表找到只出现一次且位置最靠前的数字。

import sysdef find_first_unique(nums):count = {}first_position = {}for i, num in enumerate(nums):if num in count:count[num] += 1else:count[num] = 1first_position[num] = ifirst_unique_position = len(nums)for num, cnt in count.items():if cnt == 1:first_unique_position = min(first_unique_position, first_position[num])if first_unique_position == len(nums):print("None")else:print(nums[first_unique_position])sys.exit(0)N_nums = list(map(int, input().split()))
N = N_nums[0]
nums = N_nums[1:]
find_first_unique(nums)

方法3:使用有序字典

Python的collections.OrderedDict可以记录插入顺序,同时保留哈希表的高效查找性能。

import sys
from collections import OrderedDictdef find_first_unique(nums):count = OrderedDict()for num in nums:if num in count:count[num] += 1else:count[num] = 1for num, cnt in count.items():if cnt == 1:print(num)sys.exit(0)print("None")N_nums = list(map(int, input().split()))
N = N_nums[0]
nums = N_nums[1:]
find_first_unique(nums)

优化分析

  • 时间复杂度:所有方法的时间复杂度均为O(N),因为我们只需要遍历输入列表一次。
  • 空间复杂度:
  • 方法1的空间复杂度为O(M),其中M是数字的范围(即max_value)。
  • 方法2和方法3的空间复杂度为O(N),因为在最坏情况下,我们需要存储所有数字的出现次数和位置。

小结

这些方法在时间复杂度上并没有比原始方法更优,但在某些情况下可以减少空间占用,或者代码更加简洁和清晰。希望这些方法能对问题有所帮助。


哲学和编程思想

在编写和优化算法的过程中,我们不仅要考虑代码的正确性和效率,还会运用到一些计算机科学和编程的哲学思想。这些思想能够帮助我们更好地理解问题、设计解决方案和分析性能。以下是一些相关的哲学和编程思想,以及它们在上述方法中的具体应用。

1. 时间和空间权衡(Time-Space Tradeoff)

这个思想强调在编程中时间复杂度和空间复杂度之间往往存在一种权衡关系。有时为了提高运行速度,我们需要使用更多的空间,反之亦然。

  • 方法1:使用数组模拟哈希表 这里我们用一个固定大小的数组来替代字典,以减少哈希表的开销。虽然这在空间上可能会有些浪费(因为我们假设了一个最大值),但它可以提高访问速度。

2. 单一职责原则(Single Responsibility Principle)

这是编程中的一个重要原则,强调每个模块或函数应该有且只有一个职责。

  • 方法1、方法2、方法3 在这些方法中,我们将统计次数和记录位置的逻辑分离成不同的数据结构(如字典或数组),使得每个数据结构只负责一件事:记录次数或记录第一次出现的位置。这种分离使得代码更清晰、更易于维护。

3. 最小化遍历次数(Minimizing Passes Over Data)

在处理大数据集时,减少对数据的多次遍历可以大大提高算法效率。

  • 所有方法 这些方法都只遍历输入列表一次或两次,避免了多次遍历带来的时间开销。一次遍历统计次数,第二次遍历查找第一个只出现一次的元素。

4. 哈希表和数组的使用(Hash Tables and Arrays)

哈希表和数组是高效的数据结构,常用于快速查找。

  • 方法1、方法2、方法3 在这些方法中,我们使用了哈希表和数组来记录每个数字的出现次数和位置。哈希表提供了常数时间的查找和插入操作,而数组在已知取值范围时可以更高效地模拟哈希表。

5. 顺序保持(Order Preservation)

在某些问题中,保持数据的输入顺序是非常重要的。

  • 方法3:使用有序字典 OrderedDict在插入元素时会保持元素的插入顺序,这对于需要保持顺序的算法非常有用。在这个方法中,我们利用了OrderedDict的顺序保持特性来确保按输入顺序查找第一个只出现一次的元素。

6. 空间优化(Space Optimization)

通过巧妙的设计,可以在不增加时间复杂度的情况下优化空间使用。

  • 方法1:使用固定大小数组 通过使用一个固定大小的数组,我们减少了哈希表的开销。这在数字范围已知且不大的情况下是非常高效的。

7. 提前退出(Early Exit)

当我们已经找到所需结果时,可以提前退出,避免不必要的计算。

  • 所有方法 这些方法在找到第一个只出现一次的数字后都会立即退出程序,避免了继续进行无用的计算。这种提前退出的策略可以在实际应用中显著提高性能。

具体方法中的编程思想应用总结:

  • 方法1:一次遍历,使用数组模拟哈希表
    • 时间和空间权衡:用固定大小的数组来提高访问速度。
    • 最小化遍历次数:只需两次遍历。
    • 空间优化:使用数组代替哈希表。
  • 方法2:双哈希表优化
    • 单一职责原则:分离统计次数和记录位置的逻辑。
    • 最小化遍历次数:只需两次遍历。
    • 哈希表的使用:高效记录和查找。
  • 方法3:使用有序字典
  • 顺序保持:使用OrderedDict保持插入顺序。
  • 最小化遍历次数:只需两次遍历。
  • 哈希表的使用:高效记录和查找。

这些编程和计算机科学的哲学思想在实际编码中帮助设计出高效、可读、可维护的代码。在处理具体问题时,理解和应用这些思想是非常重要的。


举一反三

理解并应用编程和计算机科学的哲学思想能够帮助你在面对不同问题时举一反三,找到高效的解决方案。以下是一些具体的技巧和方法,结合上面的思想,能帮助在编程中更加灵活和高效。

1. 理解问题和数据特性

  • 分析输入输出:仔细阅读并理解问题,明确输入输出要求,并注意数据范围和边界情况。
  • 数据特性:了解数据的特性,比如数据的范围、是否有重复、是否有序等,这些特性可以启发你选择合适的数据结构和算法。

2. 选择合适的数据结构

  • 数组:适用于已知范围且大小固定的数据,访问速度快。
  • 哈希表:适用于需要快速查找和插入的数据,常数时间复杂度。
  • 有序字典/集合:保留数据插入顺序的同时,提供快速查找。

3. 最小化遍历次数

  • 一次遍历完成多任务:尽量在一次遍历中完成多个任务,比如统计和记录位置。
  • 提前退出:找到答案后立即退出,避免不必要的计算。

4. 空间优化

  • 合理分配空间:根据数据范围选择合适的空间,比如用数组替代哈希表。
  • 使用位图/布隆过滤器:在空间紧张的情况下使用位图或布隆过滤器来高效记录数据。

5. 单一职责原则

  • 分而治之:将复杂问题拆分为多个子问题,每个子问题独立解决。
  • 模块化设计:将逻辑功能拆分为不同的模块或函数,每个模块或函数只负责一个任务。

6. 时空权衡

  • 时间优先:在时间要求严格的情况下,可能需要牺牲空间来提高速度。
  • 空间优先:在空间有限的情况下,可能需要采用较慢但空间占用小的算法。

7. 算法思想

  • 贪心算法:每一步选择中都采取当前状态下最优的选择,适用于很多优化问题。
  • 动态规划:将复杂问题拆分为简单子问题,通过缓存子问题的结果来避免重复计算。
  • 分治法:将问题递归地分解为更小的子问题,然后合并解决。
  • 回溯法:通过递归尝试所有可能的解决方案,并在不符合要求时回溯。

举一反三技巧:

技巧1:模式识别

  • 识别常见问题模式:比如“寻找第一个不重复的元素”这类问题可以归为统计和查找问题。
  • 借鉴类似问题的解决方案:尝试记住常见问题的解决方法,并在遇到类似问题时进行适配。

技巧2:优化现有算法

  • 分析瓶颈:找出当前算法的瓶颈,看看是否能用更高效的数据结构或算法替代。
  • 空间换时间:在条件允许的情况下,用额外的空间来换取时间上的性能提升。

技巧3:保持代码可读性和可维护性

  • 注重代码风格:保持代码简洁清晰,注释清楚,便于他人理解和维护。
  • 测试覆盖:编写全面的测试用例,确保代码的正确性和鲁棒性。

实例应用:

假设现在遇到一个问题,需要找到一个字符串中第一个不重复的字符。可以遵循上述技巧进行举一反三:

分析问题:
  • 输入是一个字符串,输出是第一个不重复的字符。
  • 需要统计每个字符的出现次数,并保持字符的顺序。
选择数据结构:
  • 字符频率统计:使用哈希表记录每个字符的出现次数。
  • 顺序保持:使用有序字典或维护两个数组,一个记录字符出现顺序,一个记录字符频率。
设计算法:

from collections import OrderedDictdef first_unique_char(s):count = OrderedDict()for char in s:if char in count:count[char] += 1else:count[char] = 1for char, cnt in count.items():if cnt == 1:return charreturn Nones = input("Enter a string: ")
result = first_unique_char(s)
if result:print(f"The first unique character is: {result}")
else:print("No unique character found.")
优化和测试:
  • 测试不同输入场景(空字符串、全重复字符、有多个不重复字符等)。
  • 分析性能,确保对大字符串也能高效运行。

通过这些技巧和方法,可以在实际编码中灵活运用,解决各种复杂问题。

相关文章:

  • java使用资源过高排查
  • 解析Java中1000个常用类:Cloneable类,你学会了吗?
  • linux-gpio
  • 【代码随想录算法训练营第37期 day21 | LeetCode530.二叉搜索树的最小绝对差、501.二叉搜索树中的众数、236. 二叉树的最近公共祖先】
  • Java集合【超详细】
  • 实战经验分享之移动云快速部署Stable Diffusion SDXL 1.0
  • K8S中Prometheus+Grafana监控
  • Wpf 使用 Prism 实战开发Day30
  • YOLOv5训练自定义数据集模型的参数与指令说明
  • Flutter 中的 SliverFillRemaining 小部件:全面指南
  • Golang | Leetcode Golang题解之第120题三角形最小路径和
  • kafka-消费者组-发布订阅测试
  • linux同步搭建多台服务器
  • Caused by: java.lang.IllegalStateException
  • docker安装Mysql5.7版本
  • CSS3 聊天气泡框以及 inherit、currentColor 关键字
  • Elasticsearch 参考指南(升级前重新索引)
  • iOS帅气加载动画、通知视图、红包助手、引导页、导航栏、朋友圈、小游戏等效果源码...
  • Java 最常见的 200+ 面试题:面试必备
  • JavaScript HTML DOM
  • LeetCode541. Reverse String II -- 按步长反转字符串
  • Mysql优化
  • React as a UI Runtime(五、列表)
  • RxJS: 简单入门
  • SpriteKit 技巧之添加背景图片
  • 个人博客开发系列:评论功能之GitHub账号OAuth授权
  • 关于extract.autodesk.io的一些说明
  • 互联网大裁员:Java程序员失工作,焉知不能进ali?
  • 两列自适应布局方案整理
  • 前端_面试
  • 网络应用优化——时延与带宽
  • 为物联网而生:高性能时间序列数据库HiTSDB商业化首发!
  • 我的业余项目总结
  • 用Python写一份独特的元宵节祝福
  • [Shell 脚本] 备份网站文件至OSS服务(纯shell脚本无sdk) ...
  • Nginx惊现漏洞 百万网站面临“拖库”风险
  • Python 之网络式编程
  • 昨天1024程序员节,我故意写了个死循环~
  • ​linux启动进程的方式
  • ​十个常见的 Python 脚本 (详细介绍 + 代码举例)
  • # .NET Framework中使用命名管道进行进程间通信
  • #HarmonyOS:Web组件的使用
  • #if #elif #endif
  • $con= MySQL有关填空题_2015年计算机二级考试《MySQL》提高练习题(10)
  • (AngularJS)Angular 控制器之间通信初探
  • (Charles)如何抓取手机http的报文
  • (C语言)输入自定义个数的整数,打印出最大值和最小值
  • (STM32笔记)九、RCC时钟树与时钟 第一部分
  • (编译到47%失败)to be deleted
  • (附源码)springboot猪场管理系统 毕业设计 160901
  • (万字长文)Spring的核心知识尽揽其中
  • (新)网络工程师考点串讲与真题详解
  • (原創) X61用戶,小心你的上蓋!! (NB) (ThinkPad) (X61)
  • (转)3D模板阴影原理
  • .bashrc在哪里,alias妙用