python 常見的較為基礎的演算法題解析
1. 結尾0的個數
給你一個正整數列表 L, 輸出L內所有數字的乘積末尾0的個數。(提示:不要直接相乘,數字很多,相乘得到的結果可能會很大)。
輸入示例
輸入:L=[2,8,3,50]
輸出示例
輸出:2
解析
所有元素相乘, 算最後是有幾個0, 如果 [2, 5] 相乘後 是 10 , 0 個數位 1
本質上為了避免最後數字太大, 則每次乘算完了再除以 10 一次一次疊加即可
程式碼
import functools import collections def func(data): num = 1 c_num = 0 for i in data: num *= iwhile num % 10 == 0: num //= 10 c_num += 1 return c_num def test(data): res = collections.Counter(str(functools.reduce(lambda x, y: x * y, data))) return res.get("0") if __name__ == '__main__': data_1 = [2, 8, 3, 50] print(func(data_1)) print(test(data_1)) data_2= [2, 8, 3, 50, 6] print(func(data_2)) print(test(data_2)) data_3 = [2, 8, 3, 50, 6, 5] print(func(data_3)) print(test(data_3)) """ 2 2 2 2 3 3 """
2. 結尾非零數的奇偶性
給你一個正整數列表 L, 判斷列表內所有數字乘積的最後一個非零數字的奇偶性。如果為奇數輸出1,偶數則輸出0。
輸入示例
輸入:L=[2,8,3,50]
輸出示例
輸出:0
解析
在上題的基礎上的改造題, 這裡直接用另一種方式字串來處理, 轉換成字串後剔除掉所有的0後的的部分能否整除2即可判斷
程式碼
import functools def func(data): a_num = 1 for i in data: a_num *= i while str(a_num).endswith("0"): a_num = str(a_num)[:-1] a_num = int(a_num) return a_num % 2 # 最後的數字不重要, 不用最後的算也是一樣的 # last_num = int(str(a_num)[-1]) # print(last_num) # return last_num % 2 def test(data): return int(str(functools.reduce(lambda x, y: x * y, data)).split("0")[0]) % 2 if __name__ == '__main__': data_1 = [1, 7, 77, 77, 777] print(test(data_1)) print(func(data_1)) data_2 = [1, 2, 3, 4] print(test(data_2)) print(func(data_2)) """ 1 1 0 0 """
3. 光棍的悲傷
光棍們對1總是那麼敏感,因此每年的11.11被戲稱為光棍節。小Py光棍幾十載,光棍自有光棍的快樂。
讓我們勇敢地面對光棍的身份吧,現在就證明自己:給你一個整數a,數出a在二進位制表示下1的個數,並輸出。
輸入示例
輸入:a = 7
輸出示例
輸出:3
解析
7的二進位制是111,所以輸出答案是3。這道題考的是如何將十進位制整數轉化為二進位制數,方法就是:除二取餘,逆序讀取
因為判斷是1 的個數, 所以計算 1 既可以
程式碼
import collections def func(num): c_num = 0 while num: if num % 2: c_num += 1 num //= 2 return c_num def test(num): print(bin(num)) return collections.Counter(str(bin(num))).get("1") if __name__ == '__main__': data_1 = 7 print(test(data_1)) print(func(data_1)) data_2 = 10 print(test(data_2)) print(func(data_2)) """ 0b111 3 3 0b1010 2 2 """
4. 判斷三角形
給你三個整數a,b,c, 判斷能否以它們為三個邊長構成三角形。 若能,輸出YES,否則輸出NO
輸入示例
輸入:a = 5 b = 5 c = 5
輸出示例
輸出:YES
解析
三角形任意兩邊之和大於第三邊,可以利用列表排序求出較小的兩邊,然後相加再和第三邊比較,進行判斷
程式碼
def func(a, b, c): m_num = max(max(a, b), c) s_num = a + b + c return m_num < s_num - m_num if __name__ == '__main__': res = func(1, 2, 2) print(res)
5. 公約數的個數
給你兩個正整數a,b, 輸出它們公約數的個數。
輸入示例
輸入:a = 24 b = 36
輸出示例
輸出:6
解析
從1到兩數較小的數,看看是否可以同時被兩數整除,若可以則結果加一
基於此題可以繼續延展,
程式碼
def func(a, b): num = 0 for i in range(1, min(a, b) + 1): if a % i == 0 and b % i == 0: num += 1 return num if __name__ == '__main__': res = func(24, 36) print(res) // 6
6. 公約數的個數
我們經常遇到的問題是給你兩個數,要你求最大公約數和最小公倍數。今天我們反其道而行之,給你兩個數a和b,
計算出它們分別是哪兩個數的積的最大公約數和最小公倍數。輸出這兩個數,小的在前,大的在後,以空格隔開。
若有多組解,輸出它們之和最小的那組。
輸入示例
輸入:a=3, b = 60
輸出示例
輸出:12 15
解析
最大公約數與最小公倍數的乘積就是所求的兩數之積
然後題目要求兩數和最小,在兩數積不變的情況下,二者越接近,和越小。
所以從積的平方根開始,求第一個能被積整除的數即可
程式碼
def func(a, b): j = a * b for i in range(1, j + 1): _ = j / i if j % i == 0 and i >= j / i: return [int(j / i), i] if __name__ == '__main__': res = func(3, 60) print(res) # [12, 15]
8. 迴文子串
給你一個字串a和一個正整數n,判斷a中是否存在長度為n的迴文子串。
如果存在,則輸出YES,否則輸出NO。
迴文串的定義:記串str逆序之後的字串是str1,若str=str1,則稱str是迴文串,如"abcba".
輸入示例
輸入:a = "abcba" n = 5
輸出示例
輸出:YES
解析
根據正整數n進行分割字串,然後判斷字串是不是迴文串。
由於python中字串沒有直接提供reverse函式(列表list有,但需要先將字串轉換為列表,較麻煩),
所以採用字串切片。若一個字串為s,其逆序為s[::-1],前兩個空表示提取全部,-1表示逆序。
最常規的方法則是頭尾指標前後移動, 然後這樣可以從運算過程中就可以提前獲得結果
程式碼
def func(s): pre = 0 last = len(s) - 1 flag = True while pre <= last: if s[pre] != s[last]: flag = False break pre += 1 last -= 1 return flag def test_1(s): s_list = [i for i in s] s_list.reverse() rev_s = "".join(s_list) return rev_s == s def test_2(s): return s[::-1] == s if __name__ == '__main__': print(func("aabbcc")) print(test_1("aabbcc")) print(test_2("aabbcc")) print(func("abccba")) print(test_1("abccba")) print(test_2("abccba")) """ False False False True True True """
9. 山峰的個數
十一假期,小P出去爬山,爬山的過程中每隔10米他都會記錄當前點的海拔高度(以一個浮點數表示),
這些值序列儲存在一個由浮點陣列成的列表h中。
回到家中,小P想研究一下自己經過了幾個山峰,請你幫他計算一下,
例如:h=[0.9,1.2,1.22,1.1,1.6,0.99], 將這些高度順序連線,會發現有兩個山峰,故輸出一個2(序列兩端不算山峰)
輸入示例
輸入:h = [0.9, 1.2, 1.22, 1.1, 1.6, 0.99]
輸出示例
輸出:2
解析
一次遍歷即可,分別比較其與前一位和後一位的大小,注意列表不要越界
def func(s): if not s or len(s) < 3: return 0 c_num = 0 pre_i, cur_i, next_i = 0, 0, 1 for i in s: if cur_i == 0 or cur_i == len(s) - 1: pre_i += 0 cur_i += 1 next_i += 1 continue if i > s[pre_i] and i > s[next_i]: c_num += 1 pre_i += 1 cur_i += 1 next_i += 1 return c_num if __name__ == '__main__': print(func([0.9, 1.2, 1.22, 1.1, 1.6, 0.99])) # 2 print(func([0.9, 1.2, 1.22, 1.1, 1.6, 0.99, 1.9, 1.3])) # 3
10. 移動零
給定一個數組 nums,編寫一個函式將所有 0 移動到陣列的末尾,同時保持非零元素的相對順序
輸入示例
輸入: [0,1,0,3,12]
輸出示例
輸出: [1,3,12,0,0]
解析
雙指標, 或者冒泡
冒泡: 兩次迴圈, 第一次迴圈是所有的元素, 第二次是未排序的元素
第一次迴圈的元素如果是 0 就跟所有未排序的元素進行互換, 將 0 換到最後面
然後繼續下一個未排序的元素進行對比
x = [0, 8, 9, 0, 10, 0] for i in range(len(x)): if x[i] == 0: for j in range(i, len(x)): if j + 1 < len(x) - i: x[j], x[j + 1] = x[j + 1], x[j] print(x)
雙指標: 設定一個指標 j 從 0 開始, 然後另一個指標遍歷陣列
將指標i,j先指向第一個元素,讓i不斷後移,如果i指向的是一個非零元素,
那麼將 i 指向元素的值直接填入 j 指向的位置,然後 j 指標後移;
如果 i 指向的是一個零元素,那麼指標 i 就直接後移,而 j 不變。
當 i 已經全部遍歷完後,從 j 的位置開始,直接往後全部填入零。
x = [0, 8, 9, 0, 10, 0] j = 0 for i, v in enumerate(x): if v != 0: x[i], x[j] = x[j], x[i] j += 1 print(x) # [8, 9, 10, 0, 0, 0]
11. 有序陣列的平方
給你一個按 非遞減順序 排序的整數陣列 nums
,返回 每個數字的平方 組成的新陣列,要求也按 非遞減順序 排序。
輸入示例
輸入:nums = [-4,-1,0,3,10]
輸出:[0,1,9,16,100]
解釋:平方後,陣列變為 [16,1,0,9,100]
排序後,陣列變為 [0,1,9,16,100]
輸出示例
輸入:nums = [-7,-3,2,3,11] 輸出:[4,9,9,49,121]
解析
如果是都是整數都是負數就過於簡單了. 因此這題的本質是兩個有序陣列的組合後進行拆分併合並
方法比較多, 比如先找出最中心的數字後轉換成兩個陣列進行歸併運算, 但是雙指標方法是最簡單的
截止條件定在為正負分界線上, 一旦達到表示其中一個數字遍歷完畢, 剩下的直接進行追加即可
x = [-7, -5, -3, -1, 2, 4, 8, 10] res = [] i = 0 j = len(x) - 1 while x[i] < x[j]: if x[i] * x[i] > x[j] * x[j]: res.insert(0, x[i] * x[i]) i += 1 else: res.insert(0, x[j] * x[j]) j -= 1 for each in x[i:j]: res.insert(0, each * each) print(res)
12. 盛最多水的容器
給你 n 個非負整數 a1,a2,…,an,每個數代表座標中的一個點 (i, ai) 。
在座標內畫 n 條垂直線,垂直線 i 的兩個端點分別為 (i, ai) 和 (i, 0)。
找出其中的兩條線,使得它們與 x 軸共同構成的容器可以容納最多的水。
說明:你不能傾斜容器,且 n 的值至少為 2。
輸入示例
輸出示例
輸入:height = [1,1] 輸出:1
解析
運用雙指標 + 貪心演算法
前後各一個指標往中間移動, 貪心演算法每次保留最大值
具體的移動邏輯: 對比前後指標的所在柱子的高度. 高度低的那個進行移動, 左指標往右移, 右指標往左移
x = [1, 8, 6, 2, 5, 4, 8, 3, 7] # x = [1, 1] res = [] i = 0 j = len(x) - 1 max_num = 0 while i < j: max_num = max(max_num, min(x[i], x[j]) * (j - i)) if x[i] < x[j]: i += 1 else: j -= 1 print(max_num) # 49
13. 顏色分類
給定一個包含紅色、白色和藍色,一共 n 個元素的陣列,
原地對它們進行排序,使得相同顏色的元素相鄰,並按照紅色、白色、藍色順序排列。
此題中,我們使用整數 0、 1 和 2 分別表示紅色、白色和藍色。
輸入示例
[2,0,2,1,1,0]
輸出示例
[0,0,1,1,2,2]
解析
使用計數器計算出來 0, 1, 2 的數量之後對陣列進行重構
但是此方法不算是特別合理, 沒有做到原地排序
import collections x = [2, 0, 2, 1, 1, 0] c = collections.Counter(x) res = [] for i in [0, 1, 2]: res.extend([i for each in range(c[i])]) print(res) # [0, 0, 1, 1, 2, 2]
三指標操作, p0 設定為 0 的最後一個索引, p2 設定為 2 的最開始的索引.
遇到 0 更新 p0 往後一位,
x = [2, 0, 2, 1, 1, 0] p_0_last = cur = 0 p_2_first = len(x) - 1 while cur < p_2_first: if x[cur] == 0: x[cur], x[p_0_last] = x[p_0_last], x[cur] cur += 1 p_0_last += 1 elif x[cur] == 2: x[cur], x[p_2_first] = x[p_2_first], x[cur] p_2_first -= 1 else: cur += 1 print(x)
14. 遞增的三元子序列
給定一個未排序的陣列,判斷這個陣列中是否存在長度為 3 的遞增子序列。
輸入示例
輸入: [1,2,3,4,5] 輸出: true 示例 2: 輸入: [5,4,3,2,1] 輸出: false
輸出示例
輸入: [1,2,3,4,5] 輸出: true 示例 2: 輸入: [5,4,3,2,1] 輸出: false
解析
注意題意是可以不連續, 比如 [5, 3,6, 3, 2] 中的 [5, 3, 2] 即滿足了要求可以作為遞增的三序列
本題用到貪心演算法
快取下來前三序列的前兩個數字, 最小值和中間值, 中間值預設給到正無窮, 最小值給到一個元素
然後不斷更新最小的數字和中間數
每次對比, 如果當前的值比中間數大則表示找到了
若比中間值小但是比最小值大則更新 中間值
若比最小值還小. 則更新最小值, 但是注意此時最小值的數字的位置其實是比中間值的數字位置後面的
但是中間值前面還是存在一個數字比中間值小的(就是被替換掉的老的最小值, 替換掉了不代表就不存在了.)
比對依舊成立, 依次遍歷到最後即可
class Solution: def increasingTriplet(self, nums) -> bool: n = len(nums) if n < 3: return False first, second = nums[0], float('inf') for i in range(1, n): num = nums[i] if num > second: return True if num > first: second = num else: first = num return False if __name__ == '__main__': s = Solution() nums = [5, 1, 6] print(s.increasingTriplet(nums))
15. 和為s的連續正數序列
輸入一個正整數 target ,輸出所有和為 target 的連續正整數序列(至少含有兩個數)。
序列內的數字由小到大排列,不同序列按照首個數字從小到大排列。
輸入示例
輸入:target = 9 輸出:[[2,3,4],[4,5]]
輸出示例
輸入:target = 15 輸出:[[1,2,3,4,5],[4,5,6],[7,8]]
解析
利用滑動視窗的原理, 記錄當前視窗內的值的和
若和小於目標值. 右邊界向右移動, 視窗擴大
若和大於目標值. 左邊界向右移動, 視窗縮小, 拋棄之前的左邊界的值, 此值做不到和為目標值
序列最少要求有兩個數字, 至少兩個數字相加才等於目標值, 且數字是遞增. 則迴圈次數限制到 目標值的一半即可
def findContinuousSequence(target): i = 1 # 滑動視窗的左邊界 j = 1 # 滑動視窗的右邊界 sum = 0 # 滑動視窗中數字的和 res = [] while i <= target // 2: if sum < target: # 右邊界向右移動 sum += j j += 1 elif sum > target: # 左邊界向右移動 sum -= i i += 1 else: # 記錄結果 arr = list(range(i, j)) res.append(arr) # 左邊界向右移動 sum -= i i += 1 return res if __name__ == '__main__': print(findContinuousSequence(9))
17. 除自身以外陣列的乘積
給定長度為 n 的整數陣列 nums,其中 n > 1,返回輸出陣列 output ,
其中 output[i] 等於 nums 中除 nums[i] 之外其餘各元素的乘積
輸入示例
[1,2,3,4]
輸出示例
[24,12,8,6]
解析
不允許使用除法
乘積 = 當前數左邊的乘積 * 當前數右邊的乘積
import functools class Solution: def multiply(self, nums): res = [] for i, v in enumerate(nums): left = 1 if i == 0 else functools.reduce(lambda x, y: x * y, nums[:i]) right = 1 if i == len(nums) - 1 else functools.reduce(lambda x, y: x * y, nums[i + 1:]) res.append(left * right) return res if __name__ == '__main__': Solution().multiply([1, 2, 3, 4])
18. 楊輝三角
實現第n行是啥
輸入示例
7
輸出示例
[1, 6, 15, 20, 15, 6, 1]
解析
遞迴處理較為簡單, 每層都是上層資料的疊加, 最頂層的 1和第二層的 1 1是確定的, 作為截止條件
tmp_dict = {} def make_list(n): if tmp_dict.get(n): return tmp_dict[n] res = [] if n == 1: return [1] if n == 2: return [1, 1] for i in range(n): if i == 0: res.append(1) elif i == n - 1: res.append(1) return res else: f_list = make_list(n - 1) res.append(f_list[i] + f_list[i - 1]) tmp_dict[n] = res return res print(make_list(7)) # [1, 6, 15, 20, 15, 6, 1]
19. 最大子陣列和
給你一個整數陣列 nums
,請你找出一個具有最大和的連續子陣列(子陣列最少包含一個元素),返回其最大和。
子陣列是陣列中的一個連續部分。
輸入示例
輸入:nums = [-2,1,-3,4,-1,2,1,-5,4] 輸出:6 解釋:連續子陣列 [4,-1,2,1] 的和最大,為 6 。
輸出示例
輸入:nums = [5,4,-1,7,8] 輸出:23
解析
1 <= nums.length <= 105
-104 <= nums[i] <= 104
利用動態規劃的思想, 找出具備一個子序列, 子序列的結尾的可能性是根據陣列的每個元素來的
以 [5,4,-1,7,8] 為例, 找到最大值的子序列的全部的情況為
結尾為 5 的所有序列 + 結尾為 4 的所有序列 + ..... + 結尾為 8 的所有序列
然後問題在分化針對 每個元素的可能性進行先一輪的判斷
結尾為 5 的所有序列中最大的一個序列 or 結尾為 4 的所有序列中最大的一個序列 or .....結尾為 8 的所有序列中最大的一個序列
這樣核心的問題被分解成了兩級的子問題來處理