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 都是記錄次數的。

Python
class StockSpanner:

def __init__(self):
self.prices = []
self.count = 0


def next(self, price: int) -> int:
self.count += 1
result = self.count

while self.prices and self.prices[-1][0] <= price:
del self.prices[-1]

if self.prices:
result -= self.prices[-1][1]

self.prices.append([price, self.count])

return result

C#

public class StockSpanner {
List prices;

public StockSpanner() {
prices = new List();
}

public int Next(int price) {
int result = 1;
int i = prices.Count - 1;
while(i >= 0 && prices[i].price <= price)
{
result += prices[i].days;
i -= 1;
}
prices.RemoveRange(i+1, prices.Count-i-1);
prices.Add((price, result));
return result;

} }

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

C#
public class Solution {
public bool CheckInclusion(string s1, string s2) {
var count = new int[26];
foreach(char c in s1)
{
count[c - 'a'] += 1;
}
int match_len = 0;
for(int i = 0; i < s2.Length; i++)
{
char c = s2[i];
while(match_len > 0 && count[c - 'a'] == 0)
{
count[s2[i - match_len] - 'a'] += 1;
match_len -= 1;
}
if(count[c - 'a'] > 0)
{
match_len += 1;
count[c - 'a'] -= 1;
if(match_len == s1.Length)
{
return true;
}
}
}
return false;
}
}

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

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

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

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

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

Python

class Solution:
def findAnagrams(self, s: str, p: str) -> List[int]:
count = Counter(p)
match_len = 0
p_len = len(p)
result = []
for i in range(len(s)):
c = s[i]
while match_len and (c not in count or count[c] == 0):
count[s[i-match_len]] += 1
match_len -= 1

if c in count and count[c] > 0:
count[c] -= 1
match_len += 1
if match_len == p_len:
result.append(i-p_len+1)

return result

C#

public class Solution {
public IList FindAnagrams(string s, string p) {
var count = new Dictionary();
foreach(char c in p)
{
if(count.ContainsKey(c))
count[c] += 1;
else
count[c] = 1;
}
var result = new List();
int match_len = 0;
for(int i = 0; i < s.Length; i ++)
{
char c = s[i];
bool hasKey = count.ContainsKey(c);
while(match_len > 0 && (!(hasKey) || count[c] == 0))
{
count[s[i - match_len]] += 1;
match_len -= 1;
}
if(hasKey && count[c] > 0)
{
match_len += 1;
count[c] -= 1;
if(match_len == p.Length)
result.Add(i - match_len + 1);
}
}
return 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() 去選出最大的數字,才能符合設計。

Python

class Solution:
def largestNumber(self, cost, target) -> str:
from functools import lru_cache
cost_dict = dict()
for i, c in enumerate(cost):
cost_dict[c] = str(i+1)
sorted_dk = sorted(cost_dict.keys())

@lru_cache(None)
def dfs(t):
ans = 0
for c in sorted_dk:
if t - c == 0:
ans = max(ans, int(cost_dict[c]))
elif t - c < 0:
break
else:
result = dfs(t - c)
if result != "0":
ans = max(ans, int(cost_dict[c] + result))
return str(ans)

return dfs(target)

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

給一個 linked list,要以 偶數位 奇數位 重新排列。
要求在 O(1) Space、 O(n) Time 解決。
分別拆成偶數奇數頭出來,再把剩下的 linked list 分給兩者,最後合併,回傳偶數頭。這符合題目要求。
或者直接在頭尾開始接,但這預期不如上者,因為上者到 n-1 位置時,就幾乎要完成了;而這方法才剛開始。

Python

class Solution:
def oddEvenList(self, head: ListNode) -> ListNode:
if not head or not head.next: return head
oddHead = OP = head
evenHead = EP = head.next
#
head = head.next.next
i = 0
while head:
i += 1
if i % 2 == 1:
OP.next = head
OP = OP.next
else:
EP.next = head
EP = EP.next
head = head.next
EP.next = None
OP.next = evenHead
return oddHead

C#

public class Solution {
public ListNode OddEvenList(ListNode head) {
if(head is null){return head;}
if(head.next is null){return head;}
ListNode OddHead = head; ListNode OddLN = OddHead;
ListNode EvenHead = OddHead.next; ListNode EvenLN = EvenHead;
head = head.next.next;
int i = 0;
while(!(head is null))
{
if(i % 2 == 0)
{
OddLN.next = head;
OddLN = OddLN.next;
}
else
{
EvenLN.next = head;
EvenLN = EvenLN.next;
}
i += 1;
head = head.next;
}
EvenLN.next = null;
OddLN.next = EvenHead;
return OddHead;
}
}

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

給一個環形陣列 A,要找出總和最大的子陣列。
這題蠻有意思的,讓我沒有順利解出來,有想到好幾種可能,看了別人的解法,發現是用最大最小的方式。
但我想到這方法的當下,沒有去進一步思考。
官方有 Solution ,內有四種解法,但我還是照步調去走從未知去導出解法的思維分析。
如果會做陣列中子陣列最大總和,那環形的也只是稍做變化。
要如何後面接回前面?這是第一個要解決的問題。

對於一個陣列來說,用Python是可以再添加一個一樣的,達到 2n 的長度,在 n – 1 位置之後,又會回到開頭的值。
但由於限制每個元素只能被取一次,這作法可能會讓一元素被選中二次(如全部為正的),並不是一種簡單可行的方法。
又或者弄 linked list ,然後作 DP 以各元素的選取保存每一位置的最大值總和。這能避免重複,但寫來會有點麻煩。
照著 Hint ,要用「 Kadane’s algorithm 」, 然而這不是算環形的陣列,需要改良才能用上。如果只跑一遍陣列,那也不會碰到同元素被選二次的狀況。
如果最大值落於陣列間,最後的位置不是 n – 1,那 Kadane’s algorithm 就可以找出正解。
但最後位置有碰到 n – 1,那麼就會接回開頭去找的需求。
再思考,如果目前正計算最大的子陣列,沒到計算結束前是不知道目前的是否處在最大子陣列中。而且 Kadane’s algorithm 的方式也沒有保存位置,判斷最後位置並不是好的方法。
但以結果回推,最大子陣列以外的元素加總就是最小子陣列。
因此取得最小子陣列的總和,用整個數列的總和去減它,也可以得到最大子陣列的總和。
如果最小子陣列在陣列之間,那最大子陣列會是環形接起的。
因此所求在「陣列(沒有環)子陣列最大總和」或「總合減陣列子陣列最小總和」中,兩者取大。
但這樣子有一個狀況,如果是遞減數列,最小總和會是全體元素集合,而總和減去它會是 0 ,也比最大總和還大。
因此,如果 總和 == 子陣列最小總和 ,那只能回傳前者。

Python

class Solution:
def maxSubarraySumCircular(self, A: List[int]) -> int:
min_now = max_now = A[0]
nmin = nmax = nsum = A[0]
for num in A[1:]:
min_now = min(num, min_now + num)
nmin = min(nmin, min_now)
max_now = max(num, max_now + num)
nmax = max(nmax, max_now)
nsum += num
if nsum == nmin: return nmax
return max(nsum - nmin, nmax)

C#

public class Solution {
public int MaxSubarraySumCircular(int[] A) {
int min_now = A[0]; int max_now = A[0];
int nmin = A[0]; int nmax = A[0]; int nsum = A[0];
for(int i = 1; i < A.Length; i++)
{
int num = A[i];
min_now = (min_now + num < num) ? min_now + num : num;
nmin = nmin < min_now ? nmin : min_now;
max_now = (max_now + num > num) ? max_now + num : num;
nmax = nmax > max_now ? nmax : max_now;
nsum += num;
}
if(nsum == nmin){return nmax;}
return (nmax > nsum - nmin) ? nmax : nsum - nmin;
}
}

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

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

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

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

Python

class Trie:

def __init__(self):
"""
Initialize your data structure here.
"""
self.trie = {}


def insert(self, word: str) -> None:
"""
Inserts a word into the trie.
"""
now = self.trie
for char in word:
if char not in now: now[char] = {}
now = now[char]
now['exist'] = True


def search(self, word: str) -> bool:
"""
Returns if the word is in the trie.
"""
now = self.trie
for char in word:
if char not in now: return False
now = now[char]
return 'exist' in now


def startsWith(self, prefix: str) -> bool:
"""
Returns if there is any word in the trie that starts with the given prefix.
"""
now = self.trie
for char in prefix:
if char not in now: return False
now = now[char]
return True

C#

class TrieNode
{
public bool End;
public Dictionary Next;
public TrieNode() {
Next = new Dictionary();
End = false;
}
}

public class Trie {
private TrieNode root;
/** Initialize your data structure here. */
public Trie() {
root = new TrieNode();
}

/** Inserts a word into the trie. */
public void Insert(string word) {
TrieNode now = root;
foreach(char c in word)
{
if(!(now.Next.ContainsKey(c)))
{
now.Next[c] = new TrieNode();
}
now = now.Next[c];
}
now.End = true;
}

/** Returns if the word is in the trie. */
public bool Search(string word) {
TrieNode now = root;
foreach(char c in word)
{
if(now.Next.ContainsKey(c))
{
now = now.Next[c];
}
else
{
return false;
}
}
return now.End;
}

/** Returns if there is any word in the trie that starts with the given prefix. */
public bool StartsWith(string prefix) {
TrieNode now = root;
foreach(char c in prefix)
{
if(now.Next.ContainsKey(c))
{
now = now.Next[c];
}
else
{
return false;
}
}
return true;
}
}

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

給一個非負整數以 string 型態表示,移除 k 個數字,讓數字變成最小。
如果把它作DP,會超時,因為 num 的長度最多到10002。
不用 DP ,我們要來想想要如何從頭開始找起,並作篩選。
如果要讓結果是最小,那可想像會在一個範圍中,保留較小的,取走較大的。如此運作,直到挑出 k 個。
仔細觀察,被挑選過後的樣子。
“1432219”, 3 -> “1219”
“4321234”, 2 -> “21234”
“10200”, 1 -> “200”
如果要排成最小,那麼會讓數列盡可能朝著「非升序排列」,也就是若左方大於右方,左方就會被挑走。
若左右相等或小於,則住右方移動。

“1432219”為例:
“0” # 預設
“01” # 左 < 右
“014” # 左 < 右
“014” “3” -> “01” “3” 挑走左方-> “013” (K = 2)
“013” “2” -> “01” “2” -> “012” (K = 1)
“0122” # 左 == 右
“0122” “1” -> “012” “1” (K = 0,將剩下的加入) -> “01219”
選完之後,先檢查 k 值,因為是 「非升序排列」,最後面的會是比較大的,若 k 有剩,從最後再拿走 k 個。
再去除前導零(leading zero),用 lstrip(“0”)。
最後檢查答案是否為 “” ,是則回傳 “0” ,否則 result。

Python

class Solution:
def removeKdigits(self, num: str, k: int) -> str:
result = "0"
for i, c in enumerate(num):
while c < result[-1] and k:
result = result[:-1]
k -= 1
if k == 0:
result += num[i:]
break
result += c

if k: result = result[:-k]
result = result.lstrip("0")
if result: return result
return "0"

C#

public class Solution {
public string RemoveKdigits(string num, int k) {
string result = "0";
int n = result.Length;
for(int i = 0; i < num.Length; i++)
{
n = result.Length;
while(num[i] 0)
{
result = result.Substring(0, n - 1);
k -= 1;
n -= 1;
}
if(k == 0)
{
n = num.Length;
result = result + num.Substring(i, n - i);
break;
}
result += num[i];
}
n = result.Length;
if(k > 0)
{
result = result.Substring(0, n - k);
}
result = result.TrimStart('0');
if(result.Length == 0)
{
return "0";
}
return result;
}
}

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 值不變,指向最後一個為單一的元素。

Python

class Solution:
def singleNonDuplicate(self, nums: List[int]) -> int:
l, r = 0, len(nums)-1
while l < r:
m = (r-l)//2 + l
if nums[m-m%2] == nums[m+1-m%2]:
l = m+1
else:
r = m
return nums[r]

C#

public class Solution {
public int SingleNonDuplicate(int[] nums) {
int l = 0; int r = nums.Length-1;
while(l < r)
{
int m = (r-l)/2 + l;
if(nums[m - m%2] == nums[m - m%2 + 1])
{
l = m + 1;
}
else
{
r = m;
}
}
return nums[r];
}
}

Leetcode題解 Python:Trapping Rain Water

給一個代表(牆)高度的非負整數數列,而下雨之後,水會積在低窪處,回傳水的體積。
水會積多少,取決於左右方的最高高度取較低者減當前高度,若小於零代表沒有積水。
這樣子很好寫出正確寫法,但是超時的問題得考慮進去。
如果每次都重找最大值,時間複雜度會是 O(n**2),得想辦法拉到 O(n) 解決。
最大值一定要重找嗎?如果從左方開始,左方的最大值會一路遞增,而右方會一路遞減。過了數列的最大值,消漲就會互換。
也就是說,若能使用 TwoPointer 去從左右方往中間靠近,邊找邊算所求,就能把時間複雜度用在 O(n)。
當然可以做到,官方也有範例可供參考。

Python(基本)

class Solution:
def trap(self, height: List[int]) -> int:
result = 0
n = len(height)
for i in range(1,n-1):
lmax = max(height[:i])
rmax = max(height[i:])
result += max(min(lmax, rmax) - height[i], 0)
return result

Python(進階)

class Solution:
def trap(self, height: List[int]) -> int:
result = 0
li, lmax = 0, 0
ri, rmax = len(height)-1, 0
while li <= ri:
if lmax > rmax:
if height[ri] > rmax: rmax = height[ri]
else: result += rmax - height[ri]
ri -= 1
else:
if height[li] > lmax: lmax = height[li]
else: result += lmax - height[li]
li += 1
return result

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

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

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

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

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

Python

class Solution:
def floodFill(self, image: List[List[int]], sr: int, sc: int, newColor: int) -> List[List[int]]:
rows, cols = len(image), len(image[0])
oldColor = image[sr][sc]

@lru_cache(None)
def search(y, x):
if y < 0 or x < 0 or y == rows or x == cols:
return
if image[y][x] == oldColor:
image[y][x] = newColor
search(y-1, x)
search(y+1, x)
search(y, x-1)
search(y, x+1)

if oldColor != newColor: search(sr, sc)
return image

C#

public class Solution {
int rows;
int cols;
public int[][] FloodFill(int[][] image, int sr, int sc, int newColor) {
var oldColor = image[sr][sc];
rows = image.Length;
cols = image[0].Length;
if(oldColor != newColor)
{
Fill(image, sr, sc, newColor, oldColor);
}
return image;
}

public void Fill(int[][] image, int y, int x, int newColor, int oldColor)
{
if(y >= 0 && y = 0 && x < cols && image[y][x] == oldColor)
{
image[y][x] = newColor;
Fill(image, y-1, x, newColor, oldColor);
Fill(image, y+1, x, newColor, oldColor);
Fill(image, y, x-1, newColor, oldColor);
Fill(image, y, x+1, newColor, oldColor);
}
}
}

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

Python

class Solution:
def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]:
result = set()
candidates.sort()
n = len(candidates)
stack = [[candidates[i], [i]] for i in range(n)]
while stack:
total, index = stack.pop()
if total == target:
result.add(tuple(candidates[i] for i in index))
continue
for i in range(index[-1]+1, n):
new_total = total + candidates[i]
if new_total > target: break
stack.append([new_total, index+[i]])
return result