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軸的元素。

如果只是單純的目標值小就往左上找,反之找右下,這樣是行不通的。

設想一下情境,找了一個元素,但不是目標值,接著就可以將矩陣切成四分,直接捨棄左上或右下的,剩下左下、右上及右下或左上的部分矩陣也需要尋找,於是最後會產生三個矩陣再繼續遞迴下去搜尋。