2020 年最全 Python 面试题汇总 (五)
@Author:Runsen
文章目錄
- 81、逆序對
- 82、手寫一個棧
- 83、有效的擴號
- 84、擴號的生成
- 85、最長有效括號
- 86、比特位計數問題
- 87、判斷給定的數是否為丑數
- 88、找出第 n 個丑數
- 89、超級丑數
- 90、合并區間
- 91、區間列表的交集
- 92、刪除被覆蓋區間
- 93、最長上升子序列
- 94、接雨水
- 95、逛街
- 96、滑動窗口的最大值
- 97、最長回文子串
- 98、 回文子串
- 99、編輯距離
- 100、島嶼個數
81、逆序對
逆序對應的題目來源:Leetcode劍指 Offer 51. 數組中的逆序對
""" @Author:Runsen 一、逆序數:給定一個數組array[0,...,n-1], 若對于某兩個元素array[i],array[j],若i<j and array[i]>array[j],則認為array[i],array[j]是逆序對。一個數組中包含的逆序對的數目稱為該數組的逆序數。設計一個算法求一個數組的逆序數 二、利用 歸并排序 的思想在歸并排序中,會將兩個升序的數組進行合并,利用升序數組的特性,可以快速求得逆序數 """class Solution:def reversePairs(self, nums: List[int]) -> int:self.cnt = 0def merge(nums, start, mid, end, temp):i, j = start, mid + 1while i <= mid and j <= end:if nums[i] <= nums[j]:temp.append(nums[i])i += 1else:self.cnt += mid - i + 1temp.append(nums[j])j += 1while i <= mid:temp.append(nums[i])i += 1while j <= end:temp.append(nums[j])j += 1for i in range(len(temp)):nums[start + i] = temp[i]temp.clear()def mergeSort(nums, start, end, temp):if start >= end: returnmid = (start + end) >> 1mergeSort(nums, start, mid, temp)mergeSort(nums, mid + 1, end, temp)merge(nums, start, mid, end, temp)mergeSort(nums, 0, len(nums) - 1, [])return self.cnt82、手寫一個棧
順序棧是使用順序表存儲數據的棧,在很多面試中需要手寫一個棧。
class Stack(object):def __init__(self):"""創建一個Stack類對棧進行初始化參數設計"""self.stack = [] #存放元素的棧 ?def push(self, data):"""壓入 push :將新元素放在棧頂當新元素入棧時,棧頂上移,新元素放在棧頂。"""self.stack.append(data) ?def pop(self):"""彈出 pop :從棧頂移出一個數據- 棧頂元素拷貝出來- 棧頂下移- 拷貝出來的棧頂作為函數返回值"""# 判斷是否為空棧if self.stack:return self.stack.pop()else:raise IndexError("從空棧執行彈棧操作") ?def peek(self):"""查看棧頂的元素"""# 判斷棧是否為空if self.stack:return self.stack[-1] ?def is_empty(self):"""判斷棧是否為空"""# 棧為非空時,self.stack為True,再取反,為Falsereturn not bool(self.stack) ?def size(self):"""返回棧的大小"""return len(self.stack)83、有效的擴號
這是Leetcode的第20題。給定一個只包括'(',')','{','}','[',']'的字符串,判斷字符串是否有效。
''' @Author: Runsen @WeChat:RunsenLiu @微信公眾號: Python之王 @CSDN: https://blog.csdn.net/weixin_44510615 @Github: https://github.com/MaoliRUNsen @Date: 2020/9/8 ''' class Solution:def isValid(self, s: str) -> bool:stack = []d = {"{": "}", "[": "]", "(": ")"}for i in s:if i in d:stack.append(i)else:# 記住如果使用了stack.pop() 那么就已經默認刪除了 pop這里已經執行了# 還有not stack和 len(stack) == 0等價if (len(stack) == 0) or (d[stack.pop()] != i):return Falsereturn len(stack) == 084、擴號的生成
這是Leetcode的第22題。數字 n 代表生成括號的對數,請你設計一個函數,用于能夠生成所有可能的并且 有效的 括號組合。
class Solution:def generateParenthesis(self, n: int) -> List[str]:res = []# 回溯算法# 括號生成第一個必須是左括號,那么下一個可能是左括號和右括號,def g(left,right,n,result):if left == n and right == n:res.append(result)if left < n:g(left+ 1,right,n,result + "(")# 有效的前提需要左括號大于右括號if left > right and right < n:g(left,right+ 1,n,result + ")")g(0,0,n,"")return res85、最長有效括號
這是Leetcode的第32題。給定一個只包含 ‘(’ 和 ‘)’ 的字符串,找出最長的包含有效括號的子串的長度。
這道題在今年的秋招中見過,而且牛客在每周的周四算法面試題也講過。解決的方法就是動態規劃和棧。
棧的方法很容易想到,九月份我用的棧方法通過AC。
class Solution:def longestValidParentheses(self, s: str) -> int:'''@Author:Runsen思路:對于遇到的每個 ‘(’ ,我們將它的下標放入棧中 對于遇到的每個 ‘)’ ,我們先彈出棧頂元素表示匹配了當前右括號: 如果棧為空,說明當前的右括號為沒有被匹配的右括號,我們將其下標放入棧中來更新我們之前提到的「最后一個沒有被匹配的右括號的下標」 如果棧不為空,當前右括號的下標減去棧頂元素即為「以該右括號為結尾的最長有效括號的長度」'''if not s: return 0stack = []res= 0for i in range(len(s)):# 入棧條件if not stack or s[i] == '(' or s[stack[-1]] == ')':stack.append(i) # 下標入棧else:# 計算結果stack.pop()res = max(res, i - (stack[-1] if stack else -1))return res對于dp,這里分兩種情況,一個是,s[i]=‘)’ 且s[i - 1] =‘(’,也就是字符串形如 “……()”“……()”,我們可以推出dp[i] = dp[i -2]+2。
還有一個是,s[i]=‘)’ 且 s[i - 1] = ‘)’,也就是字符串形如 “……))”,可以推出:dp[i] = dp[i - 1] + dp[i - dp[i - 1] - 2] +2。
這里狀態轉移方程不好理解,建議去官網查看
class Solution:def longestValidParentheses(self, s: str) -> int: maxans = 0dp = [0]*len(s)for i in range(len(s)):if s[i] == ")":if s[i - 1] == "(":dp[i] = (dp[i -2] if i >= 2 else 0 ) + 2elif i - dp[i - 1] > 0 and s[i - dp[i - 1] - 1] == "(":dp[i] = dp[i - 1] + (dp[i - dp[i - 1] - 2] if i - dp[i - 1] >= 2 else 0) +2maxans = max(maxans, dp[i])return maxans86、比特位計數問題
在阿里筆試比較常見二進制和丑數等數學的問題。
這是Leetcode338面試題比特位計數:給定一個非負整數 num。對于 0 ≤ i ≤ num 范圍中的每個數字 i ,計算其二進制數中的 1 的數目并將它們作為數組返回。
輸入: 2
輸出: [0,1,1]
示例 2:
輸入: 5
輸出: [0,1,1,2,1,2]
最快速的方法就是直接count進行計數就可以了。
class Solution:def countBits(self, num: int) -> List[int]:res = []for i in range(num+1):count = bin(i).count("1")res.append(count)return res但很多的時候,面試官要求換一個思路,其實就是動態規劃
觀察:
十進制0: 二進制0
十進制1: 二進制1
十進制2: 二進制10
十進制3: 二進制11
十進制4: 二進制100
十進制5: 二進制101
二進制中,乘以2相當于左移一位,1的個數不會改變;
由于偶數的二進制形式結尾一定是0,所以一個偶數加1變為奇數,只會將其結尾的0變為1;
所以狀態轉移方程為:
dp(i)=dp(i//2)dp(i) = dp(i//2)dp(i)=dp(i//2) 若i為偶數; 這里//2保證是整數,防止溢出
dp(i)=dp(i?1)+1dp(i) = dp(i-1)+1dp(i)=dp(i?1)+1 若i為奇數。
邊界條件: dp(0) = 0。最后利用二進制方法將兩個判斷合并起來得到dp[i] = dp[i>>1] + (i&1),這里 i>>1代表前一個二進制位的次數,i&1代表i的末尾是否為1
class Solution:def countBits(self, num: int) -> List[int]:dp = [0]for i in range(1, num + 1):dp.append(dp[i>>1] + (i&1))return dp87、判斷給定的數是否為丑數
這是Leetcode 263:判斷給定的數是否為丑數:編寫一個程序判斷給定的數是否為丑數。丑數就是只包含質因數 2, 3, 5 的正整數。
此題是簡單題:一個思路是遞歸,一個思路就是直接暴力。
第一步:只要num大于5(2,3,5中5最大)就繼續循環。
第二步:只要num不能整除其中三個數任意一個,就返回False。
第三步:繼續除以可以整除的循環。
備注:這里需要注意的是題目中“1”是返回True。
88、找出第 n 個丑數
這是Leetcode 264:找出第 n 個丑數:編寫一個程序,找出第 n 個丑數。丑數就是質因數只包含 2, 3, 5 的正整數。
Leetcode 264:
示例:輸入: n = 10 輸出: 12 解釋: 1, 2, 3, 4, 5, 6, 8, 9, 10, 12 是前 10 個丑數。此題是一個我想不到的dp。想一想丑數肯定是一個來源2,3,5 其中一個倍數,然后就是每次選出最小的丑數并添加到數組中。并將該丑數對應的因子指針往前走一步。重復該步驟直到計算完 。對應的dp狀態轉移方程:min(res[i2] * 2, res[i3] * 3, res[i5] * 5)
class Solution:def nthUglyNumber(self, n: int) -> int:res = [1]# 三指針i2 = i3 = i5 = 0for _ in range(1,n):ugly = min(res[i2] * 2, res[i3] * 3, res[i5] * 5)res.append(ugly)if ugly == res[i2] * 2: i2 +=1if ugly == res[i3] * 3: i3 +=1if ugly == res[i5] * 5: i5 +=1return res[-1]89、超級丑數
這是Leetcode 313:超級丑數:編寫一個程序,找出超級丑數,只不過因子變了。
示例:輸入: n = 12, primes = [2,7,13,19] 輸出: 32 解釋: 給定長度為 4 的質數列表 primes = [2,7,13,19],前 12 個超級丑數序列為:[1,2,4,7,8,13,14,16,19,26,28,32] 。方法和上面三指針的dp完全一樣。我們在尋找新的超級丑數的時候,只需要尋找M,并選擇M數組中最小的一個數來作為這個新的超級丑數就好了。
其實沒什么難度,算法的小邏輯做了一些調整,但是算法的思路并沒有改變。
class Solution:def nthSuperUglyNumber(self, n: int, primes: List[int]) -> int:nums = [0 for _ in range(len(primes))]result = [1]for i in range(1,n):ugly = min(result[nums[j]] * primes[j] for j in range(len(nums)))result.append(ugly)for k in range(len(nums)):if ugly == result[nums[k]] * primes[k]:nums[k] +=1return result[-1]90、合并區間
這是Leetcode 56:合并區間:給出一個區間的集合,請合并所有重疊的區間。
示例 1:輸入: intervals = [[1,3],[2,6],[8,10],[15,18]] 輸出: [[1,6],[8,10],[15,18]] 解釋: 區間 [1,3] 和 [2,6] 重疊, 將它們合并為 [1,6].原理就是:新的區間左邊的數字為原第一個區間左邊的數字,新區間右邊的數字為 原第一個區間右邊數字和原第二個區間右邊數字的最大值。
class Solution:def merge(self, intervals: List[List[int]]) -> List[List[int]]:if not intervals: return []# 對區間進行排序intervals.sort(key=lambda x:x[0])res = [intervals[0]]for i in range(1,len(intervals)):if intervals[i][0] <= res[-1][1]:res[-1] = [res[-1][0], max(intervals[i][1], res[-1][1])]else:res.append(intervals[i])return res91、區間列表的交集
這是Leetcode986 :區間列表的交集:給定兩個由一些 閉區間 組成的列表,每個區間列表都是成對不相交的,并且已經排序。
區間列表的交集,一個雙指針。關鍵就是最后一步,指針i和j什么時候應該前進呢?只要判斷兩個數組右指針的大小可以 了。
class Solution:def intervalIntersection(self, A: List[List[int]], B: List[List[int]]) -> List[List[int]]:i, j = 0, 0 # 雙指針res = []while i < len(A) and j < len(B):a1, a2 = A[i][0], A[i][1]b1, b2 = B[j][0], B[j][1]# 兩個區間存在交集if b2 >= a1 and a2 >= b1:# 計算出交集,加入 resres.append([max(a1, b1), min(a2, b2)])# 指針前進if b2 < a2: j += 1else: i += 1return res92、刪除被覆蓋區間
這是Leetcode1288:刪除被覆蓋區間:給你一個區間列表,請你刪除列表中被其他區間所覆蓋的區間。
示例:
輸入:intervals = [[1,4],[3,6],[2,8]]
輸出:2
解釋:區間 [3,6] 被區間 [2,8] 覆蓋,所以它被刪除了。
93、最長上升子序列
這是Leetcode300:最長上升子序列:給定一個無序的整數數組,找到其中最長上升子序列的長度。
輸入: [10,9,2,5,3,7,101,18]
輸出: 4
解釋: 最長的上升子序列是 [2,3,7,101],它的長度是 4。
關鍵還是怎么找出dp和轉移方程,dp[i]是第i個最長上升子序列。那么dp[i]=max(dp[i],dp[k]+1)其中0<k<i?1dp[i] = max(dp[i], dp[k] + 1) 其中 0<k<i-1dp[i]=max(dp[i],dp[k]+1)其中0<k<i?1
class Solution:def lengthOfLIS(self, nums: List[int]) -> int:# 如果定義dp dp[i] 最長上升子序列 那么 dp[i] = max(dp[i], dp[k] + 1) 0<k<i-1m = len(nums)if m <= 1:return mdp = [ 1 for _ in range(m)]for i in range(1,m):for j in range(i):if nums[i] > nums[j]:dp[i] = max(dp[i], dp[j]+ 1 ) return max(dp)94、接雨水
這是Leetcode 42 :接雨水:給定 n 個非負整數表示每個寬度為 1 的柱子的高度,計算按此排列的柱子,下雨之后能接多少雨水。
本題的關鍵點在于,具備什么條件的格子可以存水?
(1) 空間未被柱子占據;(2) 左右兩側均有比當前位置高或者等于的柱子。
其實就是尋找凹陷的地方。常規做法就是找到最高的柱子,分成兩份,尋找凹陷的地方。
# @Author:Runsen # @Date:2020/09/30class Solution:def trap(self, height: List[int]) -> int:# 尋找最大的柱子maxindex = maxvalue = 0n = len(height)for i in range(n):if height[i] > maxvalue:maxvalue = height[i]maxindex = i# 左邊找凹槽a = res = 0for i in range(maxindex):if a < height[i]:a = height[i]continueres = res + a - height[i]# 右邊找凹槽b = 0 for i in range(n-1,maxindex,-1):if b < height[i]:b = height[i]continueres = res + b - height[i]return res95、逛街
逛街此題來源于牛客,是今年秋招一個題目。
題目描述:輸出一行,包含空格分割的n個數字vi,分別代表小Q在第i棟樓時能看到的樓的數量。
輸入
6 5 3 8 3 2 5輸出:3 3 5 4 4 4
逛街此題最簡單易懂的方法使用單調棧,
n = int(input()) a = list(map(int, input().split())) # left right為棧 left = [] right = [] # l_num左邊看到的樓層 l_num = [] # r_num右邊看到的樓層 r_num = [] for i in range(len(a)): # 向左看l_num.append(len(left))if (i == 0): # 第一個值特殊處理left.append(a[i])elif (a[i] < left[-1]): # 入棧操作left.append(a[i])else:while (len(left) != 0 and a[i] >= left[-1]):left.pop(-1)left.append(a[i]) # 將a進行翻轉,進行向右看 a.reverse() for i in range(len(a)): # 向右看r_num.append(len(right))if (i == 0): # 第一個值特殊處理right.append(a[i])elif (a[i] < right[-1]): # 入棧操作right.append(a[i])else:while (len(right) != 0 and a[i] >= right[-1]):right.pop(-1)right.append(a[i]) # r_num也要翻轉 r_num.reverse() ans = [] for i in range(len(a)):ans.append(l_num[i] + r_num[i] + 1) ansstr = ' '.join([str(i) for i in ans]) print(ansstr)96、滑動窗口的最大值
這是Leetcode 239 :滑動窗口的最大值:給定一個數組 nums,有一個大小為 k 的滑動窗口從數組的最左側移動到數組的最右側。你只可以看到在滑動窗口內的 k 個數字。滑動窗口每次只向右移動一位。
''' @Author: Runsen @WeChat:RunsenLiu @微信公眾號: Python之王 @CSDN: https://blog.csdn.net/weixin_44510615 @Github: https://github.com/MaoliRUNsen @Date: 2020/9/8輸入: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3 輸出: [3,3,5,5,6,7] 解釋:滑動窗口的位置 最大值 --------------- ----- [1 3 -1] -3 5 3 6 7 31 [3 -1 -3] 5 3 6 7 31 3 [-1 -3 5] 3 6 7 51 3 -1 [-3 5 3] 6 7 51 3 -1 -3 [5 3 6] 7 61 3 -1 -3 5 [3 6 7] 7'''def maxSlidingWindow(nums,k):if not nums: return []window ,res = [],[]for i,x in enumerate(nums):# 如果存在窗口 而且窗口的第一個數 不在這個范圍,就出去if i >= k and window[0] <= i-k:window.pop(0)# 每次進入窗口的和最后一個比較,如果大了,最后一個直接刪除while window and nums[window[-1]] <= x:window.pop()# 無論是不是刪除最后一個,都要加入x到窗口中window.append(i)# 如果出了窗口,就把窗口的頭加入到res中if i >= k-1:res.append(nums[window[0]])print(window)return resprint(maxSlidingWindow(nums = [1,3,-1,-3,5,3,6,7], k = 3))97、最長回文子串
這是Leetcode 5 :最長回文子串:給定一個字符串 s,找到 s 中最長的回文子串。
最長回文子串應該采取 中心擴散的思想,最后需要判斷回文的長度是奇數還是偶數的情況,如果是奇數形回文,就以當前字符為中心左右兩邊尋找,例如回文"bab";如果是偶數形回文,需要兩個字符,并且這兩個字符是相等的,則需要以當前字符和其相鄰的字符為中心向左右兩邊尋找,例如回文"abba"。
class Solution:def longestPalindrome(self, s: str) -> str:def helper(s, l, r):# 中心擴散while l >= 0 and r < len(s) and s[l] == s[r]:l -= 1r += 1return s[l + 1:r]res = ""for i in range(len(s)):# 奇數形回文, like "aba"tmp = helper(s, i, i)if len(tmp) > len(res):res = tmp# 偶數形回文, like "abba"tmp = helper(s, i, i + 1)if len(tmp) > len(res):res = tmpreturn res98、 回文子串
這是Leetcode 647:最長回文子串:計算這個字符串中有多少個回文子串
class Solution(object):def countSubstrings(self, s):""":type s: str:rtype: int"""# 遍歷s,將每個位置的字符當做回文中心擴散n = len(s)# 一個字符也算是回文,所以開局count就是s中字符的數量count = nfor i in range(n):# 如果有兩個相同的字符,那么將這兩個相同字符作為回文中心擴散if i+1 < n and s[i+1] == s[i]:count += 1left, right = i-1, i+2while left >= 0 and right < n and s[left] == s[right]:count += 1left -= 1right += 1# 以當前字符作為回文中心開始擴散left, right = i-1, i+1while left >= 0 and right < n and s[left] == s[right]:count += 1left -= 1right += 1return count99、編輯距離
這是Leetcode 72:編輯距離:給你兩個單詞 word1 和 word2,請你計算出將 word1 轉換成 word2 所使用的最少操作數 (替換,刪除,插入)。
編輯距離考察的是二維動態規劃。dp[i][j]代表 word1到i位置轉換成word2 到j位置需要最少步數。
所以,當word1[i] == word2[j],dp[i][j] = dp[i-1][j-1];當 word1[i] != word2[j],dp[i][j] = min(dp[i-1][j-1], dp[i-1][j], dp[i][j-1]) + 1。其中,dp[i-1][j-1]表示替換操作,dp[i-1][j]表示刪除操作,dp[i][j-1]表示插入操作。
class Solution:def minDistance(self, word1: str, word2: str) -> int:n1 = len(word1)n2 = len(word2)dp = [[0] * (n2 + 1) for _ in range(n1 + 1)]# 第一行for j in range(1, n2 + 1):dp[0][j] = dp[0][j-1] + 1# 第一列for i in range(1, n1 + 1):dp[i][0] = dp[i-1][0] + 1for i in range(1, n1 + 1):for j in range(1, n2 + 1):if word1[i-1] == word2[j-1]:dp[i][j] = dp[i-1][j-1]else:dp[i][j] = min(dp[i][j-1], dp[i-1][j], dp[i-1][j-1] ) + 1#print(dp) return dp[-1][-1]100、島嶼個數
這是Leetcode 200:島嶼個數:給定一個由 ‘1’(陸地)和 ‘0’(水)組成的的二維網格,計算島嶼的數量。一個島被水包圍,
這題如果使用DFS比較簡單,我們需要找到所有的連著的"1",然后答案自增1,后面繼續尋找沒有被訪問過了"1",我們可以把訪問過了的"1"變成"0",因為我們對"0"的區域沒有做任何操作。
class Solution:def numIslands(self, grid: List[List[str]]) -> int:if not grid: return 0count = 0for i in range(len(grid)):for j in range(len(grid[0])):if grid[i][j] == '1':self.dfs(grid, i, j)count += 1return countdef dfs(self, grid, i, j):if i < 0 or j < 0 or i >= len(grid) or j >= len(grid[0]) or grid[i][j] != '1':returngrid[i][j] = '0'self.dfs(grid, i + 1, j)self.dfs(grid, i - 1, j)self.dfs(grid, i, j + 1)self.dfs(grid, i, j - 1)島嶼個數的問題其實是引出并查集的算法,更多的解法查看Leetcode官網
如果你想跟博主建立親密關系,可以關注博主,或者關注博主公眾號“Python之王”,了解一個非本科程序員是如何成長的。
博主ID:潤森,希望大家點贊、評論、收藏
總結
以上是生活随笔為你收集整理的2020 年最全 Python 面试题汇总 (五)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 猎豹式坦克歼击车?
- 下一篇: 二十四、深入Python多进程multi