一行Python解Leetcode習(xí)題
語法簡單,提供大量開箱即用的工具是Python語言的一大特點(diǎn),也是其受歡迎的重要特點(diǎn)。所謂“人生苦短,我用Python”,并不是說Python比其他語言性能好、也不是說Python比其它語言優(yōu)秀,而是說它方便,易用,可用于思路驗證、原型實現(xiàn),也可用于快速開發(fā);其開發(fā)效率高的特點(diǎn)使其的網(wǎng)絡(luò)編程、爬蟲和數(shù)據(jù)開發(fā)領(lǐng)域極受歡迎。
知乎上甚至有專門的話題討論一行Python能干什么,話題下的回答有的令人大開眼界,也有的強(qiáng)行一行。受到話題啟發(fā),在此不完全歸納Leetcode上的能用一行Python代碼解出的習(xí)題。
學(xué)習(xí)Python中的小伙伴,需要學(xué)習(xí)資料的話,可以到我的微信公眾號:Python學(xué)習(xí)知識圈,后臺回復(fù):“01”,即可拿Python學(xué)習(xí)資料
什么是Leetcode
Leetcode被稱為程序員最喜愛的網(wǎng)站之一,其主要功能是提供了一個方便、便捷的程序員做題平臺。現(xiàn)在已經(jīng)有中文網(wǎng)站:https://leetcode-cn.com。
什么樣的代碼能稱為一行解題
Leetcode中,Python解題一般是給定一個Solution類,給定一個類方法,要求你填寫方法體。如果方法體中只有一行代碼,則稱為用一行代碼解了該題。
讓我們開始看題吧!
349 兩個數(shù)組的交集
給定兩個數(shù)組,編寫一個函數(shù)來計算它們的交集。
示例 1:
輸入: nums1 = [1,2,2,1], nums2 = [2,2]
輸出: [2]
示例 2:
輸入: nums1 = [4,9,5], nums2 = [9,4,9,8,4]
輸出: [9,4]
說明:
輸出結(jié)果中的每個元素一定是唯一的。
我們可以不考慮輸出結(jié)果的順序。
解題思路:
按照一般思路,我們可以構(gòu)造一個新的列表,遍歷和對比輸入的兩個列表,將輸入列表中相同的元素加入新的列表,注意新的列表中不要加入重復(fù)元素即可。這咱思路,需要遍歷輸入的兩個列表,還要查詢要加入的元素是否已經(jīng)存在于目標(biāo)列表,對目標(biāo)列表也要不斷查詢,性能并不理想,也不會是面試官能接受的答案。更進(jìn)一步的思路,我們可以利用集合或字典中鍵的不重復(fù)性來構(gòu)造目標(biāo)元素的容器。具體解題方法可以參加Leetcode上本題題解。
而在Python中,提供了集合這一數(shù)據(jù)類型,其支持集合的交并補(bǔ)等操作,所以我們可以直接利用這一特性。
class Solution:
def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
return list(set(nums1) & set(nums2))
可以看到,我們先將列表轉(zhuǎn)成集合,再對兩個集合取交,最后又轉(zhuǎn)成列表返回。
557. 反轉(zhuǎn)字符串中的單詞 III
給定一個字符串,你需要反轉(zhuǎn)字符串中每個單詞的字符順序,同時仍保留空格和單詞的初始順序。
示例 1:
輸入: "Let's take LeetCode contest"
輸出: "s'teL ekat edoCteeL tsetnoc"
注意:在字符串中,每個單詞由單個空格分隔,并且字符串中不會有任何額外的空格。
解題思路:
在Python中,許多可迭代對象都支持[::-1]反轉(zhuǎn)操作,字符串也支持這樣的操作。于是我們只要利用string.split()方法將句子切割開,再對每一個單詞反轉(zhuǎn)即可。
class Solution:
def reverseWords(self, s: str) -> str:
return ' '.join([e[::-1] for e in s.split(' ')])
注意:此處用到的列表推導(dǎo),Python中的大部分可迭代對象都支持就地推導(dǎo)。這也是“一行Python”中的常用操作。
561. 數(shù)組拆分 I
給定長度為 2n 的數(shù)組, 你的任務(wù)是將這些數(shù)分成 n 對, 例如 (a1, b1), (a2, b2), ..., (an, bn) ,使得從1 到 n 的 min(ai, bi) 總和最大。
示例 1:
輸入: [1,4,3,2]
輸出: 4
解釋: n 等于 2, 最大總和為 4 = min(1, 2) + min(3, 4).
提示:
n 是正整數(shù),范圍在 [1, 10000].
數(shù)組中的元素范圍在 [-10000, 10000].
解題思路:
憑直覺,我們認(rèn)為只要使得“損失”掉的量最小即可,可須對原數(shù)組排序,再再兩兩組合,求其中較小數(shù)的和即可。這么做可行的數(shù)學(xué)證明可見:https://leetcode-cn.com/problems/array-partition-i/solution/minshu-dui-bi-shi-you-xu-shu-lie-shang-xiang-lin-y/
class Solution:
def arrayPairSum(self, nums: List[int]) -> int:
return sum(sorted(nums)[::2])
這里用到了[::2]利用步長迭代操作。
509. 斐波那契數(shù)
斐波那契數(shù),通常用 F(n) 表示,形成的序列稱為斐波那契數(shù)列。該數(shù)列由 0 和 1 開始,后面的每一項數(shù)字都是前面兩項數(shù)字的和。也就是:
F(0) = 0, F(1) = 1
F(N) = F(N - 1) + F(N - 2), 其中 N > 1.
給定 N,計算 F(N)。
示例 1:
輸入:2
輸出:1
解釋:F(2) = F(1) + F(0) = 1 + 0 = 1.
示例 2:
輸入:3
輸出:2
解釋:F(3) = F(2) + F(1) = 1 + 1 = 2.
示例 3:
輸入:4
輸出:3
解釋:F(4) = F(3) + F(2) = 2 + 1 = 3.
提示:
0 ≤ N ≤ 30
解題思路:
斐波那契數(shù)是常見題,是屬于必須掌握的題。最常見的思路,遞歸就完事了。但是要謹(jǐn)防遞歸中出現(xiàn)的“遞歸災(zāi)難”,也就是對同樣的輸入進(jìn)行重復(fù)求值。這里給出一種使用遞歸一行解題的實現(xiàn)方式。
class Solution:
def fib(self, N: int) -> int:
return N if N = 0 or N = 1 else self.fib(N - 1) + self.fib(N - 2)
注意,上面的代碼中,就會出現(xiàn)“遞歸災(zāi)難”,性能較差,也是屬于面試官不可接受的一種實現(xiàn)方式。更多常見的實現(xiàn)方式可自行查閱。以下給出網(wǎng)友提供的通項公式法(本人并沒有驗證過此通項公式):
# 通項公式
class Solution:
def fib(self, N):
"""
:type N: int
:rtype: int
"""
return int((5**0.5)*0.2*( ((1+5**0.5)/2)**N-((1-5**0.5)/2)**N))
476. 數(shù)字的補(bǔ)數(shù)
給定一個正整數(shù),輸出它的補(bǔ)數(shù)。補(bǔ)數(shù)是對該數(shù)的二進(jìn)制表示取反。
注意:
給定的整數(shù)保證在32位帶符號整數(shù)的范圍內(nèi)。 你可以假定二進(jìn)制數(shù)不包含前導(dǎo)零位。 示例 1:
輸入: 5
輸出: 2
解釋: 5的二進(jìn)制表示為101(沒有前導(dǎo)零位),其補(bǔ)數(shù)為010。所以你需要輸出2。
示例 2:
輸入: 1
輸出: 0
解釋: 1的二進(jìn)制表示為1(沒有前導(dǎo)零位),其補(bǔ)數(shù)為0。所以你需要輸出0。
解題思路:
本題涉及到二進(jìn)制操作,對于一個整數(shù)i, i >> 1 表示其二進(jìn)制開式向右移一位,等同于其十進(jìn)制形式除以2. 以下是一種常見思路(Go語言):
func findComplement(num int) int {
tem := num
c := 0
for tem >0 {
tem >>= 1
c = (c << 1) + 1
}
return num ^ c
}
以下是網(wǎng)友提供的一行解題方法:
return int(bin(num)[2:].replace('0', '2').replace('1', '0').replace('2', '1'), 2)
以上代碼用到了bin()函數(shù),它將十進(jìn)制數(shù)轉(zhuǎn)換成二進(jìn)制數(shù)的字符串形式。再連續(xù)使用string.replace()方法,最后轉(zhuǎn)成int類型。
852. 山脈數(shù)組的峰頂索引
我們把符合下列屬性的數(shù)組 A 稱作山脈:
A.length >= 3
存在 0 < i < A.length - 1 使得A[0] < A[1] < ... A[i-1] < A[i] > A[i+1] > ... > A[A.length - 1]
給定一個確定為山脈的數(shù)組,返回任何滿足 A[0] < A[1] < ... A[i-1] < A[i] > A[i+1] > ... > A[A.length - 1] 的 i 的值。
示例 1:
輸入:[0,1,0]
輸出:1
示例 2:
輸入:[0,2,1,0]
輸出:1
提示:
3 <= A.length <= 10000 0 <= A[i] <= 10^6 A 是如上定義的山脈
**解題思路**:
腦子轉(zhuǎn)得快的同學(xué),一眼就知道本題求出數(shù)組的最大值的索引即可。
return A.index(max(A))
另一種符合直覺的想法是,從第一位開始往后數(shù),當(dāng)數(shù)字下一位開始變小時,就把當(dāng)前數(shù)字的索引返回,用Go解釋如下:
func peakIndexInMountainArray(A []int) int {
for i := 1; i < len(A); i++ {
if A[i] < A[i - 1] {
return i - 1
}
}
return len(A) - 1
}
## 292\. Nim 游戲
你和你的朋友,兩個人一起玩 Nim 游戲:桌子上有一堆石頭,每次你們輪流拿掉 1 - 3 塊石頭。 拿掉最后一塊石頭的人就是獲勝者。你作為先手。
你們是聰明人,每一步都是最優(yōu)解。 編寫一個函數(shù),來判斷你是否可以在給定石頭數(shù)量的情況下贏得游戲。
示例:
輸入: 4
輸出: false
解釋: 如果堆中有 4 塊石頭,那么你永遠(yuǎn)不會贏得比賽;
因為無論你拿走 1 塊、2 塊 還是 3 塊石頭,最后一塊石頭總是會被你的朋友拿走。
**解題思路**:
可以用歸納法證明(可自行證明),只要給定石頭數(shù)量不是4的倍數(shù),先手穩(wěn)贏。Python:
class Solution:
def canWinNim(self, n: int) -> bool:
return n % 4 != 0
## 461\. 漢明距離
兩個整數(shù)之間的漢明距離指的是這兩個數(shù)字對應(yīng)二進(jìn)制位不同的位置的數(shù)目。
給出兩個整數(shù) x 和 y,計算它們之間的漢明距離。
注意: 0 ≤ x, y < 231.
示例:
輸入: x = 1, y = 4
輸出: 2
解釋:
1 (0 0 0 1)
4 (0 1 0 0)
↑ ↑
上面的箭頭指出了對應(yīng)二進(jìn)制位不同的位置。
**解題思路**:
一個符合直覺的思路是,先把兩個數(shù)都轉(zhuǎn)成二進(jìn)制形式,將長度較短的數(shù)的開頭補(bǔ)成0,再依次比較每一位是否相同。以下是這種思路的一種實現(xiàn):
class Solution:
def hammingDistance(self, x: int, y: int) -> int:
binx, biny = bin(x)[2:], bin(y)[2:]
length = max(len(binx), len(biny))
binx, biny = binx.zfill(length), biny.zfill(length)
distance = 0
for ex, ey in zip(binx, biny):
distance += ex != ey
return distance
更高級一點(diǎn)的方法,是使用位或操作,再統(tǒng)計位或計算后的數(shù)的二進(jìn)制形式中的1的數(shù)量。
class Solution:
def hammingDistance(self, x: int, y: int) -> int:
return bin(x^y).count('1')
## 657\. 機(jī)器人能否返回原點(diǎn)
在二維平面上,有一個機(jī)器人從原點(diǎn) (0, 0) 開始。給出它的移動順序,判斷這個機(jī)器人在完成移動后是否在 (0, 0) 處結(jié)束。
移動順序由字符串表示。字符 move[i] 表示其第 i 次移動。機(jī)器人的有效動作有 R(右),L(左),U(上)和 D(下)。如果機(jī)器人在完成所有動作后返回原點(diǎn),則返回 true。否則,返回 false。
注意:機(jī)器人“面朝”的方向無關(guān)緊要。 “R” 將始終使機(jī)器人向右移動一次,“L” 將始終向左移動等。此外,假設(shè)每次移動機(jī)器人的移動幅度相同。
示例 1:
輸入: "UD"
輸出: true
解釋:機(jī)器人向上移動一次,然后向下移動一次。所有動作都具有相同的幅度,因此它最終回到它開始的原點(diǎn)。因此,我們返回 true。
示例 2:
輸入: "LL"
輸出: false
解釋:機(jī)器人向左移動兩次。它最終位于原點(diǎn)的左側(cè),距原點(diǎn)有兩次 “移動” 的距離。我們返回 false,因為它在移動結(jié)束時沒有返回原點(diǎn)。
**解題思路**:
腦子轉(zhuǎn)得快的同學(xué)已經(jīng)知道,只要機(jī)器人向左右走的步數(shù)相同且向上下走的步數(shù)相同,就肯定會回到原點(diǎn)。我們可以利用string.count()方法。
100%
class Solution:
def judgeCircle(self, moves: str) -> bool:
return moves.count('U') == moves.count('D') and moves.count('L') == moves.count('R')
## 832\. 翻轉(zhuǎn)圖像
給定一個二進(jìn)制矩陣 A,我們想先水平翻轉(zhuǎn)圖像,然后反轉(zhuǎn)圖像并返回結(jié)果。
水平翻轉(zhuǎn)圖片就是將圖片的每一行都進(jìn)行翻轉(zhuǎn),即逆序。例如,水平翻轉(zhuǎn) [1, 1, 0] 的結(jié)果是 [0, 1, 1]。
反轉(zhuǎn)圖片的意思是圖片中的 0 全部被 1 替換, 1 全部被 0 替換。例如,反轉(zhuǎn) [0, 1, 1] 的結(jié)果是 [1, 0, 0]。
示例 1:
輸入: [[1,1,0],[1,0,1],[0,0,0]]
輸出: [[1,0,0],[0,1,0],[1,1,1]]
解釋: 首先翻轉(zhuǎn)每一行: [[0,1,1],[1,0,1],[0,0,0]];
然后反轉(zhuǎn)圖片: [[1,0,0],[0,1,0],[1,1,1]]
示例 2:
輸入: [[1,1,0,0],[1,0,0,1],[0,1,1,1],[1,0,1,0]]
輸出: [[1,1,0,0],[0,1,1,0],[0,0,0,1],[1,0,1,0]]
解釋: 首先翻轉(zhuǎn)每一行: [[0,0,1,1],[1,0,0,1],[1,1,1,0],[0,1,0,1]];
然后反轉(zhuǎn)圖片: [[1,1,0,0],[0,1,1,0],[0,0,0,1],[1,0,1,0]]
說明:
1 <= A.length = A[0].length <= 20
0 <= A[i][j] <= 1
**解題思路**:
同學(xué)們可以自行實現(xiàn)一下本題,再來看一行題解。
class Solution:
def flipAndInvertImage(self, A: List[List[int]]) -> List[List[int]]:
return [[{0: 1}.get(e, 0) for e in line[::-1]] for line in A]
# return [[j ^ 1 for j in i[::-1]] for i in A]
本題使用了列表推導(dǎo)的嵌套,使用了位或操作,使用了[::-1]負(fù)步長反轉(zhuǎn)列表,對解題人的Python特性了解程度有一定的要求。
## 237\. 刪除鏈表中的節(jié)點(diǎn)
請編寫一個函數(shù),使其可以刪除某個鏈表中給定的(非末尾)節(jié)點(diǎn),你將只被給定要求被刪除的節(jié)點(diǎn)。
現(xiàn)有一個鏈表 -- head = [4,5,1,9],它可以表示為:

示例 1:
輸入: head = [4,5,1,9], node = 5
輸出: [4,1,9]
解釋: 給定你鏈表中值為 5 的第二個節(jié)點(diǎn),那么在調(diào)用了你的函數(shù)之后,該鏈表應(yīng)變?yōu)?4 -> 1 -> 9.
示例 2:
輸入: head = [4,5,1,9], node = 1
輸出: [4,5,9]
解釋: 給定你鏈表中值為 1 的第三個節(jié)點(diǎn),那么在調(diào)用了你的函數(shù)之后,該鏈表應(yīng)變?yōu)?4 -> 5 -> 9.
說明:
鏈表至少包含兩個節(jié)點(diǎn)。
鏈表中所有節(jié)點(diǎn)的值都是唯一的。
給定的節(jié)點(diǎn)為非末尾節(jié)點(diǎn)并且一定是鏈表中的一個有效節(jié)點(diǎn)。
不要從你的函數(shù)中返回任何結(jié)果。
**解題思路**:
有人說本題出的巧妙,本人卻并不這么認(rèn)為,甚至認(rèn)為本題出得相當(dāng)自認(rèn)聰明。核心思路是將本節(jié)點(diǎn)的上一節(jié)點(diǎn)的Next指向本節(jié)點(diǎn)的Next即可。
class Solution:
def deleteNode(self, node):
"""
:type node: ListNode
:rtype: void Do not return anything, modify node in-place instead.
"""
node.val, node.next = node.next.val, node.next.next
本題涉及到鏈表這種數(shù)據(jù)結(jié)構(gòu),如果有同學(xué)不了解這種數(shù)據(jù)結(jié)構(gòu),可以先補(bǔ)一補(bǔ)數(shù)據(jù)結(jié)構(gòu)的知識,畢竟鏈表題是面試題的最高頻的結(jié)構(gòu)題。
## 905\. 按奇偶排序數(shù)組
給定一個非負(fù)整數(shù)數(shù)組 A,返回一個數(shù)組,在該數(shù)組中, A 的所有偶數(shù)元素之后跟著所有奇數(shù)元素。
你可以返回滿足此條件的任何數(shù)組作為答案。
示例:
輸入:[3,1,2,4]
輸出:[2,4,3,1]
輸出 [4,2,3,1],[2,4,1,3] 和 [4,2,1,3] 也會被接受。
提示:
1 <= A.length <= 5000 0 <= A[i] <= 5000
解題思路:
本題的實質(zhì)是一個排序題,同學(xué)們可以趁機(jī)自己實現(xiàn)一些排序算法,但是Python本身已經(jīng)提供了列表排序函數(shù)sorted()。值得注意的是,許多萌新同學(xué)并不了解這個函數(shù)可以傳入一個參數(shù)key來按key排序。key是 一個函數(shù),這只使用lambda匿名函數(shù)來實現(xiàn)一行定義函數(shù)。
class Solution:
def sortArrayByParity(self, A: List[int]) -> List[int]:
# return [e for e in A if e % 2 == 0] + [e for e in A if e % 2 == 1]
return sorted(A, key=lambda x: x % 2 == 1)
# return sorted(A, key=lambda x: x & 1)
x & 1是什么操作?如果不了解,可以補(bǔ)充一下Python位操作的知識,據(jù)我經(jīng)驗,判斷奇偶數(shù)時,x & 1比x % 2略好。
709. 轉(zhuǎn)換成小寫字母
實現(xiàn)函數(shù) ToLowerCase(),該函數(shù)接收一個字符串參數(shù) str,并將該字符串中的大寫字母轉(zhuǎn)換成小寫字母,之后返回新的字符串。
示例 1:
輸入: "Hello"
輸出: "hello"
示例 2:
輸入: "here"
輸出: "here"
示例 3:
輸入: "LOVELY"
輸出: "lovely"
解題思路:
沒什么好說的,用Python中的string.lower()方法就好了。代碼如下:
Go:
import "strings"
func toLowerCase(str string) string {
return strings.ToLower(str)
}
Python:
class Solution:
def toLowerCase(self, str: str) -> str:
return str.lower()
771. 寶石與石頭
給定字符串J 代表石頭中寶石的類型,和字符串 S代表你擁有的石頭。 S 中每個字符代表了一種你擁有的石頭的類型,你想知道你擁有的石頭中有多少是寶石。
J 中的字母不重復(fù),J 和 S中的所有字符都是字母。字母區(qū)分大小寫,因此"a"和"A"是不同類型的石頭。
示例 1:
輸入: J = "aA", S = "aAAbbbb"
輸出: 3
示例 2:
輸入: J = "z", S = "ZZ"
輸出: 0
注意:
S 和 J 最多含有50個字母。J 中的字符不重復(fù)。
本題可以利用Python中字符串的就地推導(dǎo)以及sum()函數(shù)對可迭代對象的求和。Python中真值屬于數(shù)字類型(許多語言中真值即布爾類型,布爾類型與數(shù)字類型是并列的兩大類型,但在Python中,布爾型是數(shù)字類型的一種,不信的話,可以試試isinstance(True, int),看看返回什么結(jié)果),可以參與數(shù)字運(yùn)算,所以以下代碼中,sum(s in J for s in S)是可以求和的。
class Solution:
def numJewelsInStones(self, J: str, S: str) -> int:
return sum(s in J for s in S)
# return sum(S.count(i) for i in J)
總結(jié)
從題目難度來說,大部分題目在Leetcode中都屬于“簡單”,有的題目甚至讓你產(chǎn)生“這也算習(xí)題”的感覺。
從技巧上來說,一行Python主要利用了Python的可迭代對象的就地推導(dǎo)特征,豐富的字符串方法,豐富的容器操作(列表、集合、字典等),豐富的內(nèi)建函數(shù)(sum, bin等),以及方便的二進(jìn)制操作。
但要注意的是,一行Python解題,并不意味著Pythonic,我也見過老長老長、強(qiáng)行一行的操作,而枉顧代碼的可讀性和性能。一行Python解題,也不意味著性能強(qiáng)悍,比如斐波那契數(shù)題中,如果使用暴力遞歸,會被面試官丟掉簡歷的;按奇偶排序數(shù)組題中,使用兩次列表推導(dǎo),分別求出奇數(shù)數(shù)組和偶數(shù)數(shù)組,可能還不如多寫幾行代碼對列表按奇偶排序,以少迭代列表一次。