牛客网经典例题
引言
收录一些在做在线题的过程中遇到的经典的问题和解决方法,便于之后的复习和回顾。
1 合唱队
思路:将该序列分成两部分,找出左边部分递增的最大连续序列长度和右边部分的最大连续递减序列长度。将序列分成两部分的位置可以从1开始到序列末尾,关键是如何找最长连续递增或者递减序列。
一个思路如下:分析的比较清楚
和上面具有相同思想的是:两遍最长递增子序列,第一遍从左往右,第二遍从右往左,然后把两遍动态规划的结果相加,取最大的那个,比如8 186 186 150 200 160 130 197 200,第一遍dp的结果是 1 1 1 2 2 1 3 4,第二遍dp的结果为3 3 2 3 2 1 1 1,那么相加最大是5,所以需要出列的同学个数就是8-5+1=4.代码如下:
def get_max_length(li):
seq = [[] for i in range(len(li))]
seq[0] = [li[0]]
for i in range(1, len(li)):
for j in range(i):
if li[i] > li[j] and len(seq[i]) < len(seq[j]) + 1:
seq[i] = seq[j][:]
seq[i].append(li[i])
# print(seq)
return [len(item) for item in seq]
def get_max_length_reverse(li):
# 为了让元素从后向前进行递增排序,那么可以将序列转换一下,之后可以从前向后递增排序,得到的结果要与之对应,因此结果在逆序一下即可。
li = li[::-1]
seq = [[] for i in range(len(li))]
seq[0] = [li[0]]
for i in range(1, len(li)):
for j in range(i):
if li[i] > li[j] and len(seq[i]) < len(seq[j]) + 1:
seq[i] = seq[j][:]
seq[i].append(li[i])
result = [len(item) for item in seq][::-1]
return result
def get_max_length_reverse_01(li):
# 如果是递减序列的话,对应的修改遍历条件就可以了。
seq = [[] for i in range(len(li))]
seq[len(li) - 1] = [li[len(li)-1]]
for i in range(len(li) - 2, -1, -1):
for j in range(len(li) - 1, i, -1):
if li[i] > li[j] and len(seq[i]) < len(seq[j]) + 1:
seq[i] = seq[j][:]
seq[i].append(li[i])
result = [len(item) for item in seq]
return result
n = int(input())
li = input().split(" ")
increase_list = get_max_length(li)
decrease_list = get_max_length_reverse_01(li)
result = list(map(lambda x: x[0] + x[1], zip(increase_list, decrease_list)))
print(result)
print(n - max(result) + 1)
需要注意的是在牛客网上进行提交的时候没有通过,可能的一个原因是给的测试用例存在问题:当n=692的时候,后面的序列长度貌似是693个,这两个值不匹配。
1.1 最长递增子序列
从一个给定的序列中找出一个最长的序列,该序列从小到大进行排序。比如:一个给定的序列如下所示:0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15
那么,它的最长子序列就是:0, 2, 6, 9, 11, 15
一种思路如下图,就是从最长的序列开始,逐步减小为1,找最大的递增子序列,这种方法的时间复杂度较高,如下所示:
另一种思路就是动态规划:比如针对序列:{3,2,6,4,5,1},设L[i]存储以第i个元素结尾是的最大序列,则有:
L[0] = [3] L[1] = [2] L[2] = [2,6] L[3] = [2,4] L[4] = [2,4,5] L[5] = [1]
使用动态规划的思想,主要是考虑L[i]目前已经求得的L[0]至L[i-1]的基础上进行,判断条件是:
L[i] = max(L[j] | j<i ,D[j]< D[i]) +"D[i]"
代码如下:
arr = [3,2,6,4,5,1]
L = [[] for i in range(len(arr))]
print(L)
L[0] = [arr[0]]
for i in range(1,len(arr)):
for j in range(i):
if arr[i] > arr[j] and len(L[i]) < (len(L[j])+1):
L[i] = L[j][:]
L[i].append(arr[i])
print(L)
通过上面的结果可以发现,L[2]不是[3,6],而是[2,6],这是因为代码中j遍历的时候,先得到[3,6],之后被[2,6]覆盖了。
1.2 最长公共子序列
2 最长回文字符串
2.1 判断一个字符串是否为回文字符串
# idea: 将字符串对半分,判断从前到后和从后到前的字符串是否完全一样,如果一样的话就是回文字符串
def is_palindromic_str1(st):
""" judge the st is a Palindromic String or not :param st: a string :return: True or False """
st1 = st[:math.ceil(len(st)/2)]
st_reverse = st[::-1]
st2 = st_reverse[:math.ceil(len(st)/2)]
if st1 == st2:
return True
else:
return False
# idea: 判断对应位置的元素是否相等
def is_palindromic_str2(st):
for i in range(math.ceil(len(st)/2)):
if st[i] == st[len(st)-i-1]:
continue
else:
return False
return True
2.2 求字符串中最长的回文字符串长度
一种方法是,得到字符串的所有子字符串,然后判断每一个子字符串是不是回文串,从而找到最大的回文串。这种方法时间复杂度较高。
从中心向两端进行判断,如果以该中心向两端得到的子字符串不是回文串,那就就不用接着向下判断了,需要修改中心,然后接着重复之前的操作。需要注意的是分成中心是奇数和中心是偶数的情况进行讨论。
st = 'aBBab'
max_len = 0
# i represent the center of palindromic. From one center start,like'abbbac'
for i in range(1, len(st)-1):
temp = 0
min_i = i-1
max_i = i+1
while min_i >= 0 and max_i < len(st) and st[min_i] == st[max_i]:
temp = max_i-min_i+1
min_i -= 1
max_i += 1
if temp > max_len:
max_len = temp
# From two center start,like 'aBBab'
for j in range(1, len(st)-1):
temp = 0
min_j = j
max_j = j+1
while min_j>=0 and max_j<len(st) and st[min_j]==st[max_j]:
temp = max_j-min_j+1
min_j -= 1
max_j +=1
if temp > max_len:
max_len = temp
print(max_len)
3 兔子繁殖问题
有一只兔子,从出生后第3个月起每个月都生一只兔子,小兔子长到第三个月后每个月又生一只兔子,假如兔子都不死,问每个月的兔子总数为多少?
第一个月月底的时候只有一对幼兔,第二个月月底的时候这对幼兔长为成兔,第三个月月底的时候第一队成兔生下幼兔,变为两队兔子。
分析问题: 借鉴
假设将兔子分为三个时期,分别是child youth old,那么可以得到下面的结论:
1月:1,0,0 共1
2月:0,1,0 1
3月:1,0,1 2
4月:1,1,1 3
5月:2,1,2 5
6月:3,2,3 7
7月:5,3,5 13
…
比如第5月,根据第4月知道,在第5月的时候,child为2(一个是old生的兔子,另一个是youth长到old然后生的兔子),youth为1(4月的child为1个),old为2(一个为4月的old,一个为4月的youth–>old)。
通过分析发现,每个月份的数量符合“斐波那契数列”。使用递归解题比较简单,但是复杂度较高,下面给出程序的非递归解法
# non-recursion method
# idea: use two pointer(prev,next), then continually change them.
def fibo(n):
if n <= 1:
return n
fibo_prev = 1
fibo_next = 1
for i in range(2, n):
temp = fibo_next
fibo_next += fibo_prev
fibo_prev = temp
return fibo_next
print(fibo(45))
如果使用递归解法,那么计算fibo(45)的时候需要花费的时间是很长的,而非递归方法可以很快的输出结果。
4 称砝码问题
现有一组砝码,重量互不相等,分别为m1,m2,m3…mn;
每种砝码对应的数量为x1,x2,x3…xn。现在要用这些砝码去称物体的重量(放在同一侧),问能称出多少种不同的重量。
注:称重重量包括0。
思路:先求出每一种砝码质量的可能组合,之后依次累加得到新的砝码的重量,直至全部组合完成
举个例子:比如有重量分别为1 2 3的三种砝码,它们的数量分别为2,1,3个,那么,首先得到每一种砝码的质量组合,如第一种砝码,它的质量组合可以为[0, 1, 2]三种,依次计算出三种砝码的质量组合如下:
[{0, 1, 2}, {0, 2}, {0, 9, 3, 6}]
上面的列表分成3个组,接下来就是需要从每一组中选择1个值,加起来,得到一个和值,计算总共的可能的和值。
那么首先可以将第一个组和第二个组进行求和,得到{0, 1, 2, 3, 4}。然后将第二个组和第三个组进行求和,得到{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13}。因此总共有14中组合质量。
代码如下:
# num 表示砝码的数量,weights表示每一种砝码的重量(list),quantities表示每一种砝码的数量(list)
weights = [int(item) for item in input().split(" ")]
quantities = [int(item) for item in input().split(" ")]
# 存储每一种砝码不同数量时候的重量。
fama_l = [set() for i in range(len(weights))]
# 计算每一种砝码可能的重量组合,如重量为1,数量为3的砝码可以组合成4中重量[0, 1, 2, 3]
for i in range(len(weights)):
for k in range(quantities[i]+1):
fama_l[i].add(weights[i]*k)
print("每一种砝码的重量组合为:")
print(fama_l)
# 首先,将第2种砝码中所有的重量组合加到第一种所有的重量中,得到它们的和,然后赋值给第2种砝码的重量。
# 然后用第3重砝码的重量与第二种砝码的重量每一个进行求和,赋值给第三种砝码的重量。
length = len(fama_l)
i = 0
while i < length-1:
temp = set()
for m in fama_l[i]:
for n in fama_l[i+1]:
temp.add(m + n)
i += 1
fama_l[i] = temp
print(fama_l)
print("总共有%d 种重量组合" % len(fama_l[-1]))
输入
1 2 3 # 每一种砝码的重量
2 1 3 # 每一种砝码的数量
输出
每一种砝码的重量组合为:
[{0, 1, 2}, {0, 2}, {0, 9, 3, 6}]
[{0, 1, 2}, {0, 1, 2, 3, 4}, {0, 9, 3, 6}]
[{0, 1, 2}, {0, 1, 2, 3, 4}, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13}]
总共有14 种重量组合
5 学英语
题目链接:learnEnglish
思路:将长数字串从后向前切分成每三个组成的数字串,然后对长度小于等于3的数字字符串进行处理,如12345,可以切分成12,345,然后分别进行处理。具体代码如下:
#question: https://www.nowcoder.com/practice/1364723563ab43c99f3d38b5abef83bc?tpId=37&tqId=21265&rp=0&ru=/ta/huawei&qru=/ta/huawei/question-ranking
# 首先,定义数字和英语的对应词表。
num_dict = {
1: 'one',
2: 'two',
3: 'three',
4: 'four',
5: 'five',
6: 'six',
7: 'seven',
8: 'eight',
9: 'nine',
10: 'ten',
11: 'eleven',
12: 'twelve',
13: 'thirteen',
14: 'fourteen',
15: 'fifteen',
16: 'sixteen',
17: 'seventeen',
18: 'eighteen',
19: 'nineteen',
20: 'twenty',
30: 'thirty',
40: 'forty',
50: 'fifty',
60: 'sixty',
70: 'seventy',
80: 'eighty',
90: 'ninety'
}
unit = ['thousand', 'million', 'billion']
def split_num(i):
"""将数字进行切分,每三个数字组成一组,如12345,可以切分成345,12"""
l = []
while len(i) > 3:
temp = i[-3:] # 切分最后3个字符
i = i[:-3] # 得到除去最后3个字符的所有字符
l.append(temp)
l.append(i)
return l
def get_res(i):
"""i为int型。将小于3位数字的数以合适的方式表示,比如123表示成one hundred and twenty two"""
h = i // 100 # 百分位上的数
h_y = i % 100 # 除去百分位之后的数
res = ""
if h != 0:
# 将百分位的数转换成对应的英文字母,然后添加单位hundred和连接词and
res += num_dict[h] + ' hundred '+'and '
s = h_y // 10 # 十分位上的数
s_y = h_y % 10 # 个位数
if s > 1: # 十分位上的数大于1的时候
if s_y != 0:
s = int(str(s) + '0')
res += num_dict[s] + " " + num_dict[s_y]+" "
else:
s = int(str(s) + '0')
res += num_dict[s] + " "
# 当十分位上的数为1的时候,此时直接将十位数和个位数联合在一起得到对应的英文字母即可
if s == 1:
res += num_dict[h_y] + " "
if s == 0:
res += num_dict[s_y] + " "
return res
def run(str_num):
"""程序运行入口"""
three_nums = split_num(str_num)
res = ""
for str_num, v in enumerate(three_nums):
res = get_res(int(v)) + unit[str_num] + " " + res
return res
i = input()
result = run(i)
print(result)
6 n皇后问题
6.1 递归解法
问题描述可以参考这里 here
为了不重复的造轮子,分析问题,可以参考这里here
这里只记录一下自己解题时候遇到的问题和需要注意的地方。
首先,可以写一个判断(i,j)位置是否是一个可以放置皇后的位置的函数is_correct(i,j),这个方法比较简单。之后是使用递归的方法实现一下,首先第一个皇后的位置是第一列的第一个位置,然后开始找第二列中合适的位置,如果找到,就将该位置的值该为1,然后找第三列、第四列中皇后的位置。如果位置不合适,需要回溯到前面一层,并将该位置的值修改为0就可以了。详细代码如下:
# N-Queens question
# reference:https://segmentfault.com/a/1190000003733325
# the number of queens
N = 8
Q = [[0 for i in range(N)] for j in range(N)]
count = 0
def is_correct(i, j):
# judge (i,j) is or not a correct position to place a queen
global N
# global Q
# 1 judge the row
for k in range(N):
if Q[i][k] == 1 and k != j:
return False
# 2 judge the col
for k in range(N):
if Q[k][j] == 1 and k != i:
return False
# 3 judge the upper left
for t_i, t_j in zip(range(i-1, -1, -1),range(j-1, -1, -1)):
if Q[t_i][t_j] == 1:
return False
# 4 judge the upper right
for t_i, t_j in zip(range(i+1, N), range(j-1, -1, -1)):
if Q[t_i][t_j] == 1:
return False
# 5 judge the left bottom
for t_i, t_j in zip(range(i-1, -1, -1), range(j+1, N)):
if Q[t_i][t_j] == 1:
return False
# judge the right bottom:
for t_i, t_j in zip(range(i+1, N), range(j+1, N)):
if Q[t_i][t_j] == 1:
return False
return True
# use recursion method
def Queue(j):
global count
# global Q
for i in range(N):
if is_correct(i, j): # if the position(i,j) is a proper place to place the queen
Q[i][j] = 1 # place the queen
Queue(j+1) # go deeper
Q[i][j] = 0 # go back to the previous level
if j == N:
count += 1
# print(Q)
Queue(0)
print(count)
6.2 非递归解法
分析:我们可以尝试在将第一个皇后摆放在第0行第0列,为了不冲突,将第二个皇后摆放在第1行第3列…依次下去
然后发现第5行每个位置都有冲突,这说明上面4行肯定不能这么摆放,不然就无解。于是又回到上一行(第4行),找到另一个不冲突的位置。又继续在第5行摆放皇后,如果还不满足,又回到第4行,找另一个不冲突的位置。这个时候,如果第4行已经找不到另一个可以摆放的位置了,那么就又回到上一次(第3行),将皇后摆放在另一个不冲突的位置。然后又开始第4行摆放皇后……这就是回溯法。
参考链接here 这篇文章中使用一个栈实现的,比我这里的实现较为简单的多,可以在需要的时候借鉴一下。
注意:需要一个列表q用来存储对应第i行皇后所在的列,这样当回溯回来的时候,可以接着该列继续往后寻找符合条件的位置。另一方面,需要在找到一种符合情况的解时,能够将对应位置归0.
# Non Recursion
# N-Queens question
# reference:https://segmentfault.com/a/1190000003733325
# the number of queens
N = 8
Q = [[0 for i in range(N)] for j in range(N)]
count = 0
q = [-1 for i in range(N)]
def is_correct(i, j):
# judge (i,j) is or not a correct position to place a queen
global N
# global Q
# 1 judge the row
for k in range(N):
if Q[i][k] == 1 and k != j:
return False
# 2 judge the col
for k in range(N):
if Q[k][j] == 1 and k != i:
return False
# 3 judge the upper left
for t_i, t_j in zip(range(i-1, -1, -1),range(j-1, -1, -1)):
if Q[t_i][t_j] == 1:
return False
# 4 judge the upper right
for t_i, t_j in zip(range(i+1, N), range(j-1, -1, -1)):
if Q[t_i][t_j] == 1:
return False
# 5 judge the left bottom
for t_i, t_j in zip(range(i-1, -1, -1), range(j+1, N)):
if Q[t_i][t_j] == 1:
return False
# judge the right bottom:
for t_i, t_j in zip(range(i+1, N), range(j+1, N)):
if Q[t_i][t_j] == 1:
return False
return True
# use recursion method
def Queue():
global count
i = 0
while i < N:
flag = 0 # 判断是否找到了一个符合条件的位置
for j in range(q[i]+1, N):
if is_correct(i, j): # 如果找到一个符合条件的位置
q[i] = j # 使用q记录该位置,便于下一次从该位置之后寻找
Q[i][j] = 1 # 该位置标记为1
flag = 1 # 说明找到了符合条件的位置
break
if flag == 0: # 如果没有找到
i -= 1 # 回退到上一层
Q[i][q[i]] = 0 # 注意上一层的对应位置元素需要归0
q[i+1] = -1 # 表示下一层仍然需要从第0个位置开始遍历
if i == -1: # 如果已经返回到第0行,说明所有情况都找完了
break
else:
if i == N-1: # 如果找到了第N-1行,表示此结果满足条件
print(Q) # 输出结果
Q[i][q[i]] = 0 # 这里已经代表找到一种,因此此时的位置应该归0,这样才能接着往后找而不受影响
count += 1
else:
i += 1
Queue()
print(count)