一起學Rust-實戰leetcode(四)

  • 2019 年 10 月 6 日
  • 筆記

給出兩個 非空 的鏈表用來表示兩個非負的整數。其中,它們各自的位數是按照 逆序 的方式存儲的,並且它們的每個節點只能存儲一位數字。

如果,我們將這兩個數相加起來,則會返回一個新的鏈表來表示它們的和。

您可以假設除了數字 0 之外,這兩個數都不會以 0 開頭。

示例:

輸入:(2 -> 4 -> 3) + (5 -> 6 -> 4)  輸出:7 -> 0 -> 8  原因:342 + 465 = 807

來源:力扣(LeetCode) 鏈接:https://leetcode-cn.com/problems/add-two-numbers

這是來源於leetcode的一道題 「兩數相加」,我們使用Rust來實現。

本次實戰目的:

了解Rust中 Box<T> 指針在鏈表中的使用,了解結構體方法,學習 Option<T> 的一些常用方法以及所有權的複習。

簡單分析:

按照往常一樣,首先對題目進行簡單分析,題目給出了兩個前提(兩個非空鏈表,除了單個的0,數字開頭不會是0),這樣可以暫不考慮這道題目的異常情況。

從題目可看出,是將相對應的位置數值相加,同時保持正常的進位,每一個節點都是個位數。每一次的進位如果存在就加到下一個節點的和中。

計算鏈表:每次循環中將計算並創建的節點,賦值到上一個節點的next。這裡會涉及兩個引用,一個是整個鏈表的所有者變數,地址指向的是鏈表起始的位置,另外一個是對於鏈表尾部的next的一個可變引用,使用可以將新的節點追加到鏈表的尾部。

結束:當輸入的鏈表全部遍歷結束,並且結尾沒有進位的情況下,鏈表計算完成。

注意:如果出現不等長的輸入,空缺的節點需要使用0代替。

本題並不難,例如在PHP中就非常容易實現,比較麻煩的是在Rust中實現會有意想不到的問題,例如所有權的問題等。

下面是題干給出的結構與方法。

#[derive(PartialEq, Eq, Clone, Debug)]  pub struct ListNode {      pub val: i32,      pub next: Option<Box<ListNode>>,  }    impl ListNode {      #[inline]      fn new(val: i32) -> Self {          ListNode {              next: None,              val,          }      }  }

下面編寫一下程式碼:

fn add_two_numbers(l1: Option<Box<ListNode>>, l2: Option<Box<ListNode>>) -> Option<Box<ListNode>> {      // 參數改為可變值      let (mut l1, mut l2) = (l1, l2);      // 初始化一個為0的起始節點。      let mut ret = Some(Box::new(ListNode::new(0)));      let mut tmp = &mut ret;      // 進位值,1或0      let mut up = 0;        while l1.is_some() || l2.is_some() || up > 0 {          // 獲取節點的值,如果沒有則為0          let v1 = if let Some(l) = &l1 {              l.val          } else {              0          };            let v2 = if let Some(l) = &l2 {              l.val          } else {              0          };            // 計算節點的和需要額外加進位的值          let sum = v1 + v2 + up;          // up就是sum/10的整數商          up = sum / 10;          let val = Box::new(ListNode::new(sum % 10));            //創建內部可變並獲取內部值的next ,賦值為當前計算的節點。          tmp.as_mut().unwrap().next = Some(val);          //將可變的next節點指針賦值給尾部變數。          tmp = &mut tmp.as_mut().unwrap().next;            // 將節點向下遍歷          l1 = if l1.is_some() {              l1.unwrap().next          } else {              None          };            l2 = if l2.is_some() {              l2.unwrap().next          } else {              None          }      }        //ret第一個節點是與結果無關的,所以不需要,只需要返回next的值      ret.unwrap().next  }

解釋:

Box::new(x:T) ,將x值存儲到堆記憶體中,而Box的變數指向此堆記憶體,對於遞歸的結構體來說用處非常大,由於結構體需要計算大小,所以通過Box<T>來固定結構體成員的大小,通過指針完成數據的連接。

is_some() , is_none() 是Option<T>枚舉的方法,返回布爾值,是用於判斷枚舉值是Some(T)還是None值。

unwrap() 是用於獲取Some(T)內的值,如果是None則會panic退出,需要注意的是,unwrap會獲取調用者的所有權,也就是調用後,調用者變數會失效。與之類似的方法 unwrap_or(def:T) 提供None時的默認值,不會發生panic, unwrap_or_else(f: F) 同樣是獲取Some內值,並會接受一個處理None情況的函數,函數需要返回與Some內值相同類型的值。這三種方法都會獲取調用者的所有權。

as_mut() 將可變的Option<T>枚舉引用轉換為內部值的可變引用,

Converts from `&mut Option<T>` to `Option<&mut T>`.

as_mut() 需要一個可變的Option引用來調用,並不會轉移所有權,並返回一個新的內部可變的Option。

impl 用於定義方法或實現trait,方法中的參數如果不存在self,則需要使用 :: 來調用,如果第一個參數使用 self , &self , &mut self 則需要使用點運算符來調用。