leetcode演算法資料結構題解---資料結構

ckxllf發表於2021-03-03

  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/,如需轉載,請註明出處,否則將追究法律責任。

相關文章