两数相加c++_LeetCode 热题 HOT 100(01,两数相加)
LeetCode 熱題 HOT 100(01,兩數相加)
不夠優秀,發量尚多,千錘百煉,方可成佛。
算法的重要性不言而喻,無論你是研究者,還是最近比較火熱的IT 打工人,都理應需要一定的算法能力,這也是面試的必備環節,算法功底的展示往往能讓面試官眼前一亮,這也是在大多數競爭者中脫穎而出的重要影響因素。
然而往往大多數人比較注重自身的實操能力,著重于對功能的實現,卻忽視了對算法能力的提高。有的時候采用不同的算法來解決同一個問題,運行效率相差還是挺大的,畢竟我們最終還是需要站在客戶的角度思考問題嘛,能給用戶帶來更加極致的體驗當然再好不過了。
萬法皆空,因果不空。Taoye之前也不怎么情愿花費太多的時間放在算法上,算法功底也是相當的薄弱。這不,進入到了一個新的學習階段,面對導師的各種“嚴刑拷打”和與身邊人的對比,才開始意識到自己“菜”的事實。
講到這,流下了沒技術的眼淚!!!
這次的題目是LeeTCode 熱題 HOT 100的第二題,難度屬于中等,涉及到了鏈表的知識。
自打接觸Python以來,都沒有從中用到過鏈表,也無法通過指針來操作鏈表。曾經也只是在備考408,學習C的過程中刷過一些鏈表相關算法,一開始拿到這道題的時候,不知道Python如何下手,不知道怎么操作鏈表,菜是原罪(ノへ ̄、)
查找資料之后才發現,我們操作鏈表的時候其實就是將其封裝成一個實例對象,而實例對象中存儲了節點的相關信息(其實和C中差不多)。但與C或C++中不同的是,Python沒有指針,也就沒有什么指向操作,所以從對鏈表的整體結構理解上,Python和C、C++還是有點區別的。意思類似下圖:
可以理解成一種俄羅斯套娃的形式。
注意:只是理解上有所區別,其實表達的意思還是一樣的。
下面,我們就來看看這道題吧。
題目:兩數相加
給出兩個 非空 的鏈表用來表示兩個非負的整數。其中,它們各自的位數是按照 逆序 的方式存儲的,并且它們的每個節點只能存儲 一位 數字。
如果,我們將這兩個數相加起來,則會返回一個新的鏈表來表示它們的和。
您可以假設除了數字 0 之外,這兩個數都不會以 0 開頭。
示例
輸入:(2 -> 4 -> 3) + (5 -> 6 -> 4)
輸出:7 -> 0 -> 8
原因:342 + 465 = 807
思路
前面也提到了,在Python中,鏈表可以理解成一種套娃的形式,通過輸入兩個鏈表,輸出一個新的鏈表,新鏈表中的每個節點的產生過程基本一致,所以我們初步判斷通過遞歸的方法來解決該問題。
當然了,遞歸能解決的問題,非遞歸同樣可以,只是將一次遞歸函數的調用替換成一次循環。所以下面將分別介紹兩種方法。
方法一:遞歸
題中給出的例子里的兩個鏈表的長度是一樣的,但題意所表明的意思是任意長度的兩個鏈表。所以,為了提高算法的“泛化性能”,在輸入兩個長度不同鏈表的前提下,我們需要對其進行一個統一,就是對長度短的鏈表我們需要做一個補0處理。補0并不影響求和操作,但又方便了我們對截止遞歸的判斷。
這種處理方式在數學上還是挺常見的,比如說在求最值的時候,我們常常會用到均值不等式,但題目中給出的數值維數可能并不會明顯地滿足均值不能式,所以常常會用到一個擴維的操作,很巧妙的將題目向均值不等式靠近,從而簡化了對題目的解答,比如下面這道題的巧妙解答:
最后,左右同開5次方,再同乘27即可證畢。
回到算法題吧。
題目既然要我們求鏈表所對應的單位數之和,那么肯定會涉及進位,我們不妨將進位表達為carry。至此,我們不難想到,每次的遞歸需要的參數有三個,分別是第一個鏈表中的值、第二個鏈表中的值、上一次進位的值(非0即1)。
前面也有說到,在Python中,鏈表其實就是一個對象,而該對象中又有倆個屬性,分別是val和next,而next本身有代表一個鏈表對象。所以我們遞歸方法傳入的第一、第二個參數可以用當前鏈表節點所獲取到。
判斷跳出遞歸的條件:前面,我們已經對鏈表進行處理過了,也就是說無論你輸入的兩個鏈表長度是否一致,我們都對其進行了統一長度的處理。所以說當我們所傳入的三個參數同時為False的時候即可跳出遞歸。舉例說明: 1、輸入兩個空鏈表和一個carry為1的參數,說明已經產生了進位,此時我們需要對其進行處理,而非跳出遞歸。2、輸入的一個鏈表節點value值為5,而另一個節點為None,carry為0,此時我們依然需要進行求和處理,即使另一個節點為None,但我們已經對其進行了補0處理。3、至于其他可能性,各位看官可自行思考。
求和處理:
求出兩個鏈表當前value值和carry的和,并對10進行一個divmod操作。說明: Python中divmod會返回一個元組,第一個值為進位,第二個值為取余,比如divmod(13, 10) = (1, 3)。注意:在求和的過程中,有可能當前節點為None,這個時候則需要將節點的value值作為0進行求和。
實例化一個鏈表,也就是我們的目標返回鏈表,其value值為上述的取余。注意:這里一定理解清楚題意,鏈表中的值是實際值的逆序。
result的next屬性所對應的值本身又是一個鏈表,而對其進行賦值的時候,我們需要調用遞歸方法。傳遞的三個參數:經過上述過程,鏈表中的當前值已經處理完畢,這個時候需要處理鏈表的下一個值,也就是將下一個節點作為參數進行一次遞歸。但這里需要考慮的是,假如我們的當前節點為空,也就是說當前鏈表已經遍歷結束,則需要傳遞None給遞歸函數。
相關代碼:
class?Solution:????def?recursion(self,?list_node1,?list_node2,?carry):
????????#?判斷跳出遞歸的條件
????????if?(list_node1?==?None)?and?(list_node2?==?None)?and?(carry?==?0):?return?None
????????#?兩個節點值和進位值的求和操作,若傳入的節點為None,則需要將value賦值為0進行求和,也就是補0
????????sum_number?=?(list_node1.val?if?list_node1?else?0)?+?(list_node2.val?if?list_node2?else?0)?+?carry
????????#?產生新的carry進位值,作為下一次遞歸的判斷。
????????carry,?value?=?divmod(sum_number,?10)
????????#?實例新的節點(result,為返回的目標節點的),其val屬性為value
????????result?=?ListNode(value)????
????????#?進行下一次遞歸
????????result.next?=?self.recursion(list_node1.next?if?list_node1?else?None,?list_node2.next?if?list_node2?else?None,?carry)
????????return?result
????#?主函數
????def?addTwoNumbers(self,?l1:?ListNode,?l2:?ListNode)?->?ListNode:
????????return?self.recursion(l1,?l2,?0)????#?執行入口,調用遞歸函數,傳入初試的兩個節點,默認進位為0
方法二:非遞歸
非遞歸的思想和遞歸差不多,只是除了返回result鏈表之外,還需要額外創建一個新的鏈表用于循環。
class?Solution:????def?addTwoNumbers(self,?l1:?ListNode,?l2:?ListNode)?->?ListNode:
????????#?初始node,result為返回結果鏈表,temp_node用作循環遍歷,鏈接產生新的節點
????????result?=?temp_node?=?ListNode(0)????
????????carry?=?0???#?初始進位為0
????????while?l1?or?l2?or?carry:????#?跳出循環的條件和遞歸一樣,三者同時為False的時候跳出循環
????????????#?兩個節點值和進位值的求和操作,若傳入的節點為None,則需要將value賦值為0進行求和,也就是補0
????????????sum_number?=?(l1.val?if?l1?else?0)?+?(l2.val?if?l2?else?0)?+?carry
????????????#?l1和l2當前節點操作完畢,指向下一個節點,準備進入下一次循環
????????????l1?=?l1.next?if?l1?else?None;?l2?=?l2.next?if?l2?else?None
????????????#?產生新的carry進位值,作為下一次遞歸的判斷。
????????????carry,?value?=?divmod(sum_number,?10)
????????????#?重新賦值temp_node節點,不斷為result鏈表進行延伸補充節點
????????????temp_node.next?=?ListNode(value);?temp_node?=?temp_node.next
????????return?result.next
總的來說,非遞歸表達的思想和遞歸差不多。
不過這里有一點值得說明下: result節點只在初始和最后返回時用到過,而在算法的核心while循環中并沒有用到,為什么還能返回正常的result鏈表呢?
這主要是因為,我們初始化創建的result已經和temp_node都賦值給新的鏈表了,其中val為0,next屬性值為None,其內存位置是固定的。而初試的result和temp_node是相等的,所指向的內存地址是一樣的,所以我們只需要的延伸temp_node節點即可,而result地址不變,而result指向的下一個節點的地址則會隨著temp_node的變化而變化。
注意: result的內存地址從始至終都是不變的,初試的時候和temp_node的地址是一樣,只不過之后變化的是temp_node,而result不變。我們可以在通過如下操作簡單測試下這個過程:
也不知道自己表述清楚了沒有?
也不知道各位看官理解了沒有?
如果沒看懂的話,強烈建議重新看一遍,細細琢磨下,這個地方還是挺重要的。
最后
在逛討論區的時候,看到了另外一種解法,感覺這算法寫的挺優雅的,在這里分享下。
"""??? Author:學廢了的Kai
"""
class?Solution:
????def?addTwoNumbers(self,?l1:?ListNode,?l2:?ListNode)?->?ListNode:
????????head?=?curr?=?ListNode()
????????carry?=?val?=?0
????????while?carry?or?l1?or?l2:
????????????val?=?carry
????????????if?l1:?l1,?val?=?l1.next,?l1.val?+?val
????????????if?l2:?l2,?val?=?l2.next,?l2.val?+?val
????????????carry,?val?=?divmod(val,?10)
????????????curr.next?=?curr?=?ListNode(val)
????????return?head.next
這種解法和上面非遞歸表達的是同種思想,只不過在求sum的時候這個是疊加的形式,而非一次性求和。上面算法還有一點值得學習的是,在最后curr.next = curr = ListNode(val)這行代碼中,其表達的意思是如下:
curr.next?=ListNode(val)curr?=?curr.next
Taoye也不知道為什么是這種賦值順序,按道理講應該是先賦值curr = ListNode(val),再賦值curr.next = curr?但經過測試之后,上述兩行代碼塊才是正確的,暫時不知道為什么,之后有機會再回過頭看看吧。
我是Taoye,研究生在讀。愛專研,愛分享,熱衷于各種技術,學習之余喜歡下象棋、聽音樂、聊動漫,希望借此一畝三分地記錄自己的成長過程以及生活點滴,也希望能結實更多志同道合的圈內朋友,更多內容歡迎來訪微信公主號:玩世不恭的Coder。
推薦閱讀:
LeetCode 熱題 HOT 100(00,兩數之和)
Taoye滲透到一家黑平臺總部,背后的真相細思極恐
《大話數據庫》-SQL語句執行時,底層究竟做了什么小動作?
那些年,我們玩過的Git,真香
基于Ubuntu+Python+Tensorflow+Jupyter notebook搭建深度學習環境
網絡爬蟲之頁面花式解析
手握手帶你了解Docker容器技術
一文詳解Hexo+Github小白建站
打開ElasticSearch、kibana、logstash的正確方式
總結
以上是生活随笔為你收集整理的两数相加c++_LeetCode 热题 HOT 100(01,两数相加)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Vue之组件之间的数据传递
- 下一篇: C语言贪吃蛇如何让蛇一直前进,c++贪吃