leetcode演算法資料結構題解---資料結構
05 替換空格
解題思路:
字串是不可變型別,本題中需要替換字串中的某個字元,可以使用replace方法直接替換;以下是遍歷字串的方式進行求解的,逐個判斷字串中的字元是否為空,若為空,將新字串新增相應字串;否則新增原來字元。該方法的時間複雜度為o(n)
程式碼:
class Solution:
def replaceSpace(self, s: str) -> str:
# return s.replace(' ', '%20')
tmp = ''
for i in range(len(s)):
tmp += '%20' if s[i] == ' ' else s[i]
return tmp
class Solution {
public String replaceSpace(String s) {
StringBuilder res = new StringBuilder(); // 可變的字串物件,沒有執行緒安全
for(Character c : s.toCharArray()){ // for寫法,字串轉字元列表
if(c == ' '){res.append("%20");} // 雙引號
else{res.append(c);}
}
return res.toString();
}
}
在Java和python3中,string都是不可變型別,需要增加新變數來得到最後的結果。而C++是可變型別,透過s.resize()可以改變字串長度,按照空格數改變字串長度,然後從後往前尋找空格,往相應地方新增題目中要求的字元。
class Solution {
public:
string replaceSpace(string s) {
// 首先先確定空格的數量,然後更改字串的長度,用兩個指標從後往前遍歷,如果遇到空格,從後往前將20%寫上
int len = s.size();
int count = 0;
for(char c : s){
if(c == ' '){ // C++中單字元用單引號
count++;
}
}
s.resize(len + count * 2);
for(int i=len-1,j=s.size()-1;i
if(s[i] == ' '){
s[j] = '0';
s[j-1] = '2';
s[j-2] = '%';
j -= 2;
}else{
s[j] = s[i];
}
}
return s;
}
};
06 從尾到頭列印連結串列
我的思路就是逐個讀取連結串列節點的value得到一個list,然後逆序輸出list即可,時間複雜度和空間複雜度都是o(n)
class Solution:
def reversePrint(self, head: ListNode) -> List[int]:
result = []
# 正序讀取O(n)
while head is not None: # 可以簡寫為:while head:
result.append(head.val)
head = head.next
# list逆序輸出,下標操作
return [result[len(result)-i-1] for i in range(len(result))]
# python中list逆序可以直接透過切片實現
# return result[::-1]
解題方法:
遞迴回溯法:
若head不是空,那麼逐個遍歷連結串列,直到head為空時,再依次回溯回去。在python的程式碼中,我認為self.reversePrint()本身就是一個遞迴的機制,表示求某條連結串列的逆序。
return self.reversePrint(head.next) + [head.val] if head else []
輔助棧法:
由於本題是單連結串列,需要逆序輸出值,考慮到棧先進後出的原則,考慮用棧來解決此題。以上我的思路就是這種方法,在python中,使用list就足夠。在Java中,需要使用LinkedList stack = new LinkedList()定義一個棧,然後stack.removeLast()出棧儲存值。
09 用兩個棧實現佇列
首先,明確佇列和棧的工作方式。佇列是“先進先出”,棧是“先進後出”。本題需要完成兩個功能,分別是新增和刪除,即往隊尾新增新元素,刪除隊首的元素。如果用一個棧來完成佇列的操作,新增元素時,直接新增即可;刪除元素時,彈出最後一個值(棧底元素)就需要逐個彈出所有元素。那麼可以用一個棧完成新增操作,另一個棧逆序儲存列表順序,完成刪除操作。在python中,可以直接用list作為棧。
class CQueue:
def __init__(self):
# 初始化佇列,即兩個空棧
self.A, self.B = [], []
def appendTail(self, value: int) -> None:
# 新增隊尾元素
self.A.append(value)
def deleteHead(self) -> int:
# 刪除隊首元素,並彈出該值
if self.B: return self.B.pop()
# 佇列為空
if not self.A: return -1
# 將A逆序儲存到B中
while self.A:
self.B.append(self.A.pop())
return self.B.pop()
# Your CQueue object will be instantiated and called as such:
# obj = CQueue()
# obj.appendTail(value)
# param_2 = obj.deleteHead()
此處注意一個點:判斷列表是否為空時,直接if list,若True表示不空,Flase表示空。is not None表示物件不存在。
20 表示數值的字串
請實現一個函式用來判斷字串是否表示數值(包括整數和小數)。例如,字串"+100"、“5e2”、"-123"、“3.1416”、"-1E-16"、“0123"都表示數值,但"12e”、“1a3.14”、“1.2.3”、"±5"及"12e+5.4"都不是。
暴力破解,邏輯判斷,if-else堆砌:(沒有得到最終答案,仍然考慮不全面)主要考慮小數點左右,e左右兩側的合格與否,判斷兩側字串是否為整數,存在給定字元中字元順序導致的各種問題。
class Solution:
def __init__(self):
self.mark = None
def isNum(self, s):
try:
int(s) # 直接轉為float就可以判斷是否為數值型字串了
return True
except ValueError:
return False
def split(self, s, c):
pos = s.find(c)
return s[:pos], s[pos + 1:]
def isNumber(self, s: str) -> bool:
sym = ['+', '-', '.', 'e', 'E']
num = [repr(i) for i in range(10)]
s = s.strip(' ')
if self.isNum(s): return True # 若本身就是整數
if s == '': return False
# 保證字元在合法範圍
for c in s:
# print(c)
if c not in sym + num:
return False
for c in s:
# 尋找小數點的位置,如果有一側為整數暫時標記成True(方便1.,.2這種的)
if c == '.':
if s.count(c) <= 1:
left, right = self.split(s, c)
if right != '' and right[0] in ['+', '-']:
return False # 小數點右側不能有符號
if (self.isNum(left) and right == '') or (self.isNum(right) and left == ''):
return True # 1. .2
elif self.isNum(left) and self.isNum(right):
return True # 1.2
else: return False
# 尋找e的位置,再判斷是否有小數點,然後判斷完成直接return
if c in ['e', 'E']:
left, right = self.split(s, c)
if (self.isNum(left) or (self.isNum(self.split(left, '.')[0]) or self.isNum(self.split(left, '.')[1]))) and self.isNum(right): # 左側是整數/小數,右側是整數
self.mark = True
# print('e', self.mark)
else: self.mark = False
return self.mark
else: self.mark = False
return self.mark
haha = Solution()
test = ['1.+2', '.-4', '9.-', '+++', '. 1', 'ee', ' ', '..', '12E', '1a3.12', '1.2.3', '+-1', '.', 'e', '1.0e', '.0e',
'1e1.2', '1.2e3.4', # 都是False
'1.0', ' .2', '1. ', '+5.4e3', '1.1e2', '0.e2', '123', '+12', '-12', '-1E-16', '5e2', '+.8'] # 都是True
for i in test:
print(i, haha.isNumber(i))
官方解題方法::看著有一個好高階的名字,其實還是考慮各種情況!
本題使用有限狀態自動機。根據字元型別和合法數值的特點,先定義狀態,再畫出狀態轉移圖,最後編寫程式碼即可。空格[ ]、數字[0-9]、正負號[+, -] 、小數點[.]、冪符號[e, E]。
狀態定義:
按照字串從左到右的順序,定義以下 9 種狀態。
開始的空格:字串左右兩側的空格,內部空格按照其他字元處理,直接判為False
冪符號前的正負號:冪符號左右都可以有正負號;冪符號後不能是小數;前後都不能為空
小數點前的數字:小數點前後只能一處為空,另一處需要數字,小數點後不可有符號,小數點前可以正負號
小數點、小數點後的數字
當小數點前為空格時,小數點、小數點後的數字
冪符號
冪符號後的正負號
冪符號後的數字
結尾的空格
結束狀態:
合法的結束狀態有 2, 3, 7, 8
補課
24.翻轉列表,輸出新列表的頭節點
方法一:迭代雙指標
遍歷單連結串列,修改next指標(時間複雜度為O(N),空間複雜度為O(1))
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def reverseList(self, head: ListNode) -> ListNode:
cur, pre = head, None # 標記當前節點,前一節點
while cur: # 若當前節點存在,執行迴圈體
tmp = cur.next # 暫存下一節點
cur.next = pre # 修改當前節點的方向為前一節點
pre = cur # 修改當前按節點為前一節點,為下一次迴圈做準備
cur = tmp # 將下一節點作為下一次的當前節點
return pre
方法二:遞迴回溯法
考慮使用遞迴法遍歷連結串列,當越過尾節點後終止遞迴,在回溯時修改各節點的 next 引用指向。(時間複雜度、空間複雜度均為O(N))。遞迴就是將一部分重複的操作提取出來,函式內部呼叫自身的方式進行簡單的迴圈,設定遞迴出口以及返回值即可。以下操作,就是逐步遍歷整條單連結串列,直到找到最後一個節點,然後用res標記反鏈的頭節點(在找到最後一個節點,發現cur為None的時候,res獲得了返回值pre,此時的pre為最後一個節點,之後res一直沒有改變),然後依次改變前面幾對節點的連線方向。
class Solution:
def reverseList(self, head: ListNode) -> ListNode:
def reverse(cur, pre): # 交換兩個節點的方向
if not cur: return pre # 遞迴出口
res = reverse(cur.next, cur) # 遞迴
cur.next = pre # 執行操作
return res
return reverse(head, None)
30 包含 min 函式的棧
包含 min 函式的棧:定義棧的資料結構,請在該型別中實現一個能夠得到棧的最小元素的 min 函式在該棧中,呼叫 min、push 及 pop 的時間複雜度都是 O(1)。
在python中,list就可以當作是一個棧,並且list本身就包含pop,push,min方法。如果自己實現這個程式,尋找最小值可能最容易想到的就是遍歷比較,這樣需要消耗的時間複雜度為O(N)。那麼,題目的難點就在於min的複雜度壓縮。我們可以設定一個最小值輔助棧來實現,在壓棧出棧的過程中,就按照大小順序將元素壓入另一個棧中。解析:易出錯的點在於壓棧的時候,需要將小於等於當前最小值的元素都壓入B棧,否則一旦pop出去,就會出錯
class MinStack:
def __init__(self):
"""
initialize your data structure here.
"""
self.A, self.B = [],[]
def push(self, x: int) -> None:
self.A.append(x) # x壓棧
if not self.B or x <= self.B[-1]: # 若x小於B中的最小值,那麼壓入B;否則不壓
self.B.append(x)
def pop(self) -> None:
if self.A.pop() == self.B[-1]:
self.B.pop()
def top(self) -> int:
return self.A[-1]
def min(self) -> int:
return self.B[-1]
# Your MinStack object will be instantiated and called as such:
# obj = MinStack()
# obj.push(x)
# obj.pop()
# param_3 = obj.top()
# param_4 = obj.min()
35. 複雜連結串列的複製
在複雜連結串列中,每個節點除了有一個next指標指向下一個節點,還有一個random指標指向連結串列中的任意節點或者null。
複製分為兩類,分別是淺複製和深複製。淺複製為複製頭節點;深複製為複製所有節點和連線關係,修改複製連結串列時原連結串列不受影響,二者完全獨立。在python中可使用的方法:copy.deepcopy(head)。解析1;解析2
將含有多個指標的連結串列看作有向圖來處理,透過DFS和BFS兩種方式遞迴解決。
35.1 DFS:透過遞迴複製所有next節點和random節點,用字典儲存
class Solution:
def copyRandomList(self, head: 'Node') -> 'Node':
# return copy.deepcopy(head) import cop
def dfs(head):
if not head: return None
if head in visited:
return visited[head]
newNode = Node(head.val, None, None) # 定義一個新節點
visited[head] = newNode
newNode.next = dfs(head.next)
newNode.random = dfs(head.random)
return newNode
visited = {}
return dfs(head)
執行過程:先不看newNode.random=dfs(head.random)這一句
遞迴:dfs(7) -> 7 -> dfs(13) -> 13 -> dfs(11) -> 11 -> dfs(10) -> 10 -> dfs(1) -> 1 -> dfs(None)【遞迴的過程創造了7 13 11 10 1五個獨立的節點,然後建立了5對原連結串列節點和新節點的對映】
回溯:dfs(None)得到返回值None,因此newNode(1).next = None -> 執行return newNode,因此newNode(10).next = 1 -> 依次進行,得到了7->13->11->10->1->None
最後分析random的遞迴回溯過程,此處newNode.random的值是透過if head in visited:return visited[head]獲得返回值的,在next遍歷的過程中,在字典中儲存了所有的節點對{7:7, 13:13, 11:11, 10:10, 1: 1},即得到dfs(1)=1,dfs(10)=10,…
35.2 雜湊表方式
深度複製單連結串列:順著next遍歷這條連結串列,用當前節點的值建立新節點,建立“源節點”和“新節點”之間的對映關係,然後按照源節點的連線方式對新節點進行連線即可。(標記新節點的前驅節點pre的next為新節點,然後當前節點後移,前驅節點更新為新節點。)
class Solution:
def copyRandomList(self, head: 'Node') -> 'Node':
if not head:return
# 形成{原:新}對映,dict[原節點] = 新節點
cur = head # 對連結串列處理時,一定要將head存起來
visited = {}
while cur:
visited[cur] = Node(cur.val)
cur = cur.next
# 新節點的連邊直接等於原節點的邊,dict.get(key)的方式等同於dict[key],有捕捉異常的作用
cur = head
while cur:
visited[cur].next = visited.get(cur.next) # 新節點.next = 對映關係(原節點.next)
visited[cur].random = visited.get(cur.random)
cur = cur.next
return visited[head]
35.3 拼接+拆分
逐個生成新節點,並修改原節點的next,使得新節點逐個跟隨在原節點之後,之間用next連線。
然後按照原節點的random方式對新節點進行random連線。
最後將新節點構成的連結串列和原節點的拆開。
class Solution:
def copyRandomList(self, head: 'Node') -> 'Node':
if not head: return
cur = head
# 1. 複製各節點,並構建拼接連結串列
while cur:
tmp = Node(cur.val)
tmp.next = cur.next
cur.next = tmp
cur = tmp.next
# 2. 構建各新節點的 random 指向
cur = head
while cur:
if cur.random:
cur.next.random = cur.random.next
cur = cur.next.next
# 3. 拆分兩連結串列
cur = res = head.next
pre = head
while cur.next:
pre.next = pre.next.next
cur.next = cur.next.next
pre = pre.next
cur = cur.next
pre.next = None # 單獨處理原連結串列尾節點
return res # 返回新連結串列頭節點
58 II. 左旋轉字串
給定一個字串和一個整數,然後該整數將字串分割成兩部分,然後左右兩部分交換位置拼接,該過程切片就可以實現。
return s[n:]+s[:n]
如果不用切片該怎麼做呢?還是用list,直接遍歷兩次字串,先將後半部分填入list,然後再將前半部分填入,最後join在一起。
res = []
for i in range(n, len(s)):
res.append(s[i])
for i in range(n):
res.append(s[i])
return ''.join(res)
# 用餘數簡化程式碼 大連做人流哪裡好
for i in range(n, n+len(s)): # 如abcde,n=2,i的範圍在2,3,4,5,6,對5取餘結果:2,3,4,0,1
res.append(s[i % len(s)])
總結:要求不讓借用list和join方法如何處理:直接將list換成字串型別的,將list.append換成加號即可不用join。本題涉及到的知識點就是Java和python中的字串為不可變物件,要對字串進行操作,必須重新定義一個新字串或者list。
用切片方式最快,無冗餘操作,效率最高。
列表(Python) 和 StringBuilder(Java) 都是可變物件,每輪遍歷拼接字元時,只是向列表尾部新增一個新的字元元素。最終拼接轉化為字串時,系統 僅申請一次記憶體 。
在 Python 和 Java 中,字串是 “不可變物件” 。因此,每輪遍歷拼接字元時,都需要新建一個字串;因此,系統 需申請 NN 次記憶體 ,資料量較大時效率低下。
C++中字串是可變型別,實現方式是將左字串逆序,右字串逆序,然後整個逆序,以下分別是呼叫庫函式和自己實現reverse操作:
class Solution {
public:
string reverseLeftWords(string s, int n) {
reverse(s.begin(), s.begin() + n);
reverse(s.begin() + n, s.end());
reverse(s.begin(), s.end());
return s;
}
};
class Solution {
public:
string reverseLeftWords(string s, int n) {
reverseString(s, 0, n - 1);
reverseString(s, n, s.size() - 1);
reverseString(s, 0, s.size() - 1);
return s;
}
private:
void reverseString(string& s, int i, int j) {
while(i < j) swap(s[i++], s[j--]);
}
};
59 I. 滑動視窗的最大值
簡單題目一定會有所限制,像本題第一反應如下,用list切片作為視窗,可以得到n-k+1個視窗,每個視窗中尋找max,需要O(k)的時間複雜度。總體時間複雜度為O(n-k+1)k:
class Solution:
def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
def max(lst): # 求list的max
max_num = lst[0]
for i in range(len(lst)):
max_num = lst[i] if lst[i] > max_num else max_num
return max_num
# list,遍歷,切片,max
res = []
if len(nums) == 0:return res
for i in range(len(nums)-k+1):
res.append(max(nums[i:i+k]))
return res
因此重點就是如何降低時間複雜度!借鑑30題 包含min函式的棧,用o(1)的方式得到min。一般視窗對應的資料結構為雙端佇列,本題用單端佇列即可。Python 透過zip(range(), range())可實現滑動視窗的左右邊界i, j 同時遍歷。
複雜度分析:圖解檢視此處
時間複雜度O(n):其中n為陣列 nums長度;線性遍歷nums佔用 O(n) ;每個元素最多僅入隊和出隊一次,因此單調佇列 deque 佔用 O(2n)。
空間複雜度O(k): 雙端佇列 deque 中最多同時儲存 k個元素(即視窗大小)。
class Solution:
def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
deque = collections.deque()
res, n = [], len(nums)
for i, j in zip(range(1 - k, n + 1 - k), range(n)):
# 刪除 deque 中對應的 nums[i-1]
if i > 0 and deque[0] == nums[i - 1]:
deque.popleft()
# 保持 deque 遞減
while deque and deque[-1] < nums[j]:
deque.pop()
deque.append(nums[j])
# 記錄視窗最大值
if i >= 0:
res.append(deque[0])
return res
此處用到了python中的collections庫,學習點這裡
59 II. 佇列的最大值
**題目要求:**請定義一個佇列並實現函式 max_value 得到佇列裡的最大值,要求函式max_value、push_back 和 pop_front 的均攤時間複雜度都是O(1)。若佇列為空,pop_front 和 max_value 需要返回 -1。
67. 把字串轉換成整數
**題目要求:**將字串中的數字轉換成整數輸出。
丟棄無用的開頭空格字元
當第一個非空格字元為正負號時,尋找緊挨著的連續數字,得到最後的帶符號整數;若首個非空格字元是數字時,那就得到連續的整數(其後包含一些其他字元的干擾項,就無視啦)
任何不能轉換成整數的條件下,返回0
如果超出整數範圍,那麼輸出+/- int_max
20 59 67 三道題理解收尾;其餘題目回顧複習
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69945560/viewspace-2760920/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 資料結構演算法題資料結構演算法
- 資料結構與演算法-資料結構(棧)資料結構演算法
- 【PHP資料結構】PHP資料結構及演算法總結PHP資料結構演算法
- 用Python解決資料結構與演算法問題(三):線性資料結構之棧Python資料結構演算法
- 資料結構:初識(資料結構、演算法與演算法分析)資料結構演算法
- python演算法與資料結構-什麼是資料結構Python演算法資料結構
- 資料結構&演算法資料結構演算法
- 演算法與資料結構1800題演算法資料結構
- LeetCode之資料結構——陣列LeetCode資料結構陣列
- 結構化資料、半結構化資料和非結構化資料
- 資料結構與演算法:圖形結構資料結構演算法
- 【資料結構篇】認識資料結構資料結構
- python演算法與資料結構-演算法和資料結構介紹(31)Python演算法資料結構
- 資料結構與演算法 | Leetcode 206:Reverse Linked List資料結構演算法LeetCode
- 資料結構與演算法 | Leetcode 141:Linked List Cycle資料結構演算法LeetCode
- 資料結構與演算法 | LeetCode 224. Basic Calculator資料結構演算法LeetCode
- 演算法與資料結構1800題 圖演算法資料結構
- 資料結構與演算法1800題 圖資料結構演算法
- 演算法與資料結構1800題 圖演算法資料結構
- 資料結構與演算法入門題資料結構演算法
- 資料結構與演算法之線性結構資料結構演算法
- 資料結構小白系列之資料結構概述資料結構
- 資料結構——Floyd演算法資料結構演算法
- 基本資料結構演算法資料結構演算法
- 前端演算法 - 資料結構前端演算法資料結構
- 資料結構及演算法資料結構演算法
- 資料結構與演算法資料結構演算法
- 資料結構和演算法資料結構演算法
- 資料結構與演算法-圖解版資料結構演算法圖解
- 【整理】資料結構——題目資料結構
- 資料結構簡單題資料結構
- 資料結構基礎和演算法題系列總結資料結構演算法
- 資料結構資料結構
- 資料結構:棧詳解資料結構
- 資料結構與演算法——常用高階資料結構及其Java實現資料結構演算法Java
- 資料結構與演算法 | Leetcode 21:Merge Two Sorted Lists資料結構演算法LeetCode
- 前端進階 | 資料結構與演算法之 LeetCode 篇前端資料結構演算法LeetCode
- 資料結構和演算法總結--棧資料結構演算法