1. 程式人生 > >LeetCode[001]:兩數之和

LeetCode[001]:兩數之和

一、寫在前面

  • 初衷 想刷leetcode也不是一兩天的事情了,之前也有很多人給過建議,於是乎,就給安排上了,一來演算法的確是很重要的一塊,需要好好學,為了提升自己,再者,這也可以作為微信推文的一塊,給大家分享,當然,最重要的是這個過程中會結交到很多志趣相投,有想法的朋友。
  • 我們 目前我已經集結了7位研究生學長學姐(各個大學),一個月內不會加人,大家可以加我微信(zs820553471),進學習交流群討論,一個月後如果效果不錯,很多人感興趣,我再另建一個專門的演算法學習交流群。
  • 安排 目前打算一個星期刷2-3個題,推文分享進度可能會慢一點,但一個星期至少也會有兩篇,期待大家參與。

二、今日題目

給定一個整數陣列和一個目標值,找出陣列中和為目標值的兩個數。 你可以假設每個輸入只對應一種答案,且同樣的元素不能被重複利用。

示例:

給定 nums = [2, 7, 11, 15], target = 9

因為 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]

三、 分析

這個題目看似很簡單,在一個列表(nums)裡面找到兩數,滿足和為事先指定好的數(target),其實有陷阱,第一:很多人一看到題目自然想到兩層for迴圈解決問題,but這種人人都想到的問題你若是也這麼做,如果你就這種程度,面試失敗也不算虧;第二:這題的返回值到底是什麼?你看清了嗎?它的返回值是一個列表,列表裡是int

型的資料,這個資料並不是我們找到的滿足算式的數,而是這個數在列表裡對應的下標,你中招了嗎?第三:雙for迴圈極易出錯的地方,不能出現自己加自己的情況。

四、解題

  • 方法一: 又蠢又笨的雙重for迴圈
class Solution(object):
    def twoSum(self, nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: List[int]
        """
        l = len(nums)
        for
a in range(l): for b in range(l): if a != b: if nums[a]+nums[b] == target: return [a,b] nums = [2, 7, 11, 15] target = 9 test_o = Solution() result = test_o.twoSum(nums,target) print(result)
  • 提交結果: 方法一執行結果

  • 方法二:比雙for聰明一點

我們作圖分析易發現,其實直接雙重for迴圈進行運算是有一半的運算是沒有意義的,比如a+bb+a其實是一模一樣的,如下圖分析: 小聰明 如何把重複的去掉減少計算機執行量來提升執行速度呢? 我想的比較簡單,利用標識位,建立一個和給定整數列表一樣長的陣列,初始值為全為0,第一層for迴圈執行一次,該位對應的標識值由0變成1,在第二層for運算時先判斷對應的資料位上的識別符號是否都為0,為0 則進行運算比較,否則說明互相之間已經運算過,就continue,程式碼如下:

class Solution(object):
    def twoSum(self, nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: List[int]
        """
        flags = [0, 0, 0, 0]
        l = len(nums)
        for a in range(l):
            flags[a]=1
            for b in range(l):
                if flags[b]==0:
                    if nums[a]+nums[b] == target:
                        return [a,b]
        # 另一種簡單方法             
        # l = len(nums)
        # for a in range(l):
        #    for b in range(a+1,l):
        #       if nums[a] + nums[b] == target:
        #            return [a, b]
  • 執行結果: 方法二執行結果
  • 方法三 一層迴圈(偷瞄了小詹學長的方法)
class Solution(object):
    def twoSum(self, nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: List[int]
        """
        flags = [0 for i in range(len(nums))]
        l = len(nums)
        for a in range(l):
            one_unm = nums[a]
            other_one = target - one_unm
            if other_one in nums:
	            b = nums.index(other_one)
	            if a != b:
		            if a>b:
		                return [b,a]
		            return [a,b]
  • 執行結果: 方法三執行結果
  • 方法四:一層for迴圈優化(小詹讀者【村前河水流】提供)
class Solution(object):
    def twoSum(self, nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: List[int]
        """
        flags = [0 for i in range(len(nums))]
        l = len(nums)
        dict_nums = {nums[i]:i for i in range(l)}
        for a in range(l):
            one_unm = nums[a]
            other_one = target - one_unm
            if other_one in dict_nums and a!= dict_nums[other_one]:
	            return [a,dict_nums[other_one]]
  • 執行結果: 方法四執行結果

五、疑惑

方法一和二耗時長,雙重for迴圈,時間複雜度高,耗時長沒話說,可方法三和方法四的差別,怎麼也這麼大呢? 我仔細研究了一下,方法三和四最大的差別就是:前者是列表遍歷查詢,後者是字典遍歷查詢,那麼關鍵點來了,到底是不是這個問題呢?

  • 列表與字典遍歷效能測試程式碼:
import time

list_01 = [str(i) for i in range(1000)]
start_time = time.time()
if 'a' in list_01 :
	a = 0
end_time = time.time()
print("列表遍歷耗時:"+str(end_time-start_time))

dict_01 = {str(i):i for i in range(1000)}
start_time2 = time.time()
if 'a' in dict_01 :
	a = 0
end_time2 = time.time()
print("字典遍歷耗時:"+str(end_time2-start_time2))
  • 測試結果:
資料量 列表耗時 字典耗時
1000 0 0
10000 0 0
50000 0.996ms 0
100000 1.995ms 0
1000000 20.944ms 0
10000000 208.443ms 0

注:0表示的是所花時間極少,我所測的資料可以精確到微秒,極少的意思可表示為少於1微秒。 通過上表很容易看出,列表遍歷與字典遍歷的天大差別了,不過值得一提的還有,字典生成上要比列表生成慢很多,大家可以自測一下。

六、結語

本來今天打算更新邊學敲邊記爬蟲系列的,但是,突然對演算法來了興趣,於是乎,便有了這個,希望能有更多的同學加入進來。