240308 牛客网100题 又没保存 但发现是有云端备份 有的解答还是不易读 看一下脑子就不想看了

8 9 10 11 12 13
简单题

HJ1

计算字符串最后一个单词的长度,单词以空格隔开,字符串长度小于5000。
• 字符串末尾不以空格为结尾
I:输入一行,代表要计算的字符串,非空,长度小于5000。
O:输出一个整数,表示输入字符串最后一个单词的长度。

# import sys
str = input()
List = str.split(" ")	# 分割
print(len(List[-1]))
HJ2

接收一个由字母、数字、空格组成的字符串,和一个字符,然后输出输入字符串中该字符的出现次数。
· 不区分大小写字母;1≤n≤1000
I:第一行输入一个由字母、数字和空格组成的字符串,第二行输入一个字符(保证该字符不为空格)
O:输出字符串中含有该字符的个数(不区分大小写字母)

str = input().lower()	# .upper()
char = input().lower()	# 归一化
count = 0
for a in list(str):	# list用法★
    if a == char:
        count += 1
print(count)
HJ4

输入一个字符串,按长度为8拆分每个输入字符串并输出;
· 长度不是8整数倍的字符串,在后面补数字0;空字符串不处理
I:连续输入字符串(每个字符串长度小于等于100)
O:依次输出所有分割后的长度为8的新字符串

def f(str):
    if len(str)<8:	# if 可以删去吧
        str = str +'0'*(8-len(str))
        print(str)
    else:
        while len(str)>8:
            qian8 = str[:8]	# python特性 截取、拼合
            print(qian8)
            str =str[8:]
        print(str+'0'*(8-len(str)))
    return str
a = input()
f(a)

自己优化一下,删去冗余的if

def f(str):
 while len(str)>8:
  qian8 = str[:8]
  print(qian8)
  str =str[8:]
 print(str+'0'*(8-len(str)))
 return str
a = input()
f(a)
HJ5

接受一个十六进制的数,输出该数值的十进制表示。
· 保证结果在1≤n≤2^31-1
I:输入一个十六进制的数值字符串
O:输出该数值的十进制字符串。不同组的测试用例用\n隔开。

print(int(input(),16))	# 输入 input()
HJ6

输入一个正整数,按照从小到大的顺序输出它的所有质因子(重复的也要列举)(如180的质因子为2 2 3 3 5 )
· 1≤n≤2×10^9+14
I:输入一个整数
O:从小到大输出它的所有质数的因子,以空格隔开
思路:像HJ4一样while吧× 由于每个数可重复,应该是for每个数,对每个数再while

n = int(input())
import math
for i in range(2, int(math.sqrt(n)) + 1):
    while n % i == 0:
        print(int(i), end=' ')
        n /= i 
if n > 1: print(int(n), end= ' ')
HJ7

接收一个正浮点数值,输出该数值的近似整数值。如果小数点后数值大于等于 0.5 ,向上取整;小于 0.5 ,则向下取整。
· 保证输入在 32 位浮点数范围内
I:输入一个正浮点数值
O:输出该数值的近似整数值

num = float(input())	# 浮点数类型
Int = int(num)
fra= num - Int
if fra >= 0.5:
    print(Int+1)
else:
    print(Int)

自己优化一下,三目

num = float(input())
Int = int(num)
Int = Int+1 if num-Int >= 0.5 else Int
print(Int)
HJ8 合并表value

数据表记录包含表索引index和数值value(int范围的正整数),请对表索引相同的记录进行合并,即将相同索引的数值进行求和运算,输出按照index值升序进行输出。
· 0 <= index <= 11111111,1 <= value <= 100000
I:先输入键值对的个数n(1 <= n <= 500),接下来输入n行成对的index和value值,以空格隔开
O:输出合并后的键值对(多行)
例:

输入:
4
0 1
0 2
1 2
3 4
输出:
0 3
1 2
3 4

思路:字典转?最后仍以index的升序输出
★map(int,x)
★.split()
★字典语法 in

n = int(input()) 
dict = {}
for i in range(n):	# 这里是逐行操作键及值
    input_list = list(map(int,input().split())) # 先转为了list map★
    current_key = input_list[0] # 键
    current_value = input_list[1] # 值
    
    if current_key in dict:	# 字典语法 in★
        dict[current_key] += current_value
    else:
        dict[current_key] = current_value # 字典的 增
keys = list(dict.keys()) # 又转成list 有点冗余
keys.sort()

for element in keys: # 按格式输出
    print(str(element)+' '+str(dict[element]))	#

优化:
实现是一模一样的,但看起来简洁
★学这个while try except break

dic = {}
n = int(input())
while True:
    try:
        line = input()
        tmp = line.split()
        index = int(tmp[0])
        value = int(tmp[1])
        if index in dic:
            dic[index] += value
        else:
            dic[index] = value
    except:
        break
for k, v in sorted(dic.items()):
    print(f'{k} {v}')

另解:
★动态建构字典 dic.get(key, 0) 用到字典.get方法、和增加表项时的特征

n = int(input())
dic = {}

for i in range(n):
    line = input().split()
    key = int(line[0])
    value = int(line[1])
    dic[key] = dic.get(key, 0) + value  # 使用.get★ 去增 或改 机智

for each in sorted(dic):  # 最后的键值对按照升值排序
    print(each, dic[each])	# print语法 默认就是如此输出
map

语法:
map(function, iterable, ...)
参数:
function - 一个函数
iterable - 一个列表、元组、集合、字典或其他可迭代对象。
返回值:
将iterable中每个元素依次传给function处理,并返回一个map对象。map对象是一个可迭代的序列,每项是一个元组、列表、字典或其它可迭代的对象;可用for遍历,或用list()转为列表,但不支持直接以索引来访问。
示例:

def double(x):
    return x * 2

numbers = [1, 2, 3, 4, 5]
result = map(double, numbers)
print(list(result))

这里出现常用连招list(map())
示例:结合map() 和 lambda匿名函数表达式

numbers = [1, 2, 3, 4, 5]
result = map(lambda x: x ** 2, numbers)

print(list(result))

示例:结合map() 和 filter()/reduce( )▲
filter语法:大概类似map,返回参数2带入参数1执行时结果为 true 的可迭代输入项

import math

def isPositive(n):
   return n >= 0 

def Sqrt(nums):
   filteredItems = map(math.sqrt, filter(isPositive, nums))
   return list(filteredItems)	#是正数,就执行开方

inputList= [16, -10, 625, 25, -50, -25]
print(Sqrt(inputList))
split

语法:
string.split(separator, maxsplit)
参数:
separator - 可选,指定分隔符,必须为字符,默认为所有空字符,包括 空格、换行(\n)、水平制表符(\t)、 垂直制表符(\v)、 换页(\f)、回车(\r)
maxsplit - 可选,指定最大分割次数,即分割m次形成m+1个子串后,符合也不再分割。若无或为-1,则无限制
返回值:
split()方法返回一个列表,为分割后的子字符串
示例:
string = "Hello, world! How are you?"
result = string.split()
print(result)
怎么实现多个分隔符分割的需求?
法一:使用处理正则表达式的re模块的split()
.escape▲

import re

def split_multiple_delimiters(text, delimiters):
    regex_pattern = '|'.join(map(re.escape, delimiters))
    return re.split(regex_pattern, text)

# 测试代码
text = "apple,banana;orange|grape"
delimiters = [",", ";", "|"]
result = split_multiple_delimiters(text, delimiters)
print(result)

法二(泛用性感觉不太好):反复用join()+split(),逐一替换各分隔符为某一字符如|

def split_multiple_delimiters(text, delimiters):
    for delimiter in delimiters:
        text = '|'.join(text.split(delimiter))
    return text.split('|')
	
# 测试代码
text = "apple,banana;orange|grape"
delimiters = [",", ";", "|"]
result = split_multiple_delimiters(text, delimiters)
print(result)

法三:用字符串的replace()方法,将多个分隔符换成同一符号

def split_multiple_delimiters(text, delimiters):
    for delimiter in delimiters:
        text = text.replace(delimiter, '|')
    return text.split('|')

# 测试代码
text = "apple,banana;orange|grape"
delimiters = [",", ";", "|"]
result = split_multiple_delimiters(text, delimiters)
print(result)
字典.get()方法

好理解,以键查值;大概可以代替字典的in用法,判断某个键的存在性(in也是判断键而非值的)
语法:
dict.get(key, default=None)
参数:
key − 要在字典中搜索的键
default − 如果键不存在,则返回的值
返回值:
返回给定键的值。若键不可用则返回默认值None

字典.keys()方法
返回一个包含所有键的可迭代对象,也可以以此判断某个键的存在性

HJ9

输入一个 int 型整数,按照从右向左的阅读顺序,返回一个不含重复数字的新的整数。
· 保证输入的整数最后一位不是 0
· 1≤n≤10^8

num = input()
# List = list(map(int,str(num))) # 法2 字符串变为列表 map★
ans = []
for n in reversed(num):	# list语法 reversed★
    if n not in ans:
        ans.append(n)
res = int("".join([str(i)for i in ans]))	# 拼接列表成为数字*
print(res)

思路:字符串转为list,最后拼回字符串
这里用到 将列表直接拼接

内置函数reversed()

语法:reversed(sequence)
返回值:它接受一个可迭代对象(iterable),返回一个反向迭代器
用法(见HJ12):list = ''.join(reversed(input()))

HJ10

计算字符串中含有的不同字符的个数。
· 字符在 ASCII 码范围内( 0~127 ,包括 0 和 127 ),换行表示结束符,不算在字符里。不在范围内的不作统计。
· 1≤n≤500
I:输入一行没有空格的字符串
O:输出 输入字符串 中范围在(0~127,包括0和127)字符的种数。
思路:字典
★学一下对输入作简单过滤 输出list
★list也有in用法

num = list(map(str,input()))
ans = []
for a in num:
    if a not in ans:
        ans.append(a)
print(len(ans))
HJ11

输入一个整数,将这个整数以字符串的形式逆序输出
· 不考虑负数情况,若含有0,则逆序形式也含有0,如输入为100,则输出为001
· 0≤n≤2^30 −1
I:输入一个int整数
O:将这个整数以字符串的形式逆序输出
思路:list
★list.reverse()方法

num = list(map(int,input()))
num.reverse()
res = str("".join(str(i)for i in num))
print(res)
HJ12

接收一个只包含小写字母的字符串,然后输出该字符串反转后的字符串。
· 字符串长度不超过1000
I:输入一行,为一个只包含小写字母的字符串。
O:输出该字符串反转后的字符串。
思路:一样的吧×多一步 将字符串转成list:list()的用法

num = list(str(input()))
num.reverse()
ans = str("".join(i for i in num))
print(ans)

另解:
1.用reversed函数 print(''.join(reversed(input())))
2.★切片函数 print(input()[::-1])

切片操作符

Python中最常用的字符串截取方法是切片。
语法:
[start:stop:step]
参数:默认值分别为0 len 1
示例:

str = "Hello, World!"
print(str[7:])    # 输出:World!
print(str[-6:-1]) # 输出:World

常用用法:

copy_numbers = numbers[:]
reverse_numbers = numbers[::-1]
HJ13

多组输入;将一个英文语句以单词为单位逆序排放,单词间用空格隔开,如“I am a boy”,排放后为“boy a am I”
· 保证输入只包含空格和字母
· 1≤n≤1000
I:输入一个英文语句,每个单词用空格隔开
O:得到逆序的句子
思路:split成list,然后[::-1],再' '.join
忘了要map一下

In = list(map(str,input().split()))
In.reverse()
print(' '.join(In))

注意.reverse()方法没有参数和返回值

HJ14

给定 n 个字串,请对 n 个字串按照字典序排列。
· 1≤n≤1000,1≤len≤100
I:输入第一行为一个正整数n(1≤n≤1000),随后为n个字串(长度≤100),字串中只含有大小写字母
O:数据输出n行,输出结果为按照字典序排列的字串
★列表的.sort()方法

n = int(input())
temp = []
for i in range(n):
    temp.append(input())
temp.sort()
for i in range(n):
    print(temp[i])
列表的.sort()方法

其使用TimSort算法,一种稳定排序算法。这意味着,相同的元素在排序后的顺序不会改变。
语法:list.sort(key=None, reverse=False)
参数:key - 可以传入一个函数,自定义排序的规则,函数中还可以定义多个排序条件
返回值:修改原列表,返回值为None
key用法示例:

fruits = ['apple', 'banana', 'cherry', 'date']
def custom_key(word):
    return (len(word), -ord(word[0]))	#这是个元组吧
fruits.sort(key=custom_key)
print(fruits)
列表的sorted()函数(常用)

若想不修改原列表,而是获得一个新的已排序列表,可以new = sorted(list)
若用来排序字典的,排完变成list:

d = {'c':2,'b':4,'a':6}
d1 = sorted(d.values(), reverse=True)
>>> [6, 4, 2]
HJ15

输入一个 int 型的正整数,计算出该 int 型数据在内存中存储时 1 的个数。
· 保证在 32 位整型数字范围
I:输入一个整数(int类型)
O:这个数转换成2进制后,1的个数
★bin()函数

num = int(input())
N = list(bin(num))
n = 0
for a in N:
    if a == '1':
        n+=1
print(n)

万物转list的感觉

HJ21

现在有一种密码变换算法。
九键手机键盘上的数字与字母的对应: 1--1, abc--2, def--3, ghi--4, jkl--5, mno--6, pqrs--7, tuv--8 wxyz--9, 0--0,把密码中出现的小写字母都变成九键键盘对应的数字,如:a 变成 2,x 变成 9.
而密码中出现的大写字母则变成小写之后往后移一位,如:X,先变小写,再后移一位,成y,特例:Z 后移是 a 。
数字和其它的符号都不作变换。
· 1≤n≤100
I:输入一组密码,长度不超过100个字符。
O:输出密码变换后的字符串
思路:字典吗 怎么做到多映射一呢
法2:先if找出字母,再if n[i] in 'abc':
法2的优化:

while True:
    try:
        A="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
        B="22233344455566677778889999bcdefghijklmnopqrstuvwxyza0123456789"
        list1=list(A)
        list2=list(B)
        C=[]
        In=input()
        for i in In:
            if i in list1:
                C.append(list2[list1.index(i)])
            else:
                C.append(i)	# 其它字符不作处理而输出
        print(''.join(C))
    except:
        break

法2的再优化:
★list也有+=运算符

nine = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
nums = "22233344455566677778889999bcdefghijklmnopqrstuvwxyza"
ans = ""
In = input()
for i in In:
    if i.isalpha():
        ans += nums[nine.find(i)]
    else:
        ans += i
print(ans)

法1:
同样用到list的+=

while True:
    try:
        password = input()
        list1 =  ['abc', 'def', 'ghi', 'jkl', 'mno', 'pqrs', 'tuv', 'wxyz']
        result = ''
        for i in password:
            # 若是字母
            if i.isalpha():
                # 是大写字母
                if i.isupper():
                    if i=='Z':
                        result += 'a'
                    else:
                        result += chr(ord(i.lower())+1)
                # 是小写字母
                else:
                    for j in list1:
                        if i in j:
                            result += str(list1.index(j)+2)	# +2是因为abc已对应2了
                            break
            # 是非字母 包含了数字0和1时
            else:
                result +=i
        print(result)
    except:
        break
HJ22

某商店规定:三个空汽水瓶可以换一瓶汽水,允许向老板借空汽水瓶(但是必须要归还)。
小张手上有n个空汽水瓶,她想知道自己最多可以喝到多少瓶汽水。
· 输入的正整数满足 1≤n≤100,本题存在多组输入。
I:输入文件最多包含 10 组测试数据,每个数据占一行,仅包含一个正整数 n( 1<=n<=100 ),表示小张手上的空汽水瓶数。n=0 表示输入结束,你的程序不应当处理这一行。
O:对于每组测试数据,输出一行,表示最多可以喝的汽水瓶数。如果一瓶也喝不到,输出0。
思路:要意识到2空瓶=1瓶,即2空瓶最终能且仅能换到1瓶;
★输入方式sys.stdin

import sys
data = sys.stdin
for x in data:
    x = int(x.strip())
    if x != 0:
        print(int(x // 2))

但,也可以模拟的,看一下模拟思路1:

ans =[]	# 每个是一行的答案
while True:
    In = int(input())
    temp = 0	# 每次能兑换多少瓶
    if In == 0: break	# 到输入0时结束程序
    while In != 0:
        if In >= 3:
            temp += In//3	# 暂存答案

            In = In//3 + In%3	# 兑换完又喝完了有多少瓶
        elif In == 2:
            temp += 1
            In = 0
            ans.append(temp)	# 输出答案
        else:
            In = 0
            ans.append(temp)
for c in ans:
    print(c)

模拟思路2:递归
这怎么也用sys.stdin输入 因为没有说输入有多少行

import sys
def f(n):
    if n == 0: return 0
    if n == 1: return 0
    if n >=2: return f(n-2) + 1

if __name__ == '__main__':
    data = sys.stdin
    for x in data:
        x = int(x.strip())
        if x != 0:
            print(f(x))
sys.stdin

是Python标准库中的一个对象,表示标准输入流。需要import sys模块,然后可以用sys.stdin.readline()方法逐行读取输入内容;或用sys.stdin.read()方法一次性读取全部内容;Ctrl+D(Win为Ctrl+Z+回车)后输入结束.
但通过看题,更常用用法是:

data = sys.stdin
for s in data:

通过重定向标准输入流,sys.stdin还可用于从其他文件或程序中读取内容。

import sys
file_path = "input.txt"  # 假设要读取的文件名为input.txt

with open(file_path, "r") as file:
    sys.stdin = file  # 将标准输入重定向到文件

    text = sys.stdin.read()
    print("文件内容:\n",text)

sys.stdin = sys.__stdin__	# 恢复标准输入
HJ31

对字符串中的所有单词进行倒排。
· 构成单词的字符只有26个大写或小写英文字母;
· 非构成单词的字符均视为间隔符;
· 要求倒排后的单词间隔符以空格表示;相邻多个间隔符转换后也只允许出现一个空格;
· 每个单词最长20个字母;
· 字符串长度满足 1≤n≤10000
I:输入一行,表示用来倒排的句子
O:输出句子的倒排结果
思路:存在多种间隔符,怎么隔开呢 还是转列表怎么样 感觉可以

Str = str(input())
ans = []
tmp = []
for i,a in enumerate(Str):
    if a in 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ':
        tmp.append(a)
    else:
        ans.append("".join(tmp))	# 过程中直接去形成单词、ans;这里遇到分隔符,就把之前的字符合成单词加入ans结果中
        tmp=[]
ans.append("".join(tmp))	# 最后没有分隔符 手动操作一下
ans.reverse()
print(" ".join(ans))

优化:
★序列的.replace()、.split()方法,遍历list并逐一替换字符
★*[list]× ✓
★list的切片还是list

a = input() # a是str
for i in a:
 if not i.isalpha():
  a = a.replace(i,' ')
b = a.split() # b是list
print(*b[::-1])
# print(type(*b[::-1]))   # 语法错误 说是参数太多
# print(b[::-1])	# 显示[,,]
用*、**进行序列的解包

不知道这里是不是这个作用,仅列一下两者的示例:

*a, b = [5, 6, 7, 8]
print(a)  # 输出:[5, 6, 7]
print(b)  # 输出:8

使得我们能够灵活地处理不确定长度的序列
这里大致上是用到了这个用法,不过示例是
a->a,答案里是a->*a

>>> dict_vec = {'y': 0, 'z': 1, 'x': 1}
>>> print_vector(**dict_vec)
<1, 0, 1>
HJ34

Lily上课时使用字母、数字的图片教小朋友们学单词,每次需要把这些图片按照ASCII码值从小到大排列收好。
· 图片使用字符"A"到"Z"、"a"到"z"、"0"到"9"表示。
· 每组输入的字符串长度满足 1≤n≤1000
I:一行,一个字符串,字符串中的每个字符表示一张Lily使用的图片。
O:Lily的所有图片按照从小到大的顺序输出
示例:

>>> Ihave1nose2hands10fingers
0112Iaadeeefghhinnnorsssv

思路:转list

Str = list(str(input()))
Str.sort()
ans = "".join(Str)
print(ans)
HJ35

蛇形矩阵是由1开始的自然数依次排列成的一个矩阵上三角形,N≤100
示例:
1 3 6 10
2 5 9
4 8
7
思路:

for j in range(1,n)
 for i in range(j,1)
  (j,i) = tmp
  tmp += 1

类似答案:
★for的变体写法
常见连招:list = [x for x in range(1, 5)]
★生成n*n阵 有没有其它写法▲

jishu = 1
dim = int(input())
mat = [[' ']*dim for i in range(dim)]

for i in range(dim):  # 0~dim-1
	for j in range(i+1):	# 0~i
		mat[i-j][j] = str(jishu)
		jishu += 1
for i in range(dim):
	print(' '.join(mat[i])) # 这里mat[i]就表示一行了

一行行输出即可:

for i in range(num):
    print(*result[i])

还有几种类型的蛇形矩阵,回形,Z形
回形,看到的做法是用4个for来填数,不知道有没有更好的

range

range是左闭右开的
语法:
range(stop),range(start, stop[, step])
· range(0) 不循环
· range(1)=range(0,1) 循环一次
· o~i是range(i+1)


文章到这没了,没找到相关刷题资源,先看一下中等题,这是必考,大概35道,时间紧张

HJ16

王强决定把年终奖用于购物,他把想买的物品分为两类:主件与附件,附件是从属于某个主件的,下表就是一些主件与附件的例子:
电脑 打印机,扫描仪
书柜 图书
书桌 台灯,文具
工作椅 无

  • 如果要买归类为附件的物品,必须先买该附件所属的主件,且每件物品只能购买一次。

  • 每个主件可以有 0 个、 1 个或 2 个附件。附件不再有从属于自己的附件。

  • 王强查到了每件物品的价格(都是 10 元的整数倍),而他只有 N 元的预算。

  • 除此之外,他给每件物品规定了一个重要度,用整数 1 ~ 5 表示。他希望在花费不超过 N 元的前提下,使自己的满意度达到最大。满意度=购买的每件物品的价格与重要度的乘积的总和。

  • I:输入的第 1 行,为正整数N,m,用空格隔开, N<32000:预算, m<60:可购买的物品数(看后文感觉意思是主件数);
    从第 2 行到第 m+1 行,第 j 行将给出编号为 j-1 的物品的3个基本数据v p q, v<10000:价格, p:1~5重要度, q:q=0 主件,q>0 附件,且q为所属主件的编号

  • O:输出一个正整数,为张强可获得的最大满意度。

思路:动态规划,0-1背包的扩展,但自己写好像还是不行;参数好多啊
完了
有些解答写得也不对,这种叫多重背包(此外有0-1背包、完全背包),思路是待取物品拆成124...个,0-1背包,实现则有点难懂:

for (int i = 1; i <= n; i++) {
 int num = min(p[i], V / w[i]);
 for (int k = 1; num > 0; k <<= 1) {
  if (k > num) k = num;
  num -= k;
 for (int j = V; j >= w[i] * k; j--)
  f[j] = max(f[j], f[j - w[i] * k] + v[i] * k);
 }
}

感觉意思大概是,某物品限制x个,拆分到∑1248≥x种物品即可
拆解二进制物品并不是多重背包的最优解,但最优的单调队列思想写起来有些繁琐▲
牛客网题解好像都不是拆的,好像是max{ ... }

  1. 不考虑附件,其就是0-1背包,主件数量限制只是限制了dp[x][money]
  2. 考虑每个物品时要考虑每种可能出现的情况,1、主件,2、主件+附件1,3、主件+附件2,4、主件+附件1+附件2,不一定每种情况都出现,只有当存在附件时才会出现对应的情况。

w[i][k]表示第i个物品的第k种情况,k的取值范围0~3,分别对应以上4中情况,v[i][k]表示第i个物品对应第k种情况的价值,现在就把购物车问题转化为了0-1背包问题。


0312 看一下其它文章

动态规划的重点有共识,分为三点:状态定义,边界条件,转移方程(回溯三问有不同)
例如:leetcode 32.最长有效括号:在一个只含 '(' ')' 的字符串,找出最长有效(格式正确且连续)括号子串的长度
状态:在 <满足问题的条件> 下,<问题的最佳答案>
例如: 在<长度为s.length>条件下,《最长有效括号子串的数量》,记为状态数组dp[n]
边界:dp[1] = 0; answer = dp[s.length]
转移:由已知状态推导未知状态的过程
例如: 加入第二个字符时,通过已知状态dp[1]+已知第二个字符,推导未知状态dp[2],即dp[i] = ( dp[i-1], s[i] ),可能用到的有( dp[i-1], s[i-1], s[i] )等临近数据。

解题步骤:

  1. 根据状态的固定句式,假设一个合理的状态,并说明答案和状态的关系。
  2. 分类讨论每种状态对应的含义,写出状态转移方程。
  3. 若第2步无法写出方程,则检查状态是否完备,并继续步骤1。
    Q: 如何正确设置状态? i. 状态需要包含推导所需所有可能,不漏。 ii. 状态i 和 此前状态不能有交集,不重。

这时我们发现,在上文状态设置下,用dp[i-1]不大能够推算出dp[i],因为dp[i-1]和dp[i]都包含了可能 在[1, i-1]的某一段出现了《最长有效括号子串的数量》,违背状态的正确性

这时笔者将告知你一个提示,状态句式中常使用“以i为结尾”

故修改:
状态: dp[i]:在<长度为i>条件下,《以i为结尾的最长有效括号子串的数量》
边界:answer = max{ dp[1],...,dp[n] }
转移:已知dp[i-1]的值,计算未知状态dp[i],对dp[i-1]进行讨论:

  • 情况a. dp[i-1] 为0,则表示i-1位置的符号不能跟前面的任意长度括号匹配,又有两种情形:
    情形i. 前i-1个字符可能是: * ( ,若此时s[i] = ')',则 dp[i] = 2(自己漏了这种情况)
    情形ii. 也可能是: ( ) ),则 dp[i] = 0。
  • 情况b. dp[i-1] 为k, k>0,则表示从i-1往前k个是恰好匹配的连续括号子串,即前面k个已配对,那么dp[i] 只能尝试与 前面第k+1个字符 配对,即仅当 s[i-k]='(',且s[i]=')'时,dp[i] = dp[i-1] + 2。

可以写出代码(c++):

class Solution {
public:
    int dp[30000 + 10], pos;
    int longestValidParentheses(string s) {	// 最长合法括号.
        s = "*" + s;	// 填充一个字符,使下标从1开始.
        // 边界
        dp[0] = 0;
        dp[1] = 0;
        for (int i = 2; i <= s.length(); i++)
        {
            // 情形ii.
            dp[i] = 0;
            // 情况a:dp[i-1]为0.
            if (dp[i - 1] == 0)
            {
                // 情形i.
                if (s[i - 1] == '(' && s[i] == ')')
                    dp[i] = 2;
            }
            // 情况b.
            else
            {
                // 往前k+1个字符的位置
                // 需举例:i=10,dp[i-1]=4,表示[6,7,8,9] 是匹配的子串,则 pos=5
                pos = i - dp[i - 1] - 1;
                if (s[pos] == '(' && s[i] == ')')
                    dp[i] = dp[i - 1] + 2;
            }
        }

        int ans = 0;
        // answer = max{...}
        for (int i = 1; i <= s.length(); i++)
            ans = max(ans, dp[i]);
        return ans;
    }
};

但应该想到,有所遗漏。遗漏在情形i:dp一定=2吗?如"...()()(",此时以i为结尾的最长有效括号子串,即dp[i],应=6(≥6)。不仅遗漏了情形i,情况b时dp也一样,可能在基础上变动。
故在i位置发生匹配,即()或(*)时,就要注意前面是否还有相邻匹配,如 * * * √ √ √ √ ( * ) ,此时dp[i] = 本次匹配的结果 + 上一个紧挨着的连续合法括号串长度(位置是pos=i-dp[i])。

  • 情况c.第三条转移方程:当i位置匹配成功(即dp[i]>0)时,也要加上 上一个紧挨着的连续合法括号串长度(即dp[pos]);

注意到这个转移方程是触发式的,结构上和前面不同,故转移方程组们未必是树形罗列的,而是有向图状的。

pos = i - dp[i];
if (dp[i])
    dp[i] += dp[pos]

// 简写一下
if (dp[i])
    dp[i] += dp[i - dp[i]]

只需稍稍加入这段,即可形成可行代码:

最终代码
class Solution {
public:
    int dp[30000 + 10];
    int longestValidParentheses(string s) {
        s = "*" + s;
        dp[0] = 0;
        dp[1] = 0;
        for (int i = 2; i <= s.length(); i++)
        {
            dp[i] = 0;
            if (dp[i - 1] == 0)
            {
                if (s[i - 1] == '(' && s[i] == ')')
                    dp[i] = 2;
            }
            else
            {
                int pre_pos = i - dp[i - 1] - 1;
                if (s[pre_pos] == '(' && s[i] == ')')
                    dp[i] = dp[i - 1] + 2;
            }

            // c. 加这里 位置怎么这么合适
            if (dp[i])
            {
                dp[i] += dp[i - dp[i]];
            }
        }

        int ans = 0;
        for (int i = 1; i <= s.length(); i++)
            ans = max(ans, dp[i]);

        return ans;
    }
};

文章完了,看一下原题


0-1背包:
状态:前i个物品,在背包重量为j的情况下能装的最大价值。
边界
转移
多重背包+分组背包(主附件):
状态:前i个物品,在背包重量为j的情况下能装的最大价值。
边界
转移

dp = [[0]*(total+1) for _ in range(m+1)]	# +1是肯定的,因为要包含边界条件=0
for i in range(m+1):
  for j in range(total+1):
      dp[i][j] =dp[i-1][j]
      if j-w1>=0:	# 应对多重背包
          dp[i][j] = max(dp[i][j],dp[i-1][j-w1]+v1)
      if j-w1-w2>=0:
          dp[i][j] = max(dp[i][j], dp[i-1][j-w2-w1]+v1+v2)	# 应对分组背包
      if j-w1-w3>=0:
          dp[i][j] =  max(dp[i][j], dp[i-1][j-w3-w1]+v1+v3)
      if j-w1-w2-w3 >=0:
          dp[i][j] =  max(dp[i][j], dp[i-1][j-w3-w2-w1]+v1+v2+v3)

好像明白了,这个if j-w1>=0直接把限制拿s个物品的具体处理递归到dp[i-1][j-w1]里了,而它算过一次后就会储存到dp[][]里。好了,现在这部分还能单拎出来优化:
dp = [0 for _ in range(total+1)] _怎么查不到

dp = [0 for _ in range(total+1)]
for i in range(1, m+1):
  for j in range(total,-1,-1):
      dp[j] =dp[j]	# 可不写,为了对照前文
      if j-w1>=0:
          dp[j] = max(dp[j],dp[j-w1]+v1)
      if j-w1-w2>=0:
          dp[j] = max(dp[j], dp[j-w2-w1]+v1+v2)
      if j-w1-w3>=0:
          dp[j] =  max(dp[j], dp[j-w3-w1]+v1+v3)
      if j-w1-w2-w3 >=0:
          dp[j] =  max(dp[j], dp[j-w3-w2-w1]+v1+v2+v3)

重要不同:for j 从0-total 变成total-0。这次感觉懂了,大概能解释得比较清楚,因为根据代码,dp[j]依赖于其左侧元素的值,即dp([i-1])[j-w1]先决定了dp([i])[j],而我们为了节省空间只使用一维数组滚动更新,在边界初始条件dp([0])[0][total]已确定的情况下,dp([1])[0][total]任意一项都是可以计算的,但算完存到哪呢,还是存在原列,那么由于dp[i][j]是由其左上方的dp([i-1])[j-w1]算出,假设从左到右计算,现在算出dp[i][j]并存好,计算dp[i][j+w1]时得用到的dp[i-1][j]还没用就被覆盖了,就没法算对了。那从右到左计算可以解决这个问题吗?可以,此时,该列的原数据已被使用过,可以删除,即允许覆盖。
看一下其最终代码:
是没优化版本的好像
★input().split(" ")
★对字典W赋值为list的键值对;通过访问字典取出list[i]
for i in range(1,k+1): W[i]=[0,0,0] #给键i赋值

while True:
    try:
        # 第1行输入为total预算,k可购买的物品数(主附件总数应该是)
        total,k = list(map(int,input().split(" ")))
        ## 单价;这里创建dict存x行3列数据,而非矩阵
        W = {}
        ## 单价* 重要程度=价值
        V = {}
        # 因为价格是10的倍数,为方便运算,价格/10
        total = int(total/10)
        # 主件个数
        main_key = []
        # 构造字典
        for i in range(1,k+1):
            W[i]=[0,0,0]	# 注意这个存的,是值为list键为i的数据,物理意义是主件i及其附件的W值(题中提到最多2个附件),下面可以看到,分别是W[i+1][0],W[q][1],W[q][2]
            V[i]=[0,0,0]
        for i in range(k):
            # 第2~行输入为 单价|重要度|类别
            v,p,q = list(map(int,input().split(" ")))
            if q == 0:	# 通过q,先存主件的数据;i+1:1~x
                W[i+1][0] = int(v/10)	# 注意这个是i+1键对应的list里的第[0]项,即主件i
                V[i+1][0] = int(v*p/10)
                main_key.append(i+1)	# 储存主件位置
            else:
                if W[q][1]==0:	# 若是该主件的首个附件 存入对应位置
                    W[q][1] = int(v/10)
                    V[q][1] = int(v*p/10)
                else:
                    W[q][2] = int(v/10)
                    V[q][2] = int(v*p/10)
        W_lst = []	# 这次开一list另存主件数据,元素也为list
        V_lst = []
        for key in W.keys():
            if key in main_key:
                W_lst.append(W[key])	# 为什么存的一样呢▲
                V_lst.append(V[key])
        m = len(W_lst)
        dp = [[0]*(total+1) for _ in range(m+1)]
        for i in range(1,m+1):	# i:1~m,和前面是对应的
            w1 = W_lst[i-1][0]	# 后面用起来简洁一点;注意这里w,v具体含义随着第i个主物品变动而变动,每次只会用到这几个变量
            w2 = W_lst[i-1][1]
            w3 = W_lst[i-1][2]
            v1 = V_lst[i-1][0]
            v2 = V_lst[i-1][1]
            v3 = V_lst[i-1][2]
            for j in range(total+1):
                # 1. 不放入:
                dp[i][j] =dp[i-1][j]
                # 2. 放入一个主件
                if j-w1>=0:
                    dp[i][j] = max(dp[i][j],dp[i-1][j-w1]+v1)
                # 3. 1个主件+附件1
                if j-w1-w2>=0:
                    dp[i][j] = max(dp[i][j], dp[i-1][j-w2-w1]+v1+v2)
                # 4. 一个主件+附件2
                if j-w1-w3>=0:
                    dp[i][j] =  max(dp[i][j], dp[i-1][j-w3-w1]+v1+v3)
                # 5. 一个主见+附件1+附件2
                if j-w1-w2-w3 >=0:
                    dp[i][j] =  max(dp[i][j], dp[i-1][j-w3-w2-w1]+v1+v2+v3)
        print(int(dp[m][total]*10))		# answer = dp[][]
    except:
        break

评论区指出还有点问题:w1 = W_lst[i-1][0]等地方应该是w1 = W_lst[i][0],且感觉说得对,但按其说法修改后Error:没有输出;原因在于W_lst是从0开始的[],W是从1开始的{},故原代码没错.把优化部分改进去,

while True:
    try:
        # 第1行输入为total预算,k可购买的物品数(主附件总数应该是)
        total,k = list(map(int,input().split(" ")))
        ## 单价;这里创建dict存x行3列数据,而非矩阵
        W = {}
        ## 单价* 重要程度=价值
        V = {}
        # 因为价格是10的倍数,为方便运算,价格/10
        total = int(total/10)
        # 主件个数
        main_key = []
        # 构造字典
        for i in range(1,k+1):
            W[i]=[0,0,0]	# 注意这个存的,是值为list键为i的数据,物理意义是主件i及其附件的W值(题中提到最多2个附件),下面可以看到,分别是W[i+1][0],W[q][1],W[q][2]
            V[i]=[0,0,0]
        for i in range(k):
            # 第2~行输入为 单价|重要度|类别
            v,p,q = list(map(int,input().split(" ")))
            if q == 0:	# 通过q,先存主件的数据;i+1:1~x
                W[i+1][0] = int(v/10)	# 注意这个是i+1键对应的list里的第[0]项,即主件i
                V[i+1][0] = int(v*p/10)
                main_key.append(i+1)	# 储存主件位置
            else:
                if W[q][1]==0:	# 若是该主件的首个附件 存入对应位置
                    W[q][1] = int(v/10)
                    V[q][1] = int(v*p/10)
                else:
                    W[q][2] = int(v/10)
                    V[q][2] = int(v*p/10)
        W_lst = []	# 这次开一list另存主件数据,元素也为list
        V_lst = []
        for key in W.keys():
            if key in main_key:
                W_lst.append(W[key])	# 为什么存的一样呢▲
                V_lst.append(V[key])
        m = len(W_lst)
        dp = [0 for _ in range(total+1)]
        for i in range(1, m+1):	
            w1 = W_lst[i-1][0]	# 后面用起来简洁一点
            w2 = W_lst[i-1][1]
            w3 = W_lst[i-1][2]
            v1 = V_lst[i-1][0]
            v2 = V_lst[i-1][1]
            v3 = V_lst[i-1][2]
            for j in range(total,-1,-1):
                if j-w1>=0:
                    dp[j] = max(dp[j],dp[j-w1]+v1)
                if j-w1-w2>=0:
                    dp[j] = max(dp[j], dp[j-w2-w1]+v1+v2)
                if j-w1-w3>=0:
                    dp[j] =  max(dp[j], dp[j-w3-w1]+v1+v3)
                if j-w1-w2-w3 >=0:
                    dp[j] =  max(dp[j], dp[j-w3-w2-w1]+v1+v2+v3)

        print(int(dp[total]*10))
    except:
        break

Error:有输出但不对噢,forj忘记缩进了.本题,暂完.

分组背包

一个组有两种决策选或不选,每一组只能最多选择一个物品,
状态:dp[i][j]:前i组物品,在背包重量为j的情况下能装的最大价值。
转移:记第i组有s个物品,每个体积v价值w,dp[i][j]将由s次更新最终确定:

for(int i=1;i<=n;i++)
 for(int j=0;j<=m;j++)
  for(int k=1;k<=s[i];k++)
   if(j>=v[i][k])	// 剩余背包容量j>第i组第k个物品的体积
   {
    dp[i][j] = max(dp[i][j],dp[ i-1 ][ j-v[i][k] ]+w[i][k]);	// 体积+w利润 怎么感觉写错了×没错,dp[][]是二维,在前i-1组、背包重量为j-v(第i组k个)的情况下的最大价值
   }

好像没其它部分了

依赖背包

树形结构。略像本题
设dp[u][j]表示在以u为根节点,背包剩余容量为j的最大价值和,那么可想而知dp[u][j]这个值一定是由子节点更新来的

/*这里i最小为v[u]因为你要选子节点的话,u这个节点必选,给u留空间*/
for(int i=m;i>=v[u];i--) 
 for(int k=0;k<=i-v[u];i++)
  f[u][i] = max(f[u][i],f[u][i-k]+f[s][k]);//s是u的子节点

例题:
有线电视网转播一场足球比赛,有权决定给哪些用户提供信号,转播网和用户终端构成一棵树状结构,从转播站到转播站、转播站到所有用户终端的传输费用已知,请在不亏本的情况下,使观看转播的用户尽可能多。https://www.luogu.com.cn/problem/P1273
https://blog.csdn.net/TheWayForDream/article/details/116567088
https://blog.csdn.net/qq_46014180/article/details/131216586 这个有几道例题及题解,还没看▲

HJ24 合唱队

N 位同学站成一排,需要请尽量少的同学出列,而使剩下 K 位同学排成合唱队形。即存在某同学,两边同学的身高都严格递减。
I:两行数据,第一行是同学总数 N ,第二行是 N 位同学的身高,以空格隔开
O:最少需要几位同学出列
例:输入 8
186 186 150 200 160 130 197 200
输出 4,说明:最终剩下的队列为186 200 160 130或150 200 160 130

思路:显然是dp,状态是什么呢,前i个人里的剩余人数+第i、i+1的身高 能推出i+1个时吗
dp[l][r]这样可以吗;不对,是出列,类似编辑距离,用的是啥来着

法1:严格递增,就要想到此题是最长递增子序列的变体,基本思路是对原序列从左到右、从右到左分别求出到每个前缀/后缀子串的最长递增子序列的长度L、L'。如,原序列[8,20,12,15,10,9],每个前缀子串的最长递增子序列长度为l1=[1,2,2,3,2,2],从右至左为l2=[1,4,3,3,2,1],那么计算l1+l2=[2,6,5,6,4,3],最长合唱团 = max(l1+l2)-1,减1是减的重复计算的那个元素本身;answer = N-L。

最长递增子序列

有一个很简明(但数学)的计算方法,
这个方法创建并维护(增/改)一个数组arr[]存储目前前缀串里最优的递增子序列的值,遍历完成后arr[]的长度即为最长递增子序列的长度L。
arr[0]先设为element[0];从 i=1 开始,依次比较ele[i]与目前arr末尾即arr[-1]的大小,当其使arr[i]严格递增就加入arr,否则将arr中首个≥ele[i]的值换成ele[i],即此时会修改一下序列。遍历完就ok了。
这个替换操作,有没有可能使得ele里原本更长的递增子序列消失呢?不会的,因为这个从i=1起 就用小中杯替换中杯的操作,保持着任意可能的递增子序列的单调性,即原来哪几个位置能构成递增子序列,替换后还是递增子序列。故L值在操作后不会变,且由于这个替换操作每次都使递增子序列arr变低,我们总是会得到目前最优的递增子序列,那么自然是最长的。
如,arr=[10,11,12,1,2,3,4]。可以看到虽然 [10,11,12] 和 [1,2,3] 在前缀串遍历到3的位置时 都是最长递增子序列,但[1,2,3]比[10,11,12]低,也就是我们需要设法舍弃[10,11,12]。当我们遍历到1,用1替换10,arr变成了[1,11,12],再继续遍历,可以想象 到3的位置时,维护的递增子序列arr就变成了[1,2,3]。

实现:
找首个≥的数时,可用二分,这里使用了模块bisect实现,
★用模块bisect来查找≥ele[i]的位置
★f(s[::-1])[::-1]:逆转字串s后输入,再将返回值逆转的意思
本题代码:

import bisect
def inc_max(l):	# 输入list,返回list(各前缀串的最长递增子序列长度)
    dp = [1]*len(l) # 初始化dp,最小递增子序列长度至少为1
    arr = [l[0]] # 创建数组 这个数组存的是目前最优的递增序列
    for i in range(1,len(l)): # 从第二个元素开始用算法增/改
        if l[i] > arr[-1]:
            arr.append(l[i])
            dp[i] = len(arr)	# 这为啥还要用dp数组,噢必定用到的,只是可能可以优化成
        else:
            pos = bisect.bisect_left(arr, l[i]) # 用二分法找到arr中首个≥ele_i的位置
            arr[pos] = l[i]
            dp[i] = pos+1	# 奇怪 这是什么,不应该也=len(arr)吗;dp表长度,pos+1即arr前缀0到替换位置pos的长度;看来理解还有问题▲ dp[i] 这是最长子序列最少有多长的意思吗,应该不是啊 不是 为什么我换成len(arr),输出答案也是对的▲
    return dp	# 返回list

while True:
    try:
        N = int(input())
        s = list(map(int, input().split()))
        left_s = inc_max(s) # 从左至右
        right_s = inc_max(s[::-1])[::-1] # 从右至左
        sum_s = [left_s[i]+right_s[i]-1 for i in range(len(s))] # 相加并减去重复计算
        print(str(N-max(sum_s)))
    except:
        break

f(s[::-1])[::-1]
逆转字串s后输入,再将返回值逆转的意思
例如:如何在数字字符串右侧填充零到指定宽度?一种方法:
Python的字符串对象 提供了zfill()方法,该方法可在字符串的左侧填充零,使其达到指定的宽度。那么就先逆转再填零再逆转:

num = '123'
padded_num = num[::-1].zfill(5)[::-1]
print(padded_num)  # 输出:'00123'

评论区,没看懂:
我解释一下:arr的本质是一个缓冲器,arr的每一个元素arr_i代表着两件事: 1. 在之前的循环里我访问过这个元素。 2. 对arr_i之后的元素来说,arr_i的位置曾经/正在有一个属于LIS的元素。 也就是说缓冲两个信息:1. 该位置上一个元素;2.这个位置的有/无 举个例子,1 3 4 2 最后的arr = 1 2 4 这个arr其实等于每个位置的LIS的重叠:(位置1、2略) 1 3 4 (位置3的LIS) 1 2 (位置4的LIS) 比较容易的理解方式就是,将这些LIS从上到下重叠,遵守两个逻辑: 1. 下面若无,则保留 2. 下面若大,则替代 最后就能得到arr,可以看到arr的长度一定等于LIS的长度(若无则保留)。
疑问没搞懂,暂时先过了.

HJ20 密码合法性

密码要求:
1.长度超过8位
2.包括大小写字母.数字.其它符号,以上四种至少三种
3.不能有长度大于2的包含公共元素的子串重复 (注:其他符号不含空格或换行)
I:一组字符串,长度1≤n≤100
O:符合要求输出OK,否则输出NG

思路:前面都有办法,主要考重复子串,这是怎么弄的,是用递归吗
★输入输出回忆一下怎么写:

while True:
    try:
        pw = input()	# pw password
        if check(pw):
            print('OK')
        else:
            print('NG')
    except:
        break

先写前2个要求的判断:

def check(pw):
    if len(pw) <= 8:			# 判断长度
        return False
    
    checks = [0,0,0,0]			# 四种情况满足三种,辅助列表 也只能这么写好像
    for c in pw:
        if c.isupper():			# 大写字母
            checks[0] = 1
        elif c.islower():		# 小写字母
            checks[1] = 1
        elif c.isdigit():		# 数字
            checks[2] = 1
        else:					# 其他字符
            checks[3] = 1
    if sum(checks) < 3:
        return False
    return True

第3个要求 长度>2的相同子串 的判断:
法1:由数学,NG<=>存在至少一对长度3的相同子串,因此可取出所有的3字符子串,暴力比对,但不是乱比,也要有序:
★字符串的in用法 使用in判断子串

for i in range(len(pw) - 2):        # pw password
    if pw[i:i+3] in pw[i+3:]:       # 在剩下字串中顺序匹配当前字串
        return False

时间复杂度即O(n²)

法2:只遍历一遍,每次存新的(未出现过的)长度3的子串到字典里,而由于字典的组织形式是哈希表,搜索开销小.
★字典的in用法 用in判断键的存在性 前面见过

dc = {}
for i in range(len(pw) - 2):        # 注意pw:0~len,因此长度3的子串:0~len-3,即range(len-2)
    if pw[i:i+3] in dc:             # 在字典中搜索
        return False
    else:
        dc[pw[i:i+3]] = 1           # 未曾出现则加入键值对,值随便

法3:★还看到用字符串的.count()方法;其它方法也都是算长度3的子串.

for i in range(len(pw)-2):
    if pw.count(pw[i:i+3]) >=2:
        return False
HJ17 计算坐标

开发一个坐标计算工具,A表示向左移动,D向右,W向上,S向下,从(0,0)开始,读取一些操作,将最终结果输出到文件里.
I:一个字串,操作之间以;分隔.
合法操作:A/D/W/S + 0<x<100的数字
非法操作:丢弃。如AA10; A1A; $%$; YAD;
· 每组输入字串长度1<n<1w,数字部分仅含正数(它这个每组或指有多行输入,故有的答案用到stdin而非input().split(';'),前面见过)
O:坐标
示例:
A10;S20;W10;D30;X;A1A;B10A11;;A10;
10,-10

输入:
★字符串.strip()方法,去除两端空格(或指定字符),及换行符
★list的in用法

import sys
cmd_list = sys.stdin.readline().strip().split(';')

处理:

pos = [0,0]
for cmd in cmd_list:
    if not 2 <= len(cmd) <= 3:		# 这个操作必然是2~3位;大多解答都是类似实现
        continue
    try:
        direction = cmd[0]
        step = int(cmd[1:])
        if direction in ['A', 'D', 'W', 'S']:	# 又用到了in,这次是list的
            if 0 <= step <= 99:
            ......

怎么写更通用呢
★re模块
★多个数同时赋值
★字典可以储存lambda函数×✓ 是词法闭包;字典可以储存一般函数吗,f(x)肯定是不行的,这是闭包了已经,f(1)行不行呢
★lambda函数的调用

x,y=0,0

import sys
cmd_list = sys.stdin.readline().strip().split(';')

import re
fun={
    'A':lambda a,b,p:(a-p,b),
    'D':lambda a,b,p:(a+p,b),
    'W':lambda a,b,p:(a,b+p),
    'S':lambda a,b,p:(a,b-p)
}
for cmd in cmd_list:
    if re.search(r'^[A|S|W|D]\d\d?$', cmd) and len(cmd)<=3:
        x,y=fun[cmd[0]](x,y,int(cmd[1:]))
print(f'{x},{y}')
字符串.strip()方法

去除两端空格(或指定字符),及换行符
去除指定字符时,用的是正则匹配,如,去除字串两端的多个指定字符:

string = "?????Hello, World?????"
new_string = string.strip("?!")
print(new_string)  # 输出:Hello, World
re中.search()匹配时的语法

^
[||]
\d\d
?
$

lambda函数

匿名函数,方便灵活地定义函数
示例:

#!/usr/bin/python3
sqr_fun = lambda x: x * x
print(sqr_fun(4))

lambda+map()
回忆:将 参数1func 应用于 参数2iterable 的每一项,并返回一个迭代器对象。

# 对list每项作平方
nums = [1, 2, 3, 4, 5, 6]
nums_squared = map(lambda x: x*x, nums)
for num in nums_squared:
    print(num)

类似地有
lambda+filter()

# 过滤掉list中的偶数
nums = [1, 2, 3, 4, 5, 6,]
nums_filtered = list(filter(lambda x: x%2, nums))
print(nums_filtered)

其它示例:

# 依某项对元组作排序
tuples = [(1, 'd'), (2, 'b'), (4, 'a'), (3, 'c')]
sorted(tuples, key=lambda x: x[1])	# 依元组第二项
>[(4, 'a'), (2, 'b'), (3, 'c'), (1, 'd')]

lambda用于词法闭包

词法闭包

JavaScript的闭包
JavaScript闭包大概指有权访问另一函数作用域中变量的函数。在JavaScript里,所有JavaScript函数都是闭包。
具体来说,函数及其对其周围状态(词法环境)的引用,捆绑在一起构成闭包。

python的闭包
即词法闭包(Lexical Closure),是函数式编程的重要语法结构。定义是差不多的,一个函数里定义了一个内部函数,这个内部函数引用了外部函数的相关参数或变量,且返回的是这个内部函数,那么这个内部函数被称为闭包。示例:

def func(x):
    def inner(y):
        return x + y
    return inner
>>> print(func(2)(8))	# 使用起来 就只看到func函数名了,那能有多个闭包吗▲
>>> 10

特性:即使生成闭包的环境已经释放(程序流已经不在作用域中),闭包仍然存在,传给闭包的变量也不会被释放(能记住来自某个封闭词法作用域的值)
示例:有一组数据,既要计算每个数的二次方,又要计算三次方

from math import pow
data = [1,2,3,4,5,6]

def get_result(n):
    def do_pow(x):
        return pow(x,n)
    return do_pow

pow_2 = get_result(2)
pow_3 = get_result(3)

for i in data:
    pow_2(i)
    pow_3(i)

看到相比前一示例,有一个赋值操作pow_2 = get_result(2),这里的pow_2是func类型的,这就和本题有点像了.
本题这里的fun,确实是dict类型的,而fun['A']却是func类型的。这种用法就先记着吧.

fun={
    'A':lambda a,b,p:(a-p,b),
    'D':lambda a,b,p:(a+p,b),
    'W':lambda a,b,p:(a,b+p),
    'S':lambda a,b,p:(a,b-p)
}

应用:

装饰函数、装饰器

如,有需求:计算函数的运行时间。当有多个函数需要计算时,重复性工作量大,且耦合度高。可以使用装饰器解决:

import time
def run_time(func):
    def wrapper(*args,**kwargs):
        time_start = time.time()
        func(*args,**kwargs)
        time_end = time.time()
        print("运行时间为{}秒".format(int(time_end-time_start)))
    return wrapper

@run_time 
def func(x):
    time.sleep(x)

>>> func(2)
>>> 运行时间为2秒

可以看出装饰器实际就是将一个函数传入一个(内部)函数中,在其中增加一些功能再传出来,实际已经变为了另一个对象,但使用者觉察不出来。

HJ26 字符串

将输入字符串中的字符按如下规则排序:
1 :英文字母从 A 到 Z 排列,不区分大小写
2 :同一字母的大小写同时存在时,按照输入顺序排列
3 :其它字符保持原位
示例:

>>> A Famous Saying: Much Ado About Nothing (2012/8).
>>> A aaAAbc dFgghh: iimM nNn oooos Sttuuuy (2012/8).

思路:感觉是排序 实在不行可以用一个映射索引表表示 原第几位-待处理字符里第几位-排序后第几位
法1:将if s[i].isalpha()用两遍,第一遍是为取出排序,第二遍是为得知取出的位置,则非字母的位置累加即可。开一新字符串,逐字添加,这便能轻松地保持非字母的位置.
★sorted()里的key参数
★list的+=

while True:
    try:
        s = input()
        a = ''
        for i in s:
            if i.isalpha():
                a += i
        b = sorted(a, key=str.upper)	# 先取出所有字母,排序;a.upper可以吗
        
        index = 0
        d = ''
        for i in range(len(s)):	# 逐字添加
            if s[i].isalpha():
                d += b[index]	# 添加排好的字母
                index += 1
            else:
                d += s[i]
        print(d)
    except:
        break
HJ27 查找兄弟单词

定义一个单词的“兄弟单词”为:任意交换该单词字母顺序 而不增删改原字母就能生成的单词。
兄弟单词要求和原单词不同。
现在给定 n 个单词,再给一个单词 x ,让你寻找 x 的兄弟单词里,按字典序排列后的第 k 个单词是什么?
· 字典中可能有重复单词.
I:
输入只有一行,先输入字典中单词的个数n,再输n个单词作为字典单词。然后输入一个单词x,一个整数k
O:
第一行输出x的兄弟单词的个数m
第二行输出按字典序排序后的第k个兄弟单词,没有则不用输出。
示例

>>> 3 abc bca cab abc 1
>>> 2
>>> bca

思路:这个和递归有关吧,生成全排列的字典然后in;这种字典序的怎么处理,应该也是生成全排列后处理
法1:类似上一题,把输入单词归一化成字典排序后的字符串,然后比对.怎么全是这种做法,找不到全排列怎么写.

while True:
    try:
        data1 = input().split()
        n1 = data1[0]			# 单词个数
        n2 = data1[-1]			# 要找第几个兄弟词
        data2 = data1[1:-2]		# n1个单词
        data3 = data1[-2]		# 待查词

        n3 = 0					# 字典里兄弟词的数量
        data4 = []				# tmp,存字典里的兄弟词

        for word in data2:
            if word == data3:
                continue
            elif sorted(word) == sorted(data3):
                n3 = n3 + 1
                data4.append(word)	# 查到就增入tmp
        print(n3)
        data5 = sorted(data4)	# 将tmp按字典排序
        print(data5[int(n2)-1])
    except:
        break

优化:
★HJ17的同时赋值
★sys.stdin作为可迭代对象,其每项为字符串
★可以赋不同类型的值变成不同类型的对象,但相关名词是什么不知道▲ s = s.strip().split()
★字符串.pop()方法,及其会修改本体
[w for w in ws if ...]
这代码也写得太好了

import sys
for s in sys.stdin:	# 可不用这么写
    s = s.strip().split()	# 哇 从str类型变成list类型
    k, x, ws = int(s.pop()), s.pop(), s[1:]
    ws = [w for w in ws if w!=x and sorted(w)==sorted(x)]
    print(len(ws))
    if k < len(ws):
        ws.sort()
        print(ws[k-1])

自己再改一下输入

import sys
s = sys.stdin.readline().strip().split()
k, x, ws = int(s.pop()), s.pop(), s[1:]
ws = [w for w in ws if w!=x and sorted(w)==sorted(x)]
print(len(ws))
if k < len(ws):
    ws.sort()
    print(ws[k-1])
HJ29

对输入的字符串进行加解密,并输出。
加密方法:
当内容是英文字母时,用其后一个字母替换,同时变换大小写
当内容是数字时,数字加1,如9替换0
其他字符不做变化。
解密方法:逆过程。
I:
第一行输入明文
第二行输入密文
O:
第一行输出对应密文
第二行输出对应明文
思路:见过类似题,用字符串映射简便
★list有类似字典的 倒过来的以值找键的.index()方法

def check(a,b):
    L1 = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
    L2 = "BCDEFGHIJKLMNOPQRSTUVWXYZAbcdefghijklmnopqrstuvwxyza1234567890"
    result = ""
    if b == 1:
        for i in a:		# 还是用in
            result += L2[L1.index(i)]	# 还是+=
    elif b == -1:
        for i in a:
            result += L1[L2.index(i)]
    return result
while True:
    try:
        print(check(input(),1))
        print(check(input(), -1))
    except:
        break
HJ32

Catcher是情报员,他发现敌国会用一些对称的密码进行通信,比如ABBA,A,123321,但他们有时会在开始或结束时加入一些无关字符,如,ABBA->12ABBA,123->51234。因为截获的串太长,且存在多种可能的情况(abaaab可看作是aba,或baaab的加密形式),你能帮Catcher找出最长的有效密码串吗?
思路:这肯定是dp了,用什么状态呢
并没有

法1:干脆不找,枚举所有回文串× 用两个参数ij遍历,每次取str[i:j]和str[j:i:-1]比对,好像时间复杂度也不高▲
★list有max()函数
★切片参数±都行吗

str = input()
n = len(str)
list = []
for i in range(0,n-1):			# 先算清是哪到哪:j-1~i i+1~j 这是对称的吗 是 一个是从i增到j-1 一个是从j减到i+1,都变动了j-i-1;× 不对 好像是从两端往中间比 j,j-1,...;i,i+1,...
    for j in range(1,n):		# 每次都从1开始
        if str[j] == str[i] and str[i+1:j] == str[j-1:i:-1]:	# 用切片来判断回文
            list.append(len(str[i:j+1]))
print(max(list))

看起来大概可以保持不漏,重肯定是有重的,但这方法技巧性感觉有点强,不容易写对× 好写对,前面理解错了.

法2:法1是两端往中间比,这个是从xx或xyx往两边比
回文有两种形式:aa类和aca类,那么先查找符合长度为2的xx和长度为3的xyx,再向两边延申比较.怎么查找呢,这里for len in (2,3),然后也是取切片s[i:i+len]和s[i:i+len][::-1]比对.如果需要返回最长回文串,法2也可以改成像法1一样,开一list,append每一个由i和x确定的回文串.

while True:
    try:
        s = input()
        res = 1#初始为1,即使单个字符,对称字符的长度就是一
        for lenth in range(2,4):#窗口为2或者窗口为3
            for i in range(len(s)-lenth+1): #从0到lenS-lenth取长度为lenth的字符串片段
                if s[i:i+lenth] == s[i:i+lenth][::-1]: #判断是否回文
                    for x in range(min(i,len(s)-i-lenth)+1): #向两边拓展,要注意两边是否还有拓展的余地,即两边还能各加多少(取最小值)
                        if s[i-x:i+lenth+x] == s[i-x:i+lenth+x][::-1]: #若是相同
                            res = max(res,lenth+2*x) #取现有的res和新得到的回文大小的最大值
                        else:
                            break
        print(res)
    except:
        break
HJ33 整数与IP地址间的转换

4段0-255 如10.0.3.193 -> 167773121
I:
1 输入IP地址
2 输入10进制型的IP地址
O:
1 输出转换成10进制的IP地址
2 输出转换后的IP地址
思路:预先列一映射表吗

是这样的,十进制<->二进制 的函数都是内置的,且十->二或二->十后返回的都是字符串;只需要实现 二进制字符串<->ip就行了
★内置函数int()、bin()
★字符串的rjust()填充对齐方法

def transfer_ip2num(ip):
    ip_list = ip.split('.')
    bin_str = ''
    for x in ip_list:
        bin_str += bin(int(x))[2:].rjust(8,'0')
    return int('0b'+ bin_str, 2)

def transfer_num2ip(num):
    result = ''
    bin_str = bin(int(num))[2:].rjust(32,'0')	# [2:]是因为开头是0b,不需要;先统一成32位 
    for idx in range(4):
        result = result + str(int('0b' + bin_str[idx*8:idx*8+8], 2)) + '.'	# idx命名得不太好;这里是根据ip的结构,每4位一切片,切成四组字串,然后添上0b,用内置函数int()转成十进制,再转成字串添加到answer里.
    return result[:-1]

if __name__ == '__main__':
    while True:
        try:
            print(transfer_ip2num(input()))
            print(transfer_num2ip(input()))
        except EOFError: break
内置函数bin()

示例:

num = 10
print(bin(num))
>>> 0b1010

另一种转二进制的 字符串的格式化方法

num = 10
binary = '0b{:b}'.format(num)
print(binary)
字符串的rjust()方法

返回指定宽度的字符串,右对齐
语法:str.rjust(width[, fillchar])
参数:fillchar − 指定填充字符,默认空格

这两个知识点再遇到时再查吧

HJ36 字符串加密

有一种加密方法,它使用一个单词作为密匙:
如,TRAILBLAZERS,先删去重复字母而只保留首个,然后将结果作为新字母表开头,并将未出现的字母按正常顺序接上:
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
T R A I L B Z E S|C D F G H J K M N O P Q U V W X Y
I:输入key和要加密的字串
· 保证输入字串中仅含小写字母,1≤n≤100
O:加密后的字串
思路:属于复习题,看能不能想起来怎么写;去重也可以通过开一新str逐字添加来实现;密码本也可以通过逐字添加吧,只是看怎么写最简洁
★chr(ord('a'))
★zip(,)
可以 自己记得[... for x in range]

l = [] 
#for i in range(26):	# letter 有什么用呢 一会就看到
#  l.append(chr(ord('a')+i))
l = [chr(ord('a')+i) for i in range(26)]

while True:
  try:
    key, s = input(), input()
    new = []
    for i in key:
        if i not in new:
            new.append(i)
    for i in l:				# for if in 一个变动的list
        if i not in new:	# 哇 优雅
            new.append(i)

    m = dict(zip(l,new))

    res = []
    for i in s:
        res.append(m[i])
    print(''.join(res))
  except:
    break
zip(,)

输入两个可迭代的对象(有的说可以多个,感觉是翻译问题,主要是搜不到多个示例),返回的是zip object,是一个生成器函数,而不会实例化,即里面不含任何数据项;但迭代时,其每项是一个n元(2元吧)组
大体上就是两个list合一个二元组list
不对 好像是可以合多个,见后
常见用法:

list(zip(xi, yi))
dict(zip(,))

示例 手动将list合并成字典(等价于dict(zip(,))):

keys = ['a', 'b', 'c']
values = [1, 2, 3]
dictionary = {keys[i]: values[i] for i in range(len(keys))}
print(dictionary) # {'a': 1, 'b': 2, 'c': 3}

zip的逆操作

>>> a = [1,2,3] b = [4,5,6]
zipped = zip(a,b)
>>> [(1, 4), (2, 5), (3, 6)]
zip(*zipped)				# 不对 这里好像是合了三个,成一个三元组list
>>> [(1, 2, 3), (4, 5, 6)]
a1, a2 = zip(*zip(a,b))
>>> a1 = [1, 2, 3]
print(*zip(a,b))
>>> (1, 4) (2, 5) (3, 6)	#
rzip = *zip(a,b)
>>> SyntaxError: can't use starred expression here

这里看到输出还是是zip后的,不知道为什么×;
print(*zip())这样写没语法错误,但下面这样就有了
噢知道了,zip后的还没实例化,*后就会将其解包再传入print()函数,但是为什么print的不是元组 而是像序列一样的 没有分隔符的▲✓ 可能就是转成可迭代对象 而不是元组 噢 转成元组是用法1,这里是用法2了,就是转成可迭代对象,本题暂完.

星号表达式

原来前面见过的那个叫星号表达式
· 星号表达式(*expressoin)不可单独使用
两种作用:

  1. *args会将传入的可迭代参数解析出来,并且
    *将传入的参数放入名为args的元组中
    ** 将传入的参数放入名为args的字典中
    示例:
    函数输入参数不够的话,是默认为啥啊,为什么不是语法错误▲
def f(*args, **kwargs):
    print(args)
    print(kwargs)
f(*['a', 'b', 'c'])
>>> ('a', 'b', 'c')
>>> {}
  1. 用于unpacking可迭代的变量,将序列中的部分内容解包至一个列表中
    示例 感觉功能好厉害:
record = ('alice', 50, 1322,45, (12, 18, 2018))
name, *_1, (*_2, year) = record
>>> name = 'alice'
>>> _1 = [50, 1322, 45]
>>> _2 = [12, 18]
>>> year = 2018

再试一下看zip可以合几个:可以合多个

a,b,c = [1,2,3],[4,5,6],[7,8,9]
print(*zip(a,b,c))
>>> (1, 4, 7) (2, 5, 8) (3, 6, 9)
HJ38 小球轨迹

假设一个球从任意高度自由落下,每次落地后跳回原高度的一半,再落下;求第5次落地时,共经历多少米,这次反弹多高
I:起始高度,int,1<n<1000
O:共经过多少米、第5次反弹多高
· 保留六位或以上小数的输出结果可以通过此题
思路:数学题
法1:最高赞,评论区搞笑
★input()无换行符 line in sys.stdin有

import sys
for line in sys.stdin:
    num=int(line[:-1])	#这个-1是剔去的换行符还是啥
    print(num*2.875)
    print(num*0.03125)

法2:朴素地模拟

while True:
    try:
        H1 = float(input())
        H2 = H1/2
        H3 = H2/2
        H4 = H3/2
        H5 = H4/2
        H6 = H5/2
        SUM_H=H1+2*(H2+H3+H4+H5)
        print(SUM_H)
        print(H6)
    except:
        break

法3:应该这样模拟

while True:
    try:
        H = int(input())
        S = 0 - H	# 这个-H就很强 +H也可以的 看出了一般模式 和特例/边界情况
        for i in range(5):
            S = S + H * 2
            H = H/2
        print(float(S))
        print(float(H))
    except:
        break
HJ41 称砝码

现有n种重量互不相等的砝码m1...n,每种数量x1...n,
现在要用这些砝码去称物体的重量(放在同一侧),问能称出多少种不同的重量。
· 称重重量包括 0
· 1≤n≤10, 1≤m≤1000, 1≤x≤10
I:
n
m1...n
x1...n
思路:来了道看着复杂的,感觉也不是dp,无从下手;可以求出最少和最多先划一个范围;搞一个字典,像x进制一样直接枚举的话,复杂度是O(∏x),但这个枚举的过程,好像要用到递归×不是递归,没想懂写法而已

法1:
★[1,]{1,}是合法的,会自动无视掉逗号

while True:
    try:
        n = int(input())
        m = list(map(int,input().split()))
        x = list(map(int,input().split()))
    except:
        break
    else:
        amount = []
        weights = {0,}		# 噢 可以这样写
        for i in range(n):
            for j in range(x[i]):	# 不同数量 好像m反复加到list里
                amount.append(m[i])
        for i in amount:
            for j in list(weights):	# 
                weights.add(i+j)
        print(len(weights))

这怎么看不懂啊
评论区:简简单单两三行,就实现了递归,这就是道法自然吗 估计是类似dp数组,先不看这种方法▲
评论区:矩阵乘法

法1的类似代码:
★list的.extend()方法
★[]*num

while True:
    try:
        n = int(input())
        m = input().split(" ")
        x = input().split(" ")
        mx, l = [], {0}		# mx一会=所有单个砝码(含重复)的集合
        for i in range(n):
            mx.extend([int(m[i])] * int(x[i]))	# 这是[]*num,结果是num个[]而非一个[]

        for i in mx:
            # 每次加一块砝码,使用union(并集)得到新去重的组合,如果不使用union则稍微麻烦一点,需要考虑循环中改变set
            l = l.union({i+j for j in l})		# 也是两重for,i in mx,j in l本身
        print(len(l))
    except:
        break

这回好像懂了,关键知道了mx的物理含义,这两个for其实也是x进制的一种实现,第二个for的长度一开始短,后面将会很长吧,这个复杂度感觉不低;内部的小循环,就是依次固定一个砝码,看能与目前能得出的称重数组合出什么新的数目,不断扩充;
有没有可能由于固定的顺序不同,漏掉一些可能的数目呢,感觉不会,因为l里初始有0,那么mi肯定都是在里面的,如果视作一个n维空间,也就是单位向量都在集合里,现在虽然每种数量限制xi个,但不同可能向量叠加形成的线性组合这一集合都是一样的.

HJ43 迷宫

定义一个二维数组 N*M,编程序找出从左上角到右下角的路线。
· 1墙壁,0通路,只能横或竖着走。
· 入口点为[0,0],即第一格是通路。
· 保证有唯一解,不考虑多解。
I:输入两个整数,表行列,再输入地图数组
O:左上角到右下角的最短路径,格式如样例所示。
示例:
5 5
0 1 0 0 0
0 1 1 1 0
0 0 0 0 0
0 1 1 1 0
0 0 0 1 0
(0,0)
(1,0)
(2,0)
(2,1)
(2,2)
(2,3)
(2,4)
(3,4)
(4,4)
思路:深度优先遍历还是啥;不知道具体怎么写

法1 文章:
看了下其他题解,有很多写法没有运用到dfs的核心思想,好多还要判断上下左右有没有墙,然后再决定往哪个方向走(这一步应该交给代码自己遍历。本题可以参考leetcode 200 岛屿数量https://leetcode-cn.com/problems/number-of-islands/.

而用深度优先,代码自己可以跑完所有含‘0’的路径,重点是如何返回到达goal的路径,并记录每次坐标。我们可以标记走过的map[x][y]=1,表示这个点已经访问了(建个墙,类似于leetcode200中“冲岛”处理)。这样操作后,当代码探索其中某一条路径时候不会走“回头路”(否则,会围着点不断打转)。同时将该点添加到路径route中。

然后应用回溯递归的思想,如果我们无法到达指定终点,则需要沿原路返回,再把标记过的路去掉 map[x][y]=0,route.pop(), 直到到达当初的分岔路口,进入下一次选择(这个过程有点像stack里的pop())。

如何知道该点是否到达goal呢?如果某个点,四周都是1(无法访问)说明“这条路”到头了,如果这个点坐标并不等于goal,那么返回 return,递归开始回退,沿“原路”返回,直到最近的一个分岔口,进入到另个方向的探索。DFS搜索的本质就是递归回溯!
★ 两个一维数组去表方向向量

def dfs(i,j):
    dx = [0,0,-1,1]		# 四个方向向量
    dy = [-1,1,0,0]
    if i == m-1 and j == n-1:	# 若为终点 输出route
        for pos in route:
            print('('+str(pos[0])+','+str(pos[1])+')')
        return
    
    for k in range(4):
        x = i+dx[k]
        y = j+dy[k]
        if x>=0 and x<m and y>=0 and y<n and map1[x][y]==0:
            map1[x][y]=1		# 
            route.append((x,y))
            dfs(x,y)
            map1[x][y]=0		# 搜索树恢复现场;关键代码就几行,只需写出判定可走与否,不能走就会返回上一层dfs继续for判断
            route.pop()
    else:
        return

while True:
    try:
        m,n = list(map(int,input().split()))	# 同时赋值
        map1=[]
        for i in range(m):
            s=list(map(int,input().split()))
            map1.append(s)
        route = [(0,0)]		# 将入口标为已访问
        map1[0][0]=1
        dfs(0, 0)			# 
    except:
        break

优化 不相上下,但这个感觉便于修改:

def findway(x,y,path):
    if x == m-1 and y == n-1:
        [print(f'({l[0]},{l[1]})') for l in path]
    if x+1 <= m-1 and (x+1,y) not in path and maze[x+1][y] == '0':
        findway(x+1, y, path + [(x+1, y)])
    if y+1 <= n-1 and (x,y+1) not in path and maze[x][y+1] == '0':
        findway(x, y+1, path + [(x, y+1)])
    if x-1 >= 0 and (x-1,y) not in path and maze[x-1][y] == '0':
        findway(x-1, y, path + [(x-1, y)])
    if y-1 >= 0 and (x,y-1) not in path and maze[x][y-1] == '0':
        findway(x, y-1, path + [(x, y-1)])
while 1:
    try:
        m, n = map(int, input().split())
        maze = [input().split() for _ in range(m)]
        findway(0,0,[(0,0)])
    except:
        break
HJ45

给出一个仅由小写字母组成的字符串,定义“漂亮度”是其所有字母“漂亮度”的总和。
每个字母都有一个“漂亮度”,范围在1到26之间。没有任何两个不同字母拥有相同的“漂亮度”。字母忽略大小写。
· 本题含有多组数据,计算每个字符串最大可能的“漂亮度”。
示例:

2
zhangsan
lisi
>>> 192
>>> 101
说明:如lisi,使i为26,l为25,s为24,则漂亮度为25+26+24+26=101.

思路:这很简单嘛,26项频率p[],但怎么写得简洁呢,写不太出来,怎么依据频率高低赋值呢

while True:
  try:
  N = int(input())
  for j in range(N):
    letter = input()
    p=[0 for i in range(26)]
    for i in letter:
      p[ord(i)-ord('a')]+=1
	  ...
  except:
    break

法1:字典,存键为word、值为p,既映射了又能排序
★复习sorted(),可以用来排序字典×;注意是字典.values()方法,没有什么.values
排序后是list,妙的在于现在不需知道是什么字母了,只需知道p大小即可求出"漂亮度"

d = {}
for word in data:
  if word not in d:
    d[word] = 1
  else:
    d[word] = d[word] + 1
    d1 = sorted(d.values() ,reverse=True)

求和容易实现:

m = 0
for word in d1:
  ans = ans + (26-m)*word
  m += 1
print(ans)
HJ48 单向链表增删

按如下规则对一个空链表进行操作:
· 链表的值不重复
如:
6 2|1 2|3 2|5 1|4 5|7 2|2
6表示共6节点
2表示头结点值为2
剩下2个一组,表示值为参数2的节点后面插入值为参数1的节点
最后一个数为2,表示要删掉值为2的节点
O:输出操作后的序列

思路:看起来比较简单,有几个答案很长
★步长在周期性提取字串的应用
★list的.insert(位置)方法、list的.remove(值)方法
★字符.join(list)的使用
★print()里可带+

while True:
    try:
        l = input().split()
        head = l[1]
        rm = l[-1]
        l = l[2:-1]
        res = [head]
        for i in range(0, len(l), 2):	# 设步长即可解决
            a = l[i]
            b = l[i+1]
            res.insert(res.index(b)+1, a)

        res.remove(rm)
        print(' '.join(res)+" ")		# print里带个+即可补字符串
    except:
        break
list.insert(位置)、list.remove(值)
HJ50 四则运算 表达式求值

· 保证字符串中的有效字符为 [‘0’-‘9’],‘+’,‘-’, ‘*’,‘/’ ,‘(’, ‘)’,‘[’, ‘]’,‘{’ ,‘}’。且表达式一定合法。
思路:学校必学,可延伸的难题,栈吧,但怎么写不记得了

其它解法:eval()
字符串.replace()里面有没有模式匹配的▲

s = input()
s = s.replace("{", "(")
s = s.replace("}", ")")
s = s.replace("[", "(")
s = s.replace("]", ")")
print(int(eval(s)))

C解法:
i.用两个栈,分别压数字和运算符;
ii.如果当前运算符优先级('*/')高于栈顶运算符('+-')优先级,则将运算符入栈;反之,从数字栈中弹出两个数、并从运算符栈中弹出栈顶运算符进行运算、再将运算结果压入数字栈、并将当前运算符压入栈。重复操作直到不满足条件。
iii.出现(,则直接压入;出现),则从数字栈中弹出两个数,从运算符栈中弹出栈顶运算符,进行运算,数字栈压入运算结果,重复该操作直到栈顶弹出)位置。

有人参考C写了python的递归解法:
★一行里replace完

st = input().replace('[','(').replace(']',')').replace('{','(').replace('}',')')
def func(i):
    nums = []				# nums栈和str栈
    flag = None
    while i < len(st):
        num = 0
        if st[i] == '(':
            i, num = func(i+1)		# 入栈了好像
        if flag == ')':
            return i, sum(nums)

        while i < len(st) and st[i].isdigit():
            num = num*10 + int(st[i])		# num(tmp)每次置为0,就能恢复成十进制数
            i += 1
        if not nums:
            nums.append(num)		# 此时才入栈
        if flag == '+':
            nums.append(num)
        elif flag == '-':
            nums.append(-num)
        elif flag == '*':			# pop后 该数就没了哈
            nums.append(nums.pop()*num)
        elif flag == '/':
            nums.append(nums.pop()//num)
        if i < len(st): flag = st[i]					# 哇 这么处理括号的 感觉也是个栈啊
        i += 1
    return i, sum(nums)	# 返回两个参数 i表已处理字串数
print(func(0)[1])

感觉看了也记不住啊;后面再看c的实现吧▲

HJ52 编辑距离(dp,须会)

许可的编辑操作包括将一个字符替换成另一个字符,插入一个字符,删除一个字符。其算法由俄国科学家 Levenshtein 提出,故又叫 Levenshtein Distance 。
先分析,真忘了,
状态:?
边界:dp数组第0行、0列为0,1,2...

状态:dp[i][j]表示子串A[:i]到B[:j]的编辑距离
转移:
判断A[i]是否等于B[j]
· 如果相等,则dp[i][j]=dp[i-1][j-1],因为最后一位相同,不需要参与编辑
· 如果不等,就看看走三条路哪条路近

dp[i][j]=min(
dp[i-1][j]+1, 把A[i]删掉需要1次操作,剩下就是dp[i-1][j]
dp[i][j-1]+1, 把B[j]删掉需要1次操作,剩下就是dp[i][j-1]
dp[i-1][j-1]+1, 把A[i]替换为B[j]需要1次操作,剩下就是dp[i-1][j-1]
)

因此初始算法如下:

def dp(i,j):
    if i==0 or j==0:
        return max(i,j)
    else:
        if A[i]==B[j]:
            return dp(i-1,j-1)
        else:
            return min(
                dp(i-1,j)+1,
                dp(i,j-1)+1,
                dp(i-1,j-1)+1
            )
A=input()
B=input()
print(dp(len(A)-1,len(B)-1))		# 没懂-1是为什么▲

下面改成dp数组:

A=input()
B=input()
dp=[[0 for j in range(len(B)+1)] for i in range(len(A)+1)]	# 建表 0
for i in range(len(B)+1):
    dp[0][i]=i				# 初始化第一行边界
for i in range(len(A)+1):
    dp[i][0]=i				# 初始化第一列边界
for i in range(1,len(A)+1):
    for j in range(1,len(B)+1):
        if A[i-1]==B[j-1]:	# 一层一层地遍历A[i-1]B[j-1]
            dp[i][j]=dp[i-1][j-1]
        else:
            dp[i][j]=min(
                dp[i-1][j]+1,
                dp[i][j-1]+1,
                dp[i-1][j-1]+1
            )
print(dp[len(A)][len(B)])

记得这个也能dp一维化;怎么感觉这个得从左往右刷新;[i-1][j-1]怎么和[i-1][j]同时使用呢,用tmp吗 后面顺便的时候再看一下▲
HJ65做完,看到这里优化问题尚在;这和HJ65不一样,由上一行dp还不能推出本单元dp,但若推出了本单元dp,本列的上一行dp在计算下一列dp时还要用到,怎么解决;不过用两行一维数组应该怎么都能解决吧hh,用两行的话,也得从右往左,然后第二行第一行交替刷新吧▲
另外很多答案是如下建表的,怀疑只是为了少些几行初始化边界的代码,弄得易读性有点差▲

bp=[[x for x in range(len(str2)+1)] for y in range(len(str1)+1)]
挑7

输出 1到n之间 与7有关数字的个数
★字符串.count() 需熟悉增删查改 查字符

while True:
    try:
        n = int(input())
        c = 0
        for i in range(1,n+1):
            if i % 7 == 0:
                c += 1
            elif str(i).count('7') > 0 :
                c += 1
        print(c)
    except:
        break
HJ57 高精度整数加法(须会)

输入两个用字符串 str 表示的整数,求和。
· 1≤len(str)≤10000

法2:python语言支持大整数、不受位数限制,因此转成整型相加即可

while True:
    try:
        n1 = int(input())
        n2 = int(input())
        print(n1+n2)		# 直接输出整型数字相加之和的结果
    except:
        break

法1:
★转成list(map(int,...)),数组方便处理
看代码可以看到,每次输入只涉及a b和进上来的位,输出本位的计算结果 和进位

while True:
    try:
        s1 = list(map(int, input()))[::-1]
        s2 = list(map(int, input()))[::-1]		# 低位变高位,先处理 这样不用对齐了
        res = ""
        i = 0                                    # 位置
        addd = 0                                 # 进位
        summ = 0                                 # 和
        while i < max(len(s1), len(s2)):
            a = 0 if i >= len(s1) else s1[i]     #
            b = 0 if i >= len(s2) else s2[i]     #
            summ = (addd + a + b) % 10           # 和
            addd = (addd + a + b) // 10          # 新的进位
            res = str(summ) + res                # 字符串的+
            i += 1
        if addd > 0:                             # 处理边界
            res = "1" + res
        print(res)                               # 输出
    except:
        break

有的答案则是先对齐:

if len(b) <= len(a):
    b = b.rjust(len(a),"0")
else:
    a = a.rjust(len(b),"0")
HJ67 24点游戏算法

I:读入4个[1,10]的整数,数字允许重复,保证无异常数字
O:输出能否得到24点

思路:看了一眼解答,用递归,确实,每次的操作就+-*/,可能的结果有限,但具体怎么做,看来递归的结构并不是很熟;而且有括号即先后处理顺序的存在;好像可以解决,每次选两个数处理完返回一个,下次再从几个里面挑,那用什么数据结构呢,有没有比字典更好的,循环链表感觉可以
list的删除函数是什么
要处理重复的数,这个好像还有点难搞
如果要输出这个表达式怎么写呢,用括号,怎么记录中途的符号呢,用一个字符串感觉可以诶▲

if list只剩一个数 and == 24: return True

for a in list
  tmp1 = a
  删除a
  for b in list
    tmp2 = b
    删除b

    m=tmp1*tmp2
    cal()
    删除m
    ...
return False

感觉写出来了
★list的.len()方法
★用切片构造新list,来代替删除
其它部分大差不差

def helper(arr,item):#先写一个利用递归+枚举解决算24的程序
    if item<1:
        return False
    if  len(arr)==1:#递归终点,当数组arr只剩一个数的时候,判断是否等于item
        return arr[0]==item
    else:#如果arr不是只剩一个数,就调用函数本身(直到只剩一个为止返回真假) 
        for i in range(len(arr)):
            m=arr[0:i]+arr[i+1:]
            n=arr[i]
            if helper(m,item+n) or helper(m, item-n) or helper(m, item*n) or helper(m, item/n):
                return True
        return False
while True:
    try:
        if helper(list(map(int,input().split())), 24):
            print('true')
        else:
            print('false')
    except:
        break
HJ69 矩阵乘法(须会)

Cij = ∑AikBkj
I:
第一行 正整数x,代表第一个矩阵的行数
第二行 正整数y,代表第一个矩阵的列数和第二个矩阵的行数
第三行 正整数z,代表第二个矩阵的列数
之后x行 每行y个整数,代表第一个矩阵的值
之后y行 每行z个整数,代表第二个矩阵的值
O: 对于每组输入数据,输出x行,每行z个整数,代表两个矩阵相乘的结果
需要反复熟悉输入输出的处理
★一次一行字符串转成数字list:.append(list(map(int, input().split()))) 下题又用到了一次

while True:
    try:
        n=int(input())
        m=int(input())
        s=int(input())
        firstArray=[]
        secondArray=[]
        for i in range(n):
            firstArray.append(list(map(int, input().split())))
        for j in range(m):
            secondArray.append(list(map(int, input().split())))
        res=[[0 for i in range(s)] for j in range(n)] # 答案数组 n*s
        for i in range(n):
            for j in range(s):
                for k in range(m):
                    res[i][j]+=firstArray[i][k]*secondArray[k][j]
        for line in res:
            # print(' '.join(line)) × int需要转换成str
            print(' '.join(list(map(str, line))))
    except:
        break
HJ70 矩阵乘法计算量估算

矩阵乘法的运算量 与乘法顺序强相关,例如:A 50×10,B 10×20 ,C 20×5,
((AB)C)或(A(BC)),前者需要计算15000次乘法,后者只需要3500次。
计算指定计算顺序需要进行的乘法次数
I:先输入矩阵个数n,然后输每个矩阵的行,列数,最后输入指定的计算法则,
计算的法则为一个字符串,仅由左右括号和大写字母('A'~'Z')组成
· 保证括号匹配、输入合法
O:乘法次数
示例:

3
50 10
10 20
20 5
(A(BC))
>>> 3500

思路:考的是栈吗
这题有栈和递归两个相似方法,栈的好像短一点
和表达式一样,也需要一个数栈一个符号栈;不用了,此题不用符号栈了(而非数栈)
另外需要先分析Aik·Bkj的计算量:ikj,所以(Aij (Bjk Ckl) )=ijk + jkl.而从栈顶取出时,Bkj对应b[0],b[1],Aik对应a[0],a[1]

while True:
    try:
        n = int(input())
        arr = []
        order = []
        res = 0
        for i in range(n):
            arr.append(list(map(int, input().split())))    # 行数据处理,每行为一矩阵行列数
        f = input()
        for i in f:
            if i.isalpha():
                order.append(arr[ord(i)-65])               # 转换成第i个矩阵 以提取行列数信息
            elif i == ')' and len(order) >= 2:             # 由于不用考虑表达式合法性▲ 读到)就处理其两面两个准没错
                b,a = order.pop(),order.pop()
                res += a[1]*b[1]*a[0]
                order.append([a[0], b[1]])
        print(res)
    except:
        break

递归解法 有点长:
该方法还是得用到栈
感觉易读性不是很高,不如把.append写到res后面;好像不行;这个递归函数的作用是 找到字符串里最宽的括号,然后开始递归-直至最小的括号,然后读取括号内两个矩阵入栈,然后通过i+1继续递归、入栈,入完了才开始出栈、计算、得出一个新矩阵的信息,然后才调用.append()入栈
感觉不太流畅,但是大概好像又得这么做
对比前一解法,其在遇到字母时就入栈,遇到)就出栈,不够健壮;但它改成双栈,也不会有后面这么绕吧;说明表达式求值那里仍不是很熟悉,看了一下,前面题在(之后就进入递归了,在括号栈=0时return,这个的return位置看起来怪怪的,感觉应该是

if (
  level+=1
  recurse()
elif 字母
  st.append
elif )
  level-=1
  if level==0
    tmp1,2 = st.pop
	cal
	st.append

对吧▲ 自我感觉良好


def recurse(s, arr):										# s:待分析字串,arr:矩阵
    st = []
    i = 0
    while i < len(s):
        if s[i] == '(':                                      # 从碰到第一个字符串内的左括号开始标记
            j = i+1											# j
            level = 1
            while j < len(s):
                if s[j] == '(':
                    level += 1
                elif s[j] == ')':
                    level -= 1
                    if level == 0:                           # 一直到找到s[i]对应的)为止 哇 第一次找的时候就找的最外面的括号 然后往里递归
                        st.append(recurse(s[i+1:j], arr))    # 递归子字符串,按照括号划分的区域继续往内部递归;注意这里直接是.append(),因此最后一个append的应该是answer
                        break
                j += 1
            i = j
        elif s[i].isalpha():                                 # 在遍历当前字符串时 如果遇到了A.B.C等矩阵符号,则相应加入st中
            x = ord(s[i]) - 65
            st.append(arr[x])								# 将矩阵数据入栈;还是得用到栈
        i += 1		# 此时i才+1
    b = st.pop()                                             #
    a = st.pop()
    res = [a[0], b[1], a[2]+b[2]+a[0]*a[1]*b[1]]             # 由于计算完了要放回栈内,计算结果应当是新矩阵的长、宽及计算次数;a[2]+b[2]里存的是之前算得的∑
    return res

while True:
    try:
        n = int(input())
        arr = []
        order = []
        for i in range(n):
            l = input().split()
            l.append('0')
            arr.append(list(map(int, l)))    # 
        s = input()
        print(recurse(s[1:-1], arr)[2])      # 调用递归函数
    except:
        break
HJ59

找出字串中第一个只出现一次的字符
· 1≤n≤1000
O:输出字符,若不存在输出-1
思路:感觉可以弄个list,里面每个字符都有两个,遇到就删掉,但只适用于python、且不知道首个是什么,不行
那这样,如'B',首次存的是'B',第二次if'B' in list则改为'B2',然后每次if'B2' in list则continue,最后for i in range,if len(list[i])=1则输出

法2:注意这里需要flag 标志是否完成要求
★字符串.count(字符)方法

while True:
 try:
  s = input()
  flag = 0
  for i in s:
    if s.count(i) == 1:
      print(i)
      flag = 1
      break
  if not flag:
    print('-1')
 except:
  break

★不需要flag的写法,则利用了for+else的语法:
for+else,只有在for没有被break打断,且循环完毕的情况下才执行else.(就是说,break不仅会离开for也会离开else)
while+else,则在while不满足的情况下执行else.

while True:
    try:
        s = input()
        for i in s:
            if s.count(i) == 1:
                print(i)
                break
        else:
            print('-1')
    except:
        break

法1:最容易想到的,就是各开一个letter、频率count的list,但没想清楚,这个方法里频率的存储顺序不必跟随letter出现顺序,而应该是字典序,这样在letter append的过程中,频率一样能稳定统计,最后for i in letter,if count对应元素=1 return i;或者for i in count,if count=1 return str(索引转成字母)
噢,原来这个解法里频率的存储顺序就是跟随letter出现顺序的,letter append的时候count也append一个数字1就行,最后总有办法索引到,如这里的letter[count.index(1)]
★list的.index(元素)方法:返回指定元素索引位置的方法

while True:
    try:
        s = input()
    except:
        break
    letter = []
    count_list = []
    for c in s:
        if c in letter:
            count_list[letter.index(c)] += 1
        else:
            letter.append(c)
            count_list.append(1)
    try:
        index = count_list.index(1)
        print(letter[index])
    except:
        print(-1)
HJ63 分析DNA序列

一个 DNA 序列由 A/C/G/T 四个字母的排列组合组成。 GC比例(GC-Ratio)是序列中 GC两个字母的总出现次数除以总字母数目(即序列长度)。高的 GC-Ratio 可能是基因的起始点。
给定一个很长的 DNA 序列,及指定的子串长度 N ,请序列中从左往右找出 GC-Ratio 最高且指定长度为 N 的第一个连续子串(子串定义就是连续的吧,子序列才不是)。
I: string和int
O: GC比例最高、长度为N的子串,若有多个也输出首个
示例:

ACGT
2
>>> CG
说明:长度为2的子串有AC,CG,GT3个,其中AC和GT2个的GC-Ratio都为0.5,CG为1,故输出CG

思路:读题时以为是在前缀子串里找GC-Ratio最高的,看完示例是找长度为N的子串里GC最多的,那不是也很简单,for+tmp(rate,pos)就弄完了吧
即 法2:

for i in range(len(s)-k):                      # 计算子串的起点 0~len-k-1,共len-k项
  sub = s[i:i+k]                             # 切片
  count = sub.count('C') + sub.count('G')
  if count > mx:                             # 更新tmp
    mx = count
    res = sub

法1:噢 原来是考用滑动窗口,目的是优化内层循环 在统计GC时的遍历开销.

滑动窗口

那滑动窗口具体怎么写呢,看代码是先写边界,然后写一般模式,即窗口边界的变化,可用两个并列的if

def solution(s, k):
    sub = s[:k]											# 边界
    cur = mx = sub.count('C') + sub.count('G')
    res = sub
    for i in range(1, len(s)-k):                          # 一般模式 1~len-k-1,共len-k-1项
        sub = s[i:i+k]
        if s[i - 1] == 'C' or s[i - 1] == 'G':            # 如果左侧刚刚退出一个字符C或G,则计数器-1
            cur -= 1
        if s[i + k - 1] == 'C' or s[i + k - 1] == 'G':    # 如果右侧刚刚进入一个字符C或G,则计数器+1
            cur += 1
        # print(sub, cur, sub.count('G')+sub.count('C'))  # debug
        if cur > mx:                                      # 是否要更新新的结果
            res = sub
            mx = cur
    print(res)
    return
while True:
    try:
        s = input()
        k = int(input())
        solution(s, k)
    except:
        break
HJ64 模拟MP3按键后光标位置

MP3Player因为屏幕较小,显示歌曲列表的时候每屏只能显示几首歌曲,用户要通过上下键才能浏览所有的歌曲。下面简化处理,假设每屏只能显示4首,光标初始位置为第1首。

现在要实现通过上下键控制光标移动来浏览歌曲列表,控制逻辑如下(给得比较详细):

  1. 曲目≤4时,不需翻页,只需挪动光标位置。首尾相连。
    这意味着:
    1.a. 在首尾时,再按首尾,会到尾首
    1.b. 否则光标正常移动
  2. 曲目≥4时,
    2.a.在屏幕显示第一页(即1–4首)时,若光标在第一首上,按Up键后显示最后一页(即7-10首),同时光标放到最后一首上。同样地,屏幕显示最后一页时...
    2.b.在屏幕显示的不是第一页(1-4首)时,若光标在当前屏幕第一首上,用户按Up键后,屏幕从当前歌曲的上一首开始显示,光标也挪到上一首歌曲。同样地,...
    2.c 否则仅挪动光标

即 有时挪动光标,有时挪动光标又动页面,有时动页面不挪光标
· 指令1≤s≤100,曲目1≤n≤150
· 进阶:O(n),O(n)
I:
1 输入曲目
2 输入指令,字符为'U'或'D'
O:
1 输出当前屏幕所显示的列表
2 输出当前选中歌曲
示例:

10
UUUU
>>> 7 8 9 10
>>> 7

思路:感觉流程控制图给得很详细,翻译就行了,写出流程控制图可能比实现稍难,这里需要先写特殊情况,再写一般性的仅挪动光标,否则流程图结构繁复
答案大致都是很多if 感觉可先不看了▲

HJ65、HJ75 查找两个字符串的最长公共子串

查找两个字符串的最长公共子串。若有多个,输出在A,B较短串中最先出现的.
· 子串定义:将一个字符串删去前后缀(也可以不删)形成的字符串,请和“子序列”的概念分开.
· 进阶:时O(n³),空O(n)
示例:

abcdefghijklmnop
abcsafjklmnopqrstuvw
>>> jklmnop

思路:dp,好像也不太 if a == b

法2:通过in方法比较
法1:dp,考察不同末尾项的公共子串,找出最长的那个.
状态:dp[i][j]: 字串A,B分别以i,j为末尾项形成的最长公共子串的长度
转移:if A[i]=B[j] dp[i][j] = dp[i-1][j-1] + 1
else 这里自己漏了,dp[i][j] = 0就行了
边界:都=0吧
★dp好像常是判断[i-1]和[j-1]的,不熟悉容易写错

while True:
    try:
        s1 = input()
        s2 = input()
        if len(s1) > len(s2):			# 灵活运用python的便利
            s1,s2 = s2,s1		        # 需使s1为更短的,因为要求输出短串中最先出现的
        m = len(s1)
        n = len(s2)
        max_len = 0
        max_loc = [0, 0]
        dp = [[0 for j in range(n+1)] for i in range(m+1)] # 初始化dptable
        for i in range(1, m+1):
            for j in range(1, n+1):
                if s1[i-1] == s2[j-1]:				# 从1开始,则既符合非边界,又符合转移方程
                    dp[i][j] = dp[i-1][j-1] +1
                    if dp[i][j] > max_len:			# 这个部分不知道有没有其它写法▲
                        max_len = dp[i][j]
                        max_loc = [i, j]
                else:
                    dp[i][j] = 0
        print(s1[max_loc[0]-max_len:max_loc[0]])
    except:
        break

注意,dp数组可以优化,想一想j即列要从哪个顺序遍历呢,式子是要用到左侧的dp([i-1])[j-1]得到dp[j],也就是说dp[j]处理完暂时就用不到了,故得从右往左
HJ52

法3:双指针+剪枝
剪枝:更新下次至少要找长度为k的字串,每次找到就把k+1;代码好像很短
★双指针:在第一个串里找起点,在第二个串里找终点

def solution(s1, s2):
    if len(s1) > len(s2):                        # 较短的为s1
        s1, s2 = s2, s1
    mxlen = 0
    for i in range(len(s1)):                     # 找一个子串起点
        for j in range(i+1+mxlen, len(s1) + 1):  # 找一个子串终点 这个下限+mxlen就剪完枝了 优雅
            sub = s1[i:j]                        # 截取
            if sub in s2 :                       # 子串是否在s2中
                res = sub
                mxlen = j - i
                continue
    print(res)
    return
while True:
    try:
        s1,s2 = input(),input()
        solution(s1, s2)
    except:
        break
HJ66 配置文件恢复

根据残留的指令字符串 匹配出是否有符合的原指令,共6条原指令:
指 令 | 执行含义
reset | reset what
reset board | board fault
board add | where to add
board delete | no board at all
reboot backplane | impossible
backplane abort | install first
完整的判断逻辑很多:
0.以“最短唯一匹配原则”,从首字母开始匹配:
1、若只输入一字串,则只匹配一个关键字的指令。如,res,匹配到指令reset,返回的是reset what;
2、若只输入一字串,但匹配到的指令有两个关键字,则匹配失败。如:reb,指令reboot backpalne有两个关键字,所有匹配失败,返回的是:unknown command;
3、若输入两字串,则先匹配第一关键字,如果有匹配,继续匹配第二关键字,如果仍不唯一,匹配失败。如,r b,找到匹配命令reset board 和 reboot backplane,执行结果为:unknown command;
4、若输入两字串,则先匹配第一关键字,如果有匹配,继续匹配第二关键字,如果唯一,匹配成功;
5、若输入两字串,第一关键字匹配成功,第二关键字无匹配,失败;
6、其他匹配失败,打印“unknown command”
· 多组输入.
· 进阶:O(n),O(n)

思路:匹配不用re的话 怎么弄啊 先split看有没有空格,然后if讨论;匹配的时候 for i in order: if i[:x]==x
法2:可以,有这么写的,不细看了
法1:
★字符串.startwith(待查前缀子串, 起始位置, 结束位置)

字符串.startwith()

语法:string.startswith(prefix, start, end)
参数:prefix - 必需,待判断是不是前缀子串的字串

HJ71 字符串通配符(Leetcode44)

实现如下2个通配符:
:匹配0个或以上的字符
?:匹配1个字符
· 能被
和?匹配的字符仅由英文字母和数字0到9组成
· 匹配时不区分大小写
I: 通配符表达式,一组字符串
O: 返回匹配结果true/false
示例:

te?t*.*
txt12.xls
>>> false

思路:就本题而言,感觉不难,就把按顺序查这些切片,然后删去,应该就可以了吧
法2:★用re模块的.findall()、字符串的.replace()正则做,后面再看吧,应该不是想考这个▲

import re
while 1:
    try:
        a = input().lower()
        b = input().lower()
        a = a.replace('.','\.').replace('?','[a-z0-9]{1}').replace('*',"[a-z0-9]*")
        if b in re.findall(a,b):#这题问的是上面一个字符串能不能完全代表下面一个字符串
            print('true')
        else:
            print('false')
    except:
        break
re模块的.findall()

查找所有匹配的子字符串
因此法2就是把通配符表达式转成了re正则表达式 再用re模块去匹配

法1:递归,说写了两个多小时,感觉有点难,再遇到再看▲
法3:dp,0或1,也可以dp的
状态:dp[i][j=]=1/0: 到s1[i]和到s2[j]的前缀子串能否匹配
转移:回忆一下,常常是涉及[i-1][j-1]的:
对s1[i-1]和s2[j-1]的关系进行讨论:

for i in range(1,len(s1)+1):
 for j in range(1,len(s2)+1):
  if s1[i-1].lower()==s2[j-1].lower():		#
   dp[i][j]=dp[i-1][j-1]
  elif s1[i-1]=='?' and s2[j-1].isalnum():	# 通过if字符==某某来分类
   dp[i][j]=dp[i-1][j-1]
  elif s1[i-1]=='*':
   dp[i][j]=dp[i-1][j] or dp[i][j-1]	# 分别对应匹配0个 匹配多个字符 因为这两种情况在该dp状态定义下确实导向不同状态

边界:
它这里将s2设为列,有j列,通配符表达式s1设为行,有i行,符合一般的顺序,但还不太熟
第一列=False(通配符表达式为空),但[0,0]=True;第一行里,若前缀子串一直都由'?'或'*'组成,就设为True,直到通配符表达式不再类似'?**'.

while True:
    try:
        matching=input()
        str1=input()
        # 以下皆边界
        bp=[[False for _ in range(len(str1)+1)] for _ in range(len(matching)+1)]
        bp[0][0]=True
        for i in range(1,len(matching)+1):
            if matching[i-1]=='?' or matching[i-1]=='*':
                bp[i][0]=True
            else:
                break
HJ74 参数解析

在命令行输入如下命令:xcopy /s c:\ d:\e,请编写一个参数解析程序,将各个参数解析出来,解析规则:
1.参数不定长,参数分隔符为空格
2.对于用""包含起来的参数,中间有空格也解析为1个参数。如,输入xcopy /s "C:\program files" "d:"时,参数仍然是4个。且输出参数时,需要去掉"".
· 保证引号不存在嵌套情况;不出现不符合要求的输入
I: 输入一行字符串,可以有空格
O: 参数个数,分解后的参数
示例:

xcopy /s c:\\ d:\\e
>>> 4
>>> xcopy
>>> /s
>>> c:\\
>>> d:\\e

思路:由于保证不存在嵌套,感觉可以硬做
法1:仍是先都split开,然后考虑如何把""内的合起来,可以用flag置1置0,类似模拟括号;这里具体是通过一个字符一个字符+=,连普通的参数字串都如此,这是因为区分的逻辑在每个字符的判断中.

while True:
    try:
        str1=str(input())
        str1=str1.replace(' ','\n')
        e = ''
        flag = False
        for i in str1:		# 呃呃 这里是一个字符一个字符添加仅
            if i == '"':  # 经过一次引号则拨动一次开关
                flag = not flag
            elif flag == True and i == '\n':
                e += ' '
            else:
                e += i
        b = e.count('\n') + 1
        print(b)
        print(e)
    except:
        break

能不能不这么写呢,自己改一下,没测,感觉可以

s = .split()
ans = []
for i in len(s):
  tmp = ''
  if word[i][0] == '"':
    while word[i][-1] != '"':
      tmp += word[i][-1]
      i += 1
    tmp = tmp[1:-2]
    ans.append(tmp)
  else
    ans.append(word)

法2:用list结构的栈,感觉不是很栈,没有pop什么的,而是用了list的切片方法

s=str(input())
stack=[] #栈 存储当前字符串
l=[] #列表 存储全部字符串
counter=0 #字符串计数器
for i in s: 
    if i=='"' and '"' in stack: #分情况1 
        l.append(''.join(stack[1::]))
        stack=[]
        counter+=1
    elif i==' ' and '"' not in stack: #分情况2
        if len(stack)!=0:
            l.append(''.join(stack[0::]))
            stack=[]
            counter+=1
    else: #分情况3
        stack.append(i)
if len(stack)!=0: #最后再看下栈中是否还有字符串
    l.append(''.join(stack[0::]))
    counter+=1
print(counter)
for j in l:
    print(j)
HJ77 火车进站 呃 这个叫什么队列来着 双端队列 单端队列?

给定一个正整数N代表火车数量,0<N<10,接下来输入火车入站的序列,一共N辆火车,每辆火车以数字1-9编号,火车站只有一个方向进出,同时停靠在火车站的列车中,只有后进站的出站了,先进站的才能出站。
要求输出所有火车出站的方案,以字典序排序输出。
I: 第一行输入一个正整数N(0 < N <= 10),第二行包括N个正整数,范围为1到10。
O: 输出以字典序从小到大排序的火车出站序列号,每个编号以空格隔开,每个输出序列换行
示例:

3
1 2 3
输出:
1 2 3
1 3 2
2 1 3
2 3 1
3 2 1
说明:
第一种方案:1进、1出、2进、2出、3进、3出
第二种方案:1进、1出、2进、3进、3出、2出
第三种方案:1进、2进、2出、1出、3进、3出
第四种方案:1进、2进、2出、3进、3出、1出
第五种方案:1进、2进、3进、3出、2出、1出
而如[3,1,2],是不可能实现的。

思路:这种肯定是递归,虽然不会写;二叉树好像也不行,有些枝是不合法的;感觉也有可能可以实现,但本质也得递归了;怎么储存各字符串呢×
全排列肯定也考了,但不记得怎么写了,太久了
判断可以用栈,但感觉也可以以list的形式,可能不够方便吧

path = []
if len>=1:

法1:有点复杂 没看懂▲
俩考点:
1、dfs回溯算法 写全排列 参考 leecode 46. 全排列
2、判断是否为合法输出 参考 leecode 946. 验证栈序列

# 在这里直接用两个方法 封装起来
def dfs(values,nums):
    path,result,used = [],[],[False for _ in range(nums)]

    def dfs_inner(values):            # 这什么结构啊▲
        if len(path)==nums:           # 这也能看懂 判断什么时候输出
            if illegal(path.copy()):     # 调用函数2 否则若用前后相衔接的方式写▲,会超时
                result.append(path.copy())	# 还没行 就先append
        for i in range(nums):
            if used[i]==True:         # 这里不知道在干啥▲
                continue

            used[i] = True
            path.append(values[i])    # 此时用path记录
            dfs_inner(values)       # 排列和组合这里有区别,这里是排列
            path.pop()
            used[i] = False           # 恢复现场  这个看得懂

    dfs_inner(values)                 # 这是啥 是不是一个是入栈的递归 一个是出栈的▲
    return result

def illegal(result)->bool:            # 模拟出入栈,判断出栈顺序是否合理.
    stack_ = []
    for i in values:                # 入栈 values哪传来的,这是闭包吗▲
        stack_.append(i)
        while stack_!=[] and stack_[-1]==result[0]: # 栈顶==出栈值,则栈和出栈序列一起弹出;
            stack_.pop()
            result.pop(0)
    if stack_==[]: 
        return True 
    else: return False 

if __name__=='__main__':
    nums = int(input())
    values = input().split(' ')

    result_all = sorted(dfs(values,nums))
    for i in result_all:
        print(' '.join(i))

来看一个比较短的答案,但感觉复杂度有待优化
输出时都是用sorted()
想起来一点,这里递归dfs()由于含输出处理out+,因此不用path记录

res = []

def dfs(wait, stack, out):
    if not wait and not stack:	# 若待处理数字、栈皆非空
        res.append(" ".join(map(str, out)))
    if wait:  # 入栈 这个看懂了 入栈的递归 下面是出栈的,同时把list、stack、输出的变化 传入递归函数
        dfs(wait[1:], stack + [wait[0]], out)    # 入 所以待处理变短、栈增、出队没变化
    if stack:  # 出栈
        dfs(wait, stack[:-1], out + [stack[-1]]) # 出 所以待处理不变、栈切片、输出更新

n, nums = int(input()), list(map(int, input().split()))
dfs(nums, [], [])
for i in sorted(res):
    print(i)

再看一个,都用递归,写起来也有不同结构,还得积累经验▲✓
这里结构就不如第二个,原因在于流程图繁复了,第二个是if什么情况下入栈(变量,记录) if什么情况下出栈(变量,记录),这里是把if条件拆成特殊情况和一般情况分开写了,能概括成一个条件的话就喝第二个解法相同了.
注释里一句话感觉挺好(改了一下),递归有一个“遍历”的本质:利用指涉自身部分+if条件句特点,如果满足条件,会一直循环至验证所有合理选项
感觉下划线型命名方法不如驼峰型,搅在一起易视作两个变量
这一解也是用切片来作为递归参数,避免用path记录

def func():
    while True:
        try:
            n = int(input())
            trains = input().strip().split(' ')
            res = []
            def rec_trains(cur_idx, in_trains, out_trains):
                # 若待处理列表都在栈内,只能出站,且需倒序加入出站火车中
                if trains[-1] in in_trains:
                    res.append(' '.join(out_trains + in_trains[::-1]))
                    print(*in_trains[::-1],'出站')
                    print('出站顺序:',res[-1])
                # 若栈为空,只能进站,进站列表加上当前火车,出站列表不变
                elif in_trains == []:
                    print(trains[cur_idx],'进站')
                    rec_trains(cur_idx + 1, in_trains + [trains[cur_idx]], out_trains)
                # 否则,既有可能进站也有可能出站
                else:
                    # 出站,当前火车索引不变,进站火车列表减去最后一个元素,出站列表加上进站列表刚刚出站的火车
                    print('选A:', in_trains[-1],'出站')
                    rec_trains(cur_idx, in_trains[:-1], out_trains + [in_trains[-1]])
                    # 进站,当前火车索引加1,进站列表加上当前火车,出站列表不变
                    print('选B:',trains[cur_idx],'进站')
                    rec_trains(cur_idx + 1, in_trains + [trains[cur_idx]], out_trains)
                    ## 递归的“遍历”本质:利用指涉自身部分+if条件句特点,如果满足条件,会一直循环至验证所有合理选项
            rec_trains(0, [], [])
            res.sort()
            #print('\n'.join(res))
        except:
            break
if __name__ == '__main__':
    func()

递归与回溯
递归是一种算法结构,形式上表现为直接或间接的自己调用自己;
而回溯是一种算法思想,是用递归实现的,过程类似于穷举法,但回溯有“剪枝”功能、即自我判断过程。

HJ82 将真分数分解为埃及分数

分子为1的分数称为埃及分数。现输入一个真分数,请将该分数分解为埃及分数。如:8/11 = 1/2+1/5+1/55+1/110。
· 真分数指分子<分母的分数,其分子分母有可能gcd不为1!
· 如有多个解,输出任一个
I: String型的真分数
O: 分解后的String
示例:

2/4
>>> 1/3+1/6 或1/2 也是可以的

思路:急着弄完,没想直接看.
由小学数学,有理分数是两整数约分来的.
记a为分子,b分母。a从a到0进行倒序遍历,看有没有数可以被b整除,如果有,那么就把这个埃及分数添加到一个空列表中;遍历完也没有,分子分母同乘10:
★学习一下输出 result以统一模式储存到list里,再.join

while True:
    try:
        a,b = map(int,input().split('/'))
        a = a * 10
        b = b * 10
        res = []
        while a:
            for i in range(a,0,-1):
                if(b % i == 0):
                    res.append('1' + '/' + str(int(b / i)))
                    a = a - i
                    break
        print('+'.join(res))
    except:
        break
HJ90 合法IP

数据组数1≤t≤18
I: 输入一个ip地址,保证不含空格
· 进阶O(n),O(n)
O: 合法YES or NO
思路:反正大概取出来分析就行了吧

while True:
    try:
        s = input().split('.')
        c = 0  # 计数器,指导输出
        if len(s) != 4:  # 1.位数不够4,直接NO
            print('NO')
            continue
        for i in s:
            if not i.isdigit():  # 2.存在非数字字符,直接NO
                print('NO')
                continue
            elif int(i) > 255 or (i.startswith('0') and len(i) > 1):  # 3.最大值大于255 或者数字为'0xx'这种格式的,NO
                print('NO')
                continue
            else:
                c +=1
        if c ==4:  # 4.计数器,4次循环结束后再执行YES输出
            print('YES')
    except:
        break
HJ92 在字符串中找出连续最长的数字串

输入一个字符串,返回其最长的数字子串,以及其长度。若有多个,全部输出(不要空格)
示例:

abcd12345ed125ss123058789
a8a72a6a5yy98y65ee1r2
>>> 123058789,9
>>> 729865,2
说明:样例一最长的数字子串为123058789,长度为9
样例二最长的数字子串有72,98,65,长度都为2

思路:剪枝
法1:利用python的便利,把非数字全部换成空格,然后切割;为了输出全部最长子串,遍历一次得到最大长度,再遍历一次,取出符合长度的,拼接
★str(字符串)函数 作用是啥?不能str(list),会输出[]和,的

while True:
    try:
        s = input()
        for c in s:
            if not c.isdigit():
                s = s.replace(c, " ")
        s = s.split()
        maxLen = 0
        res = ""
        for c in s:
            if len(c)>maxLen:
                maxLen = len(c)
        for c in s:
            if len(c)==maxLen:
                res = res+c
        print(str(res)+','+str(maxLen))
    except:
        break

法2:看一下剪枝怎么写,和之前遇到的一样,先两个for

while True:
    try:
        s = input()
        l1 = []
        l2 = []
        for i in range(len(s)):
            for j in range(i, len(s)):
                if s[i:j+1].isdigit():
                    l1.append(s[i:j+1])
                    l2.append(j+1-i)
        s1 = ''
        for i in range(len(l2)):
            if l2[i] == max(l2):
                s1 += l1[i]
        print(s1+','+str(max(l2)))
    except:
        break

自己改成剪枝:

while True:
    try:
        s = input()
        l1 = []
        maxLen = 0
        for i in range(len(s)):
            for j in range(i+maxLen, len(s)):          # i和maxLen同时变动时会不会有问题
                if s[i:j+1].isdigit() and j-i+1==maxLen:	# 不对诶 这个==必须在>前面 因为maxLen改了
                    l1.append(s[i:j+1])
                elif s[i:j+1].isdigit() and j-i+1>maxLen:  # i往后j+maxLen项是什么? s[i:j]包括j的吧×一直都错的——不包括j的 改i:j->j+1
                    # del l1[:]
                    l1 = []
                    l1.append(s[i:j+1])
                    maxLen = j-i+1                     # maxLen += 1× 不一定只+1

        res = ""
        for c in l1:
            res = res+c
        print(str(res)+','+str(maxLen))
    except:
        break

Error:答案不对,预期输出729865,2->72,3改完if顺序 输出还是一样错的

while True:
    try:
        s = input()
        l1 = ""
        maxLen = 0
        for i in range(len(s)):
            for j in range(i+maxLen, len(s)):          # i和maxLen同时变动时会不会有问题
                if s[i:j+1].isdigit() and (j-i+1==maxLen):	# 不对诶 这个==必须在>前面 因为maxLen改了
                    l1 += str(s[i:j+1])
                elif s[i:j+1].isdigit() and j-i+1>maxLen:  # i往后j+maxLen项是什么? s[i:j]包括j的吧×一直都错的——不包括j的 改i:j->j+1
                    # del l1[:]
                    l1 = ""
                    l1 += str(s[i:j+1])
                    maxLen = j-i+1                     # maxLen += 1× 不一定只+1
        print(str(l1)+','+str(maxLen))
    except:
        break

查了好一会 查不出错 先过▲

HJ103 走法

Redraiment是走梅花桩的高手。可以选择任意一个起点,从前到后,但只能从低处往高处的桩子走。他希望走的步数最多,你能替Redraiment研究他最多走的步数吗?
I: 数据共2行,第1行先输入数组的个数,第2行再输入梅花桩的高度
O: 步数
写得不清楚,即求最长递增子序列 的长度,那这题做过

思路: max,这肯定是dp
状态:
噢不对,之前是用数学方法算的
法2:法2好像前面没见到
状态: dp(n):长度为n的后缀子串中的最长子序列
边界:dp(1)=1
转移:这个状态的转移很简单,之前没发现,dp(i) = max{dp(i-1),dp(i-2)...dp(1)}+1,但实现则要通过两个for,第二个for用来

# 它这里状态定义的稍微不同,dp(n):第n位往后的后缀子串中的最长子序列
while True: 
  try: 
    n = int(input())
    s = [int(x) for x in input().split()] 
    L = [1]* n
    for i in reversed(range(n)):	# 状态定义不同,所以倒着for,依此得出dp(1),dp(2)...
      for j in range(i+1, n):		# 这个for是为了计算max{dp(i-1),dp(i-2)...dp(1)}
        if s[j] > s[i]:
          L[i] = max(L[i], L[j] + 1)
    max_len = max(L)
    print(max_len)
  except:
    break

IndentationError: unexpected indent: indentation是缩进,except忘改缩进了

HJ107 求解立方根

计算一个浮点数的立方根,不使用库函数。
保留一位小数。
示例:

19.9
>>> 2.7

思路:类似开平方吧,夹逼;仅保留1位,那么把num*1000是不是够用了,怎么处理四舍五入呢,算一下x^3末位可不可以

目前位数=0,10^0
for i in range(0,10):
n = 1000*double(input())
x = 0
while True:
 if x*x*x<n:
  x+=1
 else
  break

看来不太行,应该改成二分法算,即修改上/下限,就可以了:

while True:
    try:
        a = float(input().strip())
        epsilon = 0.0001
        low = min(-1.0, a)
        high = max(1.0, a)
        ans = (low + high)/2
        while abs(ans**3 - a) >= epsilon:
            if ans**3 < a:
                low = ans
            else:
                high = ans
            ans = (low + high)/2.0
        print('%.1f' % ans)
    except:
        break
posted on 2024-03-18 17:05  蠢的刘龙  阅读(16)  评论(0编辑  收藏  举报