2023人工智能程序设计NOJ作业
下载专区:
开局一张王炸截图:
推荐网站(资料):
Python3 教程 | 菜鸟教程 (runoob.com) 里面基本包含了关于 P y t h o n Python Python 的全部基本语法,不懂的一定要去看看。
编程经验
这块主要是我写这篇文章需要搜的一些资料,包括 P y t h o n Python Python 的语法知识和其它的资料,算是写给我自己看的,以后复习用得着。后面看我的代码或者题目思路看不懂的,可以来这里瞧瞧,如果不想看这块,可以直接从 n o j 前言 noj 前言 noj前言 开始看。
Pycharm
如果
P
y
c
h
a
r
m
Pycharm
Pycharm 中出现
s
i
m
p
l
i
f
y
c
h
a
i
n
e
d
c
o
m
p
a
r
i
s
o
n
simplify \ chained \ comparison
simplify chained comparison ,意思是简化链式比较(波浪号提醒),说明可以写得更简洁一点,如0< a and a < 1
可以写为0 < a < 1
。
定义一个函数之后,它的前后都要空两行,否则会有波浪号。
程序写完后,程序末尾要空一行,否则会有波浪号。
有时候,空格写多了也会有波浪号,像[key: value]
而不是[key : value]
(之前我一直的写法)。
Latex
在
L
a
t
e
x
Latex
Latex 公式里面插入空格的方法:~、\、\quad、\qquad
。
花括号可以用{\
和\}
来表示。
省略号可以使用\ldots
等命令来表示。参考资料:Latex–矩阵中省略号_latex 省略号怎么打-CSDN博客
不等号
≠
\ne
= :\ne
或者\neq
。
类
参考资料:Python 面向对象 | 菜鸟教程 (runoob.com)
类是面向对象里的概念。用来描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。对象是类的实例。
重点在于如何定义一个类,主要包括定义变量和函数(即属性和方法),通过实例化对象,访问类变量和方法。
复数
c
o
m
p
l
e
x
complex
complex 函数:a = complex(1, 2)
或者a = complex("1+2j")
可以建立一个变量
a
=
1
+
2
j
a = 1+2j
a=1+2j 。
c
o
n
j
u
g
a
t
e
conjugate
conjugate 函数:可以求出一个复数的共轭复数。a = conjugate(1 + 2j)
。
字符串
初始化:s = str()
或者s = ""
。
z f i l l zfill zfill 函数:( z e r o f i l l zero \ fill zero fill)。
字符串反转:见 005 回文数 005 \ 回文数 005 回文数 这一题。
i
s
d
i
g
i
t
isdigit
isdigit 函数:用来检测字符串是否只由数字组成。用法:str.isdigit()
,返回
T
r
u
e
True
True 和
F
a
l
s
e
False
False 。
r
e
p
l
a
c
e
replace
replace 函数:把字符串中的
o
l
d
old
old (旧字符串) 替换成
n
e
w
new
new (新字符串)。用法:str.replace(old, new)
,返回替换后的新字符串。
i s u p p e r isupper isupper 和 i s l o w e r islower islower 函数:对于一个字符串,如果其包含的字符存在可以区分大小写的字符,则判断这些字符是否全为大写字符或小写字符。如标点符号没有大小写,不算。英文字母有大小写之分,就算,就要对它进行判断。
列表
初始化:lst = list()
或者lst = []
。
列表推导式:[表达式 for 变量 in 列表]
或者[表达式 for 变量 in 列表 if 条件]
,可以生成一个列表。
反转列表:方法很多。使用切片:lst1 = lst0[: : -1]
。
排序:用列表专有的 s o r t sort sort 函数进行排序。方法和内置函数 s o r t e d sorted sorted 很相似。
参考:Python List sort()方法 | 菜鸟教程 (runoob.com)
i n d e x index index 函数:从列表中找出某个值第一次出现的索引位置,如果没有找到对象则抛出异常 V a l u e E r r o r ValueError ValueError 。
p o p pop pop 函数:用于移除列表中的一个元素(默认最后一个元素),并且返回该元素的值。
元组
元组推导式:与列表很像,将 l i s t list list 改为 t u p l e tuple tuple 即可。
反转元组:参考列表,使用切片:tp1 = tp0[: : -1]
。
字典
初始化字典:dic = dict()
或者dic = {}
。
输出:
print(a, b, sep = ',', end = '')
通常会这么用
p
r
i
n
t
print
print 函数,
s
e
p
sep
sep 表示同行元素之间用什么分隔开,且没有空格,只有引号内的符号;
e
n
d
end
end 则表示结尾以什么结束,默认是回车。
在
P
y
t
h
o
n
Python
Python 中,可以使用字符串的format()
方法来进行格式化输出。
# 设置对齐、宽度等属性:
number = 42
text = "Hello World!"
print("{:<10} {:>8d}".format(text, number)) # {}里其实也可以什么都不写,表示没有什么具体格式要求
# 输出结果为:Hello 42
其中,<
表示左对齐,>
表示右对齐,:d
表示十进制整数类型。
还可以用 f − s t r i n g f-string f−string 的方法。
解包
解包可以用于函数返回多个值的情况,也可以用于交换变量的值,以及将容器中的元素分配给函数的参数等。解包是 P y t h o n Python Python 中一个非常方便和常用的特性,可以简化代码并提高可读性。
参考资料:python 解包详解 - 紫陌红尘雪落无声 - 博客园 (cnblogs.com)
函数
闭包函数
参考资料:什么是闭包,Python闭包(初学者必读) (biancheng.net) (这篇文章讲述了闭包函数的好处)
Python中的闭包详解-Python教程-PHP中文网、Python的闭包函数 - 知乎 (zhihu.com)
闭包( c l o s u r e closure closure)指的是在函数内部定义了另外一个函数,并返回了这个内部函数作为函数对象,同时还保存了外层函数的状态信息。这个内部函数可以依赖外层函数的变量和参数,而且外层函数返回的是这个内部函数的引用。这种在函数内部定义函数并返回的方式称为闭包。
lambda
匿名函数。通常用于编写简单的、单行的函数,通常在需要函数作为参数传递的情况下使用,例如在
m
a
p
(
)
map()
map() 、
f
i
l
t
e
r
(
)
filter()
filter() 、
r
e
d
u
c
e
(
)
reduce()
reduce() 等函数中。用法:lambda arguments: expression
。如:f = lambda x: x ** 2
将定义好的匿名函数赋给函数变量
f
f
f 。
参考资料:100秒学会Python lambda函数_哔哩哔哩_bilibili
range
range(0, n)
是从
0
0
0 取到
n
−
1
n-1
n−1 。range(n)
是从
0
0
0 取到
n
−
1
n - 1
n−1 。所以说二者等价。
min和max
两个函数的第二个参数可以是根据什么来排序,如长度 l e n len len 。
示例:列表
l
s
t
lst
lst 里存放着诸多字符串,找到长度最小的那个,并赋值给
s
0
s0
s0 。代码:s0 = min(lst, key=len)
。
len
求字符串的长度,用法是len(s)
,而不是s.length()
,后者是
C
+
+
C++
C++ 的用法
enumerate
有个用法是:for i, c in enumerate(s0): # enumerate可以得到i为索引下标,c为i对应的字符,s0是一个字符串
。例子可见
007
字符串处理
007 \ 字符串处理
007 字符串处理 。
all和any
a l l all all 或者 a n y any any 用来判断参数中是否所有组成元素都为 T r u e True True 或者 是否有一个元素为 T r u e True True 。除了 0 0 0、空、 N o n e None None、 F a l s e False False 以外的都算是 T r u e True True 。返回值是 T r u e True True 或者是 F a l s e False False 。
sorted
s
o
r
t
e
d
(
)
sorted()
sorted() 函数对所有可迭代的对象进行排序操作。与
s
o
r
t
sort
sort 函数有区别,后者是只针对列表的方法。列表的
s
o
r
t
sort
sort 方法是对已经存在的列表进行操作,无返回值,而内置函数
s
o
r
t
e
d
sorted
sorted 方法返回的是一个新的列表,而不是在原来的基础上进行的操作。用法:sorted(iterable, cmp = None, key = None, reverse = False)
。如lst = sorted(lst)
。
参考:Python sorted() 函数 | 菜鸟教程 (runoob.com)
zip
压缩、拉练的意思。它有两个操作,一个是压缩,另一个就是解压。有点类似于一些压缩软件的功能。
- 压缩:将若干个可迭代的对象的对应元素组合成一个元组,返回由这些元组组成的对象(一个迭代器)。可以用
list()
将其转换为列表进行输出。如果两个对象的长度不一样,则取决于短的那个对象的长度。用法:zip(iterable, iterable)
。 - 解压:利用 ∗ * ∗ 号操作符,可以将元组解压为列表。
具体例子见如下示例(摘自菜鸟教程):
>>> a = [1,2,3]
>>> b = [4,5,6]
>>> c = [4,5,6,7,8]
>>> zipped = zip(a,b) # 返回一个对象
>>> zipped
<zip object at 0x103abc288> # 返回一个可迭代对象
>>> list(zipped) # list() 转换为列表
[(1, 4), (2, 5), (3, 6)]
>>> list(zip(a,c)) # 元素个数与最短的列表一致
[(1, 4), (2, 5), (3, 6)]
>>> a1, a2 = zip(*zip(a,b)) # 与 zip 相反,zip(*) 可理解为解压,返回二维矩阵式
>>> list(a1)
[1, 2, 3]
>>> list(a2)
[4, 5, 6]
>>>
map
参考资料:python中的map函数_python map函数-CSDN博客(讲的很全)
用法:map(function, iterable)
。根据函数对可迭代对象的每个元素进行映射,返回映射后的一个迭代器。可以用list()
将其转换为列表。
>>> def square(x) : # 计算平方数
... return x ** 2
...
>>> map(square, [1,2,3,4,5]) # 计算列表各个元素的平方
<map object at 0x100d3d550> # 返回迭代器
>>> list(map(square, [1,2,3,4,5])) # 使用 list() 转换为列表
[1, 4, 9, 16, 25]
>>> list(map(lambda x: x ** 2, [1, 2, 3, 4, 5])) # 使用 lambda 匿名函数
[1, 4, 9, 16, 25]
>>>
eval
e
v
a
l
(
)
eval()
eval() 函数用来执行一个字符串表达式,并返回表达式的值。简单用法:eval(expression)
。
e
x
p
r
e
s
s
i
o
n
expression
expression 是一个字符串,如:eval('2 + 2')
,返回结果为
4
4
4 。
库函数
math
提供了许多对浮点数的数学运算函数。如果需要计算复数,可以使用 cmath 模块中的同名函数。里面有 s i n sin sin , c o s cos cos , t a n tan tan 等函数。参考:Python math 模块 | 菜鸟教程 (runoob.com)。
杂项:
切片和索引:列表,字符串的索引都是从 0 0 0 开始。参考资料:Python 变量类型 | 菜鸟教程 (runoob.com) 。里面有字符串、列表和元组的切片和索引。
f
o
r
for
for 循环中的一个用法:for i in lst:
,其中的
l
s
t
lst
lst 其中只要是可以迭代的数据结构都可以,如字符串,字典都可以。
python中,1 <= a and a <= 8
可以简写为1 <= a <= 8
。
对于可迭代的对象(包括很多种类,比如一些函数如 m a p map map 和 z i p zip zip 的返回对象就是可迭代对象),就可以用以下代码去读取它的元素
a = [1, 2, 3]
b = [4, 5, 6]
c = zip(a, b)
print(c)
for i in c: # c可以是任意迭代对象,这里只是举了一个例子
print(i)
nonlocal:用来在函数或其他作用域中使用并修改外层(非全局)变量(逐层到外面作用域找,直到全局作用域之前的局部作用域)。它能够弥补 g l o b a l global global 和闭包的两个问题。参考:python nonlocal深度理解-CSDN博客
noj前言
让我头疼的一些题: 18 , 22 , 27 , 32 , 36 , 37 , 60 18,22,27,32,36,37,60 18,22,27,32,36,37,60 。
第32题和第60题难度很大,需要用到动态规划,而且是线性 d p dp dp ,状态转移方程不是很好建立。
再说说考试,期末考完了,有以下感触想要分享。(不过我的感受主要基于 2023 2023 2023 年的课程,后面可能会有点变化)
- 期末前面的7道题确实不难,只要把基础语法学好,会点贪心思想,基本没什么问题,感觉把前 30 30 30 道题弄会了,就行了。
- 后面三道题有点难度,第八题考了元组的用法,好像要用到 l a m b d a lambda lambda 函数进行元组排序,因为 n o j noj noj 前 60 60 60 道题没有元组的题,所以杀了我一个措手不及,失策失策。第九题题面有点劝退,而且当时其它题还没做完,就没仔细看。第十题正中我下怀,出乎我意料的考了 01背包问题 ,而且样例还是错的,笑死,那题我早会了,我很快就写出来了,而且还用了滚动数组,耶!
- 所以总结一下就是,最后三道,一道会出元组,再加点语法知识;一道会出题面劝退题,估计是用点贪心策略,再加点繁琐判断;还有会出一道动态规划的题目,但一般是比较简单的模板题。如果觉得有难度的话,不妨战术性放弃后面两道题。
n o j noj noj 的特点是数据弱,基本不涉及算法,更不会在时间时间复杂度上卡你,所以不用考虑算法时间复杂度优化,暴力基本都能做。
对了,顺便提一句, n o j noj noj 能早点写完就早点,因为它实在是太不稳定了,这很难评。它很有可能因为各种原因写不了,老师估计也很少看消息,主打一个不吱声,越到最后人越慌,所以有空赶紧写。
看到这,各位大佬,可否一键三连,鼓励一下菜鸡呢(右下角处)
第一季:水题
001 Hello World
print('Hello World')
"""
这样写也是对的:中间空多少格都没关系,只是会影响阅读而已,只要字符串中间不乱加空格就好
print( 'Hello World' )
"""
002 整数运算
思路:三个除法( % \% % 和 / / // // 和 / / / )都需要判断除数为 0 0 0 的情况;幂运算要判断底数为0的情况。但因为 n o j noj noj 的数据较弱,所以下面的代码虽然没有考虑完全,但也能过。(百分号%在 L a t e x Latex Latex 中要用 \ 才能表示出来)
知识点:
-
p r i n t print print 的用法,可以在格式字符串和变量之间用%来连接,用来输出指定格式。如果变量有多个的话,需要用英文括号
()
括起来。详情见经验里的 p r i n t print print 函数。 -
t r y try try 和 e x c e p t except except 的用法,需要知道一些常见错误的关键字,如除数为零错误( Z e r o D i v i s i o n E r r o r ZeroDivisionError ZeroDivisionError)、值错误( V a l u e E r r o r ValueError ValueError)等;意思是尝试 t r y try try 做某事,如果碰到 e x c e p t except except 指定的错误的话,就执行它下面的语句,否则就执行 t r y try try 下面的语句。相当于 e x c e p t except except 的优先级更高,优先处理它的情况。
-
a b a ^ b ab 可以用
a ** b
来表示。
这道题也考察了输出的格式。
a = int(input())
b = int(input())
s = input()
if s == '+':
print("%d+%d=%d" % (a, b, a + b)) # %后面如果变量大于一个的话要加上()
# 也可以用print("{}{}{}={}".format(a, c, b, a-b))
# 还可以用print("{a}{s}{b}={a+b}")
elif s == '-':
print("%d-%d=%d" % (a, b, a - b))
elif s == '*':
print("%d*%d=%d" % (a, b, a * b))
elif s == '/':
try:
c = a / b
print("%d/%d=%d" % (a, b, c))
except ZeroDivisionError:
print('ERROR')
elif s == "//":
try:
c = a // b
print("%d//%d=%d" % (a, b, c))
except ZeroDivisionError:
print('ERROR')
elif s == '%':
try:
c = a % b
print("%d%%d=%d" % (a, b, c))
except ZeroDivisionError:
print('ERROR')
else:
print("%d**%d=%d" % (a, b, a ** b))
其实还有更简单一点的写法,就是不用 t r y try try 和 e x c e p t except except 语句,直接判断除数是否为0即可。
003 动态宽度
思路:此题的方法就是将正整数转化为二进制,并且高位补零。
参考文献:python 如何将十进制转为二进制,并且高位补0? - 知乎 (zhihu.com)
b
i
n
bin
bin 函数得到的字符串是以0b
开头的,所以后面的
01
01
01 数才是真正的二进制表示。
m = int(input())
n = int(input())
binary_str = bin(m)[2 :].zfill(n) # bin得到字符串,利用切片得到01表示的子串,然后高位补0
print(binary_str)
另一种方法:b = '{:08b}'.format(x)
可以将正整数
x
x
x 转为二进制并且补零到8位。
m = int(input())
n = int(input())
s = "{:0" + str(n) + 'b}'
print(s.format(m))
"""
测试数据:
data1:
input:
8
8
output:
00001000
"""
004 二进制,八进制和十六进制
知识点:
可以通过
i
n
t
int
int 函数将输入的字符串变为二、八、十六进制,默认是十进制。方法是:一般可与
i
n
p
u
t
input
input 合写为int(input(), i)
,
i
=
2
,
8
,
16
i = 2,8,16
i=2,8,16 。
同样可通过
b
i
n
bin
bin ,
o
c
t
oct
oct,
h
e
x
hex
hex 三个函数将整数变为
2
,
8
,
16
2,8,16
2,8,16 进制的字符串,故用
p
r
i
n
t
print
print 输出时对应的是%s
。
maxi = 0
for i in [2, 8, 10, 16]:
num = int(input(), i)
maxi = max(maxi, num)
# print(i) 调试所用
print("%s,%s,%d,%s" % (bin(maxi), oct(maxi), maxi, hex(maxi))) # bin、oct、hex返回的是字符串
测试数据:
"""
data1:
input:
1001100
115
78
4f
output:
0b1001111,0o117,79,0x4f
"""
005 回文数
思路:将输入的正整数作为字符串反转后与原字符串相比,相同则为回文数,不同则不是。
参考:python字符串反转的四种方法详解 - Python技术站 (pythonjishu.com)
n = int(input())
while n > 0:
n -= 1
s = input()
if n == 0:
print(s == s[::-1]) # 后者利用切片将字符串反转
else:
print(s == s[::-1], end=',') # 这里必须是end=',',否则输出的,和数之间有空格
006 加密
这道题阅读起来非常难以理解。意思是:利用后面两个一位正整数(姑且按顺序称为
b
b
b 和
c
c
c )对第一个四位正整数(称为
a
a
a )进行加密,然后将加密的结果输出。加密的步骤是:第一个数字(也就是
a
a
a )加上第二个数字(也就是
b
b
b ),的和对第三个数字(也就是
c
c
c )取余,的结果(姑且称为
d
d
d )去替代第一个数字(也就是
a
a
a ),但是呢,根据推理可知
d
d
d 是一位正整数,而
a
a
a 是四位正整数,所以其实替代的结果是000d
(不然后面第一、二、三、四位都没法做了)。然后,第一和第四位交换,第二和第三位交换,结果为d000
,其实不就是d * 1000
吗?
a = int(input())
b = int(input())
c = int(input())
d = (a + b) % c
print(d * 1000) # 此题有个暗示就是 取余得到的结果只有一位数 最后输出的三位数只是在后面填三个零
007 字符串处理
解题思路:输入的字符串数目至少为2。取第一个字符串为基准,以后每输入一个字符串,就将其与基准字符串对比。基准字符串是指在第一个字符串的基础上非严格递减的前缀子串,因为随着越来越多的字符串加入,公共前缀只会越来越短,每输入一个字符串,就与基准字符串取公共前缀,删掉基准字符串不匹配的字符,最后剩下的就是所有字符串的公共前缀。
# 以下是我的做法
n = int(input())
s0 = input() # s0即为基准字符串,初始化为第一个字符串,后面随着字符串的比对会越来越短
n -= 1 # 选取了基准字符串之后,剩下的字符串数量要减一
pos = len(s0) # 需要初始化为s0的长度,用来记录基准字符串的右开边界,最后的结果是公共前缀的右开边界
while n > 0:
n -= 1
s1 = input() # 得到输入的一个字符串
for i in range(0, min(len(s0), len(s1))): # 索引从0开始比对字符
if s0[i] != s1[i]:
pos = i
break
if pos == 0: # 如果为零,说明一直遍历到这里的字符串已经没有公共前缀了,那么就没有遍历后面字符串的必要了
break
s0 = s0[: pos]
if pos == 0: # 为零,说明没有公共前缀
print('None')
else: # 输出基准字符串,也是公共前缀
print(s0)
另一种解法的思路:查找多个字符串的公共前缀
大致思路:用变量 p o s pos pos 记录公共前缀的长度加一。先找到长度最小的字符串 s 0 s0 s0 ,那么公共前缀的长度肯定小于等于它的长度。然后遍历 s 0 s0 s0 的每一个字符,对于每个字符,再遍历剩下的所有字符串对应位置的字符(要用到 e n u m e r a t e enumerate enumerate 函数)。如果都相等,显然是公共前缀里的字符,那就继续遍历 s 0 s0 s0 的字符;只要有一个不相等,显然寻找公共前缀的工作已经结束,那就跳出循环,进行判断加输出即可。
# 以下是别人的解题思路,也能AC
n = int(input())
lst = []
for i in range(0, n):
lst.append(input()) # 列表直接用函数添加即可,不用重新赋给列表
s0 = min(lst, key=len) # 按照字符串长度选出最小那个为基准字符串
pos = len(s0) # 初始化为s0的长度,接下来的两个for循环是为了更新pos的值,pos表示公共前缀的长度加一(加一是因为后面要用到切片)
for i, c in enumerate(s0): # enumerate可以得到i为索引下标,c为i对应的字符
for s in lst:
if s[i] != c:
pos = i
break # 一旦到了这一步,其实是想结束比较,要跳出两层循环,故后面还要再加一个break
if pos == i:
break
if pos == 0: # 如果为0,说明在第一个字符处就没有达成一致,即所有字符串的第一个字符并不相等,那就没有公共前缀
print(None)
else:
print(s0[ : pos])
总结:其实以上两种方法,我自己在做的时候,关于 p o s pos pos 的初始化搞错了,这个其实很重要,不然一些刁钻的测试数据就过不了,不妨试试以下三个测试数据。
测试数据:
"""
测试数据:
data1:
input:
2
123
123
output:
123
data2:
input:
4
expect
expand
excited
express
output:
ex
data3:
input:
2
123
456
output:
None
"""
008 复数运算
思路:直接将两个输入转化为复数类型,然后调用函数得到结果,输出即可。
知识点:关于复数的操作:可以用 c o m p l e x complex complex 函数将字符串转化为复数类型, c o n j u g a t e conjugate conjugate 函数可以求共轭复数, a b s abs abs 函数可以求模。见前述的编程经验中的复数操作。
a = complex(input()) # 将字符串转化为复数类型
b = complex(input())
print(a + b, a - b, a * b, a / b, a.conjugate(), abs(a), sep=",")
009 all()和any()函数的使用
知识点:
a
l
l
all
all 和
a
n
y
any
any 处理的对象都要是可迭代的,如
l
i
s
t
list
list 和
t
u
p
l
e
tuple
tuple ,且判断为空的情况有四种:0,False,'',None
。
思路:将每一个正整数转化为二进制表示,此时为字符串形式,要转化为整数形式,然后用 a l l all all 和 a n y any any 两个函数判断是否全为 1 1 1 或者存在 1 1 1 即可。
n = int(input())
while n > 0:
n -= 1
num = int(input())
lst = list(bin(num)[2:]) # 转为二进制数并切片取01值
lst = [int(i) for i in lst] # 用列表推导式将每个字符变成数字,因为all和any只能识别0等
if n == 0:
print(all(lst))
else:
print(all(lst), end=',') # 输出用逗号间隔
010 罗马数字
解题思路:这道题稍显麻烦。罗马数字的特点:一般情况下,小的数字在大的数字的右边,但有六种特殊情况(如题所述)。我的做法是,每次输入都是一个字符串,从左开始遍历每个字符,对于每个字符,都判断它是否比它的下一个字符小。
- 如果是小的话,那就是六种情况中的一种。此时,减去这个字符所代表的值,而加上下一个字符所代表的值。而加上下一个字符的值,遍历下个字符会处理,所以这个阶段只需要减去就完了。
- 如果不小,即大于等于的话,那就好处理,直接加上这个字符所代表的值即可。
dic = {'I' : 1, 'V' : 5, 'X' : 10, 'L' : 50, 'C' : 100, 'D' : 500, 'M' : 1000}
n = int(input())
while n > 0:
n -= 1
sum = 0 # 用来记录最后求和的结果
s = input()
if len(s) == 1: # 如果只有一个数,就直接输出就好
print(dic[s], end='')
else:
for i in range(0, len(s) - 1): # 如果有两位数及以上,从左到右依次遍历,对于每个位置的数,若它比右边的数小,那一定是6种情况之一
if dic[s[i]] < dic[s[i + 1]]:
sum -= dic[s[i]] # 处理操作都是此位置的数取负,右边的数取正,至于右边的数,下次遍历它加上即可
else:
sum += dic[s[i]]
sum += dic[s[len(s) - 1]] # 不要忘记加上最后一个数,因为for循环里的i没有遍历到最后一个数
print(sum, end='')
if n != 0:
print(',', end='') # 输出逗号后面也要加end=''
测试数据:
"""
data1:
input:
3
MMXXI
MMMCMXCIX
CMXLIX
output:
2021,3999,949
"""
第二季:简单程序
011 搜索插入位置
思路很简单,从左到右遍历已经排好序的列表,碰到第一个大于等于目标值的数,就返回它的索引即可。
lst = input().split(',') # 这样得到的是一个列表,索引从0开始
num = int(input())
for i in range(0, len(lst)):
if int(lst[i]) >= num: # 为真说明碰到了大于等于它的数
print(i)
break
测试数据:
'''
data1:
input:
1,3,5,6
4
output:
2
'''
012 1-10的立方
这题很简单,只要会列表推导式的语法就会做,当然还有幂运算也要会。
lst = [i ** 3 for i in range(1, 11)]
print(lst)
013 三角函数的使用
知识点:学会使用 m a t h math math 库。参考资料:Python中的数学魔法:如何使用sin函数 (baidu.com)。
思路:按照题意调用 m a t h math math 库里的 r a d i a n s radians radians 和 s i n sin sin , c o s cos cos , t a n tan tan 函数进行对于操作即可。
import math # 导入math库,要用到sin等函数
theta = float(input()) # 可能是小数
radian = math.radians(theta)
print("%.4f" % radian) # 保留四位小数
z = math.sin(radian) + math.cos(radian) - (math.tan(radian / 4)) ** 2
print("%.4f" % z)
014 反转元组
如何将Python 元组进行反转 - 知乎 (zhihu.com)
python元祖对应元素相加_python两个元组相加-CSDN博客
解题思路:将输入读入为元组,并将元素转为正整数类型,记为 t p 1 tp1 tp1 ,然后反转赋给 t p 2 tp2 tp2 ;重头戏来了,通过 z i p zip zip 函数将两个元组捆绑到一起,然后用 m a p map map 函数对捆绑后的两个元组进行对应元素求和,然后转为元组类型,输出即可。
知识点:读入输入为元组;元组推导式;反转元组; z i p zip zip 函数; m a p map map 函数。用法见前述的函数。
元组推导式与列表推导式几乎一样;将输入的字符串放入元组中的操作也和列表差不多(见代码中的前两行)。
tp_1 = tuple(input().split(" ")) # 输入转化为元组
tp_1 = tuple((int(i) for i in tp_1)) # 元组推导式
tp_2 = tp_1[ : : -1] # 反转元组
zipped = zip(tp_1, tp_2) # 把两个元组捆绑到一起
mapped = map(sum, zipped) # 求和
print(tuple(mapped)) # 转为元组 再输出
015 今年多少天
思路:判断一个年份是否闰年的模板题。若是闰年,输出 366 366 366 即可,不是闰年,输出 365 365 365 即可。
num = int(input())
if num % 4 == 0 and num % 100 != 0 or num % 400 == 0: # 四年一闰,百年不闰,四百年再闰
print(366)
else:
print(365)
016 百分制成绩转换五分制
思路:此题较为简单,主要是用 i f − e l s e if-else if−else 语句判断所有情况,分门别类地处理,另外要记得先处理数据非法的情况。
num = int(input())
if num < 0 or num > 100: # 特殊判断
print("data error!")
# 分门别类进行判断
if 90 <= num and num <= 100: # 在python中,有更简洁的写法,90 <= num <= 100
print('A')
elif 80 <= num and num < 90:
print('B')
elif 70 <= num and num < 80:
print('C')
elif 60 <= num and num < 70:
print('D')
elif 0 <= num and num < 60:
print('E')
017 水仙花数
思路:本题的做法就是暴力枚举。从 100 100 100 枚举到 1000 1000 1000 ,对于每个数都进行判断,是否满足水仙花数的条件,若是则输出即可;不是就轮到下一个数。
参考文献:在python中/与//有什么区别_python中/和//的区别-CSDN博客
for i in range(100, 1000):
# 用a, b, c分别记录i的个位数、十位数和百位数
a = i % 10
b = (i // 10) % 10 # 这里得用地板除法
c = i // 100
if a ** 3 + b ** 3 + c ** 3 == i:
print(i)
018 类型转换
思路:此题颇坑,指的是数据点,可能是我自己的做法有问题。将输入读入为列表中的若干个字符串,遍历每个字符串,依次判断是否为整数和浮点数,若是,则进行相应转换后加入新列表,不是则直接加入新列表。最后输出新列表即可。
知识点:如何判断一个由字符串表示的数是整数,浮点数,需要用到几个字符串的内置函数。 i s d i g i t isdigit isdigit 和 r e p l a c e replace replace 两个函数的用法见字符串。
参考资料:python如何判断整数和浮点数 - 百度文库 (baidu.com)
先看看我刚开始的错误代码:
s0 = input().split()
sm = 0
s1 = list()
for i in s0:
if i.isdigit():
j = int(i)
s1.append(j)
sm += j
elif i.replace('.', '').isdigit(): # 这一步碰到0.3.2这种数据就寄了
j = float(i)
s1.append(j)
sm += j
else:
s1.append(i)
print(s1)
print(sm)
正确 A C AC AC 代码:
其实与错误代码大同小异,区别在于对于浮点数的判断更严谨了。
s0 = input().split()
sm = 0 # sum用来记录整数和浮点数的和
s1 = list() # 初始化为空,随后每遍历一个元素,就将处理后的元素加入其中
for i in s0:
if i.isdigit(): # 判断是不是数字组成的,若是则为正整数
j = int(i)
s1.append(j)
sm += j
elif i.replace('.', '').isdigit(): # 这题最坑的一点就是他的数据可能是0.3.2这种包含多个小数点的数字,这时用之前的代码会导致运行时错误
j = i # 这里定义j的目的是为了不改变i的值,用j代替i完成接下来的操作
if len(i.split('.')) == 2: # 如果只有两部分,即整数+小数,则为浮点数;否则不是浮点数
j = float(i)
sm += j
s1.append(j)
else: # 啥都不是,就直接加入即可
s1.append(i)
print(s1)
print(sm)
测试数据:
"""
测试数据:
data1:
input:
1 0.3.2 yo.3 is 1.3
output:
[1, '0.3.2', 'yo.3', 'is', 1.3]
2.3
data2:
input:
1 1.3 you is
output:
[1, 1.3, 'you', 'is']
2.3
data3:
input:
4a 3.0 700 a4 1.2.2
output:
['4a', 3.0, 700, 'a4', '1.2.2']
703.0
"""
019 用内置函数操作列表
知识点:这道题用到了 l e n , m a x , m i n , s u m , a b s , s o r t e d len,max,min,sum,abs,sorted len,max,min,sum,abs,sorted 这么多函数。基本都是顾名思义的函数。
参考文献:Python sorted() 函数 | 菜鸟教程 (runoob.com)
lst = input().split()
lst = [int(i) for i in lst] # 需要转为整数类型,因为后面要求绝对值
'''
也可以这样读入输入
lst = list(map(int, input().split())
'''
l = len(lst) # 求列表长度
mx = max(lst) # 求列表中的最大值
mn = min(lst) # 求列表中的最小值
s = sum(lst) # 求列表中所以元素的和
print(l, mx, mn, s, sep=",")
lst = [abs(i) for i in lst]
lst = sorted(lst)
print(lst)
测试数据:
"""
data1:
input:
1 -3 5 -6
output:
4,5,-6,-3
[1, 3, 5, 6]
"""
020 重复数字
思路:这题给我的感觉是主要考察字典的应用。输入存为列表后,遍历每个元素,若在字典中,则次数加一,若不在,则创建一个键,值为一。最后输出字典。
lst = list(map(int, input().split(',')))
lst = sorted(lst)
dic = dict()
for i in lst:
if i in dic: # 在字典中
dic[i] += 1
else: # 不在字典中,创建一个键
dic[i] = 1
print(dic)
第三季:循环
021 三个忍者
思路:这道题思路很简单,暴力枚举即可,根据 i < j < k i<j<k i<j<k ,建立三层循环,每层遍历所有可能的取值,满足题目要求的条件,则最终结果就加一。
arr = input().split(' ')
num = input().split(' ')
arr = [int(i) for i in arr] # 列表推导式,将字符转化成数字
num = [int(i) for i in num] # 同上
a, b, c = num # 列表解包,见解包
cnt = 0 # count计数,记录有多少可能
length = len(arr)
for i in range(0, length - 2): # 不能取最后两位,因为j和k要取最后两位
for j in range(i + 1, length - 1): # 要从比i大的位置开始取,不能取最后一位
for k in range(j + 1, length): # 要从比j大的位置开始取
if abs(arr[i] - arr[j]) <= a and abs(arr[j] - arr[k]) <= b and abs(arr[i] - arr[k]) <= c:
cnt += 1
print(cnt)
测试数据:
"""
data1:
input:
3 0 1 1 9 7
7 2 3
output:
4
"""
022 解方程
思路:这道题很烦,前前后后提交了 7 , 8 7,8 7,8 次,简直是 60 60 60 道题中最让我头疼的题,虽然它看起来那么简单。我用的是二分法,基本上都是二分法,其它两种方法未免有点大材小用。需要三个变量: l e f t , m i d d l e , r i g h t left,middle,right left,middle,right ,分别记录线性可行域的左边界,中间点和右边界。初始时, l l l 是 0 0 0 , r r r 是 n n n ,而 m m m 总是等于 ( l + r ) / 2 (l + r) / 2 (l+r)/2 。通过 m m m 可以把可行域划分为左、右两个区域。
- 当 m 2 < n m ^ 2 < n m2<n 时,说明真实解在右区域,所以 l = m l = m l=m ,将可行域缩减至右区域。
- 当 m 2 > n m ^ 2 > n m2>n 时,说明真实解在左区域,所以 r = m r = m r=m ,将可行域缩减至左区域。
- 当 m 2 = n m ^ 2 = n m2=n 时,说明真实解就是 m m m ,所以已经找到结果。
这里我要贴很多代码,我想弄清楚我是怎么错的,以及我是怎么对的。
Code-1
# 自己的原始WA代码(后面居然能过了)
num = int(input())
l = 0
r = num
while r - l >= 0.0001: # 满足精度条件
mid = (l + r) / 2 # 取中间点
if mid * mid >= num:
r = mid # 说明在左区域
else:
l = mid # 说明在右区域
print("%.4f" % ((r + l) / 2))
Code-2
开外挂的代码,调用了 m a t h math math 模块里的 s q r t sqrt sqrt 函数。但是,好像 W A WA WA 了,不知道为啥。。。
import math
num = int(input())
print("%.4f" % math.sqrt(num))
Code-3
这段代码,我感觉有点问题,用 r o u n d round round 函数取精度的方法不对,当 n = 16 n = 16 n=16 时,结果是 4.0 4.0 4.0 ,但正确答案是 4.0000 4.0000 4.0000 。
def sqrt(n): # 求一个正整数的平方根,且保留四位小数
left, right = 0, n
while right - left > 0.0001:
mid = (left + right) / 2
if mid * mid < n:
left = mid
elif mid * mid > n:
right = mid
else:
return round(mid, 4) # 用round函数取精度的方法不推荐,可能会出错,当mid为整数时,保留不了4位小数
return round(left, 4)
# 测试
n = int(input())
print(sqrt(n))
Code-4
这段代码好像也能过,后来反思了一下,输出最后结果的时候,最好输出 ( l + r ) / 2 (l + r) / 2 (l+r)/2 ,因为单独输出 l l l 或者 r r r 都可能导致差一点点精度。不信可以试试二者的差距。
num = int(input())
l = 0
r = num
mid = (l + r) / 2
while r - l >= 0.0001:
if mid * mid >= num:
r = mid
else:
l = mid
mid = (l + r) / 2
# print(l, mid, r, r - l, sep = ',') # 调试用
print("%.4f" % mid)
# 启示:输出最后结果的时候,最好输出(l + r) / 2,因为单独输出l或者r都可能导致差一点点精度
Code-5
同学使用的方法,牛顿迭代法,了解了一下原理,其实和二分法差不多,也要两个变量记录可行域的边界,但感觉收敛速度比二分法要快一点。可自行了解一下。
def sqrt(n):
x = n
y = (x + n / x) / 2
while abs(y - x) > 1e-5:
x = y
y = (x + n / x) / 2
return x
n = int(input())
print('%.4f' % sqrt(n))
测试数据:
"""
data1:
input:
13
output:
3.6055
data2:
input:
8
output:
2.8284
"""
023 字符统计
知识点:这道题需要用到三个函数: i s u p p e r , i s l o w e r , i s d i g i t isupper,islower,isdigit isupper,islower,isdigit 。三个判断函数,判断是否大写、小写、数字。
思路:对于输入的字符串,依次遍历每一个字符,用三个函数判断,是的话,就相应的数量加一,最后输出三个数量。
s = input()
a = b = c = 0
for i in s:
if i.isupper(): # 是否大写字母
a += 1
elif i.islower(): # 是否小写字母
b += 1
elif i.isdigit(): # 是否数字
c += 1
print(a, b, c, sep = ' ')
测试数据:
"""
data1:
input:
aB 1a
output:
1 2 1
data2:
input:
eo156h u1564i h48o ei
output:
0 9 9
"""
024 水上石阶
知识点:这题其实是斐波那契数列( F i b o n a c c i Fibonacci Fibonacci)。可以用顺推的方法,也可以用倒推的方法。我用的倒推的方法,用递归函数来实现。递归边界条件是: n = 1 , 2 n=1,2 n=1,2 时,答案分别是 1 , 2 1,2 1,2 。
def fibnq(n): # 递归函数
if n < 3:
return n
return fibnq(n - 1) + fibnq(n - 2) # 递推关系式f(n) = f(n - 1) + f(n - 2)
num = int(input())
print(fibnq(num))
025 列表中不相邻整数和的最大值
初始思路:这是我初始 A C AC AC 的代码,但其实不太对。因为排序后的列表的元素的相对位置已经发生了变化,没办法保证在原列表中不相邻。所以仅供参考。
lst = input().split()
lst = [int(i) for i in lst]
lst = sorted(lst) # 对列表进行排序
l = len(lst)
print(lst[l - 1] + lst[l - 3]) # 取最大的数和第三大的数求和,因为要求不相邻
正确思路:这其实相当于从一堆数中选择两个数,不考虑顺序,那就是组合问题,但是有个额外要求就是不相邻。做法是从左往右遍历列表中的每个数,将其与之后的所有不相邻的数的最大的那个相加,得到的结果参与总的最大值的竞选。因此要取两次最大(即 m a x max max 操作)。
正确代码:
lst = list(map(int, input().split()))
res = 0 # 记录最终结果,初始化为0,因为列表中的元素全是正整数
for i in range(len(lst) - 2): # 从左往右遍历列表,倒数两个元素不用遍历
res = max(res, lst[i] + max(lst[i + 2:])) # 比较每种可能,对于每个遍历到的数,将其与之后的不相邻元素的最大值相加与res进行比较
print(res)
测试数据:
"""
data1:
input:
1 2 3 4 5
output:
8
data2:
input:
2 1 4 3 5
output:
9
"""
026 神奇的计算
思路:直接五层循环,暴力枚举。有个要求就是所有数都不相等,需要加一些判断即可。
cnt = 0
for a in range(1, 10):
for b in range(1, 10):
if b == a: # 第二个数与第一个数不相等
continue
for c in range(1, 10):
if c == a or c == b: # 第三和第一。二不等
continue
for d in range(1, 10):
if d == c or d == b or d == a: # 第四和第一、二、三不等
continue
for e in range(1, 10):
if e == d or e == c or e == b or e == a: # 第五和第一、二、三、四不等
continue
if (a * 10 + b) * (c * 100 + d * 10 + e) == (a * 100 + d * 10 + b) * (c * 10 + e):
cnt += 1 # 记录最终结果
print(cnt)
027 逆时针原地旋转头像
知识点:倒序枚举:python列表遍历(包括倒序遍历的三种方法) - GumpYan - 博客园 (cnblogs.com)
往空列表中添加元素:Python中空列表增添元素的方法_笔记大全_设计学院 (python100.com)
思路:这道题有点难度,相当于对二维数组进行操作,得找到规律才行。由题意知,在原二维数组上看,从最后一列开始往前输出一直到第一列,对于每一列,从第一行开始输出一直到最后一行,即先确定输出的列,再确定输出的行。则对于输出数组的第 r r r 行第 c c c 列的数,对应于原数组的第 n − r n - r n−r 列第 c c c 行的数,这点很重要,这就是规律。由于我把输入的二维数组转化为了一维数组,所以对应于一维数组的第 ( c − 1 ) × n + n − r (c - 1) \times n + n - r (c−1)×n+n−r 个数,至此,就可以写出代码了。
n = int(input())
lst0 = list() # 用一维列表存储输入的原数组,初始化为空
for i in range(0, n):
lst = input().split()
lst0 = lst0 + lst # 列表的+操作,将二维列表转化为一维列表
# print(lst0) # 调试可用
for i in range(n - 1, -1, -1): # 倒序枚举,i相当于思路里的n - r
for j in range(0, n): # 正序枚举,j相当于思路里的c - 1,因为j是从0开始的,而c是从1开始的
if j != 0: # j为0,说明是输出的某行最后一个数,不输出空格,其实这里输出空格也没关系,不会错
print(" ", end = '')
print(lst0[j * n + i], end = '')
# print(i)
print("\n", end = '') # 每一行输出完后,输出一个换行
测试数据:
"""
data1:
input:
3
1 2 3
4 5 6
7 8 9
output:
3 6 9
2 5 8
1 4 7
data2:
input:
3
3 6 9
2 5 8
1 4 7
output:
9 8 7
6 5 4
3 2 1
"""
028 100~200素数累加求和
思路:对于 100 100 100 到 200 200 200 的每个数,判断是否为素数,如果是则加到最终结果里,若不是,则跳过。判断一个数是否为素数的方法是:从 2 2 2 开始遍历到它的平方根取整加一,对于这里面的任何一个数,若都不能被整除,则为素数,否则为合数。
import math
ttl = 0 # 记录最终的结果
for i in range(100, 201):
flag = True # 在判断每个数之前,初始化flag为True,表示它是素数,经过筛选后可能会变成False
for j in range(2, int(math.sqrt(i)) + 1): # 这个循环用来判断是否是素数
if i % j == 0:
flag = False # 能被整除,说明不是素数,那就改变标记,并且结束判断
break
if flag: # 为True表示是素数
ttl += i
print(ttl)
029 数字规律相加
思路:这道题其实就是找规律的题。初始值设为 0 0 0 ,每次循环都加上 b a s e base base 这个变量。 b a s e base base 变量的初始值为 2 2 2 ,它会随着循环次数的增加而变化,变化的规律是每次都在它前面添个数字 1 1 1 。循环结束后,输出结果需要减一,因为其实多加上了一个一。
n = int(input())
ttl = 0 # 记录最终的结果
base = 2 # 每次循环要加上的变量
for i in range(1, n + 1):
ttl += base
base += 10 ** i # 变量的变化规律
# print(base)
print(ttl - 1) # 最后要减一
030 长城最长凸起
思路:首先得理解题目所说的凸起的意思,必须得同时有
<
<
< 和
>
>
> 的关系,只有一个是不行的。比如:1 < 3 < 5 > 4 > 2
是凸起,而1 < 3 < 5
和5 > 4 > 2
不是凸起。在此基础上,再考虑是否最长。
这道题要用两层循环解决。第一层循环从索引 1 1 1 开始遍历列表中的每个数,一直到索引为 l − 1 l - 1 l−1 ,即第一个数和最后一个数肯定没有凸起。对于每个数,往左遍历它左边的数,得到严格递减的最大长度;同理,往右遍历它右边的数,得到严格递减的最大长度。最后就可以得到以它为最高点的凸起的最大长度,参与总的最长凸起的竞选。
lst = input().split()
lst = [int(i) for i in lst]
l = len(lst) # 特殊判断,如果数组长度小于3,直接输出结果
if l < 3:
print(0)
res = 0
for i in range(1, l - 1): # 除去第一和倒数第一这两个数,遍历每个数
j = i # 将i的值赋给j,后面要两次用到i的初始值,i的值会发生变化,所以提前备份一次
le = 0 # left,记录这个数左边严格递减的最大长度
while i >= 1 and lst[i - 1] < lst[i]: # 计算严格递减的最大长度
le += 1
i -= 1
if le == 0: # 如果为0,说明没有可能是凸起,不用后面白费功夫了,直接下一下
continue
ri = 0 # right,记录这个数右边严格递减的最大长度
while j < l - 1 and lst[j + 1] < lst[j]: # 计算严格递减的最大长度
ri += 1
j += 1
if ri == 0: # 如果为0,说明没有可能是凸起,不用后面白费功夫了,直接下一下
continue
res = max(res, le + ri + 1) # 能够到这一步,说明存在凸起,那么这里就计算出凸起的长度(最大)
print(res)
测试数据:
"""
data1:
input:
2 1 4 7 3 2 5
output:
5
data2:
input:
2 1 4 7 7 3 2 5 4 3 2 1
output:
6
"""
第四季:函数
031 Sort与Lambda函数
知识点:这道题要用到两个函数: s o r t sort sort 和 l a m b d a lambda lambda 匿名函数。见编程经验里的讲解。还要弄清楚 P y t h o n Python Python 中 s o r t sort sort 和 s o r t e d sorted sorted 的区别。
思路:会用列表的 s o r t sort sort 函数,再加上匿名函数做参数就很简单了。
n, x = list(map(int, input().split()))
lst = list(map(int, input().split()))
lst.sort(key = lambda i: abs(i - x)) # 按照匿名函数的要求进行排序
for i in lst:
print(i, end = ' ')
测试数据:
"""
data1:
input:
9 5
1 2 3 4 5 6 7 8 9
output:
5 4 6 3 7 2 8 1 9
"""
032 “python”的编辑距离
点评:评价是,难度很大,弄得我焦头烂额(我确实菜)。不过动态规划本身有点难度,所以别慌,我的思路能听懂就听懂,听不懂当我没说好。耶!
思路:动态规划首先需要定义状态,然后构建状态转移方程,初始化后开始递推,得到最终结果。此题的状态是使用一个二维数组来表示输入字符串 s s s 到字符串 s 0 = p y t h o n s0 = python s0=python 的编辑距离。设 l s t [ i ] [ j ] lst[i][j] lst[i][j] 表示将 s s s 的前 i i i 个字符转换为 s 0 s0 s0 的前 j j j 个字符所需的编辑距离。
状态转移方程的建立(我们只针对两个字符串的最后一个字符进行讨论):
- 当 s [ i ] = s 0 [ j ] s[i] = s0[j] s[i]=s0[j] 时,有 l s t [ i ] [ j ] = l s t [ i − 1 ] [ j − 1 ] lst[i][j]=lst[i - 1][j - 1] lst[i][j]=lst[i−1][j−1] ;
- 当 s [ i ] ≠ s 0 [ j ] s[i] \ne s0[j] s[i]=s0[j] 时, l s t [ i ] [ j ] = m i n ( l s t [ i − 1 ] [ j ] , l s t [ i − 1 ] [ j − 1 ] , l s t [ i ] [ j − 1 ] ) + 1 lst[i][j] = min(lst[i - 1][j],lst[i - 1][j - 1],lst[i][j - 1]) + 1 lst[i][j]=min(lst[i−1][j],lst[i−1][j−1],lst[i][j−1])+1 。
公式 1 1 1 :当两个字符串的最后一个字符相同时,等价于同时消去最后一个字符,两种情况的编辑距离相同。
公式 2 2 2 :当两个字符串的最后一个字符不相同时,这时候按照题意可对字符串 s s s 进行三种字符操作。删除操作对应的状态为 l s t [ i − 1 ] [ j ] lst[i - 1][j] lst[i−1][j] (删除 s s s 最后一个字符),插入操作对应的状态为 l s t [ i ] [ j − 1 ] lst[i][j - 1] lst[i][j−1] (插入肯定是插入一个和 s 0 s0 s0 的最后一个字符相等的字符,所以转化为了公式 1 1 1 的情况,相当于删除 s 0 s0 s0 最后一个字符),更改操作对应的状态为 l s t [ i − 1 ] [ j − 1 ] lst[i - 1][j - 1] lst[i−1][j−1](更改肯定是将 s s s 的最后一个字符改为与 s 0 s0 s0 的最后一个字符相等的字符,所以转化为了公式 1 1 1 的情况,相当于删除 s s s 和 s 0 s0 s0 的最后一个字符) 。并且这三种操作都会使得两个字符串的编辑距离加一。
初始化:首先将二维数组 l s t lst lst 全部初始化为 0 0 0 。然后是初始化它的第一行和第一列,因为后面递推时需要这些值。具体初始化的方法见代码。
s0 = "python"
s1 = input()
# 后面需要两个字符串的长度
l0 = len(s0)
l1 = len(s1)
""""
这里第一行和第一列都是用来初始化,方便递推,所以其实从1开始才有意义
lst[i][j]表示s0从第1个字符一直到第i个字符之间的字符串与s1从第1个字符一直到第j个字符之间的字符串的编辑距离,即字符串的第一个字符的索引是0,以此类推
"""
lst = [[0 for i in range(l1 + 1)] for i in range(l0 + 1)] # 首先,初始化全为0
for i in range(l0 + 1):
for j in range(l1 + 1):
if i == 0 or j == 0:# 初始化第一行、列,这个初始化很重要,当一方为空字符串时,值取决于另一方
lst[i][j] = max(i, j)
else:
if s0[i - 1] == s1[j - 1]: # 这里注意两个字符串的索引是要减一的
lst[i][j] = lst[i - 1][j - 1]
else:
lst[i][j] = min(lst[i - 1][j], lst[i][j - 1], lst[i - 1][j - 1]) + 1 # 状态转移方程
print(lst[l0][l1]) # 这里不用加1,因为前面的range是右开区间
测试数据:
"""
data1:
input:
fpimuomh
output:
6
data2:
input:
python
output:
0
"""
033 数列求和
思路:题目那么多要求,其实就是想让你写个函数求解而已。这题和 024 水上石阶 024 \ 水上石阶 024 水上石阶 有点相似,都是定义一个递归函数,倒推求解。
def sumn(n): # 递归函数
if n == 1:
return 1
return n + sumn(n - 1) # 倒推关系式
n = int(input())
print(sumn(n))
034 找出目标在数组中的位置
知识点:列表的一个方法 i n d e x ( ) index() index() ,可以从列表中找出某个值第一个出现的索引位置。见编程经验。
参考资料:如何在Python中找到列表元素的位置_笔记大全_设计学院 (python100.com)
思路:得到列表之后,用 i n d e x ( ) index() index() 方法得到目标值的第一次出现位置,输出即可;如果找不到会抛出异常,此时用 t r y − e x c e p t try-except try−except 语句判断即可,输出 − 1 -1 −1 。当然也可以用遍历的方法,从左往右遍历,找到了就输出对应位置,没找到就返回 − 1 -1 −1 。
nums = list(map(int, input().split()))
target = int(input())
try:
print(nums.index(target))
except ValueError:
print(-1)
测试数据:
"""
data1:
input:
-1 0 3 5 9 12
9
output:
4
data2:
input:
-1 0 3 5 9 12
4
output:
-1
"""
035 使用Lambda函数
思路:抱歉的是,这道题由于很简单,所以我没有用匿名函数。而是做了一个整除运算的特殊判判断后,用内置函数 e v a l eval eval 直接执行,得到结果输出即可。 这里要特殊处理整除的情况,因为题目要求除法运算仅保留整数部分,但题目输入的只是一个除号( / / /),实际应该是( / / // //),所以要在字符串中加上一个除号,具体操作见代码。
e v a l eval eval 函数用来执行一个字符串表达式,并返回表达式的值。见编程经验。
s = input()
for i in range(0, len(s)):
if s[i] == '/': # 做特殊判断
s = s[ : i + 1] + '/' + s[i + 1 : ] # 在字符串中添加一个/
break
print(eval(s))
测试数据:
"""
data1:
input:
3+2*2
output:
7
data2:
input:
3+7/2
output:
6
"""
036 闭包函数实现计算器
知识点:闭包函数见编程经验里的闭包函数。下面的代码会用到关键字 n o n l o c a l nonlocal nonlocal ,也见编程经验的杂项。
思路:定义一个闭包函数,外部函数定义一个记录调用次数的变量 c o u n t count count ,返回一个可以打印出函数被调用次数的函数 p p p 。这样,用 f u n fun fun 保存返回的 p p p 函数,然后调用五次 f u n fun fun 函数,即可得到期望输出。
def cnt():
count = 0 # 用来记录调用函数的次数,初始化为0
def p(): # 打印调用次数的函数
nonlocal count # 可以引用和修改外部函数的变量,如果不加这个,就不能在cnt函数外部更改count的值,而只能访问使用
count += 1 # 每调用一次函数都加一
print(count) # 并输出调用次数
return p
fun = cnt() # 得到返回的p函数
for i in range(5): # 调用五次函数
fun()
037 合并区间
思路:首先说明一点,假设两个区间分别为 [ a , b ] [a,\ b] [a, b] 和 [ c , d ] [c, \ d] [c, d] ,且 a ≤ c a \le c a≤c ,那么两个区间能够合并,一定是 a ≤ c ≤ b a \le c \le b a≤c≤b 。换句话说,只要两个区间满足以上条件,那么一定可以合并。(称此说明为定理 1 1 1 )
基于此定理,可以将许多区间合并为一个区间。具体做法是:将输入的区间按照左端点的大小升序排序。设一个独立区间 d d d (每一个互不重叠的区间,我们都称之为独立区间),初始化为第一个区间,然后依次遍历所有排好序的区间,对于每个区间 i i i ,按照定理 1 1 1 进行判断,即这个区间的左端点是否在独立区间里。
- 如果在的话,那就说明区间 i i i 可以和已知的独立区间 d d d 进行合并,那就更新独立区间 d d d 的右端点,右端点取值为独立区间 d d d 的右端点和区间 i i i 的右端点的最大值。左端点不用更新因为独立区间 d d d 的左端点显然是合并区间后的左端点。然后遍历下一个区间。
- 如果不在,那就说明独立区间 d d d 不能和区间 i i i 合并,那就输出独立区间 d d d ,并把独立区间 d d d 更新为区间 i i i 。
需要注意的是:
- 题目没说区间是排好序的,所以不要被样例骗了,一定要记得排序。
- 由于最后一个独立区间无法在循环遍历每一个区间时输出,所以循环结束后,记得把它输出,否则会少一个独立区间。
- 本题记录一个区间的方法是,用一个列表存储所有区间的左端点,然后用字典存储右端点,即键值对为 { 左端点 : 右端点 } \{左端点:\ 右端点\} {左端点: 右端点} ,对于每个左端点,都可以在字典中查询到对应的右端点。
- 会出现左端点相同,而右端点不同的情况。对于这道题而言,只需要取右端点做大的那个区间即可,故需要判断一下。
P S PS PS :这题写了好久,最后终于改出 b u g bug bug 来了,原来是 s o r t e d sorted sorted 函数不会用,这个函数对列表进行排序后返回一个排好序的列表,而不是像.sort那样直接就排好序了,所以如果不获取它返回的列表的话,相当于没排序。
n = int(input())
lst0 = list(map(int, input().split()))
dic = dict() # 定义一个字典存储区间端点的对应关系
lst1 = list() # 用于存储所有区间的左端点
for i in range(0, int(len(lst0) / 2)): # 遍历所有区间,完成对lst1和dic的更新
if lst0[i * 2] not in dic: # 如果不在字典中,则将左右端点键值对加入字典,并将左端点加入列表
dic[lst0[i * 2]] = lst0[i * 2 + 1] # 将左右端点键值对加入字典
lst1.append(lst0[i * 2]) # 将左端点加入列表
else: # 如果在字典里,说明出现了左端点相同,而右端点可能不同的情况,那就取右端点最大的那个
dic[lst0[i * 2]] = max(lst0[i * 2 + 1], dic[lst0[i * 2]])
# print(lst1) # 调试可用
lst1 = sorted(lst1) # 将所有区间按照左端点的大小升序排序
# print(dic) # 调试可用
# print(lst1) # 调试可用
# 独立区间初始化为第一个区间
l = lst1[0] # 独立区间的左端点
r = dic[l] # 独立区间的右端点
for i in range(1, len(lst1)): # 遍历剩下的所有区间
if lst1[i] <= r: # 说明可以合并
r = max(r, dic[lst1[i]]) # 合并后的独立区间右端点取二者最大值
else: # 说明不能合并,那就输出已存在的独立区间,并更新独立区间
print(l, r, sep = ' ', end = ' ')
l = lst1[i]
r = dic[lst1[i]]
print(l, r, sep = ' ', end = ' ') # 记得输出最后一个独立区间
测试数据:
"""
data1:
input:
4
1 3 2 6 8 10 15 18
output:
1 6 8 10 15 18
data2:
input:
9
1 5 2 6 2 4 3 6 4 9 4 8 7 10 9 12 13 14
output:
1 12 13 14
data3:
input:
1
1 6
output:
1 6
data4:
input:
5
1 3 2 6 8 10 8 9 15 18
output:
1 6 8 10 15 18
data5:
input:
3
2 8 4 6 5 7
output:
2 8
data6:
input:
6
1 1 2 5 1 4 4 4 8 9 6 6
output:
1 5 6 6 8 9
data7:
input:
5
2 3 5 5 2 2 3 4 3 4
output:
2 4 5 5
"""
038 分发水果
思路:此题较为简单。用一个列表存储每一个小孩拿到的水果数量。由于题目所述的发苹果有数据规律,每次循环都找到对应的小朋友,发对应的苹果数量。可以用取余的方法循环得到对应的小朋友编号( 1 , 2 , ⋯ , n 1,2,\cdots , n 1,2,⋯,n),同时可以用一个变量 c n t cnt cnt 记录每次该发的苹果数量,每次循环都加一进行更新。具体操作见代码:
n, m = list(map(int, input().split()))
lst = [0 for i in range(0, n)] # 记录每个孩子拿到的水果数量,初始化为0
cnt = 0 # 记录每次给小朋友发的水果的数量,它会循环而变化,由题意知:1->2->3...
while m > cnt: # 如果剩下的水果数量小于该发的水果数量,那就结束循环,否则继续
lst[cnt % n] += cnt + 1 # 按顺序,对应的小朋友拿对应的苹果数
cnt += 1 # 更新该发的水果数量
m -= cnt # 剩下的苹果数,每次循环都要更新
lst[cnt % n] += m # 剩下的水果数量全部发给小朋友
for i in lst: # 输出最终的结果
print(i, end = ' ')
039 n的平方和立方
思路:简单题。定义一个闭包函数,外部函数接收要计算平方和立方的整数 n n n ,并返回一个可以计算整数的平方和立方的函数 c p t cpt cpt 。随后用 f u n fun fun 保存返回的 c p t cpt cpt 函数,直接调用即可得到结果。
def cpt23(n): # 外部函数
def cpt(): # 内部函数,用来返回去计算整数的平方和立方
print(n ** 2, n ** 3, sep = '\n')
return cpt
n = int(input())
fun = cpt23(n)
fun()
040 斐波那契数
思路:这道题和 024 水上石阶 024 \ 水上石阶 024 水上石阶 很类似。就是定义一个递归函数,利用倒推关系式,一直递归到边界条件再返回,就可以得到最终的结果了。
def fbnq(n): # 定义一个函数,计算斐波那契数
if n == 0 or n == 1: # 边界条件
return n
return fbnq(n - 1) + fbnq(n - 2)
n = int(input())
print(fbnq(n))
第五季:类
041 实例计数
思路:直接按题意定义一个类即可,然后创建五个实例对象,最后输出类变量。
class Person(): # 定义Person类
count = 0 # 类变量
def __init__(self): # 初始化方法,每创建一个实例就会调用该方法
Person.count += 1 # 由于每创建一个实例都会调用,所以就相当于记录了有多少个对象
lst = ['a', 'b', 'c', 'd', 'e'] # lst在这里,元素是多少不重要,只要有五个对象就可
for i in lst: # 循环五次,实例化五个不同的Person对象
i = Person()
print(Person.count)
042 计算阶乘
思路:在类里面定义一个变量存储要计算阶乘的正整数,再定义一个计算阶乘的方法。
class BigInt(): # 定义一个大数类
def __init__(self, num): # 创建每个实例的num值
self.num = num
def jc(self): # 用来计算阶乘
res = 1
for i in range(1, self.num + 1):
res *= i
return res
n = int(input())
BI = BigInt(n) # 创建实例
print(BI.jc()) # 调用实例的方法计算阶乘,再输出
043 实现栈
思路:根据题意,在类中除了定义一个初始化方法外,还要定义 p u s h push push 、 p o p pop pop 和打印三种方法。
class Stack():
def __init__(self): # 列表存储栈里面的数据,初始化为空
self.lst = list()
def S_pop(self): # 弹出数据
self.lst.pop()
def S_push(self, num): # 压入数据
self.lst.append(num)
def S_print(self): # 打印列表,可得到栈的所有数据
for i in self.lst:
print(i, end=' ')
S = Stack() # 创建实例化对象
while True:
a = list(map(int, input().split()))
if a[0] == 0: # 输入为0时截止
break
elif a[0] == 1: # 压入
S.S_push(a[1])
elif a[0] == 2: # 弹出
S.S_pop()
S.S_print()
测试数据:
"""
data1:
input:
1 3
1 5
1 0
1 1
1 9
2
0
output:
3 5 0 1
data2:
input:
1 1
1 2
1 3
2
0
output:
1 2
"""
044 判断点是否在圆内
知识点:这道题说是要用基类,这是类的继承与派生的问题,可以看看编程经验里的参考资料。
思路:不用基类。直接定义一个圆类,类变量有圆心坐标和半径,方法可以根据圆心和某点的距离与半径之间的关系得到。
class Circle():
def __init__(self, x, y, radius): # x和y分别是圆心的横纵坐标,radius是半径
self.x = x
self.y = y
self.r = radius
def InCircle(self, xx, yy): # 判断某点(xx,yy)是否在圆上
if (xx - self.x) ** 2 + (yy - self.y) ** 2 <= self.r ** 2: # 数学知识
print(1)
else:
print(-1)
xx, yy, x, y, radius = list(map(int, input().split()))
c = Circle(x, y, radius) # 创建一个圆类对象
c.InCircle(xx, yy) # 调用对象方法判断
045 BMI指数计算
思路:题目已经说得很明白了,按照题目的意思定义一个类就行。
class Person():
def __init__(self, n, w, h, g): # 四个属性
self.n = n # name
self.h = h # height
self.w = w # weight
self.g = g # gender
def bmi(self): # 计算bmi
if self.g == 'female': # 女性
print(int((self.h - 70) * 0.6))
else: # 男性
print(int((self.h - 80) * 0.7))
n, w, h, g = input().split() # n和g为字符串,w和h为数字
w = int(w)
h = int(h)
p = Person(n, w, h, g)
p.bmi()
测试数据:
"""
data1:
input:
lily 60 170 female
output:
60
"""
046 转换大小写
思路:由题意定义类,其中 g e t S t r i n g ( ) getString() getString() 方法用 i n p u t input input 实现, p r i n t S t r i n g ( ) printString() printString() 用 p r i n t print print 实现即可,没有什么难度。
class Str():
s = list() # 类变量为列表,初始化为空
def getString(self): # 输入获取字符串
self.s = input()
def printString(self): # 打印大写字母的字符串
print(self.s.upper())
s = Str()
s.getString()
s.printString()
测试数据:
"""
data1:
input:
abcdefG123
output:
ABCDEFG123
data2:
input:
ghG(Eg98
output:
GHG(EG98
"""
047 计算时间差
知识点:利用 z i p zip zip 函数将两个列表(长度相同)压缩到一起,然后用 d i c t ( ) dict() dict() 操作将其转化为字典,从而形成键值对。
思路:第五季终于有一个不用类的题目了。假设起始时间是 a a a 年 b b b 月 c c c 日,结束时间是 d d d 年 e e e 月 f f f 日。那么把起始时间转化为 a a a 年的第 t 1 t_1 t1 天,结束时间转化为 d d d 年的第 t 2 t_2 t2 天。
具体的转换方法是根据年份判断是否是闰年,从而判断二月有多少天,那么就确定了这一年中所有月份的天数了,就可以根据完全包含的月份和部分包含的月份求得起始时间在这一年的第几天,同理也可求得结束时间在它所在年份里的第几天。
然后求出 a a a 年到 d − 1 d - 1 d−1 年所包含的总天数 t 3 t_3 t3 ,则最后的结果就是 t 3 + t 2 − t 1 t_3 + t_2 - t_1 t3+t2−t1 。其实这里的 t 1 t_1 t1 就是代码中的 s u b sub sub 变量, t 2 t_2 t2 就是代码中的 a d d add add 变量。至于为什么是这个等式,因为我们计算 t 3 t_3 t3 的时候,相比于最终结果(总的间隔天数)来说,多算了 t 1 t_1 t1 天,而少算了 t 2 t_2 t2 天,所以要加上少算的,而减去多算的。
注意:这题noj的数据有漏洞,我一个稍显错误的代码也能过,就是我没有考虑闰年对二月的影响,即平月和闰月,但也过了。另外,需要考虑的两个因素:是否闰年,闰年对一年的总天数有影响;计算天数是否用到了二月的天数,闰年对二月的天数也有影响。
def leap_year(y): # 闰年英文leap year,此函数用来判断某一年是否是闰年
if y % 4 == 0 and y % 100 != 0 or y % 400 == 0: # 是否满足闰年的条件
return True
else:
return False
# 定义1年12个月对应的天数,用字典存储
m = [i for i in range(1, 13)] # 月份编号
ds = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] # 每个月对应的天数
dic = dict(zip(m, ds)) # 用zip压缩后,转化为字典
y1, m1, d1 = list(map(int, input().split())) # 起始时间
y2, m2, d2 = list(map(int, input().split())) # 结束时间
if leap_year(y1): # 如果是闰年的话,每次计算月份的天数时,都要在二月的28天基础上加1
dic[2] += 1
# 下面是计算需要减去的天数
sub = 0 # subtract,需要减去的天数,初始化为0
for i in range(1, m1): # 遍历完全包含的所有月份,加上对应的天数
sub += dic[i]
sub += d1 # 再加上部分包含的月份,加上包含的部分天数
dic[2] = 28 # 每次计算完一年的情况,二月的天数需要回归初始值28天
if leap_year(y1): # 判断是否闰年,处理二月份的天数,同上处理
dic[2] += 1
# 下面是计算需要加上的天数
add = 0 # addition,需要要加上的天数,初始化为0
for i in range(1, m2): # 遍历完全包含的所有月份,加上对应的天数
add += dic[i]
add += d2 # 再加上部分包含的月份,加上包含的部分天数
y_d = 0 # year->days,由年份计算天数(完全包含的年份)
for i in range(y1, y2): # 遍历完全包含的所有年份,加上对应的天数
y_d += 365
if leap_year(i): # 如果是闰年的话,得加一
y_d += 1
print(y_d + add - sub) # 输出最后的结果
测试数据:
"""
data1:
input:
2019 10 16
2019 10 17
output:
1
data2:
input:
0 1 1
123 4 5
output:
45020
"""
048 简单计算器
思路:显然,这个类的方法可以完成加减乘除四种操作,那就用条件语句判断再实现即可。
class compute():
def __init__(self, a, b): # 实现运算需要两个变量
self.a = a
self.b = b
def cpt(self, c): # 分四种情况,再进行计算
if c == '+':
print(self.a + self.b)
elif c == '-':
print(self.a - self.b)
elif c == '*':
print(self.a * self.b)
else:
print(int(self.a / self.b)) # 最好写成self.a // self.b,题目要求是整数除法
a, c, b = input().split()
a = int(a)
b = int(b)
case = compute(a, b)
case.cpt(c)
这道题还有更简单的做法,用 e v a l eval eval 函数可以直接运行参数里的字符串,参见编程经验里的 e v a l eval eval 函数。
测试数据:
"""
data1:
input:
10 * 15
output:
150
data2:
input:
15 / 3
output:
5
"""
049 计算重叠部分面积
思路:这道题需要一点推导能力。两个矩阵的相对位置事实上只有两个情况,右上左下式和左上右下式,这里只考虑相对位置,和其他因素如矩阵大小,名称无关。见示意图:
无论是哪种情况,我们都只需要计算出重叠矩形的左下角和右上角的两个点的横纵坐标(图中的 a a a 点和 c c c 点),就可以计算出重叠矩形的面积了。而观察两张图,左下角点的横坐标都是两个初始矩形的横坐标的最大值(靠右的那个),纵坐标都是两个初始矩形的纵坐标的最大值(靠上的那个);同理,观察可得到,右上角点的横坐标都是两个初始矩形的横坐标的最小值(靠左的那个),纵坐标都是两个初始矩形的纵坐标的最小值(靠下的那个)。据此,可计算得到 a a a 点和 c c c 点,那么就可以得到重叠矩形的长度和宽度,就算出面积了。
class Rectangle():
def __init__(self, x1, y1, x2, y2): # 左下和右上的点的坐标
self.a = x1 # 左下(x1,y1)
self.b = y1
self.c = x2 # 右上(x2,y2)
self.d = y2
def cal(self, xx1, yy1, xx2, yy2): # 计算两个矩形的重叠面积
h = max(self.a, xx1) # 左下点的横坐标
i = min(self.b, yy1) # 右上点的纵坐标
j = min(self.c, xx2) # 右上点的横坐标
k = max(self.d, yy2) # 左下点的纵坐标
print((j - h) * (i - k)) # 算出重叠矩形面积
a, b, c, d = list(map(int, input().split()))
r = Rectangle(a, b, c, d) # 实例化对象
a, b, c, d = list(map(int, input().split()))
r.cal(a, b, c, d) # 调用类的方法计算两个矩阵的重叠面积
测试数据:
"""
data1:
input:
0 2 2 0
1 2 3 0
output:
2
"""
050 朋友圈
知识点:并查集是一种高级数据结构,老夫也未过多涉猎,故不献丑了。
思路:事实上,这道题很简单,根本用不到并查集这中数据结构。假设有 n n n 个人和 m m m 个朋友关系,如果所有人都没有朋友关系,说明每个人即是一个朋友圈,总共 n n n 个朋友圈。而每输入一个朋友关系,都意味着两个朋友圈合并成了一个朋友圈,那就相当于减少了一个朋友圈。因此有多少朋友关系,就会减少多少朋友圈,所以最后剩下的朋友圈个数就是 n − m n-m n−m 。但是,我这种做法有个前提是,题目所输入的朋友关系不会重复,比如 0 0 0 和 1 1 1 是朋友关系,题目输入多次,那就会导致结果错误,不过好在 n o j noj noj 数据偏弱,没有考虑到这种坑人情况。
n = int(input()) # 总人数
m = int(input()) # 朋友关系总数
print(n - m) # 朋友圈的个数
测试数据:
"""
data1:
input:
5
3
1 2
2 3
0 4
output:
2
"""
第六季:数据结构与算法
051 每日温度
思路:遍历每日的温度,对于每个温度,都遍历在它之后日子的温度,找到第一个比它更高的温度即为答案,输出即可。如果遍历完了,都没找到,那就说明不会出现更高的气温了,输出 0 0 0 。
lst = list(map(int, input().split()))
for i in range(0, len(lst)): # 遍历每日的温度
red = i # 记录比当天温度更高的天数编号,初始化为当前天数,编号从0开始
for j in range(i + 1, len(lst)): # 遍历在这之后的每天的温度
if lst[j] > lst[i]: # 如果比当前天数的温度高,则更新red,并结束循环
red = j
break
print(red - i, end = " ") # 如果red值还是i,说明没有找到,则输出0,否则输出相差的天数
测试数据:
"""
data1:
input:
73 74 75 71 69 72 76 73
output:
1 1 4 2 1 1 0 0
"""
052 合并两个有序列表
思路:涉及到一点算法。在两个列表中各自定义一个指针,初始时指向列表中的第一个数,然后将两个列表的指针所指向的数进行比较,小的数加入新的列表,对应的指针向后移一位;大的数不动,指针也不移动。如此一直循环下去,直到某个列表已经遍历完,即全部加入了新列表中。此时,另一个列表可能并没有遍历完,即它剩下的数比已经遍历完的列表所有数都大,这时,将其剩下的数直接加入到新列表中即可。
lst1 = list(map(int, input().split()))
lst2 = list(map(int, input().split()))
l1 = len(lst1) # 后面需要两个列表的长度
l2 = len(lst2)
i = j = 0 # 两个列表各自的指针
# i, j, k = 0, 0, 0 这两种赋初值的方式都对
lst_combine = list() # 一个新列表,存储合并后的列表
while i < l1 and j < l2: # 只要有一个列表没有遍历完,那就继续遍历
if lst1[i] < lst2[j]: # 小的数加入新列表,指针后移
lst_combine.append(lst1[i])
i += 1
else:
lst_combine.append(lst2[j])
j += 1
if i < l1: # 这里判断列表是否遍历完,没完就将剩下的数全部加入到新列表中
lst_combine += lst1[i : ]
if j < l2:
lst_combine += lst2[j : ]
for l in lst_combine: # 输出合并后的新列表
print(l, end = ' ')
测试数据:
"""
data1:
input:
1 2 3
2 4 6
output:
1 2 3 4 5 6
data2:
input:
1 3 5
2 4 5
output:
1 2 3 4 5 5
"""
053 环游世界
思路:这道题没有涉及算法,直接按照题目说的做就行了。我们需要两个变量,一个记录每天达到的位置,另一个记录实现一次环游的天数,这两个值都会随着循环的进行而改变。用循环模拟旅游的过程,每次循环都是新的一天,都会访问一个地方,直到所有地方都去过了,即完成一次环游。
NextVisit = list(map(int, input().split()))
n = len(NextVisit) # 这道题暗含了一个条件,就是NextVisit的长度就是n(地点数),因为对于每个地点都可以在这里找到访问次数为奇数时下一步要去的地方
Visit_Count = [0 for i in range(n)] # 记录每个地点(从0到n - 1)的访问次数,全部初始化为0
v = 0 # 每天到达的位置
v_days = 0 # 记录实现第一次环游总共需要的天数
while 0 in Visit_Count: # Visit_Count中如果有0,说明有个地方从没去过,需要继续旅行直到所有地方都去过
Visit_Count[v] += 1 # 到达的这个地方访问次数加一
if Visit_Count[v] % 2 == 0: # 偶数
v = (v + 1) % n
else: # 奇数
v = NextVisit[v]
v_days += 1 # 环游天数加一
print(v_days % int(1e9 + 7) - 1) # 得到最终结果
测试数据:
"""
data1:
input:
0 3 3 2
output:
4
data2:
input:
0 3 3 3
output:
8
data3:
input:
0 0
output:
2
"""
054 最接近成功的三位同学
思路:又是一个组合问题,从 n n n 个人中找出 3 3 3 个人,还是按照之前的做法,三层循环,每层找一个同学,最后要使得三个人不一样,且编号之和小于目标值。参考: 026 神奇的计算 026 \ 神奇的计算 026 神奇的计算 。
lst = list(map(int, input().split())) # 学生编号
num = int(input()) # 目标值
lst.sort() # 对列表进行排序
n = len(lst)
count = 0 # 记录最终结果,初始化为0
# 三层循环找三个同学,if语句保证三个不一样的人,同时编号之和小于目标值,每找到一个组合,count加一
for i in range(0, n - 2):
for j in range(i + 1, n - 1):
if j == i:
continue
for k in range(j + 1, n):
if k == i or k == j:
continue
if lst[i] + lst[j] + lst[k] < num:
count += 1
print(count)
测试数据:
"""
data1:
input:
-2 0 1 3
2
output:
2
data2:
input:
3 0 -2 1
2
output:
2
"""
055 数组实现循环队列
思路:用一个列表作为数组,作为队列。还需要两个变量 h e a d head head 和 r e a r rear rear 分别用来指向队首元素和队尾元素后一位,即索引区间 [ h e a d , r e a r − 1 ] [head, \ rear - 1] [head, rear−1] 表示队列的现有元素的范围。对于 p u s h push push 操作,每压入一个数,队首不动,队尾向后移一位以容纳加入的元素,但是得保证不会超过队列的容量,所以在压入之前要加一个判断;对于 p o p pop pop 操作,队尾不动,队首向后移一位表示队首元素已被移出,不在索引范围内了,但是要保证队首指针 h e a d head head 小于等于队尾指针 r e a r rear rear ,即队列不为空,所以在弹出之前要判断一下。
其实,这道题的 p o p pop pop 操作可以直接用列表的方法 p o p ( ) pop() pop() 来实现, $pop() $ 函数用于移除列表中的一个元素(默认最后一个元素),并且返回该元素的值。
n = int(input())
m = int(input())
head = rear = 0 # 队首和队尾,初始化为0
lst = list() # 用列表表示循环队列
while m > 0: # 一共m个操作
m -= 1
l = list(map(int, input().split()))
if l[0] == 1: # push操作
if rear - head < n: # 判断队列是否已经满了
rear += 1 # 队尾后移一位,表示有新的元素加入到了队列中
lst.append(l[1]) # 加入队列
print(True)
else:
print(False)
else: # pop操作
if rear > head: # 判断列表是否为空
print(lst[head])
head += 1 # 队首后移一位,表示将队首的元素弹出
else:
print(-1) # 列表为空,弹出操作完成不了,输出-1
测试数据:
"""
data1:
input:
4
6
1 3
1 5
1 0
1 1
1 9
2
output:
True
True
True
True
False
3
data2:
input:
2
6
1 3
1 5
2
2
2
1 9
output:
True
True
3
5
-1
True
"""
056 集群数
思路:这道题需要用到深度优先搜索算法。用二维列表存储二维矩阵,遍历二维矩阵中的每个元素 i i i ,以 i i i 为起始点,在不越界的情况下,向周围四个方向(上下左右)进行搜索,当然前提是 i i i 是 1 1 1 才行,如果是 0 0 0 的话,那就不用搜索了。对于 i i i 的四个方向上的点,再以这四个点为基础,向其周围四个方向进行搜索,一直搜索到边界条件为止。当然,深搜里面会有剪枝判断,没有搜索过的值为 1 1 1 的点才有搜索的必要,已经搜索过的值为 1 1 1 点已经包含在集群里了,不用再考虑了。这里需要一个二维记号矩阵,用来标记二维矩阵中的点是否被访问过。
n, m = list(map(int, input().split()))
lst0 = list()
for i in range(n): # 将二维矩阵用二维列表来保存
lst0.append(list(map(int, input().split())))
# 对于二维矩阵中的每一点,要考虑它周围的四个点,故要考虑四个方向(上下左右)
dr = [0, 0, 1, -1]
dc = [1, -1, 0, 0]
# 用于标记每个点是否访问过的二维矩阵,全部初始化为False,表示都没有访问过
lst1 = [[False for i in range(m)] for i in range(n)]
# 用的是深搜的方法
def dfs(r, c): # 用来判断r,c所指向的点所在的集群里的元素个数
count = 0 # 记录每个集群的元素个数,为零说明从这个点出发找不到集群,即二维矩阵中其值为0
if not lst1[r][c] and lst0[r][c] == 1: # 没有访问过的值为1的点才有访问的必要
lst1[r][c] = True
count += 1
for i in range(4): # 搜寻四个方向
a = r + dr[i]
b = c + dc[i]
if 0 <= a < n and 0 <= b < m: # 需要判断索引是否越界
count += dfs(a, b)
return count
count = 0 # 记录最终的答案,即集群的数目
for i in range(n):
for j in range(m):
if dfs(i, j): # 只要函数dfs不返回0,就说明存在新的集群,则计数加一
count += 1
print(count)
测试数据:
"""
data1:
input:
4 5
1 1 1 1 0
1 1 0 1 0
1 1 0 0 0
0 0 0 1 1
output:
2
data2:
input:
4 5
1 1 1 1 0
1 1 0 1 0
1 1 0 0 0
0 0 0 0 0
output:
1
"""
057 数组中出现的奇异数
思路:运用字典的性质,记录每个数字出现的次数,如果次数超过一,就不输出,次数等于一,就输出。另外,根据样例的输出可以看出,最后是要输出一个列表,所以我们用列表存储所有奇异数。
lst = list(map(int, input().split()))
dic = dict() # 定义一个空字典
for _ in lst:
if _ in dic: #出现过,次数加一
dic[_] += 1
else: #没出现过,创建一个新的数字,且次数等于一
dic[_] = 1
res_lst = list() # 列表存储所有奇异数
for d in dic:
if dic[d] == 1: # 次数为一,就加入到列表
res_lst.append(d)
print(res_lst) # 直接输出列表
测试数据:
"""
data1:
input:
4 1 4 6
output:
[1, 6]
"""
058 和为K的子数组
错误思路:刚开始的做法就是假设都是非负整数,那么 l l l 向右移动, s u m sum sum 会减少,而 r r r 向右移动, s u m sum sum 会增加,那么根据结果来看,这种做法是错的。因为题目没保证,数据是非负的,可能是负数。
# 错误思路的代码,没有AC,别复制,看看就行
lst = list(map(int, input().split()))
k = int(input())
le = len(lst)
l = r = 0 # 双指针,l左指针指向数组的第一个数,r右指针指向数组的倒数第一个数
cnt = 0 # 记录结果
while l <= r < le:
s = sum(lst[l : r + 1]) # 数组求和
if s == k: # 找到了
cnt += 1
r += 1 # 右指针向后一位
elif s < k: # 小了,右指针向右增大数组和
r += 1
else: # 大了,左指针向右减小数组和
l += 1
if l > r: # 需要额外判断,防止左指针比右指针大
r += 1
print(cnt)
正确思路:连续子数组在给定的数组中主要取决于左边界和右边界,只要这两个值确定了,那这个连续子数组就确定了。因此,我们就用两层循环来枚举所有可能的左边界和右边界,确定了一个连续子数组之后,判断其和是否满足题意条件即可。另外,需要注意,数组中的单独一个数也算是一种特殊的连续子数组。
lst = list(map(int, input().split()))
k = int(input())
n = len(lst)
count = 0 # 用来记录满足条件的子数组的个数
for l in range(n): # 枚举左边界
for r in range(l, n): # 枚举右边界,r和l可以相等哟
if sum(lst[l : r + 1]) == k: # 判断数组和是否为K
count += 1
print(count)
测试数据:
"""
data1:
input:
1 1 1
2
output:
2
data2:
input:
1 1 1 2
2
output:
3
"""
059 有序数组去重
思路:很简单的一道题。如果对数据结构 s e t set set 有印象,或者说有了解的话,这道题迎刃而解。因为原数组是有序的,所以直接将输入转化为列表,再转化为集合,这样元素就是非重复的,直接输出集合大小即可。
s = set(input().split()) # 转化为集合
print(len(s))
测试数据:
"""
data1:
input:
0 0 1 1 1 2 2 3 3 4
output:
5
"""
060 判断回文字符串
点评:此题和 032 “ p y t h o n ”的编辑距离 032 \ “python”的编辑距离 032 “python”的编辑距离 并称“ n o j noj noj 双雄”,对于算法没学好的我来说,难度极大。下面具体来看一下思路。
错误思路:将字符串反转,与原字符串作对比,记下有多少个字符不相等,最后与 2 × n 2 \times n 2×n 作比较,得到结果。
# 错误代码,仅供参考
s0 = input()
n = int(input())
s1 = s0[ : : -1]
l = len(s0)
cnt = 0
for i in range(l):
if s0[i] != s1[i]:
cnt += 1
if cnt <= 2 * n:
print(True)
else:
print(False)
正确思路 — 1 —1 —1 :(以下是 G P T GPT GPT 的描述)这个算法使用双指针方法进行判断。首先,将两个指针分别指向字符串的开头和结尾。然后,循环比较指针所指的字符是否相等,如果不相等,则尝试删除其中一个字符,并将剩余删除次数 k k k 减 1 1 1 。如果 k k k 小于 0 0 0 ,说明删除次数已经用尽,返回 F a l s e False False 。如果字符相等,则将指针向中间移动,继续比较下一对字符。当指针相遇时,说明字符串可以形成回文,返回 T r u e True True 。(说实话,这个算法和代码我还没看懂,我就不解释了)
Code-1
# GPT的做法(不知道对不对,感觉是对的,用的是双指针算法)
def is_palindrome(string, k):
start = 0
end = len(string) - 1
while start < end:
if string[start] != string[end]:
k -= 1
if k < 0:
return False
# 尝试删除start或end指向的字符,继续判断
if string[start + 1] == string[end]:
start += 1
if string[start] == string[end - 1]:
end -= 1
start += 1
end -= 1
return True
# 测试
string = input()
k = int(input())
print(is_palindrome(string, k))
正确思路 — 2 —2 —2 :类似于 032 “ p y t h o n ”的编辑距离 032 \ “python”的编辑距离 032 “python”的编辑距离 的算法流程。状态是:用 l s t [ i ] [ j ] lst[i][j] lst[i][j] 表示输入字符串 s s s 的第 i i i 个字符到第 j j j 个字符的子串要变为回文字符串所需要删除的最少字符数。
状态转移方程的建立:
- 若 s [ i ] = s [ j ] s[i] = s[j] s[i]=s[j] ,即最左边和最右边的字符相同,则相当于消去最左边和最右边的字符,即 l s t [ i ] [ j ] 可以取值为 l s t [ i + 1 ] [ j − 1 ] lst[i][j] 可以取值为 lst[i + 1][j - 1] lst[i][j]可以取值为lst[i+1][j−1] 。
- 若 s [ i ] ≠ s [ j ] s[i] \ne s[j] s[i]=s[j] ,即最左边和最右边的字符不相同,那么有两种操作选择:删除最左边的字符或者最右边的字符,因此 l s t [ i ] [ j ] = m i n ( l s t [ i + 1 ] [ j ] , l s t [ i ] [ j − 1 ] ) + 1 lst[i][j] = min(lst[i + 1][j],lst[i][j - 1]) + 1 lst[i][j]=min(lst[i+1][j],lst[i][j−1])+1 。( s [ i ] = s [ j ] s[i] = s[j] s[i]=s[j] 时,也要考虑这两种操作)
不过,需要注意的是:这道题与另一道动规的题的区别在于,此题当 s [ i ] = s [ j ] s[i] = s[j] s[i]=s[j] 时,实际上 l s t [ i ] [ j ] = m i n ( m i n ( l s t [ i + 1 ] [ j ] , l s t [ i ] [ j − 1 ] ) + 1 , l s t [ i + 1 ] [ j − 1 ] ) lst[i][j] = min(min(lst[i + 1][j],lst[i][j - 1]) + 1,lst[i + 1][j - 1]) lst[i][j]=min(min(lst[i+1][j],lst[i][j−1])+1,lst[i+1][j−1]) ,即要从三种可能取最小值,而不是直接等于 l s t [ i + 1 ] [ j − 1 ] lst[i + 1][j - 1] lst[i+1][j−1] ;而当 s [ i ] ≠ s [ j ] s[i] \ne s[j] s[i]=s[j] 时, l s t [ i ] [ j ] 是直接等于 m i n ( l s t [ i + 1 ] [ j ] , l s t [ i ] [ j − 1 ] ) + 1 lst[i][j] 是直接等于 min(lst[i + 1][j],lst[i][j - 1]) + 1 lst[i][j]是直接等于min(lst[i+1][j],lst[i][j−1])+1 的。具体参考代码。
用二维数组 l s t lst lst 存储 d p dp dp 过程中的值。事实上,这是一个上三角矩阵,左下角的值都为 0 0 0 ,包括主对角线上的数都为 0 0 0 (表示单个字符当然是回文字符串)。故初始化 l s t lst lst 全为 0 0 0 ,然后按照次对角线的方向更新右上角元素的值,直到最右上角的那个数求出来为止。 C o d e − 2 Code-2 Code−2 里最后有一段代码,可以输出递推之后二维数组的状态值。
Code-2
# 自己的做法(动态规划)
s = input()
num = int(input())
le = len(s)
lst = [[0 for i in range(le)] for i in range(le)] # 构建二维数组,初始全为0
"""
枚举子串长度从2到le(对应i从1到le - 1)(长度为1,值为0,已经初始化为0了,所以不用更新值了),同时枚举所有可能的左边界。因为长度最小为2,所以在标记1处,可能会出现左边界大于右边界的情况,比如当输入为aa时,就会出现这种情况。不过没关系,这里没有做特殊判断,因为这种情况对应的值在左下角,值为0,正确,完美避开错误
"""
for i in range(1, le): # 枚举子串长度,i为1,其实子串长度为2,以此类推
for j in range(0, le - i): # 枚举子串左边界
lst[j][j + i] = min(lst[j + 1][j + i] + 1, lst[j][j + i - 1] + 1) # 不管首尾是否相等,都要考虑前述的两种操作
if s[j] == s[j + i]: # 若首尾相等,还要考虑这种情况
lst[j][j + i] = min(lst[j][j + i], lst[j + 1][j + i - 1]) # 标记1
if lst[0][le - 1] <= num: # 小于字符数限制,说明可以做到
print(True)
else:
print(False)
# 可以输出递推之后二维数组的状态值,调试时用,不用复制
# for i in range(le):
# for j in range(le):
# print(lst[i][j], end = ' ')
# print('\n')
测试数据:
"""
data1:
input:
abcdeca
2
output:
True
data2:
input:
adea
1
output:
True
"""
全文结束,欢迎各位批评指正!另外,记得一键三连哟!(右下角处)