本文始發於我的公衆號:TechFlow,原創不易,求個關注python
今天是LeetCode專題的第34篇文章,恰好接下來的題目比較簡單,不少和以前的作法相似。因此咱們今天出一個合集,一口氣作完接下來的5七、59和60這三題。算法
再次申明一下,爲了節約篇幅,保證文章的質量,我跳過了LeetCode當中全部的Easy以及少許沒什麼養分的Medium和Hard的問題。Easy的問題都不是很難,即便是新手通常來講也不須要看題解,仔細想一想也應該能搞定。因此就不佔用篇幅了,若是你們有比較感興趣的Easy問題,能夠在下方的小程序處給我留言。小程序
第一題是57題Insert Interval,插入區間。題意會給定一組區間和一個單獨的區間,要求將這個單獨的區間插入區間集合,若是有兩個區間存在交叉的狀況,須要將它們合併,要求合併以後的最終結果。從題意上來看,基本上和咱們上一篇文章講的區間合併問題徹底同樣。惟一不一樣的是,在這題當中給定的這一組區間都是自然有序的,咱們不須要對它進行排序。數組
區間已經有序了,剩下的就很簡單了,咱們只須要進行插入便可。區間插入的判斷條件仍是和以前同樣,若是A區間的左端點在B區間左端點左側,那麼只要A區間的右側端點在B區間左端點的右側便可。因此這題基本上沒有難度,就是一道裸題,我也不明白爲何官方給定的難度是Hard。數據結構
咱們直接放出代碼:app
class Solution:
def insert(self, intervals: List[List[int]], newInterval: List[int]) -> List[List[int]]:
ret = []
# l, r記錄待插入區間
l, r = newInterval
# 記錄待插入區間是否完成插入
flag = False
for x, y in intervals:
# x, y記錄當前區間
# 若是當前區間在待插入區間左側,那麼將當前區間插入答案
if y < l:
ret.append([x, y])
# 若是當前區間在待插入區間右側,那麼將兩個區間都插入答案
elif r < x:
if not flag:
flag = True
ret.append([l, r])
ret.append([x, y])
# 不然,說明當前區間與待插入區間能夠合併
# 更新待插入區間的範圍
else:
l, r = min(l, x), max(r, y)
# 若是最後尚未完成插入,說明待插入區間大於全部區間
# 手動插入,防止遺漏
if not flag:
ret.append([l, r])
return ret
複製代碼
只要理解了區間合併的條件,這題真的沒有難度。工具
前不久咱們剛出過螺旋矩陣I的題解,在螺旋矩陣I當中,咱們給定了一個矩陣讓咱們螺旋形去遍歷它。這題則相反,給定咱們矩陣的長和寬,讓咱們生成一個這樣的螺旋矩陣。學習
咱們來看下樣例:spa
在這題當中,咱們使用54題的思路也徹底能夠解出來,可是這題更加簡單了一些。因爲是讓咱們構造一個矩陣,那麼咱們其實沒有必要維護每一個方向的邊界了。只要出現出界或者是遇到了已經填好的數字那麼就說明應該轉向了。某種程度上來講,這題應該是I,以前的54題應該是II,由於這題更簡單一些。code
若是對54題解法不熟悉的同窗,能夠點擊下方的傳送門,學習一下方向數組的使用方法。
LeetCode54 螺旋矩陣,題目不重要,重要的是這個技巧
因爲咱們不須要維護每一個方向的邊界,而且移動的步數是固定的,更重要的是,轉向每次最多隻會發生一次,因此這個流程很是簡單,咱們直接來看代碼便可。
class Solution:
def generateMatrix(self, n: int) -> List[List[int]]:
# 初始化答案
ret = [[0 for _ in range(n)] for _ in range(n)]
# 初始化方向數組
fx = [[0, 1], [1, 0], [0, -1], [-1, 0]]
# 初始化方向以及起始點
dt = 0
x, y = 0, 0
# n*n的方陣,一共須要填入n*n個數字
for i in range(n*n):
ret[x][y] = i+1
# 移動的下一個位置
x_, y_ = x+fx[dt][0], y+fx[dt][1]
# 若是發生超界或者是遇到的數字大於0,說明須要轉向
if x_ < 0 or x_ >= n or y_ < 0 or y_ >= n or ret[x_][y_] > 0:
dt = (dt + 1) % 4
# 轉向以後的位置
x, y = x+fx[dt][0], y+fx[dt][1]
return ret
複製代碼
這題是一個排列組合問題,給定一個整數n,它表明[1,2,3,...,n]這n個元素的序列。而後還有一個整數K,要求這n個元素從小到大第K個排列。
這題其實蠻有意思,我以爲能夠上hard,但遺憾的是它有一個討巧的辦法,大概也許是這樣,因此才被降級成Medium的吧。這個討巧的辦法應該蠻容易想到的,很明顯,因爲n個數字是肯定的,因此最小的排列必定是[1,2,3,...,n]。而咱們以前作過一道LeetCode31題,它求的是給定一個排列,而後生成字典序比它大恰好一位的下一個排列。
既然如此,咱們能夠重複使用這個算法K-1次,就獲得了答案了。對於作過31題的同窗而言,這題毫無難度。若是對於31題解法不熟悉的同窗能夠點擊下方傳送門,回去複習一下。
LeetCode 31:遞歸、回溯、八皇后、全排列一篇文章全講清楚
但其實能夠不用這麼麻煩,由於Python當中有自帶的排列組合的工具庫,咱們能夠直接調用,只用5行代碼就能夠搞定。
class Solution:
# 引入包
from itertools import permutations
def getPermutation(self, n: int, k: int) -> str:
# 因爲最後返回的結果要是string
# 生成['1','2','3',...,'n']的序列用來計算下一個排列
base = [str(i) for i in range(1, n+1)]
# 調用permutations會獲得一個按順序計算排列的生成器
perms = permutations(base, n)
for i in range(k):
# 咱們調用k-1次next就能得到答案了
ret = next(perms)
return ''.join(ret)
複製代碼
這個方法雖然寫起來短平快,可是也有一個重大的問題,就是耗時很長。這個其實很容易算,根據當前排列生成下一個排列的複雜度是。咱們一共須要計算k-1次,因此總體的複雜度就是,極端狀況下k=n!,因此最差複雜度是。要知道n個物體的排列一共有n!種,若是k很大,這個複雜度是爆表的。不過這題沒有過多爲難人,這樣的複雜度也能AC。我我的以爲這是一個很遺憾的事情,由於簡單的算法也能夠AC會致使不少人沒有鬥志再去研究複雜的算法了。
最後,咱們來看正解。
正解其實不涉及任何新的算法和數據結構,甚至說穿了一文不值,可是不少人就是很難想到。仍是老話題,它須要咱們對問題進行深刻的思考。
既然咱們一個一個地求下一個排列很是慢,那麼咱們能不能有快速一點的辦法呢?加快速度大概有兩種辦法,第一種增長步幅,好比以前的方法是每次獲取字典序+1的排列, 咱們能不能生成字典序+k的排列?第二種想法是咱們能不能直接求解答案,直接生成出這個排列?
簡單分析一下會發現第一種是不可行的,或者說是僞命題。由於若是說咱們能夠想出一個求解字典序+k的算法,那麼咱們令這個k等於題目中要求的k不就是直接求解答案了?因此說,若是可能存在更好的辦法,必定只能是第二種,也就是直接構造答案的方法。那麼問題就剩下了,咱們怎麼直接構造這個答案呢?這就須要咱們對排列組合的理解了。
若是你親手寫過某個集合的全排列,你會發現一些規律。好比說123的全排列好了。咱們都知道,它的全排列一共是123,132,213,231,31和321。這個很簡單,咱們觀察一下會發現,123一共三個數字,6種排列。每一個數字打頭的都是2種,若是換成1234的排列呢?列一下就會知道是6種。若是你找規律你會發現,每一個數字開頭的種數是(n-1)!。
若是要推導也很簡單,由於每一個數字都是公平的,因此每一個數字開頭的種數都是同樣的。而全排列一共有n!種,因此分攤到每一個數字頭上剩下(n-1)!種。那若是咱們已經知道了第一個數字是1,請問第二個數字是2的種類數有多少種?一樣的方法能夠算出是(n-2)!種。到這裏有沒有火花閃過的感受?
咱們來舉個例子吧,假設咱們如今n=5,咱們算一下會知道,一共有120種排列。假設咱們要求第100個排列,因爲從0開始,因此也就是第99大的排列。那麼根據咱們剛纔說的,咱們已經知道每一個數字開頭的狀況都是4!也就是24種,那麼咱們用99/24獲得4,因此開頭的數字是第4大的數(第0大的是1),也就是5。答案一會兒縮小了不少,那接下來呢?接下來咱們用99減去5以前的全部狀況,也就是96種,獲得3。也就說答案是5開頭的第3個排列,那麼我再問,第二個數字是多少?
一樣的辦法,除去5以後還剩下4個數字,每一個數字排第二都有3!也就是6種,咱們用3/6=0,應該是第0大的數,也就是1。咱們繼續,除去5和1以後,還剩3個數字。每一個數字排第三的狀況有2種,咱們用3/2=1,咱們應該選擇第1大的數,這裏剩下的數是2,3,4,因此第三位應該是3。以此類推,咱們能夠獲得每一位的數字。總體的複雜度是O(n),和上面的方法相比,有了質的突破。
我把整個計算過程作成了圖,有看不懂的小夥伴能夠結合一下下圖理解:
最後,咱們把這個方法實現便可:
class Solution:
def getPermutation(self, n: int, k: int) -> str:
frac = [1 for _ in range(n+1)]
# 生成一個序列存放全部的元素
nums = [i for i in range(1, n+1)]
# 計算每一位的種類數
for i in range(1, n):
frac[i] = i * frac[i-1]
ret = ''
k -= 1
for i in range(n-1, -1, -1):
# 計算第i位的數字是當前第幾大的數
cur = k // frac[i]
# 放完以後取模,表示去除掉以前的全部狀況數
k %= frac[i]
# 求出當前元素以後,須要從序列當中移除
ret += str(nums[cur])
nums.remove(nums[cur])
return ret
複製代碼
到這裏三題就算是講完了,今天的這三道題目或多或少的都和以前的問題有關,這也是我把這三題放在一篇文章當中闡述的緣由。
這三題當中我我的最喜歡第三題,尤爲是完美解法。它的整個思路和代碼都不復雜,也沒有什麼特殊的技巧或者是方法,可是若是沒有對題目有足夠深刻的瞭解是很難想到這個算法的。這其實也是算法題的精髓所在,比賽當中多的是知道解法也作不出來的題目。因此咱們要提高算法水平,光學算法是不夠的,也須要對題目有深刻理解才行。
今天的文章就到這裏,原創不易,須要你的一個關注,你的舉手之勞對我來講很重要。