2020年6月30日 星期二

最近的規劃

目前有一些要學習,關於CCIE,就目前來看走向不是主力,很大機會拋掉,但CCNP是已經洗下頭,所以一定會去考到手。

所以關於CCNA跟CCNP的學習是一定會完成的,弄完之後,半年維護,跟原計畫是一樣的。

看了很多文章,但心裡也沒有底到底真的會遇到怎樣程度?還是誇張了點?我看事情是很淡的,所以不一定會有那麼深的感受。如果我是客戶,一定是很要求,想著錢砸下去了,給我弄好就是了。

走SI,會不會是我一輩子的路?不好說,到三十歲以前的境遇,才會決定之後的方向,現在還沒發生,所以也不會太過擔心。

比較希望有機會能去接觸大型建置案件,我覺得這算是「成就感」。有辦法去應需求去建網路,有很多領域都要跨足,還要領導其他人跟部門,那是不簡單的事,獨當一面。

2020年6月27日 星期六

遭遇網路 Flapping

關於 Flapping ,查英文翻譯是「拍打」,一直拍打?第一時間我也沒有理解。

隨後得知 Flapping 是 up down up down 也就是斷斷續續,有時通有時不通。為什麼會發生「Flapping」至今還沒有真的理解發生的原因。

畢竟這只是一種現象,會導致這個現象的原因不只一個,可能每次發生時的問題根源都不一樣,上一次能行的辦法,下一次就不見得行的通。

最近遭遇兩次Flapping,相當棘手,還是沒有那種掌握住的感覺。

2020年6月25日 星期四

Virtual Routing and Forwarding(VRF) 學習

在 Router 設定的時候,介面的網段是不能夠重壘的,如果重壘會跳出警示。

在某些型號的 Router 如 7200 可以啟用 Virtual Routing and Forwarding (VRF),可以當作產生一份虛擬的 routing table,跟本來的 global routing table 做出區隔。

使用 VRF ,可以在不同的介面上設定相同的IP位置,就像 VLAN 可以用邏輯上去區隔出不同LAN。於是我可以 1號介面設 10.1.1.1/24,同時用VRF在 2號介面設 10.1.1.1/24,不論接 1 或 2,都能ping通 10.1.1.1。

至於VRF的應用,就我初步來看可以做到 load sharing ,VRF也可以使用 Routing Protocol ,規劃好可以分割 routing table ,減少維護的難度吧(?)。總之先記錄一下。

之後再更多補充。 

2020年6月23日 星期二

Multiple Spanning-Tree Protocol (MSTP) 設定

對於Multiple Spanning-Tree Protocol的練習可以先看這裡。


關於Multiple Spanning Tree Protocol的設定,有一項要注意,要設定「 truck port」,這是指南沒寫,第一項連結也沒寫到的部分。

至於更詳細的設定或實作,我想等CCNP考完再補。

先做提醒。

GNS3的分頁式終端機軟體第二選擇 -- Super-Putty

跟 GNS3 一起搭配的終端機軟體 Solar-Putty,本身是可以分頁的,也就是可以把GNS3開啟的telnet等集中在同一個視窗。

用過 Packer Tracer 的人就知道,今天如果有八個設備點開,就會有八個子視窗,這使切視窗的操作變得難一些,甚至需要去尋找目前要設定設備是哪個子視窗。

原本搭配 GNS3 的 Solar-Putty 是預期可以發揮這項功能,但是我在第二分頁時,就會跳出「Putty」的子視窗,這沒起到分頁式的效果。但經簡單測試,改GNS3的參數是宣告無效的,如果手動在cmd輸入指令就可以是分頁式,與其這樣,倒不如換一個軟體。

Super-Putty 是以 Putty為底的,免費且開源的軟體,可以用在商業與非商業用途。

[公告]Blog ---- 2020-06-22 最近暫停寫Leetcode題解

目前(2020-06-22)

感覺要有一個可以碎碎念的地方,所以就有了這一篇。最近要準備的有點多,所以決定先暫停Leetcode題解,把時間挪給更優先的事情。

現在經營有兩大主軸︰「Leetcode題解」跟「JN的CCNA」。

手一捏,可能會停二個月,不過關於「網路」的是不會停,因為那是目前最優先的部分。

而「JN的CCNA」在準備中,等到完成 25% 就會上線,我也不喜歡看到了一堆章節,但裡面卻沒有任何內容。QQ

總之就是這樣,如果有要回來一直寫,就會再公佈。目前只會有零星的手癢來寫題解。


人生第一次面試

今天是第一次面試,有人說第一次面試一定會搞糟,嗯,感覺真的搞糟,所以面試完心裡有底了。但也感謝這場面試讓我知道哪裡不足,下次可以改進。

自己在面試上還有很多可進步的地方,讓我了解自己的不足,最近接踵而來的面試邀約,也不知道有辦法可以短時間內就完成改進。還是一句「best effort」。

雖然是第一次面試,但我倒是不緊張,畢竟也不知道會問什麼。過程中令我最難應付的是:解釋「網路」,網路是個很大的領域,如果是科普的,那不是問題。但如果是要講給對網路有很好了解的人,面試官,那要講什麼才能反映當前的水準,如果講太淺,又顯得很普通。

2020年6月21日 星期日

GNS3安裝與設定教學(2.2.10) ※疑難:GNS3無法順利連上VM。(VMware Player)

今天要來把GNS3的一步一步安裝完成,因為安裝設定是對於網路初學者是有一點困難,這裡以「沒有經驗」的為對象,如果有經驗了,也不一定要照做。

官方的安裝教學(Windows),如果不排斥看英文,個人認為這裡更好。

第一步:下載 GNS3

GNS3官網,點 Download,然後會要求要有GNS3的帳號。註冊後,就能下載。

下載 GNS3

2020年6月20日 星期六

Leetcode題解 Python & C#六月挑戰DAY20 Permutation Sequence

給一個數字 n  代表有 [1,n] 個數字要排列組合,k 代表要找第 k 小的,找出後回傳。

這題一看到時,我想起姊妹題的排列組合找下一個,使用兩兩交換,但這題不是。

這題的限制條件使n最多只有9個,也不重複,因此,使這題可以用規律性去找解法。

如:123 一共有六種組合。
123
132
213
231
312
321

Leetcode題解 Python:六月挑戰DAY19 Longest Duplicate Substring

給一字串 S,要找出最大的重複連續字串,重複字串可部分重壘。

參考︰力扣 

在這題出的這天,感覺最近要開始爆忙,也在想要不要先斷一下寫題解?但這題有難度,還是想把它做掉。

這題要用到「Rabin-Karp 's algorithm」,用來字串搜查,使用方法是把子字串做Hash轉換去對比。

但我從頭開始講,為什麼得用 Rabin-Karp 's algorithm ?

要從一字串內找的重複出現子字串時,會使用 suffix Array,中文是後綴表。

2020年6月18日 星期四

Leetcode題解 Python & C#:六月挑戰DAY18 H-Index II

給一個升序排列的非零非負的整數數列 citations , citations[i] 代表第 i 篇論文的引文次數。
找出一個 H-Index 代表至少有 n 篇被引用 n 次,要取最大值回傳。

這是一個 Rank 的排序與篩選,可以想像從 1 開始,如果有 1 篇大於 1 次就往下進行,換成看 2 。

因為是數目也是條件,所以加上計數,如果慢慢往上加到當 citations[i] > n - i 時,那就代表不合格。

題目有提示用 O(logN) Time 解決,有個經典的方式是這樣的時間複雜度:二分搜查法。

Wildcard Mask 用Python計算去簡化輸入

Repl.it (線上執行Python) ※支援手機(Android  IOS)
OneWildcardMask: 將輸入的IP位置用一組Network和WildcardMask表示。
MultiWildcardMask: 用一或多組Network和WildcardMask去表示所有輸入的IP的。

昨天教到 wildcard mask,打開我對 wildcard 只是二進位下該位 wildcard mask 為1就任意,為零就只能是 network 對應的值,這種淺的見解。

wildcard本身有萬用字元的意思,也就是通配,這也能與 wildcard mask 二進位為1就任意(01)起到關聯。

就計算結果上來看,wildcard mask 不是 subnet mask 的相反,而是比 subnet mask 計算更複雜的值。比起翻作「反遮罩」,我認為稱之「通配遮罩」更適合。

wildcard mask 用意是為了減少路由的輸入,也就是可以 match 到多網段,如果設定的好,可以用一行就涵蓋多個網段,可以減少路由器的負荷。

「0.0.0.0 255.255.255.255」(Network, WildcardMask) 代表全部network皆匹配。在設OSPF時,也不用一個個網段輸,只用一行就能解決。
但這是有風險的,也代表該路由器是可以跟所有IP位置形成夥伴關係,一個惡意就能透過路由交換去塞滿垃圾路由或是搶走路由做 Man in Middle。

最好就只開設定內且可信的進ACL,別偷懶!但是 wildcard 計算並不是那麼直觀的,常有不同的需求,如果要手算,就怕數量一大,會很累。

如我要只要開 192.168.0.7 跟 192.168.0.15 進來,只能用一個 network 跟一個 wildcard mask 就要能表示只有該兩個位置會被挑中。

二IP位置前方三部分 192.168.0 都一樣,所以可知 wildcard mask 的前三部分是 0.0.0。

到了第四位就會有些不同,這裡得換成二進位去看其運作。

二進位      十進位
00000111 <= 7 (IP 1)
00001111 <= 15 (IP 2)
 
在左方數來的第五位,同時出現 0 和 1,那代表該位應該要通配。其餘位置上只有 0 或 1 單獨出現,所有是固定的。

0000*000 => 00001000 => 8 (wildcard)

於是得到 wildcard mask 應該是設為 「0.0.0.8」。

2020年6月17日 星期三

Leetcode題解 Python & C#:六月挑戰DAY17 Surrounded Regions

給一個二維的串列,其元素為 'X' 或 'O' 要把所有 'O' 被 'X' 包圍的換成 'X'。

這規則有點像圍棋,圍住就吃掉。

那有好幾種思路:
最直觀的,就是遇到一個 'O' 時,檢查其四周是否為 'X',然後如果檢查時遇到 'O',就延伸檢查。
或者變體,直接檢查 'O' 的上下左右到邊際是否有 'X' 出現,然後給標記,最後檢查標記四周只有標記與 'X',就換成 'X'。

走到這裡,只有一種狀況會需要特別小心,就是在邊際的 'O' ,如果有碰到,就會產生要當心的狀況。

換個思考,與其費心找四周,不如把邊際或與之相連的 'O' 抓出來,其他都改成 'X' 即可。

2020年6月16日 星期二

Leetcode題解 Python & C#:六月挑戰DAY16 Validate IP Address

給一個字串 IP ,回傳其屬於 IPv4 或 IPv6 或都不是 Neighter。 

如果要找「合格」的字串,會想到用 re 去解決,這是在一般的情況下。如果是LeetCode,也許就不會用它。
不過官方的解法是有用到 re 的,直接傻眼,但那確實是最正當的解法。

通過「正則表達式(Regex)」是最嚴謹的做法,但也要有支持這項功能,在未知跟沒有的情況下,只能土炮出來。

這裡要從根本講,不論IPv6或是IPv4對電腦來說都是連續的二進位,IPv4長度為32bit,IPv6為128bit。
給人看的十進位跟十六進位是經由轉換而來的,如果換回原來的二進位形式,就只要留意是否長度是否合格就好。
所以把 string IP 轉換成連續二進位,就能輕而易舉去找出是否為合格的IP位置。(但是寫轉換的過程不容易) 

Leetcode題解 Python:Maximum Frequency Stack

實作一個 FreqStack,類似 stack,但 pop() 會先從出現次數最高的最後一個開始,也就是 出現次數 > 位置 的順序。

這題導入了 Freq 的計數,因此要有一個計數的設計,用一個 HashTable 去記錄 val 對應的出現次數。

接下來要把 stack 套上以計數為優先的方式,出現最多次的一定先被 pop() 掉,對相同次數的來說,pop() 是正常的。
但對不同次數的來說,一定要先 pop() 光更多次的,才會輪到自己這一層。

如果連 push 五次同一個 val,那麼會是第五次的 val 先被 pop() ,才會換到第四次的 val,中間會有個換層的移動,如果已經pop()光就往下一層。
fruq = {1:[val], 2:[val], 3:[val], 4:[val], 5:[val]}

在計數的時候,各 val 計數的最高值也就是最優先要被 pop() 的那一層,用一個 MaxFreq 去記錄。
fruq[MaxFreq].pop() 過後,fruq[MaxFreq] == [] 時, MaxFreq -= 1 ,同時也要把 count[val] -= 1。

雖然標 hard,但非常快就解出了,感覺要定為 medium。不過結果我寫的跟官方的只有變數命名有差。

Leetcode題解 Python & C#:Maximum Binary Tree

給一列沒有重複的整數陣列,要建立一個 maximum tree,規則如下,並回傳其 root 。

1.The root is the maximum number in the array.
2.The left subtree is the maximum tree constructed from left part subarray divided by the maximum number.
3.The right subtree is the maximum tree constructed from right part subarray divided by the maximum number.

root一定是陣列中最大的,最大值位置左方是左子樹的所有值,右方是右子樹的。
因此把 nums[:maxVal] 傳入到 constructMaximumBinaryTree 就可以得到左方子樹,右方也同理。

如此運作下去,最後會傳入一個空陣列,這就是「中止條件」,所以一遇到就馬上回傳 None 使其停止遞迴。

2020年6月15日 星期一

Leetcode題解 Python & C#:六月挑戰DAY15 Search in a Binary Search Tree

給一個二元樹的根,要找到值為 val 的 node 並回傳,沒有則回傳 None。
這裡的二元樹是嚴格的,即 node 的左子樹皆小於自身,右子樹皆大於自身,即二元搜尋樹。

用一個標準的二元樹遞迴函數也能順利地解找到答案。

不過那樣會找到太多不必要的部分,也沒有用上二元搜尋樹的特色。

由於左邊皆比自身小,如果 val 小於 node.val,那就往左方而去,反之則右方。相等就直接回傳。

Leetcode題解 Python:Allocate Mailboxes

給一個陣列 houses ,houses[i]為第 i 個房子的位置,k 為郵筒數,要找出最佳分配下,每房子到最近郵筒的總距離。 

如果郵筒數夠,在每房子前面設一個是最好的,但如果不夠,就會折衷,在房子間找個中間點去設立。

這其實是一個分群的問題。

要設一個郵筒,如果只有二間房子,那最佳位置會在中間。如果三間,那郵筒會在中間的房子前。
換句話說,如果房子有偶數個,在中間那二間之內都可以。如果有奇數個,那只能在中間的房子前。

找到如何決定最佳設置點,接下來就要分 k 組,找到最短的距離總和。

Leetcode題解 Python:Find Two Non-overlapping Sub-arrays Each With Target Sum

給一個正整數陣列 arr ,跟一整數 target ,最找出二子陣列各自總和皆為target且二子陣列不重壘的最小長度總和。若沒有回傳 -1。

關於找子陣列(注意要為連續的或是稱序列)總和為target,通常用計算其累進值,accu[0] = arr[0], accu[1] = accu[0] + arr[0], accu[-1] = sum(arr)。
如果 accu[i] - target 在 accu 內,就代表有對應另外一個的連續子陣列存在。透過 accu[j] - accu[i] 可以找到 arr 任位置的連續子陣列總和。

在計算累進值的同時,就可以開始找目標值,也是這種算法的優勢,如果搭配 hash類 的查詢,只要累進值算完,就能找完所有可能,相當於 O(N) Time。 

Leetcode題解 Python:Kth Ancestor of a Tree Node

有 n 個 node,編號為[0, n-1],給一個 parent ,parent[i] 代表第 i 個 node 的 parent 為第 parent[i] 節。
然後給一 node 編號與 k ,要找出其第 k 個祖先。若沒有則回傳 -1 。

難的不是在於能不能順利找到,找到是難度 easy 的問題。難在於能不能「快速」地找到。

我在過程中有試過保存 2 跳的祖先,最糟也能在 25000 跳內解決,不過會超時。

關聯是一對一的,如果為每個node做map,即root到node的每node,就可以 1 跳解決,但是記憶體會爆,O(N**2) Space,因為每node的map都是不一樣的。

這題的關鍵在於要再哪一塊做取捨,如果純照parent跳,會用 O(N) Time。而且本題「大量」執行查詢,最多50000次,那會使沒做查詢優化的超時。

雖然我做了 2 跳祖先,但不夠,仍是 O(N) Time。如果換成保存「指數」跳的祖,就能把查詢變成 O(logN) Time,才有辦法應付大量的查詢。

依 parnet 可以找到 1(2**0) 跳的祖先,那 2(2**1) 跳的是1跳的再1跳,那 4(2**2) 跳呢?是2跳的再2跳。依此類推。

Leetcode題解 Python & C#:六月挑戰DAY13 Largest Divisible Subset

給一組不同的正整數 nums,要找出滿足條件 s[i]%s[j]==0 or s[j]%s[i]==0 之最大的集合

這題要回傳的是「最大的集合」,所以需要先知道最大的,然後再回頭生成。

要滿足條件,大的一定要是次大的乘以某整數,這樣才能歸納於同一組,同時也符合餘數的條件。

我卡了很久,因為我一直想突破 O(N^2) 的障礙,要是想到 O(N^2) 的方法就不會去嘗試,而且對於這樣的題目被太多種DP衍生的可能給卡住。
大概花了二天,才完成我的理想型,也是最初設想的數學理論版。要把想法變成方法就是難度所在,也是樂趣所在。

一個排列過的最大集合,最大的一定是其左一個乘上某數,即「因數」。而一個數的左方會接子集合有最大數目的因數。

2020年6月14日 星期日

Leetcode題解 Python & C#:六月挑戰DAY14 Cheapest Flights Within K Stops

有 n 個城市,出發地 src 目的地 dst ,以及航次表 edges : [[cityA(src), cityB(dis), price]...],最多可轉 K 站到 dis。
要在轉K站之內,找出從 src 到 dst 的最少的總花費,如果沒有則 -1 ,有則回傳花費。

QQ,最近兩天不順,沒有順利解Leetcode,昨日的挑戰也不小心中斷了。

這是一題「Path」題,找路線不外乎就是先把 edge 串連起來,然後再開始找。
 
最常用的是 Dictionary 做映照,如 DP[0] 要映出從 city0 可到的目的地,然後看題目決定要不要雙向,這題是不用雙向。

除了位置的連結,這題還要把花費累積,因此需要不斷從各可能花費取最小的保留。

2020年6月13日 星期六

Leetcode題解 Python & C#:Insert Delete GetRandom O(1) - Duplicates allowed

這題是「Insert Delete GetRandom O(1)」的進階,連運作原理都相同。

這次允許了重複出現,也就是說,一個數值的位置保存是要能容納多個的。

這時會有 list() 或是 set() 的取捨,但是由於要在 O(1) Time 的限制,就選擇 set() 來存放同val的多個位置。

其它都跟前一題差不多,前一題能解,這題就不是大問題。

Leetcode題解 Python & C#:六月挑戰DAY12 Insert Delete GetRandom O(1)

設計一個資料結構,要在 O(1) Time 內完成 insert() remove() getRandom() 三個功能。

如果只有 insert 與 remove 那麼用 hash 系列是比較快的,不過多了 getRandom(),這使 hash 類的不能單獨使用,因為hash類的沒有 sequence 之分。
沒有序列之分,又要隨機,那就得把 hash 轉換成有 squence 的,用隨機抽一個位置。

但與其每次這樣轉換,倒不如多用一個有 squence 的做 val 的存放。更別提轉換會用到 O(N) Time。

要查 val 是否存在?去查 hash 系列的最快。要回隨機值?用有 squence 的再隨機回一個位置最快。

整合起來,val 不僅在有squence的資料類型存在,也要同時存在於hash系列。

2020年6月11日 星期四

Leetcode題解 Python:Minimum Moves to Reach Target with Rotations

有一個 n * n 大小的網格,有一條蛇佔據二格,要從 (0,0) 和 (0,1) 移動到 (n-1,n-2) 和 (n-1, n-1)。
網格上的點為 0 代表是空的, 1 代表障礙。
蛇可以往右移或往下移,或是旋轉,旋轉以左上方的那格為基準,轉朝下或右。旋轉時鄰近右下那一格必需是 0 。

這題光寫基本流程就蠻花時間的,其實並不難。

首先如果有二個點且中間有障礙,要找最短路徑。可以想像用一個越來越大的圓,圓心是出發點,最後圓納入終點的時候,其半徑即為最短路徑。

向外擴張的時候,可以把每輪向外的一步都當成新的圓半徑。為了要實現這樣的方式,不能使用 DFS ,要用像是二元樹的階層式往深探索。
起點(0,0) steps: 0,下一輪往四周 (-1,0), (0,1), (0,-1), (1,0) steps: 1,為了避免重複尋找,要把找過的位置加入 memo ,
若從 memo 找到要找的位置,就直接換下一個找。

這題蛇加入了「旋轉」,朝右或朝下會影響下一步能怎麼走,所以除了位置之外,保存狀態也是必要的。

每一步以 (row, col, state) 保存,並且在每次尋找最前方做判斷,為中止條件。

如果以一個起點出發,要是可以通,在找到路徑之前,就會先探索許多背離終點的路徑。更好的作法是再加上從終點尋找,只要二者路徑重疊到,就代表找到最短路徑。

Leetcode題解 Python & C#:六月挑戰DAY11 Sort Colors

有三種顏色,編號為 0, 1, 2。給一個陣列包含三種顏色,要在 in-place 去由小至大排序過。

Note:不要使用內建的 sort() function。

題目給了兩種方向,先 Count 後排序,二是跑一遍就完成。方向一太簡單,數完各顏色再從頭依種類、數量做替換。

二被限制只能用常數空間,換句話說,只能記沒有長度的數值。

答案一定是 0 都在陣列的最左方, 2 都在陣列的最右方。如果 pass 一遍,遇 0 往左擺,遇 2 往右擺,這樣就能快速分類。

左方擺過 0 的位置就不再使用,同理右方擺過 2 的,如果重複用到,就會破壞已經排好的序列。

因此採用 「Two Pointer」 ,而 Two Pointer 也是一個 O(1) Space 的方法。

pass 過程遇到 0 或 2 都會使左界或右界縮窄,可以想成把答案夾出來。

2020年6月10日 星期三

Leetcode題解 Python & C#:六月挑戰DAY10 Search Insert Position

給一個排序過的數列 nums 和目標值 target,要找出 目標值 insert() 到 nums 後的不影響排序的位置。 

nums是遞增(升序)排列,因此要找到一個位置 index ,該位置使 nums[index - 1] < target 且 nums[index] >= target。

可以從最前方開始找,快一點的做法是使用二分搜尋。

2020年6月9日 星期二

Leetcode題解 Python & C#:六月挑戰DAY9 Is Subsequence

給字串 s 和字串 t,找出 s 是否為 t 的子順序。子順序是相對位置不變,但中間可以刪減部分字元。

 這類的題目在LeetCode有不少,但變化性少,只會這個學會了,就能解部分類似的。

依 Follow up 的方式,我猜,要從 s 的組成字元逐一找該字元是否在 t 裡面,但我不是很懂它想表達的意思。

但沒關係,我的解法也是逐一去找的運作模式。

既然子順序的中間可以塞入非配對的字元,那可想成把那些字元拿掉後,剩下的字串跟 s 能全配對那就是 True。

從 t  的左至右搜查,如果匹對到 s[0] ,那就只能目前從 t 剩下的去匹配 s[1] ,中間沒匹配的都是可以拿掉的字元。

當 len(s) == 0 的時候,即為全匹配。如果還有 s 的字元但 t 已經到最後,那就沒有成功匹配。

2020年6月8日 星期一

Leetcode題解 Python & C#:六月挑戰DAY8 Power of Two

給一個整數,回傳該數是否為 2 的次方。

除了要注意整數 0 與負整數,其他整數都比較沒有問題。

2 的零次方是 1 ,而負次方都位於[0, 1]之間,也不是整數。所以只需要考量零以上次方要怎麼找。

如果是二的次方,除以二的餘數皆為 0,用此條件,只要餘數為 1 就不是 2 的次方。一直除到變成 1 (2**0)時,確定輸入是 2 的次方。

換一種進位,以「二進位」來看,二的次方,只會有一個bit為 1,最高位的bit為1,其餘為 0 ,所以只要判斷是否bit只存在一個1。

2020年6月7日 星期日

Leetcode題解 Python & C#:六月挑戰DAY7 Coin Change 2

給各硬幣的幣值 coins,與目標金額 amount ,要找出有幾種硬幣組合的總金額會等於 amount。

在這題琢磨了好幾小時,因為對於 DP 速度感到好奇,這題用 DFS 就是慢。

對於這種求特定組合數的題目,最重要的就是去看限制條件,再決定 DP 要取什麼要比較快。

「0 <= amount <= 5000」這意味著每道題目的答案並不是大數目,從「the answer is guaranteed to fit into signed 32-bit integer」也可得知。

如果是天文數字,就會有 % (10**9+7) ,這樣的題型不可能遍歷所有位置去解。但這題最快的方法是去跑遍所有位置。

Leetcode題解 Python:Next Greater Element I & II & III

在 contest 前,來抽一題暖手感,剛好就中這一題,有系列就依序解完吧。
(這次的contest有解完)

【Next Greater Element I】

給二個陣列 nums1 和 nums2,nums1 是 nums2 的子集合。

依 nums1 的元素找出(對應nums2位置)下一個比自身大的值。如果沒有則 -1 ,回傳所有的結果 List<int>。

由於 nums1 並非照著原來在 nums2 的相對關係排序,最快的方法是依照 nums1 的元素,在 O(N) Time 解決。

但如果從 nums2 對應的位置開始照規則找,最糟會到 O(N^2) Time。

先找出所有的答案再依 nums1 重組,時間複雜度恰好 O(MN) ,也不用在找答案過程做是否在 nums1 的判斷。

2020年6月6日 星期六

Leetcode題解 Python & C#:六月挑戰DAY6 Queue Reconstruction by Height

給一個串列 people , 其中每一個元素為 (h, k),h 代表自身的高度, k 代表自身前方有 k 個人的 h >= 自身。
people目前是亂的,要回傳照規則(即每個位置與其 h, k 值皆是對的)排列的串列。 

我沒有第一時想通,看了一下別人的題解

卡在我只有想到 brute force,但沒有想到要如何簡化。

按照逆向的想法,預想本來會有一個原形只有身高的排列,然後從身高小到大從後方取出,並標上其位置(前方有位置號個比自己 h >=)

所以逆向回去,即身高由高到矮,從前方排,依序一個個放入。

由於是按身高高到矮放入,因此直接把整理好的 people 按其 people[i][1] 去 insert(people[i][1], people[i]) ,即可確保前方有 people[i][1] 比自身高或同。
而輪到較矮的 insert() 也不會影響其排列的正確性,就能順利逆向回所求。

Leetcode題解 Python & C#:六月挑戰DAY5 Random Pick with Weight

給一串正整數權重 w ,w[i] 代表 i 的權重。然後要寫一個 pickIndex 方法,以加權的機率抽出一個 i 。

雖然說 Python 的 random 模組有方法可以模擬做加權抽樣,但是速度不夠快,以致於超時,所以要減少 random 的使用,靠自己寫更有效率的代碼。
(這也是很少見的專門模組效率會遜於自撰的)

加權抽樣若以 1 為單位,把所有 index 放入一個假想的箱子,權重為 1 放1個,權重為 3 放3個,最後從中隨機抽取一個出來。

抽中 i 的機率為 w[i] / total(總權重) ,所有的機率加起來會等於 1 。若把機率從頭逐一累加,保存累進序列,這序列以權重把[0,1]之間切割區間。

用 random.random() ,可以得到一個 [0, 1] 之間的 float ,然後我們要用這個 [0, 1]之間的 random數 去對應出一個 i。
以輸入[1,4]為例,對照累進機率,若 random數 位於 [0, 0.2],那代表抽到 0 。若 random數 位於 [0.2, 1],那代表抽到 1。

要找出 random數 落於哪個區間,可以用二分法去搜尋,預期 O(logN) Time 。(不知道random的方法是用到 O(N) Time嗎?至少二分法比O(N)快)

如果剛好落在區間界線(如 == 0.2)要算前面的 i 還是後面的 i ?其實都沒差,根據「積分」,剛好落在 0.2 的機率為 0 ,所以不會影響到權重。

2020年6月4日 星期四

Leetcode題解 Python & C#:六月挑戰DAY4 Reverse String

寫一個功能反轉 char 陣列,在內部修改,不用回傳值。

對於初學物件導向的人,很容易用新的實例來取代原本的,不過如果是 C# 等,就得宣告才會產生新的,自然不會有混淆的問題。(比較少)

反轉字串的方式,就是最前與最後開始兩兩交換,換到 len()//2 之前停止。

2020年6月3日 星期三

Leetcode題解 Python & C#:六月挑戰DAY3 Two City Scheduling

要把 2n 個人,平分給 cityA 和 cityB,costs[i] 代表第 i 個人,costs[i][0] 代表第 i 個人到 cityA 的花費,而 costs[i][1] 則是到 cittyB。要回傳最小總花費。※去 cityA 和 去 cityB 都應該有 n 個人。

一看到題目,馬上用DP解,因為我解的上一題是也是分配的問題。不過一看到用時,就知道這不是好的寫法。

對總體來說,如果每個人都以「相對代價」較低的為目的地,就可以找到最佳分配的情形。

相對代價 costs[i][0] - costs[i][1],我們讓前 n 個相對代價較小的到 cityA ,後面的到 cityB,就是最佳方案。  
(若有 3 個以上的選擇,就不能用這算法。)

Leetcode題解 Python:Probability of a Two Boxes Having The Same Number of Distinct Balls

有 2n 個球,球有 k 個顏色,寫成一串 k 長度 balls 陣列,balls[i] 代表第 i 顏色有多少顆球。

今天要隨機分到兩箱子,分完打開看,回傳最後兩箱子球的顏色數目相同的機率。

因為是隨機,所以拿到顏色順序不會按照顏色種類排列,例:[1,3] [3,1] ,而二者都是合規則的可能情形。 

按照真實隨機分配的方式,即一顆顆隨機抽出,運算次數會隨 k 與 sum(balls) 呈指數爆炸性成長。
如:[6,6,6,6,6,6],會作 36! 次探討,自然會超時,要想辦法改進。

對於隨機排列組合,像是抽籤等情況,要掌握到這是「邊抽邊開」還是「抽完再開」?二者在計算上是不同的。
「抽完再開」的如此題,同顏色的順序就不重要,如:白1白3、白3白1,都視作 白白,視為同一種。

2020年6月2日 星期二

Leetcode題解 Python & C#:六月挑戰DAY2 Delete Node in a Linked List

寫一個功能去從一 linked list 中刪除一個 node,會給那個要被刪除的 node。

這題真的難到我了,在第一眼看到的時候。明明有兩個input,怎麼只傳入一個node?一旦看懂之後就很簡單了。

如果要刪除一個node,那最簡單的方式就是把它上一個跟它下一個接起來,但是這裡沒有上一個,而是直接給要刪除的node。

沒辦法修改實例位置完成,那就修改屬性。

2020年6月1日 星期一

Leetcode題解 Python & C#:六月挑戰DAY1 Invert Binary Tree

反轉二元樹。

這題被啟發的述說蠻好笑的,會寫軟體,但不會在白板寫出反轉二元樹。
有時候會被簡單卡關是難免的。

要反轉二元樹,也就是左右對調,node.left, node.right = node.right, node.left,如此運作下去到底。

我也沒有一次到位,不過很快就察覺到該怎麼寫。

Leetcode題解 Python & C#:五月挑戰DAY31 Edit Distance

給兩個字串,要找最小的編輯次數讓 word1 變成 word2。編輯只有三種操作:insert、delete、replace。

這是hard題,蠻有意思的,可以找出像是基因序列相似的程度,或是字典中找相似的詞。

如果用人的概念去想,要怎麼選,要用哪種換法,基準點在哪裡,似乎不是那麼好想像的。
但是能確定朝著每個操作,是 word1 和 word2 之間的交換。

舉例:word1 = ABC, word2 = BABC。
可以想到把 word2[0]做delete,或 word1 insert(0,"B"),就可以得到 word1 == word2。
為了好講,這裡不探討 word2 上做操作,統一以 word1 當基準,也就是只看 word1 -> word2。

像是 word1[0] == word2[1] == "A",在這位置上不用操作,因為二者相等。說到「位置」這裡換個思考 DP[p1][p2] ,
二者的方向關係是一個表格DP,若 word1[p1] == word2[p2] , 則 dp[p1][2] 的基本值等於0,即不需要任何操作,反之則 1。
  
題目要找出最小操作次數,也可以想像把操作數往右下角,一路取最小壘加,最後的結果就是最小操作次數。

如果 word1[p1] != word2[p2],代表要進行操作,在 DP[p1][p2] 取 {DP[p1-1][p2-1], DP[p1-1][p2], DP[p1][p2-1]}三者最小值加 1 (基本值)。
分別對應{replace, delete, insert}三種操作中取最佳方案到DP[p1][p2] ,看 DP[p1][p2] 的「相對位置」來決定是哪種操作,本身基本值 1 有三種操作的可能,至少一定是其中一個。

2020年5月30日 星期六

Leetcode題解 Python & C#:五月挑戰DAY30 K Closest Points to Origin

給一串座標 Points ,回傳 K 個離(0, 0)最近的點。

要知道距離的算法,因為是找跟(0, 0)的距離,所以用 P[0]**2 + P[1]**2,就能作為比較值。(其開根號是距離)

不難,重點要放在如何排序。

使用內建的 sort() 功能就能快速排序,如同官方方法,用 sorted(points, key:lambda x: x[0]**2+x[1]**2),能實現排序,
又不會多使用空間,是最佳解之一。

時間是 O(nlogn),就是排序法(sort)的時間吧。

換個想法,有什麼排序法夠快,最好可以快速找到最小值? min heap。
不然用hashtable以距離作分類,由小開始,加到K個就回傳,這也不用把整個points重新排序。

2020年5月29日 星期五

Leetcode題解 Python & C#:五月挑戰DAY29 Course Schedule

給一個 numCourses 代表從編號 0 到 numCourses 的課程。有先修要求 prerequisites,其中元素為[a, b]代表要先修完 b 才能修 a 課程。
回傳 true 如果可以修完所有課程,否則 false。

一旦先修課程形成了迴圈,如:[a, b][b, a],那就無法完成所有課程。

這裡的課程數最多有 100000 個,數目很大,要是使用暴力破解會有超時的可能。

如果使用先修數作排列,少的先修,多的後修,但這樣可能用上 O(N^2) 的時間,直接放棄。

先串起 N 個課程,像是N-ary Tree或是Linked List等,再檢查有無迴圈產生,會用 O(N) 時 O(N) 空。

用 memo 紀錄到過的 node,如果往下的過程碰到的 node 出現在 memo 裡面,則代表有接回頭,頭尾相接就回傳 false。

用 DFS 去搜尋,如果到底,也就是沒有後續課程,那代表該條搜尋路線是沒有迴圈產生,是安全路線,之後若 DFS 來到安全路線,也不必再往下尋找。

我在想那個題目所講的方式到底是什麼?後來看懂了,大部分的解答都是用提示的想法出發。

那就是做拓樸排序,也就是把有向圖(有方向關係,如先修),轉換成排序。如果有環出現,就無法轉換。(參考)

找第一點開始,也就是沒有先修課程的課程。往下尋找,每個點到訪次數不能超過 numCourses。這樣算暴力破解法嗎?

2020年5月28日 星期四

Leetcode題解 Python & C#:五月挑戰DAY28 Counting Bits

給一個非負整數 num,要找出 [0, num] 之間(包含兩端)的所有整數在二進位下有的 1 個數。

因為很簡單,所以有限制條件,在 O(n) 時空內解決。

如果是 0,那其二進位不含 1 ,是 0 個。
如果是 1,那其二進位只有 1 個 1 。
如果是 2,那進位,二進位變成 10,只有 1 個 1。
如果是 3,那二進位是 11,有 2 個 1。
如果是 4,那進位,二進位是 100,只有 1 個 1。 

Leetcode題解 Python & C#:五月挑戰DAY27 Possible Bipartition

有 N 個人(編號 1, 2, ..., N),要依照 dislikes 去分成二組,在 dislikes 中,每組 [a, b] 代表該編號的二人不能在同一組。
回傳 True 如果可以順利分成二組,否則 False。

這題蠻有意思的,我第一念頭想到 backtrace 可以硬解,但太慢,我放棄用 DP 與 遞迴函數。

說到兩兩成對,就想到最近的表格式DP,這題一樣可以用表格式DP,最後用 DFS 的遞迴函數可以比 backtrace 快上不少。
只要表格上可以選成每列每欄都只有一組配對,那就有可行的分組,也就是True。

抛開遞迴,因為平面化的話,一有答案可以馬上回傳,而遞迴函數會一路收斂到最上方,才能停止。要是深度深,會多花上一些時間。

被自我侷限,得用別的方法來。

2020年5月26日 星期二

Leetcode題解 C#:五月挑戰DAY26 Contiguous Array

給一個二進位串列,回傳 0 與 1 數量相等的連續子陣列的最長長度。


這次補寫 C# 版的 code。

Leetcode題解 Python & C#:五月挑戰DAY26 Uncrossed Lines


給兩個數列 A B 分別寫成上下平行,如果 A[i] == B[j] ,那可以用一直線相連,要找出最大沒有交叉的連線數。

做完上一個 contest ,再看這個問題,很自然就會以相似的解法去進行。

這裡有一個特色「兩數列,各取一個兩兩配對,配對後不能重複再用到。」這樣想到可能會有一個表格狀的DP,再應題目做處理。

這題也是這樣。

DP[i][j] 定為該位置上的最大的沒交叉的相連數。

如果位置 A[i] == B[j] ,那 DP[i][j] = 1 再加右下方 DP[i+1][j+1] ,即加上後方的最大沒交叉相連數。
然後 DP[i][j]  = 從自身、右、下三者取最大值,這樣 DP[i][j] 會是最大的沒交叉的相連數。

最後回傳 DP[0][0] 即可。

2020年5月25日 星期一

Leetcode題解 Python:Max Dot Product of Two Subsequences

給兩個陣列 nums1 和 nums2。回傳兩陣列中非空子序列(皆相同長度)的最大的點積(Dot Product)。
子序列可以是以原陣列刪除部分(也可以不刪),但元素的相對順序不變。

dot product的運算:
假設從 nums1 跟 nums2 取出 A 序列 跟 B序列。
A1、A2、A3
B1、B2、B3
=> A1*B1 + A2*B2 + A3*B3 => value

我又沒在時限內做完contest,QQ。

這題使用 DP 的觀念,用 p1 p2 代表兩陣列的點乘位置並加上後續的最大值,同時也是規劃 DFS 的遞迴函數。

由於不能有空值,一定要有個兩位置相乘作為開頭。即 nums1[p1] * nums2[p2] 。

面對 DFS 的設計,要從底開始想,也就是到底的情形。即 nums1[len(nums1)-1] * nums2[len(nums2)-1]。因為不能為空串列,該位置只有 nums1[p1] * nums2[p2] 的可能。

DP[len(nums1)-1][len(nums2)-1] ==  nums1[len(nums1)-1] * nums2[len(nums2)-1]

往上回傳,上面會是 p1-1 或是 p2-1 的,這樣不是 p2 會被重複乘到,不然就是 p1 。有共用的情況下,應該要取最大的可能,自身點乘跟下一個(下方、右方)取最大值,然後決定當前位置的 DP 值。

重點來了,那要如何再取更多,也就是要變成多組的情況。

每一對 p1, p2 都是一個點乘,其左上一個 p1-1,  p2-1 。
對 p1-1,  p2-1 來說,除了乘積外,還要加上 DP[p1][p2]的值,也就是後面的最大可能,如果後方最大值小於零,則不加。

這樣就能用到多組,從多組中找出最大的數值。
 
整理一下順序。

先算出 nums1[p1] * nums2[p2] ,視狀況加上 DP[p1+1][p2+1] 的值,成為該位置的基礎值。
跟下方(共用一個)的取最大值,即 DP[p1][p2] = max(DP[p1][p2], DP[p1+1][p2], DP[p1][p2+1])。

2020年5月24日 星期日

Leetcode題解 Python & C#:五月挑戰DAY24 Construct Binary Search Tree from Preorder Traversal

給一個以「Preorder」得到的值陣列,重構出二元樹,並回傳 root 。

這是一個嚴謹的二元樹,所以左邊子樹皆小於自身,右邊皆大於自身。

preorder 是以 自身 → 左 → 右 ,因此陣列的第一個元素會是root。
接著下一個會是左邊第一個,直到比root大。
第一個比root大的是右方第一個,直到最後。

因此可用從陣列中篩選(比第一個)大小,並把 solution 當遞迴函式用,完成二元樹的重構。

(因為本題重複過,所以更多說明請點這裡。)

2020年5月23日 星期六

Leetcode題解 Python & C#:五月挑戰DAY23 Interval List Intersections

要找出 A 與 B 的區間,因為不一定會按順序交疊,所以用 indexA , indexB 來記錄目前 Traverse 的位置。

如果有交疊,範圍會是從二者的左側取大,右側取小的範圍。

那要怎麼移動 index ? 

如果有一個的右側很大,對方的下一個右側可能還沒超過它。因此,此時應該是要繼續移動右側較小的。

2020年5月22日 星期五

Leetcode題解 Python & C#:五月挑戰DAY22 Sort Characters By Frequency

給一個字串,要以字母的出現次數重新排列。

首先要有可以統計字母的方式,用 hash table 可以很容易記錄哪些字的有出現與其次數。

python 有 collections.Counter 可以快速轉換。

2020年5月21日 星期四

Leetcode題解 Python & C#:五月挑戰DAY21 Count Square Submatrices with All Ones

給一個由 0 1 組成的矩陣,回傳有多少個正方形全部都是 1 。

這題蠻容易的,就算用暴力法 + dp ,也不會被判超時。

如果本身是 1 ,自己能形成一個正方形,然後往四角延伸去看是否能構成更大的正方形。

用 y x 由上至下,由左至右,所以選右下作基準點。那麼能構成更大的正方形,是其左上、左、上都是同一個數字。

如構成 2 * 2:
[[1,1],
[1,2]] 

位置[1][1]的點,其左上、左、上都是 1 ,所以可以構成 2(1+1)。

3 * 3:
[[1,1,1],
[1,2,2],
[1,2,3]]

位置[2][2]的點,其左上、左、上都是 2 ,所以可以構成 3(2+1)。3 表示該位置有(1*1、2*2、3*3)的正方形。
位置[2][1]的點,其左上2、左1、上1,所以可以構成 2(1+1)。

也就是說,是左上、左、上中取最小值+1。前提也要該位置本來是 1 才可以這樣處理。

2020年5月20日 星期三

Leetcode題解 Python & C#:五月挑戰DAY20 Smallest Element in a BST

給一串二元樹,在找出第 k 個小的元素。

這題目給的二元樹是比較嚴謹的,子樹左邊皆小於自身,右邊皆大於自身。

因此,左邊最底層的是最小的,接著其父,其父之右子樹,這與「in-order」的 traversal 方式是一樣的。

所以用上這樣的方法,並且加入「第幾小的序數」就能得到題目所求。

對於二元樹的題目,各種traversal模式都要熟練,才會很快地想到寫法。

2020年5月19日 星期二

Leetcode題解 Python & C#:五月挑戰DAY19 Online Stock Span

做一個 StockSpanner 收集每日價格資訊 price (int),並有設方法 next() 輸入 price ,回傳當前 price 是幾日新高。

對於這樣的問題,要想會不會用上 遞增數列 的觀念,這是這類題的一種數學思維,想通就會做。
(遞增數列的典型倒是沒有想到有什麼特徵可以快速抓出該用這個概念)

如果前面不超過price(<= price),那麼就把前方的紀錄丟掉。如果沒資料可丟,代表目前是紀錄中最大的。

用一個計數 count,把用 next() 次數保留下來,就可以其減出當前price是幾日新高。(若沒紀錄,則 count - 0)

並且把 price 跟 count 添加到紀錄中,price跟count的紀錄代表前方沒有比price更高的。

官方寫法用 weight ,省下了 1 空間,但功能跟 count 都是記錄次數的。

2020年5月18日 星期一

Leetcode題解 Python & C#:五月挑戰DAY18 Permutation in String

給二字串 s1 s2,問 s2 是否有含 s1 的排列。

這題跟昨天的原理一樣,就不多說了。 DAY17

Python
class Solution:
    def checkInclusion(self, s1: str, s2: str) -> bool:
        count = Counter(s1)
        match_len = 0
        p_len = len(s1)
        for i, c in enumerate(s2):
            while match_len and (c not in count or count[c] == 0):
                count[s2[i - match_len]] += 1
                match_len -= 1
            if c in count and count[c] > 0:
                match_len += 1
                count[c] -= 1
                if match_len == p_len:
                    return True
        return False

2020年5月17日 星期日

Leetcode題解 Python & C#:五月挑戰DAY17 Find All Anagrams in a String

給一個字串 s 跟 非空字串 p 。要從 s 中找出所有 p 字謎的的起始位置。
(字謎是所有字母都出現一次,連續出現但順序可以變動)

如果順序不重要,那麼用 hashtable 記下哪些字母出現與次數及可。

從 s 開頭,依序匹配,是否在hashtable中及目前有可匹配的次數嗎?如果有,該字母的可匹配的次數 - 1,匹配長度 +1。
如果沒有,用匹配長度把最前方的已匹配字母放回hashtable所對應的次數,直到匹配或是匹配長度歸零為止。

如果匹配長度與 p 長度相同,則回推開始位置放入 result 中。

Leetcode題解 Python:Form Largest Integer With Digits That Add up to Target

給一串數字[1-9]的花費 cost,跟 總花費 target,回傳總花費相等的最大可能數字。

這題是DP,我本來感覺沒很難,結果敗在這裡 QQ 。考驗對DP與遞迴函數的熟練度。(我用了一些時間才精簡到可行)

對於規劃,函數要使用 t 也就是剩餘的花費,在同一個 t 之下,結果只會有一個最大值。
(我一開始用三個、二個都會超時,睡起再改成一個)

於是要朝著 dfs(t) 的方向前進。

在[1-9]花費有可能一樣的,為了加速,二者花費相等,只要保留較大的數字,結果也只會用到最大的數字。

如果 t == 0 做中止條件,那麼就無法知道是哪個數字讓其歸零。
得用 t - each_cost == 0,這樣就可以知道哪個數字了。

回傳值是最大的數字,若 t - each_cost > 0,那麼應該用 dfs(t - each_cost)去找出後面的最大數字,
並與 each_cost 代表的數字結合。

而 each_cost 有數種,同個 t 之下也有不同的組合,所以用 max() 去選出最大的數字,才能符合設計。

2020年5月16日 星期六

Leetcode題解 Python & C#:五月挑戰DAY16 Odd Even Linked List

給一個 linked list,要以 偶數位 奇數位 重新排列。

要求在 O(1) Space、 O(n) Time 解決。

分別拆成偶數奇數頭出來,再把剩下的 linked list 分給兩者,最後合併,回傳偶數頭。這符合題目要求。

或者直接在頭尾開始接,但這預期不如上者,因為上者到 n-1 位置時,就幾乎要完成了;而這方法才剛開始。

Leetcode題解 Python & C#:五月挑戰DAY15 Maximum Sum Circular Subarray

給一個環形陣列 A,要找出總和最大的子陣列。

這題蠻有意思的,讓我沒有順利解出來,有想到好幾種可能,看了別人的解法,發現是用最大最小的方式。
但我想到這方法的當下,沒有去進一步思考。

官方有 Solution ,內有四種解法,但我還是照步調去走從未知去導出解法的思維分析。

如果會做陣列中子陣列最大總和,那環形的也只是稍做變化。

要如何後面接回前面?這是第一個要解決的問題。

2020年5月14日 星期四

Leetcode題解 Python & C#:五月挑戰DAY14 Implement Trie (Prefix Tree)

要實作 trie ,有 insert, search 和 startsWith 的功能。

trie 很實用,但在 algorithm 中,不太好用上這樣的方法解題。昨日在學 cisco ,就使用 trie 來加速指今輸入。

由於 Leetcode 有教學,這裡就不多說如何解題。(如果懂 trie 是怎樣的型態就會知道要怎麼寫)

Leetcode題解 Python & C#:五月挑戰DAY13 Remove K Digits

給一個非負整數以 string 型態表示,移除 k 個數字,讓數字變成最小。

如果把它作DP,會超時,因為 num 的長度最多到10002。

不用 DP ,我們要來想想要如何從頭開始找起,並作篩選。

如果要讓結果是最小,那可想像會在一個範圍中,保留較小的,取走較大的。如此運作,直到挑出 k 個。

仔細觀察,被挑選過後的樣子。
"1432219", 3 -> "1219"
"4321234", 2 -> "21234"
"10200", 1 -> "200"

如果要排成最小,那麼會讓數列盡可能朝著「非升序排列」,也就是若左方大於右方,左方就會被挑走。
若左右相等或小於,則住右方移動。

2020年5月12日 星期二

Leetcode題解 Python & C#:五月挑戰DAY12 Single Element in a Sorted Array


給一個經排序後的數列,找出其中只出現一次的元素。(其他重複二次)

被限制在 時間複雜度 O(log n) 和 空間複雜度 O(1),解決。

說到 O(log n) time ,經典的方式是二分法。

由於其他都出現二次,可想 pos % 2 == 0 的位置會等於其右方的,除非有出現單次的元素在左方。

用二分搜尋法,計算的 m 在比較的時候要用 % 2 修正位置,去讓奇數位比其右方的位罝。

這裡的 r 跟往不同減了 1 ,是因應「超界」做處理,如果出現單次的在最右方,那麼一般的二分搜尋法,會在比較時比 nums[n] 。如果左方都出現兩次,那最後 r 值不變,指向最後一個為單一的元素。

2020年5月11日 星期一

Leetcode題解 Python:Trapping Rain Water

給一個代表(牆)高度的非負整數數列,而下雨之後,水會積在低窪處,回傳水的體積。

水會積多少,取決於左右方的最高高度取較低者減當前高度,若小於零代表沒有積水。

這樣子很好寫出正確寫法,但是超時的問題得考慮進去。

如果每次都重找最大值,時間複雜度會是 O(n**2),得想辦法拉到 O(n) 解決。

最大值一定要重找嗎?如果從左方開始,左方的最大值會一路遞增,而右方會一路遞減。過了數列的最大值,消漲就會互換。

也就是說,若能使用 TwoPointer 去從左右方往中間靠近,邊找邊算所求,就能把時間複雜度用在 O(n)。

當然可以做到,官方也有範例可供參考。

Leetcode題解 Python & C#:五月挑戰DAY11 Flood Fill

給一個二維陣列,要把[sr][sc]位置上的顏色(值)換成 newColor,像是油漆桶工具,回傳取代過後的二維陣列。

這題是「遞迴函數」的基本題。

遇到在這種二維陣列的題型,選一個點上下左右找,為了避免同一位置重複找,用hash類紀錄找過的位置作檢查。

如果用 for迴圈 要思考整體是由左上至右下找,會不會因此有漏網之魚,如果有,還是得回歸 遞迴函數。

Leetcode題解 Python:Combination Sum II

給一個數列 candidates,要從中選元素加總等於target,每一個元素只能選一次,回傳有幾種不同的組合。

由於要不重複的組合,所以用 hash 類可避免重複,又或者以計數每個各(不重複)元素的使用次數去推組合。

前不久說這類在「一數列中」的題目會有一些特點去知道該用什麼方法去解。
這一題則是「實際組合」,因此得保存用了什麼數字去嘗試,變成由上到下。(沒有用hash比較)

元素不必相鄰,而且都是正整數,但元素可能會出現重複,練習抓這些規則可以幫助順利寫出解法。

要撈出組合,有好幾種方法,我一開始使用 backtrace,不過這不好,backtrace是相對沒效率的方法,光是移出移回,每層就多耗了二步在搜索上。

將 backtrace 攤平後,用 stack 一層層找。

先 candidates.sort(),因為由小至大,加上避免重複,所以每個可能的組合只能往最後一個選擇位置的右方去挑選。
如 [1,2,3,4], target=3 中,若目前選到 1 ,則接下來只可以往右方,從 2 開始做選取。(若能往左,只會多出很多重複)
不過這沒辦法做到完全不重複,要是在 [1,1,1,1], t=3,[1,1,1]會出現兩次。不過這方法可以明顯加速找組合。

按照這樣的規則,再加上要比較的是總合,所以stack初始要放入的會是,當前總合(int)與每個選取位置(List<int>)。

2020年5月10日 星期日

Leetcode題解 Python:Next Permutation

給一個組合,要回傳(按各組合由小到大)下一個組合,如果是最大的組合,則回傳最小的組合。

這一題是一種數學題,如果數學想通了就很好解,沒想通,那就只好琢磨到理解為止。

這裡要講如何理解。以 [4,2,3,1] 排組為例。

如果要找到下一個,就只要從右方移一個(從尾開始),讓該組合的大小略增即可。

那要跟誰換呢?從右方開始,如果該位置的值比左方的大,那就是目標位置。
如果左方皆 >= 右方,那代表該組合是最大的。如 [4,3,2,1] [5,1,1]
([4,【2,3】,1])※選左右哪方當目標位置都可以。這裡用左右,必免混淆。

接著從目標位置右及右方開始選一個大於目標位置左的最小值,二者互相交換。可以想成進位。
([4,【2(左),3(右)】,1] -> [4,2,【3,1】] -> [4,2,【3】,1] -> [4,3,2,1])

換完之後,目標位置右方,應該要變成小到大的排序﹐才會是下一個組合。
([4,【3】,2,1] -> [4,3,1,2])

在換之前,目標位置右及右方已經是非升序排列(左>=右),若把它看成一個排列組合,那此時的右方排列會是該排列組合中最大的。

最大的組合,只要反序,就會回到最小的組合。

Leetcode題解 Python & C#:五月挑戰DAY10 Find the Town Judge

有 N 個人(編號 1,...,N),若所有人信任第x個,而且第x個不信任任何人,若只有一個符合這樣條件的人,回傳其編號。

把條件一一拿來解讀:
他必須被所有人信任,所以那人得到的信任會是 N-1 個。
他不會信任任何人,所以在 trust[i][0] 的人都不是候選人。

於是在遍歷 trust 的同時,要去統計每個編號的被信任數(t[1]),並刪除候選人(t[0])。

剩下的候選人是不相信任何人的人,再剔除其被信任數不為 N-1 的。

最後符合條件1,2,若只剩一個即符合條件3,回傳其編號。若無則-1。

Leetcode題解 Python:Number of Ways of Cutting a Pizza

給一塊矩陣 pizza,要用k-1刀切出 k 塊,每塊上面都要有至少一個 'A'(蘋果),每刀只能切一直線,而且切完會拿走右方或是上方的那一塊。要回傳有幾種切法。

這算hard中偏簡單的問題,結果我在這題犯小錯卡很久,但正確的主體寫法一下子就寫好了。

因為這題給的拿取規則,使我們隨著每一刀而往右下移動,不用考慮前方的結果。(前不影響後)

因此,使用 dfs(深度優先),以[y][x]做為dp的依據是沒問題的。(數字很大要取餘也是dp題的特色)

有刀數限制,再組合剛才的dp,組合成 dfs(y, x, pieces)的遞迴函數,而終止條件是 pieces == k。(可能有重複參數出現,可以使用 memo 紀錄)

Leetcode題解 Python:Count Triplets That Can Form Two Arrays of Equal XOR

給一列整數數列,問兩相鄰的子數列XOR總值相等的個數。

今天就差這一個沒有解出來。QQ

這種在「一數列」中取「組合」做「比較」的題目,都有一個解題方向,就是用dp的觀念去保存比較值。

比較值,比較值,比較值,因為很重要,所以說三次。而且比較也要朝著在hash類內的做處理。

2020年5月9日 星期六

Leetcode題解 Python:Substring with Concatenation of All Words

給一字串 s 跟 words 字串串列,問哪些位置可以找到全部在words中的詞只出現一次。

思路很簡單,想辦法在一個 n 的時間讓全部都搜過。(用搜尋法倒很隨意)

有一個條件可以利用,那就是每個 word 都是一樣的長度,因此不必逐字元去判斷,可以拿詞去匹配。

於是產生原型的逐詞匹配,但這還不夠!速度還太慢。

每當遇到這種匹配題型,盡可能不要反覆同一位置重複匹配。之前有提到一個方式 KMP,若全照用,這裡可能會在 prefix table 產生太多組合 。

因為 words 的詞沒有指定先後順序,所以要是當前位置沒有匹配到,就可把最前面匹配的放回待匹配的隊伍,繼續匹配,若都沒有可匹配,那就往下移動。

Leetcode題解 Python & C#:五月挑戰DAY9 Valid Perfect Square

給一個正整數,問其是否為整數的平方。

若以不轉換資料型態來計算,即只用 int 去解決,只要判斷 num 是否在(枚舉)整數的平方。
(這是一種挑戰,不然用Python內建的計算,一句就能非常迅速的解決。)

不過枚舉這樣子太慢了,如果是完美,那 num 可以用兩個相同的因數相乘。

用上找因數,就能避免枚舉很多不必要的數字,也不會有怕溢位的問題。

如果使用二分搜尋法加速,又會面臨溢位的問題,但處理好後速度會快不少。

2020年5月8日 星期五

Leetcode題解 Python & C#:五月挑戰DAY8 Check If It Is a Straight Line

檢查是否所有座標都在一條直線上。

這是很簡單的數學題,只要細節不漏掉就好。

寫出方程式: ax + b = y。但這不適用於垂直直線,因為同 x 會出現兩個以上的 y。 

這兩種情況要分開判斷,只要不是垂直直線的,靠解方程式就可以判斷。
若是,則看所有的座標的x是否為相同值。

(打完題解後,發現我本來寫好的是錯的但卻可以接受,只好幫提供testcase,默默改正code。

Leetcode題解 Python:Reverse Nodes in k-Group

給一個 Linked List,從左至右以 k 個節為一組作反轉,若不滿 k 個,則該維持原有排序。

像是在 List 中作位移的題目,多半跟 TwoPointer 的技巧是相關的。

這題也不例外,用 TwoPointer 控制兩個點互換,按題目要求解出所求。

如果由左至右,考量到後方未必有 k 個節,使用「深度優先」的遞迴函數,對於求解會比較好進行。(個人覺得比較好理解。

先檢查目前是否還有 k 個節,沒有則不逆序,有則逆序。

Leetcode題解 Python:Merge k Sorted Lists

合併 k 個 linked list 成一個 linked list,並且值由小到大排序。

說到要保持由小而大,不外乎就想到 min heap,關於 heap 的介紹,這裡就不多說了。

因為每次新的加入就得重新找出最小的,選擇 heap 是快多了。

不過不論是 heap 還是 sort() 都無法對於 ListNode 做比較,所以不是重新生成ListNode,
不然就是得找另外一種的保存方式。

我選擇用 dict() 以Node的值為鍵去存放 ListNode。但可能會出現同值有二個以上的ListNode,所以得用list存放,
同值的順序並不重要,抓哪個都可以。為了方便,使用 collections.defaultdict(list)。

用 heap 的 list 只有放 ListNode.val,只要拿值去對就可以取出下一個要接的 ListNode。

Leetcode題解 Python & C#:4Sum

如果有做出 3Sum 的人,這類題有一套公式,即使用 TwoPointer 的技巧,靠迴圈、加速搜遍數列。

3Sun是選定一個 i(由前往後),4Sum就多選一個 j(由後往前),剩下用 l、r,即 TwoPointer,用總和值來決定移動 l 還是 r。

先把數列排序,若 nums[i] + nums[l] + nums[r] + nums[j] > target,則我們要縮小總和,因此讓 r - 1,反則 l + 1,
這使得每次移動都讓總和與目標值靠近。如果跟總和跟目標值相同,記錄組合,然後移動 l 與 r 。  

由於不能重複,存放組合的資料型態可以選hash家族,這樣就能有效率的避免有同樣組合被計到二次以上。
又或者將重複的狀況跳過,不僅可加速迴圈,也可以直接用 list 放結果,不用轉換資料型態。 

Leetcode題解 Python & C#:五月挑戰DAY7 Cousins in Binary Tree

給一個二元樹的根節,找出 x、y是否為cousin(同深度,不同父)。

遍歷所有的節,再把找到的x、y資料(深度,父節)做對比,就可以辨別是否為cousin 。

要找遍二元樹上的每一個元素,除了可以用遞迴函數,也可以用一層一層的stack(queue皆可)來找。由於每節的值不會重複,不用擔心多個可能。

就效率來看,這題的情況單純,儘可能減少判斷,才是通往高效之路。

2020年5月7日 星期四

專案開發紀錄1:規劃使用環境

雖然我已經自己寫過好幾版的應用程式,不過卻很鬆散,沒有一個明確的SOP,想到什麼就做什麼。

每次都有一點突破,這次也要求自己要用上更多的進階技巧。

我在想打造一個方便跨平台,同時又能減少關卡的資訊傳送方式。不是那種要用IP位置的通訊協定、這太費工夫。

同時最好可以主機用API去修改內容,但不改變網址,如果每次更新都要開權限再重新分享,實在太麻煩。

對於跨平台是第一次嘗試,考量我之後沒辦法總是在我主機上使用,就算出門在外,也要能時時刻刻掌握動態。

線上DB + google sheet 本來是我第一個的組合。之前搭載虛擬機,可以用MSSQL,目前DB類的都不成問題。
不過,google sheet 暫時卡住我,因為我開新帳號,原因是覺得目前我用的帳號太過氣,取一個比較合身分的帳號。

我知道google新帳號,會被限縮很多功能,但主帳有很多私人的資料,我不想因為這次開發開一堆權限出去。
於是我的新帳號沒辦法使用 外掛功能 ,第一個嘗試就被迫停止在這裡。

接著我打算試試看 WordPress ,如果資料可上雲端,那麼我需要的是「隱私性」,只有我能看,不給一般尋常方式(google搜查)可找到。
WordPress有外掛功能,可以連接DB,也有權限設定。但,那需要商業版,一年300$,也就是9000台幣。
行不行?讓我不爽,因為我不知道WordPress,到底值不值,也許用起來跟我想像的不同,因為沒有試用,我的疑慮無法消。

「GIVE UP」換一種吧。

我希望主機要能即時影響所要呈現的資訊,不需要到每秒,但希望也能有每分鐘的更新頻率。

2020年5月6日 星期三

Leetcode題解 Python & C#:五月挑戰DAY6 Majority Element

給一個大小為 n 的陣列,找出出現次數超過 n/2 的元素。(可以預期這個元素一定存在)

這個是計數題,數一遍 n 並且統計出現次數,就能找到 majority element。

 Python
class Solution:
    def majorityElement(self, nums: List[int]) -> int:
        import collections
        counter =  collections.Counter(nums)
        n = len(nums)
        for key in counter:
            if counter[key] >= n/2:
                return key

2020年5月5日 星期二

Leetcode題解 Python & C#:五月挑戰DAY5 First Unique Character in a String

給一個字串,找出第一個沒有重複的字符位置,如果沒有則回傳 -1 。

沒有重複,代表該字符的計數為 1,只要找出最早的計數為 1 個字符。

最快的情形是跑一遍 N ,再挑出符合的,沒有那就是 -1 。

Python
class Solution:    
    def firstUniqChar(self, s: str) -> int:    
        import collections
        counter = collections.Counter(s)
        for key in counter:
            if counter[key] == 1: return s.index(key)
        return -1
C#
public class Solution {
    public int FirstUniqChar(string s) {
        var counter = new Dictionary();
        foreach(var c in s)
        {
            counter[c] = 1 + (counter.ContainsKey(c) ? counter[c] : 0);
        }
        foreach(var item in counter)
        {
            if(item.Value == 1)
            {
                return s.IndexOf(item.Key);
            }
        }
        return -1;
    }
}

2020年5月4日 星期一

Leetcode題解 Python & C#:五月挑戰DAY4 Number Complement、Complement of Base 10 Integer

給一個整數,回傳一個bit位互補(即 OR 後的 bit 每一位都是1)的數。
(這題跟 1009 Complement of Base 10 Integer 是相同的,只是換了名)

這題的邏輯很簡單,找出一個與 num 相同 bit 位長度且每一位都是 1 的數,再用 XOR 去求解。

為了必避免C#的int溢位,所以使用推進的方式,而不是由高位減一。

Python
class Solution:
    def findComplement(self, num: int) -> int:
        return ((1 << num.bit_length()) - 1 ) ^ num
C#
public class Solution {
    public int FindComplement(int num) {
        int r = 1;
        while(r < num)
            {r = (r << 1) + 1;}
        return r ^ num;
    }
}

Leetcode題解 Python:Find the Kth Smallest Sum of a Matrix With Sorted Rows

給一個 m*n 的矩陣,跟整數 k,而且每行已經排列。從每行取一個出來作總和,求第 k 小的總和是多少。

這題跟 373. 是很類似的,不過這題多了很多行數列,但並不是什麼難點。

若採用全部枚舉的話,最多40**40個,這樣子會超時。

若分批進行,一樣從全部的索引 0 開始,每輪後索引總和+1,繼續枚舉,因為目標大小 k 最高200個,所以這方法是可行的?

只要每輪枚舉結束,枚舉的總數超過 k 就可以找到目標值?但並不是這麼一回事。

的確一開是從最小值開始枚舉,會得到各索引+1的組合。

若只是單純以索引+1為枚舉的方式,這不能保證一定能包含完整的最小總和順序。

例如:mat = [[1,2,3,4,5,6],[1,1,1,1,1,1]], k = 3

Leetcode題解 Python:Find K Pairs with Smallest Sums

給兩個數列 nums1 nums2 ,要回傳 k 個總和由小排列的組合。參考

關於會作這一題, 是因為有一題更進階的在後頭,所以先把這個處理掉再進階。

之前我碰到 heap 都不是了解,但是又很多人會用,所以這次我得學著用 heap 去解問題。

關於 heap , 是一種「堆積法」,有 maxheap 和 minheap ,可想成是一個二元樹,把根節設為最大或最小。

好處是可以快速找到 max 或是 min ,在要用到這類的計算排列時,heap 就很好用了。

python的 heapq 是以 minheap 為主的,跟一般排序的 maxheap 是不同的。

而 minheap,也是這題會用到的 heap ,要找最小值。

如果用 heapq.heappush() 相當於 append()、sort(),這樣在枚舉時也能加速。

這題要找組合,所以要保存總和由小到大的組合的 result 。

2020年5月3日 星期日

Leetcode題解 Python:Number of Ways to Wear Different Hats to Each Other

給一組二維串列,一維代表第 i 位的人,二維代表該位置上的人可選的帽子種類編號。回傳全部人都戴上帽子且都是不同種類的方法數。

這題是DP,最大的特徵就是數目很大,需要取餘。(以前提過)

但DP的精華就是如何設計DP在放些什麼,而我這次學到了「bit」的方法。(然而我沒有在contest解出來QQ)

由於帽子種類跟人數都是有限制範圍的,若要取DP,可以由帽子做分配,或是由人開始。

要選哪一個?這我當時沒有想清楚,結果用人為主體。然後backtrace都超時。

以人為主體的話:1人可能有40種﹐最多有10人,是 40**10 是嗎?不,最糟是40*39*38*...*30。

以帽子為主體的話:則是 40*10! ,40是帽子的種類,10的階乘是代表沒有戴上帽子的人。只要能提前戴上帽子,就可以提前結束遞迴函數。

Leetcode題解 Python & C#:五月挑戰DAY3 Ransom Note

給兩個字串:ransomNote、magazine,問 ransomNote 是否能從 magazine 裡面的宇符組成。

嗯,這是一個電影會見到的情節吧…

方法很簡單,就是 ransomNote 的每個字符出現次數要比 magazine 的少或等於。

Leetcode題解 Pythonython:Longest Continuous Subarray With Absolute Diff Less Than or Equal to Limit

給一串整數陣列 nums 跟整數 limit,求連續子陣列的最大小值相減 <= limit 的最大子陣列個數。

我以前有碰到過這種問題,但我沒有在當下把 deque 的觀念內化,都選擇用dp,終於在今天吃苦頭。
(又或者可以用 two pointers 做解決。)

這種類型的問題有一個特色,就是有「連續子陣列(或串列)」,若條件不合,去掉頭再繼續。

用 時間複雜度 O(N) 解決掉,只給跑一次 nums 的機會。

基本架構是用一個串列,先把遇到的數加進串列後方,(while)若 max() - mix() > limit 則 del[0],在每輪最後檢查len()。

2020年5月2日 星期六

Leetcode題解 Python & C#:五月挑戰DAY2 Jewels and Stones

給兩個字串 J、S ,J裡面的字符是代表jewel,問 S 裡面有幾個 jewel。

這是一個相當簡單的問題,重點會放在用什麼作為搜尋的方式。

為了快速,這裡會使用 hash 家族的資料型態讓搜尋更快速。

如果是Python會用 set 或 dict 都可以,我用 collections.Counter, 只是換一點寫法。collection.Counter 是 dict 類的。

如果是C#,我直接用 string 的 Contains() ,其實做法都大同小異。 

2020年5月1日 星期五

Leetcode題解 Python & C#:五月挑戰DAY1 First Bad Version

給一個 n 代表目前有 n 個版本,要透過 isBadVersion(version) 的函數去找到第一個 True(即BadVersion)的版本號。

這題如果是有使用git去控制版本,那應該是很生活化的搜尋思維。

首先決定搜尋法,這裡使用二分搜尋法去解。

左為 l 右為 r,每次都從 m = (r-l)//2+l 位置找,我之前不太能掌握 l, r 與 m 的替換,到底是 >=, <=, m+1, m 的關係要如何連結起來,當我試著想用 r 去貼齊 target 的位置 ,就比較了解該怎麼寫對。

不過這題也不會有這樣的困擾。

由於版本會從 1 開始, l (L)= 1 ,而 r 要等於 n + 1 ,不過最壞的陷阱來了。若 n = 2147483647 ,那麼就會發生溢位,所以要做一點小修改。(如果是Python就不用擔心了)

2020年4月30日 星期四

Leetcode題解 Python & C#:四月挑戰DAY30 Check If a String Is a Valid Sequence from Root to Leaves Path in a Binary Tree

給一串二進位二元樹跟一串二進位數列,是否能依數列的值到達葉(leaf),也就是底層。

這算是「搜尋法」,只要有基本的暴力破解法觀念也可以順利解出。

如果要優化時空,使用遞迴函數就可以快速解出。

Python
class Solution:
    def isValidSequence(self, root: TreeNode, arr: List[int]) -> bool:
        if root and arr and root.val == arr[0]:
            if len(arr) == 1 and root.right is None and root.left is None: return True
            return self.isValidSequence(root.left, arr[1:]) or self.isValidSequence(root.right, arr[1:]) 
        else:
            return False

2020年4月29日 星期三

Leetcode題解 Python & C#:四月挑戰DAY29 Binary Tree Maximum Path Sum

給一串非空的二元樹,找出最大的路徑總和。

如果要使用遞迴的想法:以尋找當前的節與左右兩邊子樹最大的路徑,可以找到通過該節的最大路徑。

如果要住上傳,那會是左右或零取大者加自身再上傳。

因此遞迴函數要有兩種:
1.回傳值是到該點的最大路徑。
2.由1.得到左右最大的路徑,去計算通過該點的最大路徑。

2020年4月28日 星期二

Leetcode題解 Python:四月挑戰DAY28 First Unique Number

有一個 queue 類別 FirstUnique 特點是要回傳第一個沒有重複的整數。要完成有這項功能的 FirstUnique 。

這個題目倒是沒有 remove 的功能要實現,所以相當容易解決。

要如何知道重複出現?最快的找法就是使用hash 類的資料型態去記錄。

單獨出現過的放在一個 hashtable 不用set是因為我們要保留序列;重複出現的移到第二個 hashtable,並刪除第一個 hashtable 的鍵。

如此 showFirstUnique() 只需要看第一個 hashtable 的第一個鍵,沒有鍵則是 return -1

2020年4月27日 星期一

Leetcode題解 Python & C#:四月挑戰DAY27 Maximal Square

給一個2D矩陣,其中只有 1 或 0 ,找出最大的連續 1 組成的最大正方形面積。

首先來想要怎樣去找任意一個正方形的面積。

如果選定某一位置,那正方形會在往右下延伸、或左上,簡單來說就是四周。

不過這樣相當不容易寫,而且也會效率不佳。

再觀察一下,正方形出現時會有怎樣的情形?

假設有個面積為 4 的正方形:
基礎假設
可以發現組成邊長2的正方形,其相鄰加身自身會有2個連續出現1超過二次。

同理邊長為3的正方形。
計算流程
於是各欄只要計數1的連續出現次數,接下來問題變成要如何從數列中找出最大的正方形面積。

接著要去從數列中求出最大的正方形面積。

我這裡使用暴力破解法,以自身為左右索引的起始點,往左右找有大於自身數值的個數。
如 2 ,只要往左右再找一個,因為自身本身可以當作一個計數。

過程中,數值小於當前的最大面積的邊長也可以跳過不往左右找。

2020年4月26日 星期日

Leetcode題解 Python & C#:四月挑戰DAY26 Longest Common Subsequence

給兩個字串,請回傳兩字串有幾個相同的部分?匹配的位置可以不用相鄰,但相對位置要一致。

這不是一般的字串匹配問題,因為匹配的部分可以不相鄰。

換個思維,要如何做到不相鄰的配對?

首先看兩者的[0]位,如果相同,則兩者往右尋找。
若不,則 text2 往下一位 [j+1] 匹配,,因此,對應 text2 的索引 j 的意思是 j 之前的匹配數目。
如果沒有匹配對,那 [j] 就等於上一位 [j-1] 的數目。
此外,因為可以不相鄰,所以 text1[i] text2[j] 沒有匹配到的話,[i][j]也可以從[i-1][j] 得到匹配數目。
於是沒有匹配的話 =>  [i][j] = max([i-1][j], [i][j-1])

這是正序的方法,若看 Hint ,我第一個寫成反序的遞迴函數,其實道理都相同。

Leetcode題解 Python & C#:Constrained Subset Sum

給一串整數陣列,回傳最大非空子集合的最大總和,子集合為連續的 nums[i] nums[j] 組成,j<i 且 j-i <= k。

本來今天高高興興的,結果這一題沒有在時限內完成。光是看懂題目就已經用上三十分鐘。

等到明暸的時候,也無啥時間可寫了。檢討失敗的原因,我想是對這樣的題型陌生吧。

由於是集合,所以可選相同位置上的(如前一組的j,後一組的i),但每一個位置只會被計算到一次。

因此如果使用深度優先的遞迴函數,我倒是沒寫好在取相同位置的判斷。(我就在這裡卡住)

這顕是DP的題型,因此選擇會紀錄什麼是接下來首先思考的。

如果遞迴函數不適合,那可以攤開變成迴圈,並且由後往前找。

由於一次選擇一定要成對nums[i] nums[j],因此從 len(nums) - 2 之處開始。(-1位置是無法選一對的)

接者,在 [i+1,i+k] 的範圍中選擇最大的總和,所以DP是紀錄該位置上的總和,然後nums[i] + max([i+1:i+k+1])為dp[i]新的總和。

2020年4月25日 星期六

Leetcode題解 Python:Restore The Array

給一由數字組成的字串,可以在任意位置上切割,但每個切割出來的數字要在[1,k] 之間,而且分割出來的數字不能有前導零的出現,要回傳有幾種可能。

(可能組合數很多,回傳結果對 10^9+7 的取餘)

這題明顯是dp的題目,因為要找出所有組合數,而且答案可能超級大。

如果使用遞迴函數,因為這題的 s.Length 可達 10^5,因此可能會超出最大遞迴深度而報錯。

先繼續講如何設計。

從位置[0]開始,如果左方的數字在[1,k] 之間,可以選擇在這裡切,或是住下一位切。因為要記錄上次的切割位置,所以得設一參數接收。
memo[pos][slicePos]

如果切,往深 dfs(pos+1, pos+1);不切,dfs(pos+1, slicePos)。

要是下一位是 "0" 或是左方數字 < 1 ,那就只能不切,因為在這遞迴函數只取符合規則的方法數。

接著來思考中止條件。

如果 pos == s.length,檢查左方數字是否在範圍內,決定回傳 1 或 0 。

如果 int(s[slicePos:pos+1]) > k ,超出範圍,即回傳 0 。

Leetcode題解 Python & C#:四月挑戰DAY25 Jump Game

給一串非負數列,數列上的元素是最大能跳的次數,從位置[0]出發,回傳是否能到達最後一個索引的布林值。

從位置[0]出發,並記錄剩餘的可跳次數,同時跟當前的元素取最大值。

如果在最後一個索引前可跳次數為零,也代表無法跳到終點,可以提前回傳False。順利遍歷數列的話,則回傳True。

Python
class Solution:
    def canJump(self, nums: List[int]) -> bool:
        remainJump = 1
        nums.pop()
        for num in nums:
            remainJump -= 1
            remainJump = max(remainJump, num)
            if remainJump == 0: return False
        return True
C#
public class Solution {
    public bool CanJump(int[] nums) {
        int remainJumps = 1;
        int index = 0;
        foreach(var num in nums)
        {
            remainJumps -= 1;
            remainJumps = num > remainJumps ? num: remainJumps;
            if (remainJumps==0 && index < nums.Length-1)
            {
                return false;
            }
            index++;
        }
        return true;
    }
}

2020年4月24日 星期五

Leetcode題解 Python & C#:四月挑戰DAY24 LRU Cache

編寫一個 cache ,採取  Least recent used的方式替換已滿cache。

這道題目,有一個關鍵特點,就是會依照最近的使用次序來決定哪一個要在已滿的情形被先換掉。

所以,要有個能紀錄使用次序的方式。

如果某鍵被使用到,那就把它挪到最後方;如果需要替換,就先刪除第一個鍵。

還要有個記錄對應值的 hashtable ,如果使用 get() ,才能從其中撈出值來。

要實現這樣的功能,python有 OrderedDict 類別可以使用(from collections import OrderedDict),這是我覺得最快速的方式。

即使不用,用 dict 與 list 也能做出一樣的機制,只是慢上許多。

不然還有一種方式,使用 linkedlist 做出來,因為只要改變指向,所以會比起用 list 刪改還快上不少。

Python 練習 網路爬蟲 scrapy

看到有徵網路爬蟲的工讀生,我也想打個小工,如果時間可以配合的話。

但我已經有段時間沒有寫爬蟲程式,爬來佔硬碟空間,因為沒有想到要的資料,但我電腦每天都會運行爬蟲程式。

再次練習,除了css selector等的選擇器經驗不足,感覺自己寫得還有很多可以強化效率的部分。

這次要針對「缺失值」作更好的處理,因為大部分的爬蟲教學是不考慮爬取失敗的時候。

時代變遷,網頁也會改版,沒有永遠適用的爬蟲。一旦不能用,或是抓取的部分跑掉,這是一定要馬上處理好的事情。

因此,注重例外處理與警示是實戰必寫的。當然不寫例外直接跳出也是一種變向的警示,前提是要留意到。

況且,加載失敗也是會讓本來設定提取的部分失敗,導致爬取意外中止。格式跑掉並非是自身的問題,但是會害到你。

使用框架,可以大幅省去針對例外而寫處理的時間,因此我練習 scrapy。

2020年4月23日 星期四

Leetcode題解 Python & C#:四月挑戰 Bitwise AND of Numbers Range

給一個範圍 [m, n] where 0 <= m <= n <= 2147483647,回傳範圍內所有(整數)的 位元且 結果。

說位元計算是我很少碰到的一塊,不熟悉。

設想一個攤開的情形,答案會是最高位起的連續相同部分取 1, 剩下取 0。

1111011
1100000
11xxxxx => x都會出現0

正因為這是二進位,所以 m, n 之間的數字會使得在最高位連續相同的部分之後的各位置都會有 0 的出現。

因此將著重在如何得到目標部分。

如果 m, n 不相同,m, n往右移一位,直到最高位連續相同的部分時,m == n (※這二者不斷位移後的情形)

由於 m, n 都被動過,到這裡要向左移還原剛右移的部分,因此在剛右移的部分加一變數記錄次數。

2020年4月22日 星期三

Leetcode題解 Python & C#:四月挑戰DAY22 Subarray Sum Equals K

給一串整數數列與整數 k ,請回傳有多少個連續子數列總和為 k 。

看到這種 sum 的問題,心中就先想到 k - sum 是否在某一資料(也許 dict 或 list)內,那資料也是某種 sum。

k - sumA = sumB 利用「交換律」來解。

剛好是連續子數列,加上提示有說到「 sum(i,j) = sum(0,j) - sum(0,i)」,這點出了該如何搭配上交換律。

把出現過的 sum 值保存起來,接著再用 k - sum 去找是否它存在於剛剛保存下來的各sum值內。這是核心的想法。

這裡按照實際撰寫的順序。

先把 0 加入到 memo,其值為 1 ,代表 0 出現過一次。

用一個for迴圈,得到 sum(0, i),檢查 k - sum(0,i) 是否在 memo 內,如果有則 result += memo[sum(0,i)]。

接著再把 sum(0,i) 加入 memo 中,使 memo 保存各值的出現次數。

倉頡輸入法與打字手勢的一周學習歷程

約莫上周二晚間,我測驗我的中打速度,結果只有七十出頭,相當不滿意。再加上那時我的英打一直有按錯鍵的問題,於是想改正我的打字手勢。

渴望變厲害的性格作祟,喚醒心中強大的精力去改變習慣。

習倉頡並不是平易近人的,我發覺網路是有教材,但是幾乎都蠻舊的,說實在也是可用的。

先記憶字根,光是這點就蠻花費時間的。我建議可以在練習中記憶,這樣也可以幫助到拆解字型取碼。

這裡我是使用《五色倉頡試用版》,達到第一階段「記字根」的基本功。一直打,然後配合對照表去理解。

直到裡面的字已經熟練如何拆解。順利的話,一天就能打出許多字。

之後可以進行生活化的練打,不過一定會碰到不會拆解的字,就使用《Follow Me倉頡字典》查詢。

2020年4月21日 星期二

Leetcode題解 Python & C#:四月挑戰DAY21 Leftmost Column with at Least a One

給一個 BinaryMatrix 類別的物件,該物件有個由 0, 1 組成的二維串列屬性,要找出至少有個1的欄位最小索引,沒有則回傳-1。

該物件有兩個方法,一 get(),只能透過此方法去取得特定位置上的值。二 dimensions(),此方法會回傳列欄大小。

每列的值有經過排序(由小至大)。

如果呼叫 get() 超過1000次,也算失敗。

欄列的大小不超過100。

開始思考要如何找出目標值,除了每排有經過排序,但每欄並沒有提及。

因此除非知道每排最早出現 1 的位置,不然也暫時沒想到更好的方法。

get() 次數限制之下,每排要在 10 次內找出第一個 1 出現位置,最糟要在一百個已經排序的元素中搜查。

使用二分搜查法,50 > 25 > 13 > 7 > 4 > 2 > 1 ,最糟也能在七次內找到第一個 1 的位置。 萬一沒有,索引也會在等於欄數。這樣也能在限制內完成搜查。

2020年4月20日 星期一

Leetcode題解 Python & C#:四月挑戰DAY20 Construct Binary Search Tree from Preorder Traversal

給一串依 preorder 順序遍歷二分樹的值,要還原成二分樹,並回傳根。

這二分樹是嚴格的那種,代表左邊子樹皆小於自身,右邊皆大於自身。

依照 preorder 的順序,先自身,再左邊,再右邊。還原也依此進行。

說到要依順序還原,不管是哪種,都要有保留父節的規劃,因為子節是沒辦法往上回溯的。(或者使用遞迴函數)

※一開始先將 root 生成,最後要回傳它。

這裡使用 stack 的概念來保存父節,一旦目前迴圈的值小於 stack[-1] ,那就添加至左邊,並加入該節至 stack。

如果該節大於 stack[-1],則檢查 stack[-2] 是否小於目前的值,是則不斷刪去 stack[-1] 直到 stack[-2] 大於目前的值或 stack 只剩一個。

然後把該節指派給 stack[-1].rihgt 後刪除 stack[-1],並添加該節至 stack 中。

這做法類似實際 preorder 的途徑。

2020年4月19日 星期日

Leetcode題解 Python & C#:四月挑戰DAY19 Search in Rotated Sorted Array

今天有C#,因為我看蠻多ASP的工作缺,練習ASP也得加強C#。

給一串數組,經過排序但旋轉過(向右移動?次),且每個數字不重複,並且要求在時間複雜度 O(log n) 解決。

最直覺地使用 for 迴圈,但時間複雜度會是 O(n) ,不符合要求。

說到 O(log n) ,二分搜尋是最典型的方法。用兩次二分搜尋,一次是找到排序的分界(最大值),二是從分界往左或右找到目標值。O(log 2n) => O(log n)

設左界 l 右界 r,中間為 m = (r-l)/2 + l 。第一次找將比對目標 t 先設為 nums[0] ,如果 nums[m] > t ,更新 t 並移動左界 l = m+1,否則不更新 t 但移動右界 r = m。

直到 l == r,此時為 nums[l] or nums[r] 都是最大值。

若目標值 target 小於 nums[0] ,表示目標值在最大值右方,否則在最大值左方。針對情況移動右界至nums的長度或左界至0。

再用一次二分搜尋法,這次的比對目標就是 target,當 l 等於 r 時沒有找到就回傳 -1 ,其餘為當下的索引 m 。

2020年4月18日 星期六

Leetcode題解 Python:四月挑戰DAY18 Minimum Path Sum

給一串二維數列(格線),每個元素為非零整數,路徑只能往右或往下,找出從左上角到右下角的最短距離。

一看到往右或往下的二種可能性,DP就蠢蠢欲動,而且同個位置上的最短距離是固定的,完全可以使用DP去解。

dfs遞迴函數保存兩個訊息 y, x 記錄回傳值。自身加往下或住右較小的,即當前位置的最小距離。

如果到達邊界,由於 min() 不能比較 int 與 None , 因此得針對邊界的下個距離做處理,這裡使用 [] 把下個距離放入串列再做比較。
class Solution:
    def minPathSum(self, grid: List[List[int]]) -> int:
        rows, cols = len(grid), len(grid[0])
        
        from functools import lru_cache
        @lru_cache(None)
        def dfs(y,x):
            if y == rows or x == cols:return []                
            d2 = dfs(y+1, x) + dfs(y, x+1)
            distance = grid[y][x] + (min(d2) if d2 else 0)
            return [distance]
        
        return dfs(0,0)[0]
又或者迴圈所有位置,每個位置加上左方與上方的最小值,等到迴圈結束,此時右下角為最短距離。

2020年4月17日 星期五

Leetcode題解 Python:四月挑戰DAY17 Number of Islands

給一個二維串列,"1"代表是陸地,"0"則為水,周圍(超出範圍)都是水,每個不相連的陸地為一塊島,回傳該二維串列有幾塊島?

這題若用迴圈,要如何判定當前遇到的"1"是獨立的島嶼?是否曾經有找過?

使用 memo 去記錄已經搜尋過的"1",並且遇到"1"的時候用遞迴函數再往四周找出所有鄰近的"1"
class Solution:
    def numIslands(self, grid: List[List[str]]) -> int:
        if not grid: return 0
        landNums = 0
        memo = set()
        rows, cols = len(grid), len(grid[0])

        def search(y,x):
            if y == rows or y < 0 or x == cols or x < 0:
                return
            if (y,x) in memo: return
            else: memo.add((y,x))
            if grid[y][x] == "1":
                search(y-1,x)
                search(y+1,x)
                search(y,x-1)
                search(y,x+1)

        for y in range(rows):
            for x in range(cols):
                if (y,x) not in memo and grid[y][x] == "1":
                    search(y,x)   
                    landNums += 1
          
        return landNums
如果想省點空間,把傳入的二維串列當成 memo 使用也是一種選擇,還能稍微加速。

2020年4月16日 星期四

Leetcode題解 Python:四月挑戰DAY16 Valid Parenthesis String

給一字串,只有「 ()* 」組成,( 的右邊要對應一個 ),而 * 可以是 ( 或 ) 或 "",如果()都有對應則回傳True;否則False。

這題前身是只有 () 的條件,只需要計 ( 與 ) 的數量。

多了一個 * 有三種可能性,光是看到這個條件就直覺想到用DP。

這裡嘗試用計數的方法︰

若 ( 與 * 的數量加總小於 ) ,則可以中途回傳False。

接下來要考慮 **(() 與 ((**) 要如何抓出剩餘沒有配對到的 ( 。

2020年4月15日 星期三

Leetcode題解 Python:四月挑戰DAY15 Product of Array Except Self

給一串數列,回傳一串除了對應[i]以外的元素乘積數列。

如果要在空間複雜度 O(1) 完成,那麼只能透過修改傳入的數列。不過也有說回傳值不算在內。

最險惡的地方在於 0 的出現,否則直接用全部元素乘積除每個就好。除 0 會發生錯誤,所以要避開有 0 的情況。

列舉所有情況有三種︰
1. 0 出現兩次以上︰全部為 0 。
2. 0 出現一次︰只有 [i] == 0 為總乘積,其餘為 0。
3. 沒有 0 出現︰總乘積除[i]。

遍歷一次,取得非0元素的所有乘積與 0 出現次數,然後針對三種情形決定回傳值。
class Solution:
    def productExceptSelf(self, nums: List[int]) -> List[int]:
        product = 1
        count = 0
        for num in nums: 
            if num: product *= num 
            else: count += 1
        if count >= 2: return [0 for _ in nums]
        elif count == 1: return [0 if num else product for num in nums]
        return [product // num for num in nums]

2020年4月14日 星期二

Leetcode題解 Python:四月挑戰DAY14 Perform String Shifts

給一個字串 s,已經一串指令 shift,指令為 [direction, amount] 代表向 direction 轉 amount 次,direction {0(左), 1(右)}。

由於只需要最終結果,而且左右又可以抵銷,因此不需要傻傻跟著每個指令轉,只需要統計最後要轉哪個方向幾次就好。

如果向同個方向轉 s長度 次,就等於轉一圈,因此統計次數可以取 len(s) 餘。

若向右轉 t 次,在位置[len(s)-t]之後的字母會跑到前方,在前方的字母會移到後方,可以寫成 s[len(s)-t:] + s[:len(s)-t]

也可以簡化成 s[-t:] + s[:-t] 。
class Solution:
    def stringShift(self, s: str, shift: List[List[int]]) -> str:
        right = 0
        for each in shift:
             right += each[1] if each[0] else -each[1]
        right %= len(s)
        return s[-right:] + s[:-right]

Leetcode題解 Python:Regular Expression Matching

這裡的表達式的特殊符號只有 ".", "*" 所以蠻簡化的。

想到正則表達式,如果能夠 import re,那絕對相當地快速。用 re 的函式就不需要去寫判斷了。

今天拋棄 re,這題限制居多,輸入字串不能局部配對,一定要全部吻合。難度會比真的re運作還簡單不少。

在我心目中,不能只是解題,還要符合 re 的運作模式,才是我要的解法。

但還是先解題優先吧。

Leetcode題解 Python:Pizza With 3n Slices

一個pizza切成3n片,每片面積大小不同,每片面積當作一串數列傳入。當你選一片之後,上下兩片會被朋友拿走,請問能拿到的最大面積的為何?

這是一道難題,能解出來的不多。

對於這個 pizza 3n 片這般的難題,除了學會解法之外,更應該去探索解題思路。

暴力解法是很容易,有 3n 片,會有 3n 種取法,3n 種取法後面會衍生出 3n-3 種的取法,如此到底,時間複雜度會是 O(N**N),所以超過10片就會跑到超時。

不僅運算時間久,衍生出的各種可能也需要保存,空間複雜度也是很大,即便用上 backtrace 降低記憶體使用量,時間複雜度的問題依舊沒有解決。

得用「空間換取時間」,那麼空間要保存甚麼?對,這就是動態規劃的問題。

有沒有甚麼辦法,可以保存各種pizza的拿取情形?參考

2020年4月13日 星期一

Leetcode題解 Python:四月挑戰DAY13 Contiguous Array

這題有難度,礙於我現在的時間管理策略,若沒能在時間內想到解法,就去找別人的理念講解,然後自己突破。參考

給一串數列,只有 0, 1,回傳 0 與 1 數量相等的子串列的最大長度。

0 與 1 數量相等,第一直覺就是要想到如何計數。如果不帶技巧,暴力解法需要 O(n**3) 時間解決。

如果要減少時間,那麼「用空間換取時間」的想法就可以派上用場,那麼空間要記錄些甚麼呢?

2020年4月12日 星期日

Leetcode題解 Python:Check if There is a Valid Path in a Grid

給一個二維串列 grid,每格代表一種道路,其值可以對應下表,要回傳是否能從左上角走到右下角。
1 which means a street connecting the left cell and the right cell.
2 which means a street connecting the upper cell and the lower cell.
3 which means a street connecting the left cell and the lower cell.
4 which means a street connecting the right cell and the lower cell.
5 which means a street connecting the left cell and the upper cell.
6 which means a street connecting the right cell and the upper cell.
對於這種地圖或是面積的圖像問題,我總是感到棘手,也許有一套通用思路,不過可能需要很多經驗值累積才能解得順手。

從左上角開始,但是沒有規定左上角一定是從外面延伸進來,如果是 grid[0][0]  == 5,一開始就死局。如果等於 4,那一開始就會有兩條路線。

要怎麼解決起始有兩條路線的問題,就會是一個難題。又或者拋開心中虛擬的小車車,直接用遞迴 + memo 去從(0, 0)拔起所有合格座標,再看看右下角是否存在。

街道有六種形式,而且具有方向問題,困難點在此,要怎麼妥善解決轉向,這就值得思考。

Leetcode題解 Python:Number of Ways to Paint N × 3 Grid

給一個 n,代表 (n , 3) 的二維串列,每個位置可以填入R、G、Y三色,而每個顏色不能相鄰(上下左右)相同,要求出一共有幾種填法。

這是我第二次做到要取餘的題目,代表方法數真的很大。

如果 n < 7,可以使用暴力破解法,用 backtrace 去找出所有可能。一旦 n >= 7,就會超時。

我左右一看,感覺到這只是個數學問題,可以代公式求解,不用真的計算。結果真的找到公式,代上去只用 一百多毫秒 就能通過測試。

在我心中,用公式解,就像拋棄電腦強大計算能力不用,背棄了用程式語言邏輯去解決問題的宗旨,跟作弊是沒兩樣的。

於是我還是老實地寫出一個合格的解法。

Leetcode題解 Python:四月挑戰DAY12 Last Stone Weight

給一串數列,取出最大的兩個相減,如果結果非零,而把結果加回數列,重複進行,直到剩一個元素或是沒有元素,回傳該元素的值,若無而回傳 0。

一說到要取出最大的兩個,就會想到用 sort() 排列。 ※注意 sort() 是從小而大排列,想要反敘可以這樣寫 sort(reverse = True)。

每一次取出前兩個對減,加回去再重新排列,重複步驟直到不能再進行,這是最直覺的方式。

排列之後會照大小排列,如果取出兩元素相減後還 >= 剩下的第一個元素,代表兩者是當前最大的兩元素,就可以繼續相減。

條件不符合加回去再重新排列,這樣可以減少不必要的排列次數。

class Solution:
    def lastStoneWeight(self, stones: List[int]) -> int:        
        n = len(stones)        
        while n > 1:
            stones.sort(reverse = True)
            v = stones.pop(0)
            while n > 1 and v >= stones[0]:
                v -= stones.pop(0)
                n -= 1
            if v == 0: n-= 1
            else: stones.append(v)        
        if stones: return stones[-1]
        else: return 0

2020年4月11日 星期六

Leetcode題解 Python:四月挑戰DAY11 Diameter of Binary Tree

給一串二元樹,問直徑,也就是節對節之間的最長距離。

如果以 root 來看,通過 root 的最長距離,就是 root.left 的最大深度加上 root.right 的最大深度。

於是方向很清晰,我們要用一個函數去取得該node的最大深度。這樣就能進而找出各個距離,再找出最長距離。

Leetcode題解 Python:Find All Good Strings

這題相當困難,從通過的人數就知道。為什麼困難?因為這需要使用 KMP + 數位DP。

如果不清楚 KMP 跟 數位DP 的人,請先去看我之前的入門級介紹。本篇主要參考

給兩個字串 s1, s2,同樣是 n 大小,給一字串 evil,回傳在 s1, s2 區間內不含 evil 的字串數。

這題目的敘述,便是典型的數位DP。

因此我們先將數位DP的模型寫出來。

Python 數位DP (Digit dynamic programming) 在區間內找符合條件的組合數

數位是指數字位數,個人偏好講位數。DP是指動態規劃,一次參與進兩個,對於初學者肯定是相當難理解的。(我也花幾天去釐清思路到能用

動態規劃簡單的說法:就是用「空間換時間」,把問題拆成子問題作解決,子問題都常會被重複計算,像是費氏數列 fib(x) = fib(x-1) + fib(x-2),用空間記住參數對應的答案,就能減少計算時間。

而數位DP的特徵就是會給予「界限 Bound」,例如在 0 ~ 99 區間找不含某種條件的數字總數等,像是不包含 5 的數字總數。

一旦看到這樣的敘述,就能夠準備開打數位DP的模板了。

2020年4月10日 星期五

Leetcode題解 Python:四月挑戰DAY10 Min Stack

設計一個 stack,而且能在 O(1) 時間內回傳最小值。

stack是後進先出,如果眼前有書疊,要拿也是會從最上方拿起,最上方的書也是最後放的。

要實現stack,用list儲存資料就可以。

stack新增資料,用list.append()。 stack.pop => list.pop()。用list實現stack基本功能是沒有問題的。

這個 Min Stack 特別之處,在 O(1) 時間內回傳最小值,如果用 min(),而時間複雜度會是 O(n)。

因此在 Min Stack 的類別下,要有一個保存最小值的屬性,這樣才能第一時間回傳最小值。

2020年4月9日 星期四

Python KMP演算法:字串搜尋

在電腦科學中,Knuth-Morris-Pratt字串尋找演算法(簡稱為KMP演算法)可在一個主文字字串S內尋找一個字W的出現位置。此演算法通過運用對這個詞在不匹配時本身就包含足夠的資訊來確定下一個匹配將在哪裡開始的發現,從而避免重新檢查先前匹配的字元。(維基百科)
來講一下一般的字串搜尋,假設一個情境:
Pos:     01234
Target: aaaac
Pattern:aac

通常會逐一比對,從0開始,Target[0]對上Pattern[0],接著[1],[2],匹配失敗。繼續往下,再Target[1]比Pattern[0],繼續往下比...

這樣會有些字符會重複搜尋過,如果第二個 a 有對上,第三個配不上,因為第二個 a 對上第一個 a,所以下次搜尋應該從第一個 a 對上的後面開始。

如此一來,便能夠減少搜尋次數,提高搜尋的效率。

KMP便是一種這樣構想的演算法。

推薦教學:bilibili

Leetcode題解 Python:四月挑戰DAY9 Backspace String Compare

給一個字符串,如果遇到「#」,就等於按下Backspace鍵去修改字符串。

如果直接直譯規則書寫,寫起來就是那麼樸實無華且枯燥,這樣也能通過。
class Solution:
    def backspaceCompare(self, S: str, T: str) -> bool:
        newS = ""
        for char in S:
            if char == "#":
                if newS: newS = newS[:-1]
            else:
                newS += char
        newT = ""
        for char in T:
            if char == "#":
                if newT:
                    newT = newT[:-1]
            else:
                newT += char    
        return newS == newT
不過呢,題目希望我們能在 O(N)時間 並 O(1)空間 完成。

2020年4月8日 星期三

Leetcode題解 Python:四月挑戰DAY8 Middle of the Linked List

要回傳 Linked List 中間的節點,如果長度是偶數,則中間偏右那一個。

有兩種解法:

第一種是跑完一遍得到長度,計算距離,再從頭跑到中間即可。前進距離是 length //2 。

第二種是用快慢指針,每回合快針走兩步,慢針走一步。當到了快針不能再走的回合,慢針會在中間的位置。
class Solution:
    def middleNode(self, head: ListNode) -> ListNode:
        if not head: return head
        FP, SP = head, head
        while FP and FP.next:
            FP = FP.next.next
            SP = SP.next
        return SP

2020年4月7日 星期二

Leetcode題解 Python:四月挑戰DAY7 Counting Elements

給一個數列,如果元素其值 +1 也在數列內的個數。如果有重複,則分開計算。

不多說甚麼,順一下解題邏輯。

如果要得知元素其值+1是否也存在於數列內,就得找完所有數列,並記錄元素是否有出現。

如果有重複出現,像是 1 1 2 2, 1 出現兩次且 2 在數列內,所以答案要加兩次。

於是記錄元素出現的時候,就要順便把其出現次數記錄下來。

最後跑完 memo 的鍵,鍵就是出現過的元素,若元素+1也在 memo 裡,答案就加上該元素的出現次數。

Leetcode題解 Python:Stone Game III

Alice 跟 Bob 進行石頭遊戲,把石頭排成一排,一人一次拿 1 ~ 3 個,每個石頭都各有其分數,最後雙方誰持有的石頭分數總和高者勝。

這是 contest 的題目,在時限內沒有解出這一題,可惜。

雙方都會不會放水,採取最佳策略進行。這樣的情況下,如果是 Alice 贏就回傳 Alice,Bob 贏則 Bob,平手則 Tie。

這是一個博奕問題:有雙方,互相採取行動,兩者互相對抗。

當看到雙方都採取一樣的策略之時,雙方的判斷都是一樣的,因此用一個邏輯函數,就要能指出該下哪一步。

以拿走三顆石頭的時候來看,不論是此時是輪到 A 或者是 B ,兩者當前的最佳解都是一樣的。先後順序不影響決定最佳解,局面進行到哪裡才是。

因此,用一個 memo 記錄所有情形,其鍵會是拿走的數量或是剩餘的數量。這memo紀錄的分數對雙方都是一樣的。

Leetcode題解 Python:Flatten a Multilevel Doubly Linked List

給一串 Doubly Linked List ,其中某節可能有 child ,要求你把這串 Doubly Linked List 平坦化,而 child 會插入在 該節 與 下一節 之間。

A - B - C - D - E        => A - B - F - G - C - D - E
      F  - G

寫一個遞迴函數,如果遇到 child ,你需要它的頭尾,針對這兩點修改就好。
(尾)G.next = B.next,  G.next.prev = G 、B.next = F(頭) , B.next.prev = B

於是遞迴函數的回傳值,就設定是 head 與 tail。

頭是搜尋的起點,有頭才能夠搜尋,而尾在尚未搜尋前是未知的,於是遞迴函數的回入值是頭節。

這樣在一路到底搜尋時,若是碰到 child,則把 child node 傳給遞迴函數,取得該子串的頭尾,然後插入當前的位置與下一位置的中間。

Leetcode題解 Python:Palindrome Linked List

判別 Linked List 的值是否為回文。

凡是回文問題,就一定要知道長度,才能知道哪裡是分水嶺。

如果想用紀錄數值,最後再判別是否回文,由於該值可能為雙位數或是負數,如果想用 str 紀錄數值,就必須做特殊處理。

使用 list 紀錄,等過了分水嶺之後,就是該值與紀錄逐一對比,不符合就不是回文,找完就是回文。
class Solution:
    def isPalindrome(self, head: ListNode) -> bool:
        if not head: return True
        len_head = 1
        SP = FP = head
        while FP.next:
            len_head += 1
            FP = FP.next            
        #
        stack = []
        for _ in range(len_head//2):
            stack.append(SP.val)
            SP = SP.next
        if len_head%2==1: SP = SP.next
        while stack:
            if SP.val != stack.pop():
                return False
            SP = SP.next
                
        return True

Leetcode題解 Python:Remove Linked List Elements

Remove all elements from a linked list of integers that have value val.
給一串 Linked List ,要刪除其中節點等於 val 的節點,並回傳頭節點。

這題不難,如果用快慢指針,就需要針對刪除頭部的狀況做判斷。

對我來說,要針對非題目所求的特別情況去做 if 判斷,這種寫法在我心中的「泛用性」就在及格邊緣搖搖欲墜。

這題已經離開了雙指針的子章節,應該是可以不用雙指針去解。

用了遞迴寫法,雖然版面簡潔許多,不過時間複雜度是紮實的 O(N),每個節點都會修改。
class Solution:
    def removeElements(self, head: ListNode, val: int) -> ListNode:
        def nextNode(node):
            if node:
                node.next = nextNode(node.next)            
                if node.val == val: return node.next                    
            return node
        return nextNode(head)

2020年4月6日 星期一

Leetcode題解 Python:Intersection of Two Linked Lists

給兩串 ListNode,回傳 Intersection 的第一個點,如果沒有則回傳 None。

用 memo(hashset) 去紀錄 headA 所有走過的節。再換 headB 走,如果該節在 memo 裡,就是第一個交會點。
class Solution:
    def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
        memo = set()
        PointerA, PointerB = headA, headB
        while PointerA:
            memo.add(PointerA)
            PointerA = PointerA.next
        while PointerB:
            if PointerB in memo: return PointerB
            PointerB = PointerB.next           
        return None
有效歸有效,不過會消耗掉大量記憶體,在一個提示空間複雜度 O(1) 之下,這方法似乎不是最好的。

思考一下,如果要第一個交會,那麼兩者行走距離應該是最短的。但是這樣怎麼寫?

[左1右1]、[左1右2, 左2右1]、....。這樣的安排,太沒有效率了。

我一開始本來想用 traceback,這樣空間複雜度會超出 O(1),就沒有把它寫完了。

Leetcode題解 Python:四月挑戰DAY6 Group Anagrams

Given an array of strings, group anagrams together.
Input: ["eat", "tea", "tan", "ate", "nat", "bat"],
Output:
[
  ["ate","eat","tea"],
  ["nat","tan"],
  ["bat"]
]
把文字做分類,anagrams 意思是「 a word, phrase, or name formed by rearranging the letters of another, such as cinema , formed from iceman.」(google翻譯)

可以透過移動字母,去拼成另外一個字詞。

於是兩詞若是同一個字謎,兩詞中字母出現的種類是相同的,且字母的出現次數是相同。

要保留這兩個數值,就不能破壞詞的長度,於是去掉 set,接下來想到的是 sorted() ,同一字謎的詞都會跑出相同結果。

Leetcode題解 Python: Linked List Cycle I & II

來到了 Linked List 章節,很快就遇到這個問題,問你ListNode是否有裡面有循環。

此題位於子章節 Two Pointer Technique,所以我不用直覺想到的 memo 紀錄步驟的遞迴解法。

示範提到,使用兩個 Pointer,一個快一個慢,快的一次走兩步,慢的走一步。

兩個 Pointer 的起點都是 head,如果有循環,Fast Pointer 遲早會追上 Slow Pointer
class Solution:
    def hasCycle(self, head: ListNode) -> bool:
        if not head: return head
        fastPointer, slowPointer = head, head
        while 1:
            # FastPointer go 2 steps forward
            for _ in range(2):
                if fastPointer.next:
                    fastPointer = fastPointer.next
                    if fastPointer == slowPointer: 
                        return True                            
                else:
                    return False
            # SlowPointer go 1 steps forward
            for _ in range(1):
                slowPointer = slowPointer.next

Leetcode題解 Python: Palindrome Pairs

給一串文字串列,問說哪些兩個組合可以變成 Palindrome 回文。

這兩兩組合會有前後順序的差異,如果用最直觀的暴力破解法,跑遍每個組合,時間複雜度會是 O(n*(n-1)),O(n**2)。

列在 trie 的範圍內,這提示我們要使用 trie 去建立搜尋樹。要如何建立能找出回文的 trie ?

想一下回文的特性,如果是回文,頭跟尾會是相等的,頭跟尾往中間移動的字也會兩兩相等。

abcd|dcba, s|lls, lls|sssll => abcd|abcd., s|s.ll, lls|lls.ss

如果用顛倒的字詞建立 trie ,在搜尋過程中就可以前尾兩兩對消,如果過程找到字詞,只要確保「 . 」之後找到底也是回文的就可以。

aa|a => a.a|a.

萬一輸入的字詞找完之前,就先找到比較短的顛倒字詞,也代表後面不會再有字可以添加,先後對消後,只需要針對搜尋詞還沒找部分做回文判斷即可。

2020年4月5日 星期日

Leetcode題解 Python: Word Search II

Given a 2D board and a list of words from the dictionary, find all words in the board.
很像找詞遊戲,看看版面上有沒有要搜尋的詞。不過跟一般的規則不同的是,它可以轉彎。

此題列在 trie 章節內,所以自然而然將目標字詞寫入 trie 內,再用 y x 遍歷整個版面,把board[y][x]放入搜尋就可以了。

重複出現的字詞只算一個,所以找到的字詞放入 set 或是 dict當key,最後再取出來。

這題我是很快就解出來的,關鍵之處不是如何把詞放入 trie,而是搜尋要如何進行。

如果學過回溯法,應該很快就知道找路徑的寫法是該怎麼進行。

Leetcode題解 Python: 四月挑戰DAY5 Best Time to Buy and Sell Stock II

給一串價格數列,要求出最大利潤(先買後賣)為多少?

這題沒有涵蓋手續費,所以變得超簡單。

在價格遞增的時候不斷獲利,在價格遞減的時候不出手。所謂獲利就是一買一賣且賺錢的。

爬到價格巔峰的過程,每個階梯的高度加總就是最高到最低的差距,於是只要不斷地後減前再加到獲利。

當價格走跌時,後減前就變成負值,如果是負值就不做買賣,收益為 0。

整理之後,後減前只要 > 0 就加入,< 0 就跳過。

判斷相當簡單,於是可以濃縮成一句就解決掉了:
class Solution:
    def maxProfit(self, prices: List[int]) -> int:               
        return sum([max(prices[i]-prices[i-1], 0) for i in range(1,len(prices))])

2020年4月4日 星期六

Leetcode題解 Python: 四月挑戰DAY4 Move Zeroes

Given an array nums, write a function to move all 0's to the end of it while maintaining the relative order of the non-zero elements.

簡單來說,把數列中的零移到最後方,其他各數字的順序維持不變。

依舊很簡單,不過題目有要求:in-place。在數列直接修改,試著不用其他空間或是複製排好的數列。

如果用 for 且順序,那麼遇到0使用 append( pop() )會出錯。詳情不寫,改用倒敘法,這樣遇到 0 就能使用append( pop() )。

一次取出一次加到最後面,嫌太麻煩?不如用個 空串列 保存 pop() 出來的 0,最後再 extend() 保存 0 的串列即可恢復數列。

Leetcode題解 Python: 四月挑戰DAY3 Maximum Subarray

給一串數列,要找出當中各連續部分(contiguous subarray)的最大總和為何。

數字的範圍是甚麼?整數,包含正負整數。

按照題目規則,先用暴力破解法的思維:找出所有連續子串的總和,再從中找出最大值即可。
這樣時間複雜度會是 O(n(n-1)/2) → O(n**2)。

至少能夠先有一個可行的方法,接著繼續思考有沒有比 O(n**2) 更少次的方法?

Follow up:
「If you have figured out the O(n) solution, try coding another solution using the divide and conquer approach, which is more subtle.」

存在著 O(n) 的方法,不過題目希望能夠拆散數列再各個處理的方式(分治法)。

2020年4月2日 星期四

Leetcode題解 Python: 四月挑戰DAY2 Happy Number

給一個整數,按照十進位分切出各單個數字,這些數字的平方加總後,繼續分切...,如果最後總合等於 1,就是快樂數字。

好吧,到底哪裡快樂我也不知道。這依舊是一個非常簡單的題目。

可以分成三個步驟:
1.分切數字
2.數字平方和
3.判斷為1,否則回到步驟1.

要是碰到無限循環怎麼辦?把出現的數字記錄下來,只要有數字重複出現那就是無限循環。

用一個 memo ,字典或是集合(set),之後用 in 就能看出是否有重複出現過。

還有更偷機的做法,就是把False的數字回收進memo。這樣會相當Happy。

Leetcode題解 Python: Largest Rectangle in Histogram

直方圖中最大的長方形面積

這題感到棘手,寫好的暴力破解法會超時,只能不斷看題目去領悟點甚麼。

找出最大面積不是件難事,要怎麼才能加速呢?

看了搜尋的兩前項,別人寫著遞增數列,找局部峰值,為什麼?到底有甚麼關係?

我簡單一句講明目的:「把這張直方圖分切出各個好處理的部分。」

Leetcode題解 Python: Generate Parentheses

「Given n pairs of parentheses, write a function to generate all combinations of well-formed parentheses.」


給一個數字 n ,要產生有 n 個左右成對「 ( ) 」的所有組合 。

說實在並不難,如果有想到左右成對的規則下,任何位置的左邊的 ( 數量一定得大於等於 ),不然會無法成對。

利用這特性,很快就可以寫出一個遞迴函數。
class Solution:
    def generateParenthesis(self, n):
        result = []
        def nextIdx(ln, rn, text):
            if ln == 0 and rn == 0:
                result.append(text)
            if ln - 1 >= 0:
                nextIdx(ln-1, rn, text+"(")
            if rn - 1 >= ln:
                nextIdx(ln, rn-1, text+")")
        nextIdx(n, n, "")
        return result
這是最直觀的寫法,速度也不錯。

Leetcode題解 Python: Sudoku Solver

解數獨,非常地生活化。

這跟上一個 N-Queens 不同,你要直接修改數獨表,最後要是已經完成的狀態,這次要把答案紀錄好就不再刪除了。

而且一排排往下的概念並不好用,因為有些已經填滿了。

先把主架構寫出來,寫出一個回溯法函數(Backtracking),把排換個概念換成層,而每一層就是單一個還沒下過的位置,靠著遍歷數獨表就可以知道有哪些位置。

接者就可以 Backtrack 一路往下搜尋。

當順利解出來時,就不會再有下一層,這時會出現報錯,出現別的情況,只要當這個情況出現時,回傳一個值,就可以用回傳值判斷要不要刪掉最後下過的位置,這樣就能阻止刪掉解法的最後一步。
class Solution:
    def solveSudoku(self, board) -> None:
        """
        Do not return anything, modify board in-place instead.
        """     
        def getCandidate(y, x):            
            Candidates = {}.fromkeys([str(i) for i in range(1,10)])            
            for x2 in range(9):                
                if board[y][x2] in Candidates: del Candidates[board[y][x2]]
            for y2 in range(9):                
                if board[y2][x] in Candidates: del Candidates[board[y2][x]]     
            for y2 in range(y//3*3, y//3*3+3):
                for x2 in range(x//3*3, x//3*3+3):
                    if board[y2][x2] in Candidates: del Candidates[board[y2][x2]]
            return Candidates.keys()             

        def nextStep(level):
            try:
                row, col, _ = stepPos[level]
                for num in getCandidate(row, col):
                    board[row][col] = num                  
                    if nextStep(level+1): 
                        return True
                    else:
                        board[row][col] = "." 
            except:
                return True

        stepPos = []  
        for y in range(9):
            for x in range(9): 
                if board[y][x] == ".":
                    stepPos.append((y, x, len(getCandidate(y, x))))

        stepPos.sort(key=lambda x: x[2])
        nextStep(0)
當中多了一作法,在找出所有可下位置時算出可能的數字數,接著再用可能數字數從小到大排序,就會從最少可能組合開始下起,這就像我們玩數獨一般,會從最少可能性的地方找起。

一看可以優化,用memo紀錄各col各row各area的所有數字,這樣用 in 找值的時候,能夠最快效率。

沒有排序問題,也沒有設值需求,然後需要求出兩數組差(123456789 與 各col各row各area),所以使用集合 set ,這樣就能方便計算。

剛好可以安插在找所有可下位置的時候,將各col各row各area的所有數字都添加進去。

class Solution:
    def solveSudoku(self, board) -> None:
        """
        Do not return anything, modify board in-place instead.
        """     
        Candidates = {*'123456789'}
        memoCol = {}
        memoRow = {}
        memoArea = {}
        for i in range(9):
            memoCol[i] = set()
            memoRow[i] = set()
            memoArea[i] = set()

        def getCandidate(y, x):            
            return Candidates - memoArea[x//3+y//3*3] - memoCol[x] - memoRow[y]          

        def nextStep(level):
            try:
                row, col = stepPos[level]
                for num in getCandidate(row, col):
                    memoCol[col].add(num)   
                    memoRow[row].add(num)    
                    memoArea[col//3+row//3*3].add(num)   
                    board[row][col] = num                
                    if nextStep(level+1): 
                        return True
                    else:
                        board[row][col] = "."                   
                        memoCol[col].discard(num)
                        memoRow[row].discard(num)
                        memoArea[col//3+row//3*3].discard(num)
            except:
                return True

                
        stepPos = []  
        for y in range(9):
            for x in range(9): 
                v = board[y][x]
                if v == ".":
                    stepPos.append((y,x))
                else:   
                    memoCol[x].add(v)       
                    memoRow[y].add(v)   
                    memoArea[x//3+y//3*3].add(v)   

        nextStep(0)

2020年4月1日 星期三

Leetcode題解 Python: 四月挑戰DAY1 Single Number

「Given a non-empty array of integers, every element appears twice except for one. Find that single one.」

給一串非空整數字串列,每個元素會重複兩次,只有一個只出現一次,找出來。

這題非常簡單,一開始就有各種解法。不過我當作可能重複不只兩次,用這樣的前提寫出:
class Solution:    
    def singleNumber(self, nums: List[int]) -> int:
        memo = {}
        memo2 = {}
        for num in nums:
            if num in memo2:
                continue                    
            else:
                if num in memo:
                    del memo[num]
                    memo2[num] = 0
                    continue
                memo[num] = num   
        
        for key in memo: return memo[key]
我在想,有沒有甚麼只循環過一遍就能得到解法?不要有跑完N兩次或以上。看了前段班解法,回頭看題目才知道頂多重複兩次。(又再一次沒有看清楚題目

我的解法也只會跑過一次N。

Leetcode題解 Python: N-Queens II

求出在 N * N的棋盤放上 N 個彼此無法互吃的解法數量。

最直觀的就是暴力解法,若 N = 4,就會有 16 格,於是第一步的可能下法就會有16種。

取出前面的結果,找出第二步下法的地方,得到第二步符合規則的所有下法,依序往下傳。

傳到第 N 步下完後,剩下多少種不相同的可行下法就是解答。(這樣會找到重複解。

這種暴力解法多計算了不需要的「流程」,每一步先後順序都沒有差。

換一種想法,有沒有更快速的下法?

每排上面一定有一個皇后,於是可以逐排安排皇后。

在逐排之下,要是該欄可以放皇后,就往下排從頭搜尋,到底則次數+1,不行則取走上排的皇后,退回上排再往下一欄搜尋。

Leetcode題解 Python: Search a 2D Matrix II

這題是搜尋矩陣(Matrix)尋找是否存在該值。

被列在「遞迴」的範圍內,所以用遞迴的方式解決。

該矩陣有一個特性,相同 y 或 相同 x 的欄列會照順序排。

得利用此特性來強化搜尋機制,不然硬方法全部遍歷(Traversal)一次就一定找得到。

按照前面的範例,選一個中間的點,該值小於目標值則捨棄右下方,該值大於目標值則捨棄左上方,然後遍歷同y或同x軸的元素。

如果只是單純的目標值小就往左上找,反之找右下,這樣是行不通的。

設想一下情境,找了一個元素,但不是目標值,接著就可以將矩陣切成四分,直接捨棄左上或右下的,剩下左下、右上及右下或左上的部分矩陣也需要尋找,於是最後會產生三個矩陣再繼續遞迴下去搜尋。

2020年3月31日 星期二

Leetcode題解 Python: Unique Binary Search Trees II

今天碰到這一題,卡關很久,來記錄一下。

二元樹算是目前遇到稍微棘手級別,棘手級別目前只有莫隊算法,還沒有花時間去弄通它。

這一題屬於「遞迴」的範圍之內,給一個數字,要你回傳有相同節點數的所有可能。

一開始直覺,就是把數列排序,取一個數字,由於是二元樹,左邊皆小於該節值,右邊則大於,所以左邊樹結就是該數字左邊的所有項目組合,右邊等同處理。

接下來,要設一個節點,該節點左邊的等於左邊的遞迴結果,右邊等同處理。

2020年3月26日 星期四

python 群益API SKCOM工具

SKCOM-tool Github

python 群益API SKCOM工具 版本(API):2.13.20

檢查三格位置的SKCOM.dll版本差異與位置: 1.當前的COM元件 2.系統已註冊的COM元件 3.comtypes client所用的COM元件

並且給予相對應的建議動作。 方便查詢出目前電腦上所使用的版本。

「三個位置的SKCOM都是最新版本就沒有問題。」



若不需要GUI,執行「F.py」,查看輸出即可。

2020年3月25日 星期三

python 初學者速理解 yield與generator

這我很久以前碰過,但是也不清不楚,return 與 yield 都能回傳值,那為什麼有甚麼區分?

yeild用於function中回傳值,回傳的類型是產生器generator,這個產生器會產生回傳值,但本身並不是想要的回傳值。

所以很直覺地使用len()或是[:]切片在產生器上,會回報錯誤。

yeild產生器像是在函式中的斷點,可以得到當下變數的數值。意味著你可以在函式外影響函式內的值,也會直接牽連到yield。

要從產生器內取出回傳值,用 next() 或用 for迴圈 都可以,for迴圈會跑到產生器停止,無法再用next()取得值就跳出。

而generator進度是會保存下來的,因此一旦全部跑完一次,想要重頭,那就得重新產生generator一次。ex1a(Github)

yield能用在哪?從generator取值感覺並不直覺好用。

2020年3月19日 星期四

PYTHON 群益API 2.13.20 錯誤代碼2017 錯誤原因與解法

從2.13.16版本之後,登入前就需要 SKReplyLib_OnReplyMessage 回傳指定值 0xFFFF 才可以登入。

一開始我也是矇了,不管怎麼傳就好像沒有作用?明明照著說明做了啊!

錯誤通常分兩種,第一種就是真的忘記寫到 OnReplyMessage 然後回傳 0xFFFF或寫錯,這種就不在這裡討論。第二種就是都按照說明寫了也確認無誤,但還是不知所以然的錯誤。

到底  2017 SK_WARNING_REGISTER_REPLYLIB_ONREPLYMESSAGE_FIRST 是從何而來?網路上沒有一個明確的解釋與通用的解法,這可以是基於討論的人並不多。

的確,確實回傳值設甚麼都沒有作用,那不是錯覺,那是真的。COM接口的Py檔中 OnReplyMessage 是沒有回傳值。

於是這形成了最怪異的事情,不管怎麼樣登入,由於接口的 OnReplyMessage 沒有回傳值 sConfirmCode,所以 SKCenterLib_Login 永遠都接收不到註冊公告,永遠的錯誤代碼 2017。

問題點是錯在哪裡?本來有個見解,在 2020-03-20 被提醒之後,終於確認是暫存檔的問題。(不是dll

執行以下幾行就能夠刪除掉舊版COM的py檔模型。
import comtypes.client, os
try:
    os.remove(comtypes.client.gen_dir + r"\_75AAD71C_8F4F_4F1F_9AEE_3D41A8C9BA5E_0_1_0.py")
    os.remove(comtypes.client.gen_dir + r"\SKCOMLib.py")
except:
    pass
繼續追根究柢下去。

Python 群益API Error in sys.excepthook

從一開始使用群益API時,就經常收到這個錯誤輸出。
Error in sys.excepthook:

Original exception was:
不以為意,因為運行不會受到任何影響。

關於這個錯誤,用GOOGLE搜尋會找到一篇文章,有做探討。

確實是直指出錯誤問題來源,但是並沒有解釋到底為什麼會出錯。

前幾天著手多進程,因此更瞭解當中的運作模式,這篇會更詳細講看法與解法。

2020年3月16日 星期一

Python 資料讀寫方法比較與資料壓縮比較 (二) 多進程

上一回邊寫時就在思考多進程是否能夠幫助提升資料讀寫的速度。

答案是否定的,為什麼?

首先是資料大小,資料規模不到一定程度,光是多進程的準備時間就輸了一大截。

接著是沒有考慮到資料轉傳,測試皆採取原地寫入原地讀出,未經壓縮的資料寫入比較快,同時讀出也比較快,即便它的容量可能是上百倍甚至更多,省下處理的時間就有優勢。

多進程的優勢在於啟動更多的 cpu 計算能力,對於 壓縮 與 解壓縮 需要用到 cpu 計算的,如果需要越多計算,多進程的優勢就會能體現。如 lzma 。

此外,將越多任務交給多進程處例,有助於提升速度。例如把資料json化、bytes化、壓縮化及解壓縮等盡可能在各進程上執行,主線程只是發包資料到各進程去。

透過 win10工作排程器 定時以 potplayer 播放音樂(二)

既上一篇之後,又出現了沒有辦法自動播放的現象。

然而這次就有找到要如何觸發這種沒有辦法自動播放的現象,就是打開別的播放清單,然後方形的停止鍵,等到播放器完整停止後關掉,下次運行以開啟播放清單就不會順利自動播放。

這次又再一次面對這個問題,花時間搜尋,卻發現想找的cmd指令在播放器所在的資料夾有 CmdLine64.txt ,終於可以了解有甚麼cmd指令。

最終嘗試各種結果,我找到了更好的指令。

在工作排程器,工作,內容,動作,改成:

程式碼:PotPlayerMini64.exe
引數:"C:\BGM.dpl"
位置:J:\Program Files\DAUM\PotPlayer\


就不用再掉原本執行的批次檔 (.bat)。 上一篇

2020年3月15日 星期日

Python 資料讀寫方法比較與資料壓縮比較

python內置的讀寫方式是透過 open() 。

如果有一筆資料,例如有五萬筆的串列,要寫入檔案並且讀出,甚麼樣的方式會是最快的?

這裡先寫結論,個人依照讀寫完成速度的私心排行:

「bstr json > json zlib > json gz > str json」

""" 完成速度排行榜
Method: bstr json, Compress Ratio: 0.06:1
Data size: 400064(0.39MB), Wspeed:4.16025 MB/s, Rspeed:2.97668 MB/s, Uspeed:1.73516 MB/s
Method: json zlib, Compress Ratio: 15.32:1, level = 6
Data size: 400064(0.39MB), Wspeed:2.99731 MB/s, Rspeed:2.76445 MB/s, Uspeed:1.43809 MB/s
Method: str json, Compress Ratio: 0.06:1
Data size: 400064(0.39MB), Wspeed:3.16940 MB/s, Rspeed:2.35598 MB/s, Uspeed:1.35141 MB/s
Method: json gz, Compress Ratio: 15.31:1, CompressLevel = 9
Data size: 400064(0.39MB), Wspeed:2.93210 MB/s, Rspeed:2.36939 MB/s, Uspeed:1.31044 MB/s
Method: json xz, Compress Ratio: 330.09:1, preset = 6
Data size: 400064(0.39MB), Wspeed:0.81887 MB/s, Rspeed:1.98104 MB/s, Uspeed:0.57938 MB/s
Method: json json, Compress Ratio: 0.06:1
Data size: 400064(0.39MB), Wspeed:0.57614 MB/s, Rspeed:2.28234 MB/s, Uspeed:0.46002 MB/s
Method: bstr list, Compress Ratio: 0.08:1
Data size: 400064(0.39MB), Wspeed:3.77832 MB/s, Rspeed:0.34935 MB/s, Uspeed:0.31978 MB/s
Method: str str(list), Compress Ratio: 0.10:1
Data size: 400064(0.39MB), Wspeed:3.58643 MB/s, Rspeed:0.33802 MB/s, Uspeed:0.30890 MB/s
Method: str str(tuple), Compress Ratio: 0.10:1
Data size: 400064(0.39MB), Wspeed:3.59002 MB/s, Rspeed:0.33762 MB/s, Uspeed:0.30860 MB/s
Method: json bz, Compress Ratio: 137.76:1, CompressLevel = 9
Data size: 400064(0.39MB), Wspeed:0.22510 MB/s, Rspeed:1.76317 MB/s, Uspeed:0.19962 MB/s
"""

2020年3月8日 星期日

Python @ Property 修飾符 初學者速理解

初學者第一次看到「@」,像我,就滿頭問號。

網路上教學很多,而且不難懂,只是沒有我的風格。(推薦: 官方文件)

@又名Property屬性,property() 是python的內置函數。在定義 類別class 的時候,也經常設定其相關屬性。

Property由四個部分組成:getter setterdeleter、 doc。
以a為例,getter、 setter、 deleter分別對應:
a # call getter
a = 1 # call setter
del a # call deleter
發生以上三種狀況,就會分別執行對應的函式。

Python pyqt + logging 整合範例1

當有了圖形化介面的時候,發現工作列卡一個 cmd 實在想隱藏起來。
用 pythonw + .pyw,就能不卡 cmd。
但是一旦將 cmd 隱藏起來,就看不到輸出了甚麼。
logging可以將紀錄在輸出並同時保存下來,操作起來比 open() 還便捷。

GUI的操作功能得寫在一塊,格局被侷限了。
如果想整合更多的功能,而且是單獨寫過寫好的部分,或是打開音樂播放器聽音樂與執行某串程式碼等。
大夥都進來專案,會讓專案顯得異常混雜,也可能產生多個同名同功能但不同亞種的函式,搞得你混淆誰新誰舊。
此外,這些功能不需要傳入或只有固定參數,啟動後就能自動獨立運行完成。

那些其他程式運作情形,可以靠logging紀錄,不一定要回傳給主程式。
主程式透過查閱記錄,就能在GUI上觀察到程式進度。

然而可預期的錯誤能被處理,不可預期的錯誤會讓程式跳掉而無法用logging傳出ERROR以上的紀錄。
為了避免看不到錯誤訊息的憾事,得將錯誤訊息輸出保存下來以供來日查閱。

2020年3月7日 星期六

Python logging 初學者速理解

給對於 logging 未曾接觸過,或剛接觸過的人。

logging 可以將記錄輸出,也可同時將記錄保存。除錯除了靠print大法,也可以靠logging保存下來的日誌中找出錯誤點。

有些程式運行會因為時間改變環境而出現問題,出現嚴重錯誤就會跳掉。沒有辦法總是在編寫或除錯模式下運行,出錯就告訴你哪裡有問題。若程式未能紀錄到當下運行狀況,就少了線索去發現問題所在。

logging有很多教學,也有繁體中文教學,寫得也不錯。

這裡就簡單寫下一點筆記,幫助初學者快速理解logging運作。

透過 win10工作排程器 定時以 potplayer 播放音樂

用 工作排程器 在早上自動播放是我的習慣,不過有一個問題,它不一定每次都能播放出來。

這邊講一下基本準備:

將想要播放的音樂清單,用 potplayer 輸出成播放清單(.dpl)。
操作:把音樂擺入播放清單,按F2儲存。

在win10系統,螢幕左下有放大鏡,搜尋「工作排程器」就可以叫出來。
不然「開始」→「windows 系統管理工具」→「工作排程器」。

透過建立工作,觸發條件自行設定,我個人是設定每日早上七點。

動作新增一個:
啟動程式
程式或指令碼輸入程式名也就是 PotPlayerMini64.exe
引數為播放清單的位置如 xxx/xxx/xxx.dpl
開始位置為執行程式所在資料料夾,x:\Program Files\DAUM\PotPlayer

2020年3月6日 星期五

Python pyqt5 與 matplotlib 結合示範之範例2

範例2是關於一元二次方程的線圖。

展示以下功能
1.可以添加一元二次方程至清單或從清單刪除之
2.依清單內的項目勾選狀況顯示或隱藏一元二次方程
3.在狀態列會顯示滑鼠指到線的相關資料

這次的重心,是要做出一個可調整的清單,也可分別勾選項目顯示與否。

如果線的數量未知,那就得以可擴充可變動的前提去寫。

對於不同情況,可能想看的線是不同的。有時候會覺得太多線,而想減少顯示在繪圖區的線。

這次各線都呈現在一個軸上,就沒有做區分。要控多軸,就再加點條件判斷就可以了。

因為範例1的cmd不會被錄到,所以這次把資訊顯示在狀態列上。


2020年3月5日 星期四

Adobe Acrobat Reader DC 造成的記憶體不足

最近買了一條記憶體,跟我電腦上的是同樣大小8GB。原因是電腦出現記憶體不足崩潰的現象。

記憶體比兩年前便宜很多,順便一看SSD也是,這題外了。

查了一下解法,既然記憶體便宜許多,那就多裝一條不就解了?我看我的記憶體使用率普遍達70%以上,一個要認真操起來,絕對爆掉。

遺憾是沒有因為裝上一條新的記憶體而解決問題。

尋找根源,我學習到一些找出真兇的方法。可是那時我並沒有找到出來,狀況也隨裝上記憶體好轉。

今天再發生一次,很幸運的是,這次只有提示「記憶體不足」,沒有像之前整台電腦崩潰,看來裝新記憶體還是有幫助的。

在工作管理員的「效能」頁面
記憶體「使用中」的部分沒有全滿。
記憶體「已認可」的部分達到上限!

這樣子,推論出只要「已認可」用罄就會造成記憶體不足的現象發生。

Python pyqt5 與 matplotlib 結合示範之範例1

對於寫 python 的人來說,為了讓程式更方便使用,會想要寫點圖形化介面。

對於 python 來說,最基本的圖形化介面是 tkinter ,網路上也有許多相關教學。不過用程式碼去定義視窗與物件排版,是需要比較學習與辛苦的。

之前我寫過 c# ,用 visual studio 去寫。按鈕、標籤可以直接拖曳到版面上,效率是比較高的,也易懂執行出來的成果會長怎樣。

對於 python 來說,有 qt 可以直接設計界面的,用 qt designer 也生成一個版面,將按鈕、標籤等拖曳到版面中排版,基本樣貌可以直接反應出來。

想要學習 pyqt ,建議直接找書,按部就班,基本上就能學習到讓 python + pyqt 搭配 qt designer 設計。

寫這範例,並不是手把手從零教學,而是展示一些搭配的效果。適合給初學者想再進階的人。

像是將 matplotlib 的繪圖鑲入,並且可以用qt物件去操控繪圖。

如果想要寫股票走勢,用選單選取股票,就能讓繪圖區跑出折線圖或K線圖,甚至可以做即時走勢,如果有辦法弄到即時資料就絕對可以。

2020年3月2日 星期一

Python win32gui LoadImage Error 一個很容易解決的情況


ERROR:root:Some trouble with the icon (檔案位置): (0, 'LoadImage', 'No error message is available')

吃到這個消息,如果又想用自製 ico 檔的,將原圖片透過這個網站將圖片轉成 .ico 就可以解決了。

JPG轉ICO - 在線轉換圖標文件

本來我也認為是 ico 類的檔案,不過卻是用小畫家隨便畫了個 16*16 的圖片,然後將 副檔名 改成 .ico ,結果卻過不去,依然報錯。改回 .jpg 報錯。

上網搜尋,遇到問題不多,畢竟這是很小問題,哈哈。網路上的解法大多認為所用 .ico 是正確格式,有人用了網路大神的方法依舊無法解決問題,跟我一樣。

印象中 .ico 也是圖片檔,於是直覺用小畫家弄一個,但 .ico 需要有有固定格式,所以得透過轉換程序才能正常使用,不能用修改副檔名的方式。

2020年2月27日 星期四

Python, Matplotlib, 中文字體顯示的問題

雖然網路上有許多篇文章探討,不過最近再碰到這個問題的時候,仍舊看得有點似懂非懂的。

這裡綜合整理一下個人的看法,並且添上一些說明。

關於中文字體的顯示這是新手關卡,幾乎會碰到也幾乎需要解決。

要解決這個問題,首先可對兩個地方進行改善。

一、 系統磁碟:\\使用者\使用者名稱\.matplotlib
二、python環境\Lib\site-packages\matplotlib\mpl-data (如果使用anaconda,環境就是anaconda的資料夾。如果使用visual studio,可以從python環境打開檔案管理員進入資料夾。)


【如果你想增加字體,一定要做的步驟】

首先第一個地方,進入後會發現一個 fontList.json 的檔案,簡單來說,就是 matplotlib 讀取字體的紀錄。如果想要加入新的字體在 matplotlib 的繪圖中顯示,就得刪除  fontList.json ,讓 matplotlib 重新讀取並生成新的 json。萬一沒有刪,這個檔案就不會變更,新增的字體就不會被讀取到。(如果刪了matplotlib的字體,該json檔也會被變更。)

想顯示中文字,也不一定要新增中文字體,你可以用記事本打開來看,其中有一行「"C:\\WINDOWS\\Fonts\\kaiu.ttf",」這表示它讀取到系統字形的 kaiu.ttf,也就是知名的「標楷體」,於是在程式中只要指定字體為標楷體,就可以正常顯示中文。

plt.rcParams['font.sans-serif'] = ['DFKai-SB'] #可以直接用上此行顯示中文字,如果不嫌棄標楷體的話

""" 一定要使用「字體名稱」,不是字檔名kaiu。可以從新生的 json 檔搜尋字檔名(如: kaiu.ttf),會看到有一項 "name": "DFKai-SB",其餘字體也可以找到對應的name。"""


【新增字體】

從第二地方 mpl-data 進入 fonts 再進入 ttf 資料夾,這裡可以丟上你喜歡的字體,從網路上下載的也行。

假設我有一個喜歡的字型,範例字型,把它的 ttf 檔放進來後,去第一地方刪除 fontList.json 然後重載(import matplotlib),然後打開 json 搜尋字檔名 I.Ngaan.ttf ,就會找到該字體的 "name": "I.Ngaan"。

plt.rcParams['font.sans-serif'] = ['I.Ngaan']

這樣就能讓 matplotlib 以這個字體顯示。


【一勞永逸,懶得在程式裡加上那一行】

第二地方有個名為 matplotlibrc 的檔案,可用記事本打開它。搜尋「font.sans-serif」,把字體名,注意!是字體名!要怎麼得知字體名?可由【新增字體】這一部分去了解。在該行的冒號後面加入字體名(範例: I.Ngaan, 或是 DFKai-SB,),然後刪除前方註解(#)。

font.sans-serif     : I.Ngaan,  DFKai-SB,  DejaVu Sans, Bitstream Vera Sans, Computer Modern Sans Serif, Lucida Grande, Verdana, Geneva, Lucid, Arial, Helvetica, Avant Garde, sans-serif

至於其他很像的「font.cursive」或是「font.fantasy」,從這裡可以了解他們的意思。

!!接著這裡也可以順便解決另外一個常見的matplotlib的問題「負號無法正常顯示」。

找到解決方法通常是加上一句:
plt.rcParams['axes.unicode_minus']=False

在這個 matplotlibrc 的檔案中,搜尋「axes.unicode_minus」可以找到一句
#axes.unicode_minus  : True

將其改成
axes.unicode_minus  : False

就可以一次解決負號顯示的問題。


會特別紀錄這篇,是因為字體名的關係,網路上有教學如何添加字體,給了範例,卻沒有說為什麼字體名是這個。於是我嘗試用['標楷體']卻失敗,用字檔名['kaiu']也失敗,可是,一定得有一個可以找到對照的地方,搜尋方法,最後從json檔找到答案,。
這些方法與說明是目前我在網路沒有找到的,也許日後還會碰到這問題,到時來到在這裡就能找到解答。
個人是不推薦第三種一勞永逸的解法,因為換台電腦就要再去改寫,實在麻煩。所以推薦使用 ['DFKai-SB'] (標楷體),因為對繁體中文的系統都會自帶這個字體,這樣就不需要額外再改動任何檔案。

群益API,PYTHON,錯誤代碼2017

按下登入,得到錯誤代碼2017,查表之後得到解釋:

「SK_WARNING_REGISTER_REPLYLIB_ONREPLYMESSAGE_FIRST」

這邊是給無法順利從其他正規解決方法的人,一個解法。即便順利回報OnReplyMessage,用範例都會得到錯誤代碼2017的替代方案

使用舊版的SKCOM.dll(64位元)。下載連結

印象中這是去年我找到的方法,不過今年卻找不到出處,於是在此紀錄。因為今天剛好又再一次碰到。

以下是替換成舊版的SKCOM.dll的步驟:

一、按照群益API說明,從解壓縮後資料夾中元件裡的Uninstall.bat,解除註冊COM元件。
(依照自己的電腦位元版本選擇x86或是x64)

二、將下載來的舊版SKCOM.dll複製過去,替換同資料夾的SKCOM.dll。

三、按照正常的註冊流程,重新註冊元件:
1.用系統管理員身分執行 cmd ,進入到舊版SKCOM.dll所在的資料夾(cd 資料夾路徑),然後輸入「regsvr32.exe SKCOM.dll」。
2.用系統管理員身分執行 install.bat 。

群益API可以下載新版的,只要裡面的SKCOM.dll替換成舊版的就可以,不一定要找到舊的API才能執行。

這招大概只能撐到舊版不被支援的那一天,姑且就先將就吧。




2020年1月9日 星期四

Pyqt5 calendarWidget 標註特定日期

加入一個calendar時,除了周末之外,平日為黑色字。今天一月一日這世界普遍放假的日子,卻是黑色字標示,而不是紅色字。

除了固定日期的日子,其他像是五月第二個星期天這樣有特定序數的日子,如果能用calendar標記出來那該多好。

標記第三個週三與一月一日

首先,要先決定什麼是「特別」的日子,弄成一份表並將其讀入。
「2020/01/01, 元旦, 假日, 國定假日」
「2020/01/24, 除夕, 假日, 國定假日」如此類的格式