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 傳給遞迴函數,取得該子串的頭尾,然後插入當前的位置與下一位置的中間。

空間複雜度為 O(1)

class Solution:
def flatten(self, head: 'Node') -> 'Node':
def nextNode(node):
head = tail = node
while tail:
if tail.child:
ch, ct = nextNode(tail.child)
if tail.next:
ct.next = tail.next
ct.next.prev = ct
tail.next = ch
tail.next.prev = tail
tail.child = None
tail = ct
else:
if tail.next:
tail = tail.next
else:
break
return head, tail
return nextNode(head)[0]

另外一種則是先遍歷,記錄順序,最後再重組。

class Solution:    
def flatten(self, head: 'Node') -> 'Node':
nodes = []
def nextNode(node):
if node:
nodes.append(node)
nextNode(node.child)
nextNode(node.next)
nextNode(head)
p = None
for each in nodes:
each.child = None
each.prev = p
if p: p.next = each
p = each
return head

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


簡化回文的判斷,使用 stack == stack[::-1],這樣只需要比較一次,不用記錄長度,空間複雜度仍是 O(n)。

class Solution:
def isPalindrome(self, head: ListNode) -> bool:
stack = []
while head:
stack.append(head.val)
head = head.next
return stack == stack[::-1]

如果利用循環的概念,快指針走 長度-1 、 慢指針走 1 ,兩兩相相比,不相等就不是,直到快指針追到慢指針結束。

但是這樣會超時。

如果空間複雜度局限於 O(1),就要使用 reverse 的概念,不論是把分水嶺前面的或後面的 reverse 都可以。反序完再兩兩比較。

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
#
for _ in range(len_head//2):
FP = SP
SP = SP.next
FP.next = head
head = FP
if len_head%2==1: SP = SP.next
while SP:
if SP.val != head.val:
return False
SP = SP.next
head = head.next

return True

最近我的leetcode總是跑得比別人慢,即使是一樣的代碼,就是遜一節,不知道為什麼。

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)

上一題說到將 Linked List 反序,如果用反序的概念,也就是把目標值丟到最前方去。如此,最後只需要從頭跑 Linked List 直到不為目標值的時候,再把該節回傳。

class Solution:
def removeElements(self, head: ListNode, val: int) -> ListNode:
if not head: return head
FP = SP = head
FP = FP.next
while FP:
if FP.val == val:
SP.next = SP.next.next
FP.next = head
head = FP
FP = SP.next
else:
SP = SP.next
FP = FP.next
#
while head and head.val == val:
head = head.next
return head

如果頭部移除的情形會很困擾,看了前段班的寫法,可以加入了暫時根節變成新頭根節,這樣可以把一切都當成子根節處理,最後回傳暫時根節的下一個(原頭根節)即可,就不需要針對頭根節移除自身做額外寫法。
# 前段班大神寫法

class Solution:
def removeElements(self, head: ListNode, val: int) -> ListNode:
head_holder = ListNode(0) # the value does not matter
head_holder.next = head

cur = head_holder
while cur.next:
if cur.next.val == val:
cur.next = cur.next.next
else:
cur = cur.next

return head_holder.next

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),就沒有把它寫完了。


看了一下前段班,共同點就是 headA跑完換到headB、headB跑完換到headA。

為什麼可以這樣做?

如果有交會,headA = A part + C part、headB = B part + C part。

兩者一起往下跑,跑完換別的跑,會變成 headA->headB = A part + C part + B part + C part、headB->headA = B part + C part + A part + C part。

A part + C part + B part =  B part + C part + A part,於是兩者將會在 C part 交會。

這是一個循環的技巧應用,如果有想到,就會快很多。

class Solution:
def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
PointerA, PointerB = headA, headB
for _ in range(2):
while PointerA and PointerB:
PointerA = PointerA.next
PointerB = PointerB.next
if PointerA: PointerB = headA
else: PointerA = headB

while PointerA and PointerB:
if PointerA == PointerB: return PointerA
PointerA = PointerA.next
PointerB = PointerB.next
return None

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() ,同一字謎的詞都會跑出相同結果。


不過 sorted() 回傳的類型是 list,list是無法作為 set 或是 dict 的key。用 “”.join() 或是 tuple轉換後就可以當作key。

確定了鍵從何而來 ,該鍵的值要保存同一字謎的詞,所以是 dict(),dict[key] : list(str)。

class Solution:
def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
memo = dict()
for word in strs:
anagram = "".join(sorted(word))
if anagram not in memo: memo[anagram] = [word]
else: memo[anagram].append(word)
return memo.values()

這種寫法跟前段班有明顯的差距,前段班不外乎使用 defaultdict()

class Solution:
def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
import collections
memo = collections.defaultdict(list)
for word in strs:
memo[tuple(sorted(word))].append(word)
return memo.values()

就效率上,使用模組是有幫助的,不過 Leetcode 沒有寫到是否可以使用,或者是能夠載入甚麼模組,對我來說,能夠不用就不用到。如果一定要用的部分,像是取隨機值,才會使用。

解過多次題目之後發現,如果是可用的模組,不使用import也可以直接使用。

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


這種寫法可以減少記憶體使用,空間複雜度為 O(1)。

另外一種用 memo 紀錄步驟的方法。

紀錄步驟是需要用到不會重複的值,如果只是 val,除非保證不會有相同 val,不然可能會出錯。

最保險起見的方法,是使用記憶體位置,作為是否曾經走過的依據。

幸好物件可以直接加入set,而物件在set中做為比較的數值是記憶體位置。

class Solution:
def hasCycle(self, head: ListNode) -> bool:
if not head: return head
memo = set()
while head.next:
if head in memo: return True
memo.add(head)
head = head.next
return False

我在想,如果要得加入一個有用的變數在裡面,到底要怎麼加?這才是勾起我的興致的發想。 想出來了,硬是要在裡面加入的 steps 紀錄步數。

class Solution:
def hasCycle(self, head: ListNode) -> bool:
if not head: return head
fastPointer, slowPointer = head, head
steps = 0
while fastPointer.next:
fastPointer, steps = fastPointer.next, steps+1
if fastPointer == slowPointer: return True
if steps%2==1: slowPointer = slowPointer.next
return False

接著看到 II,這次要回傳循環起點,如果用 memo 紀錄,這個問題只是將回傳False改成當前的節點。

class Solution:
def detectCycle(self, head: ListNode) -> ListNode:
if not head: return head
memo = set()
while head.next:
if head in memo: return head
else: memo.add(head)
head = head.next
return None

如果用快慢指針來解要怎麼辦?在兩者相遇之後,把慢指針移回原點,快慢指針每次都指往前一步,就會在循環起點相遇。

解釋:快2步,慢1步,當慢指針到了循環起點時,快指針在循環中也走了循環起點距離。而兩者的所剩距離是 循環長度 – 循環起點距離。
因此,慢指針往前「循環長度 – 循環起點距離」,兩者就會在循環內的 (循環長度 – 循環起點距離)位置上相遇。
此時把慢指針挪回起點,慢快指針同時走一步,前進 循環起點距離 步時,慢指針會來到 循環起點, 快指針會來到 循環長度 的位置,也是循環起點,兩者在此相遇。

手稿示意圖
class Solution:
def detectCycle(self, head: ListNode) -> ListNode:
if not head: return head
FastPointer, SlowPointer = head, head
while 1:
# SlwoPointer go 1 step forward
SlowPointer = SlowPointer.next
# FastPointer go 2 steps forward
if FastPointer.next and FastPointer.next.next:
FastPointer = FastPointer.next.next
if FastPointer == SlowPointer: break
else:
return None
# FP SP meet in cycle(CycleLength - pos)
SlowPointer = head
while 1:
if FastPointer == SlowPointer: return FastPointer
SlowPointer = SlowPointer.next
FastPointer = FastPointer.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.

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

    def palindromePairs(self, words):
result = []
if words:
trie = {} #反向
# create Trie
n = len(words)
for i in range(n):
cur = trie
for char in words[i][::-1]:
if char not in cur:
cur[char] = {}
cur = cur[char]
cur['end'] = i
# 檢查對稱
def isSymmetry(word):
n = len(word)
for i in range(n//2):
if word[i] != word[n-1-i]: return False
return True
#
def search1(idx, temp, cur):
"""搜尋當前字詞"""
if temp:
# 如果過程中找到字詞,檢查當前的剩餘部分是否回文
if 'end' in cur and isSymmetry(temp) and idx != cur['end']:
result.append([idx, cur['end']])
if temp[0] in cur:
search1(idx, temp[1:], cur[temp[0]])
else:
search2(idx, "", cur)

def search2(idx, temp, cur):
"""搜尋剩餘部分是否回文"""
for key in cur:
if key == "end":
if isSymmetry(temp) and idx != cur['end']:
result.append([idx, cur['end']])
else:
search2(idx, temp+key, cur[key])
#
for i in range(n):
search1(i, words[i], trie)

return result

在效率上還是太慢,我搜尋是用更廣的對稱,涵蓋更多的可能性。看了一下前段班的寫法,去找更好的思路。

如果在搜尋上,是把字文拆出「可能」組合成回文的部分搜尋,就能提升效率,但我那時有思考到這種方式,但沒有寫想到要怎麼寫,因為我trie總是用成一層層,所以被侷限了。

字詞可以整個直接當成字典的鍵使用,該鍵的值就是位置。

像是 abcd,如果要拆成回文形式,就得找出左右之分。

abcd 前組後:
abcd | “” => “”是回文 => dcba(前面部分反序) => [0,1] #前組後
abc | d => d是回文 => cba => not found
ab | cd => cd不是回文 => X
a | bcd => cbd不是回文 => X
“” | abcd => abcd不是回文 => X

換個字詞

s 前組後:
s | “” => “”是回文 => s => [3,3] => 不合規則
“” | s => s是回文 => “” => not found

lls 前組後:
lls | “” => “”是回文 => sll => not found
ll | s =>> s是回文 => ll => not found
l | ls => ls不是回文 => X
“” | lls => lls不是回文 => X

這樣的搜尋只有「前組後」,沒有考慮到「後組前」,要考慮 後組前 只要把字詞反序做搜尋。
lls 後組前:
sll(字詞反序) | “” => “”是回文 => lls => [2,2] => 不合規則
sl | l => l是回文 => ls => not found
s | ll => ll是回文 => s => [3,2] #後組前
“” | sll => sll不是回文 => X

這樣搜尋速度快不少,不過會有答案會重複,所以要想辦法去掉。

class Solution:
def palindromePairs(self, words):
table = dict(map(reversed, enumerate(words)))
res = set()
for i, word in enumerate(words):
for k in range(len(word)+1):
a = word[:k]
b = word[k:]

if a == a[::-1] and table.get(b[::-1], i) not in [-1, i]:
res.add((table.get(b[::-1], -1), i))

if b == b[::-1] and table.get(a[::-1], i) not in [-1, i]:
res.add((i, table.get(a[::-1], -1)))

return list(res)

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,而是搜尋要如何進行。

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


當遍歷整個版面,取得 y x,如果 board[y][x] 在 trie 內,就繼續往四週找,看四週的字是否有出現下一層。

因為限制不能回頭,所以用一個 memo 去紀錄步驟(座標),接下來就避免重複尋找已經走過的路徑。(if (y,x) in memo)

回溯法的關鍵寫法:是在往下遞迴之前才加入步驟,在遞迴結束後要刪除之前加入的步驟。這就像是回到上一步,再往下一個方向嘗試。

最終會將全部可走的都走過,再一一收回步驟,因此當搜尋完畢之後 memo 會是空的。

class Solution:
def findWords(self, board: List[List[str]], words: List[str]) -> List[str]:
if board and board[0]:
rows, cols = len(board), len(board[0])
# create trie
trie = {}
for word in words:
cur = trie
for char in word:
if char not in cur:
cur[char] = {}
cur = cur[char]
cur['end'] = word
#
memo = {*""}
findWords = {*""}
def search(y, x, level):
if board[y][x] in level:
level = level[board[y][x]]
if 'end' in level:
findWords.add(level['end'])
if y-1 >= 0 and (y-1,x) not in memo:
memo.add((y,x))
search(y-1, x, level)
memo.discard((y,x))
if x-1 >= 0 and (y,x-1) not in memo:
memo.add((y,x))
search(y, x-1, level)
memo.discard((y,x))
if y+1 < rows and (y+1,x) not in memo:
memo.add((y,x))
search(y+1, x, level)
memo.discard((y,x))
if x+1 < cols and (y,x+1) not in memo:
memo.add((y,x))
search(y, x+1, level)
memo.discard((y,x))

for y in range(rows):
for x in range(cols):
memo.clear()
search(y, x, trie)

return list(findWords)

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))])


不過真的要買賣,當然是減少進場次數,買要判斷沒有持有,賣要判斷有持有才能進行。
(真實買賣的情況下,減少進場次數是方向,不管有沒有持有,都可以買賣。

class Solution:
def maxProfit(self, prices: List[int]) -> int:
profit = 0
if prices:
isBuy, cost = 0, 0
lastPrice = prices[0]
for i in range(1, len(prices)):
nowPrice = prices[i]
if lastPrice <= nowPrice and not isBuy:
cost = lastPrice
isBuy = 1
elif lastPrice > nowPrice and isBuy:
profit += lastPrice - cost
isBuy = 0
lastPrice = nowPrice
if isBuy: profit += lastPrice - cost

return profit

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 的串列即可恢復數列。


不過這樣沒有順著題目所期望的解法前進,用了額外的空間保存數列。

A two-pointer approach could be helpful here. The idea would be to have one pointer for iterating the array and another pointer that just works on the non-zero elements of the array.

這裡說用兩個指針,一個是迭代數列,另一個針對非0的元素。

針對迭代數列不用多心,要跑完數列自然就會用上。另外一個針對非0元素的要怎麼寫?

說到要針對非0元素,想必就要使用 if 去區分出來。遇到不是0的要怎麼處理?遇到0的又要怎麼處理?

如果要把 0 移動到最後方,路線有兩種:
1. 把0元素移動到最後方。
2. 把非0元素往前移動到最前方。

說到移動,就是把元素兩兩交換。交換就是一種 in-place 的作法,那要如何交換呢?

這裡使用 順序 去迭代數列,由前而後,所以被往前換的數字是不會再被迭代到而換到。

因此選擇把非0元素往前換,接下來只要不要動到換去前面的非0元素順序,最後就能把 0 都移動到最後方。

往前換,要往前多少?

從頭模擬起,如果一開始沒有碰到 0,就不需要換。接下來要是碰到0,也不用交換。在 0 之後又碰到非0元素,要往前換,要跟前面的0交換。

在遇到非0元素前,碰到一次0,跟前一格(0)換,碰到兩次0,跟前兩格的0交換。推理出,往前交換的位置跟碰到 0 次數是相同的。

碰到 0 ,指針 +1 。碰到 非0 ,就交換。132

class Solution:
def moveZeroes(self, nums: List[int]) -> None:
"""
Do not return anything, modify nums in-place instead.
"""
zeroPointer = 0
for i, num in enumerate(nums):
if num == 0:
zeroPointer += 1
else:
nums[i], nums[i-zeroPointer] = nums[i-zeroPointer], nums[i]

以下是我看其他大神的最貼切題目要求的寫法。

class Solution:
def moveZeroes(self, nums: List[int]) -> None:
"""
Do not return anything, modify nums in-place instead.
"""
last_nnz = 0
for i in range(len(nums)):
if nums[i] != 0:
nums[last_nnz], nums[i] = nums[i], nums[last_nnz]
last_nnz += 1

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) 的方法,不過題目希望能夠拆散數列再各個處理的方式(分治法)。


先提 O(n) 的方法。

既然是 O(n),架構通常是一個「 for in 」,先寫上去再思考就好。需要回傳最大總和,所以可以命名一個 largestSum,最後 return largestSum。

如果要跑完一回就能找到答案,得在過程中記錄甚麼,或是判斷甚麼。

觀察一下數列,思考:遇到正的就累加起來,如果數列都是正整數,加完就是最大總和。那遇到負的部分呢?

這裡想到了累加,所以命名一個 accu 的變數,存放當前累加的結果。

如果遇到負整數,會使得當前累加會減少,所以先把當前的累加值保存下來,然後再把數字加上去。

將下來再做一些細部條件判斷,就能完成 O(n) 的解法。

class Solution:
def maxSubArray(self, nums: List[int]) -> int:
largestSum = nums.pop(0)
nowAccu = largestSum
for num in nums:
if num < 0:
if nowAccu < 0:
largestSum = max(largestSum, num)
else:
largestSum = max(largestSum, nowAccu)
nowAccu += num
else:
if nowAccu < 0: nowAccu = num
else: nowAccu += num
largestSum = max(largestSum, nowAccu) #最後一項
return largestSum

至於分治法要怎麼寫?覺得麻煩就跳過。目前的概念是分成正負組合,再處理成各區域最大總和,從中回傳最大值。
。

Leetcode題解 Python: 四月挑戰DAY2 Happy Number

給一個整數,按照十進位分切出各單個數字,這些數字的平方加總後,繼續分切…,如果最後總合等於 1,就是快樂數字。

好吧,到底哪裡快樂我也不知道。這依舊是一個非常簡單的題目。

可以分成三個步驟:
1.分切數字
2.數字平方和
3.判斷為1,否則回到步驟1.

要是碰到無限循環怎麼辦?把出現的數字記錄下來,只要有數字重複出現那就是無限循環。

用一個 memo ,字典或是集合(set),之後用 in 就能看出是否有重複出現過。

還有更偷機的做法,就是把False的數字回收進memo。這樣會相當Happy。

class Solution:
def isHappy(self, n: int) -> bool:
memo = {2: False, 3: False, 4: False, 5: False, 6: False, 8: False, 9: False,
11: False, 12: False, 14: False, 15: False, 16: False, 17: False, 18: False,
20: False, 21: False, 22: False, 24: False, 25: False, 26: False, 27: False,
29: False, 30: False, 33: False, 34: False, 35: False, 36: False, 37: False,
38: False, 39: False, 40: False, 41: False, 42: False, 43: False, 45: False,
46: False, 47: False, 48: False, 50: False, 51: False, 52: False, 53: False,
54: False, 55: False, 56: False, 57: False, 58: False, 59: False, 60: False,
61: False, 62: False, 63: False, 64: False, 65: False, 66: False, 67: False,
69: False, 71: False, 72: False, 73: False, 74: False, 75: False, 76: False,
77: False, 78: False, 80: False, 81: False, 83: False, 84: False, 85: False,
87: False, 88: False, 89: False, 90: False, 92: False, 93: False, 95: False,
96: False, 98: False, 99: False, 106: False, 113: False, 117: False, 128: False,
145: False, 162: False}
if n in memo: return False
num2 = 0
while n>1:
if n in memo: return False
else: memo[n]=None
while n>0:
num2 += (n % 10)**2
n = n//10
n, num2 = num2, 0
return True

如果用字典是比較容易去偷機,如果不偷機,用集合會比較好增加資料(可以少打幾個字)。

class Solution:
def isHappy(self, n: int) -> bool:
memo = {*''}
num2 = 0
while n>1:
if n in memo: return False
else: memo.add(n)
while n>0:
num2 += (n % 10)**2
n = n//10
n, num2 = num2, 0
return True