牛客網經典例題
引言
收錄一些在做線上題的過程中遇到的經典的問題和解決方法,便於之後的複習和回顧。
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