Python小白 Leetcode刷題歷程 No.1-No.5 兩數(shù)之和、兩數(shù)相加、無重復字符的最長子串、尋找兩個有序數(shù)組的中位數(shù)、最長回文子串 (有題干 有代碼 有思路心得)

Python小白 Leetcode刷題歷程 No.1-No.5 兩數(shù)之和、兩數(shù)相加、無重復字符的最長子串、尋找兩個有序數(shù)組的中位數(shù)、最長回文子串

寫在前面:

作為一個計算機院的大學生,總覺得僅僅在學校粗略的學習計算機專業(yè)課是不夠的,尤其是假期大量的空檔期,作為一個小白,實習也莫得路子,又不想白白耗費時間。于是選擇了Leetcode這個平臺來刷題庫。編程我只學過基礎的C語言,現(xiàn)在在自學Python,所以用Python3.8刷題庫?,F(xiàn)在我Python掌握的還不是很熟練,算法什么的也還沒學,就先不考慮算法上的優(yōu)化了,單純以解題為目的,復雜程度什么的以后有時間再優(yōu)化。計劃順序五個題寫一篇日志,希望其他初學編程的人起到一些幫助,寫算是對自己學習歷程的一個見證了吧。

有一起刷LeetCode的可以關注我一下,我會一直發(fā)LeetCode題庫Python3解法的,也可以一起探討。

覺得有用的話可以點贊關注下哦,謝謝大家!
········································································································································································
題解框架:

    1.題目,難度
    2.題干,題目描述
    3.題解代碼(Python3(不是Python,是Python3))
    4.或許有用的知識點(不一定有)
    5.解題思路
    6.優(yōu)解代碼及分析(當我發(fā)現(xiàn)有比我寫的好很多的代碼和思路我就會寫在這里)

········································································································································································

No.1.兩數(shù)之和

難度:簡單
題目描述:


在這里插入圖片描述

題解代碼(Python3.8)

class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        for i in range(len(nums)-1):  
            for j in range(i+1,len(nums)):
                if nums[i] + nums[j] == target:
                    return [i,j]

解題思路:
暴力方法,運用兩個for循環(huán)遍歷數(shù)組完成表達式s[i] + s[j]的遍歷,因為加法不用區(qū)分s[i]和s[j]的順序,因此i屬于range(len(nums)-1),j屬于range(i+1,len(nums)),判斷是否有s[i] + s[j] = sum即可,因為只需要返回[i,j],所以只需要在兩個for循環(huán)內(nèi)return [i,j]即可跳出兩層for循環(huán)。

No.2.兩數(shù)相加

難度:中等
題目描述:


在這里插入圖片描述

題解代碼(Python3.8)

class Solution:
    def addTwoNumbers(self, l1: ListNode, l2: ListNode) -> ListNode:
        def add(a,b,c):
            if not( a or b ):
                return ListNode(1) if c else None
            a = a if a else ListNode(0)
            b = b if b else ListNode(0)
            val = a.val + b.val + c
            c= val/10 if val>=10 else 0
            a.val = val%10
            a.next = add(a.next,b.next,int(c))
            return a
        return add(l1,l2,0)

或許有用的知識點:
ListNode的一些介紹:
變量值=List Node.val
指針指向下一節(jié)點=List Node.next

在這里插入圖片描述

解題思路:
這個其實可以類比加法器,兩個List Node中每一位相加,得到一個結(jié)果和一個進位,結(jié)果存在L1中該位上,進位加在L1..next上。我們可以把這一過程定義為函數(shù)add,然后對add進行遞歸即可。

No.3.無重復字符的最長子串

難度:中等
題目描述:


在這里插入圖片描述

題解代碼(Python3.8)

class Solution:
    def lengthOfLongestSubstring(self, s: str) -> int:
        if not s:
            return 0
        left = 0
        lookup = set()                                #定義為set形式去重
        max_len = 0
        cur_len = 0
        for i in range(len(s)):
            cur_len += 1
            while s[i] in lookup:
                lookup.remove(s[left])
                left += 1
                cur_len -= 1
            if cur_len > max_len:
                max_len = cur_len
            lookup.add(s[i])
        return max_len

或許有用的知識點:
set(list)函數(shù)可以將list中重復元素過濾,set是無序的,set.add(key)可以在set中添加元素,set.remove(key)可以在set中刪除元素。
滑動窗口:其實就是一個隊列,比如例題中的 abcabcbb,進入這個隊列(窗口)為 abc 滿足題目要求,當再進入 a,隊列變成了 abca,這時候不滿足要求。所以,我們要移動這個隊列!我們只要把隊列的左邊的元素移出就行了,直到滿足題目要求!一直維持這樣的隊列,找出隊列出現(xiàn)最長的長度時候,求出解!

解題思路:
暴力方法,先單列出len(s)=0和len(s)=1的情況,再對其他情況進行循環(huán)求解。設置一個起始位置下標j=0,一重for循環(huán)遍歷i in range(1,len(s)),第二重for循環(huán)遍歷m in range(j,i)。再把k置零,第二重循環(huán)中,每次s[m] != s[i] 則k++,若k == i-j 則說明s[j]到s[i]無重復字符,將將其長度與max1比較即可。若出現(xiàn)s[m] == s[i]則令j=m+1,k置零。

NO.4.尋找兩個有序數(shù)組的中位數(shù)

難度:困難
題目描述:


在這里插入圖片描述

題解代碼(Python3.8)

class Solution:
    def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
        n=len(nums1)+len(nums2)       
        nums1=nums1+nums2
        nums1.sort()
        if n%2 != 0:
            num = nums1[(n-1)//2]
        else:
            num = (nums1[n//2]+nums1[(n//2)-1])/2
        return num

或許有用的知識點:
看到了復雜度為O(log(m+n))和有序數(shù)列,不難想到使用「二分查找」來解決。
這是408考試的原題,含金量還是比較高的。

解題思路:
按題目要求,復雜度應為 O(log(m + n)),應采用二分算法,當時我還沒學過,就直接冒泡排序了。
這道題應該是難在算法上,我沒考慮算法,冒泡排序完按奇偶項取中位數(shù)即可。

優(yōu)解代碼及分析:
優(yōu)解代碼(Python3.8)

class Solution:
    def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
        m, n = len(nums1), len(nums2)

        # 這里為了保證nums1一定是長度較小的數(shù)組
        if m > n:
            nums1, nums2, m, n = nums2, nums1, n, m

        # 題目給定數(shù)組不會同時為空,也就是m^2+n^2≠0,由于m≤n,故只要n≠0即可
        if not n:
            raise ValueError("數(shù)組長度不同時為零")

        i_min, i_max = 0, m
       
        # left集合元素數(shù)量,如果m+n是奇數(shù),left比right多一個數(shù)據(jù)
        count_of_left = (m + n + 1) // 2  

        while i_min <= i_max:
            i = (i_min + i_max) // 2                # left有i個nums1的元素
            j = count_of_left - i                   # left有j個nums2的元素
            if i > 0 and nums1[i - 1] > nums2[j]:
                i_max = i - 1                       # i太大,要減少
            elif i < m and nums1[i] < nums2[j - 1]:
                i_min = i + 1                       # i太小,要增加
            else:
                if i == 0:
                    max_of_left = nums2[j - 1]
                elif j == 0:
                    max_of_left = nums1[i - 1]
                else:
                    max_of_left = max(nums1[i - 1], nums2[j - 1])

                if (m + n) % 2:
                    return float(max_of_left)               # 結(jié)果是浮點數(shù)

                if i == m:
                    min_of_right = nums2[j]
                elif j == n:
                    min_of_right = nums1[i]
                else:
                    min_of_right = min(nums1[i], nums2[j])

                return (max_of_left + min_of_right) / 2.0   # 結(jié)果為浮點數(shù)

分析:


在這里插入圖片描述

在這里插入圖片描述

No.5.最長回文子串

難度:中等
題目描述:


在這里插入圖片描述

題解代碼(Python3.8)

class Solution:
    def longestPalindrome(self, s: str) -> str:
        l=len(s)
        if l==0:
            sm=""
        if l==1 or s[::]==s[::-1]:
            sm=s
        else:
            dm=1
            sm=s[0]
            l=len(s)
            for i in range(l-1):
                for j in range(i+1,l):
                    d=j-i+1
                    if d>dm:
                        if (i+j)%2 == 0:
                            if s[i: (i+j)//2 ] == s[j: (i+j)//2 :-1]:
                                sm=s[i:j+1]
                                dm=d
                        else:
                            if s[i: ((i+j)//2)+1 ] == s[j: (i+j)//2 :-1]:
                                sm=s[i:j+1] 
                                dm=d     
        return sm

或許有用的知識點:
切片操作:通常一個切片操作要提供三個參數(shù) [start_index: stop_index: step] :
start_index是切片的起始位置
stop_index是切片的結(jié)束位置(不包括)
step可以不提供,默認值是1,步長值不能為0,不然會報錯ValueError。

在這里插入圖片描述

這道題會用到動態(tài)規(guī)劃的知識。
在這里插入圖片描述

dp = [[False for _ in range(size)] for _ in range(size)]可以理解為Python創(chuàng)建二維數(shù)組的一種寫法。

解題思路
對于沒有算法基礎的我來說,Python的切片功能極其好用,他為我減少了一階的復雜度。我們先用兩個for循環(huán)遍歷所有的s[i]到s[j]區(qū)間,對其正向切片和反向切片,若值相同,即為一個回文子串,與最長回文子串比較長度即可。

優(yōu)解代碼及分析:
優(yōu)解代碼(Python3.8)

class Solution:
    def longestPalindrome(self, s: str) -> str:
        size = len(s)
        if size < 2:
            return s

        dp = [[False for _ in range(size)] for _ in range(size)]

        max_len = 1
        start = 0

        for i in range(size):
            dp[i][i] = True

        for j in range(1, size):
            for i in range(0, j):
                if s[i] == s[j]:
                    if j - i < 3:
                        dp[i][j] = True
                    else:
                        dp[i][j] = dp[i + 1][j - 1]
                else:
                    dp[i][j] = False

                if dp[i][j]:
                    cur_len = j - i + 1
                    if cur_len > max_len:
                        max_len = cur_len
                        start = i
        return s[start:start + max_len]

分析:
1、思考狀態(tài)
狀態(tài)先嘗試“題目問什么,就把什么設置為狀態(tài)”。然后考慮“狀態(tài)如何轉(zhuǎn)移”,如果“狀態(tài)轉(zhuǎn)移方程”不容易得到,嘗試修改定義,目的仍然是為了方便得到“狀態(tài)轉(zhuǎn)移方程”。
2、思考狀態(tài)轉(zhuǎn)移方程(核心、難點)
狀態(tài)轉(zhuǎn)移方程是非常重要的,是動態(tài)規(guī)劃的核心,也是難點,起到承上啟下的作用。
技巧是分類討論。對狀態(tài)空間進行分類,思考最優(yōu)子結(jié)構(gòu)到底是什么。即大問題的最優(yōu)解如何由小問題的最優(yōu)解得到。
歸納“狀態(tài)轉(zhuǎn)移方程”是一個很靈活的事情,得具體問題具體分析,除了掌握經(jīng)典的動態(tài)規(guī)劃問題以外,還需要多做題。如果是針對面試,請自行把握難度,我個人覺得掌握常見問題的動態(tài)規(guī)劃解法,明白動態(tài)規(guī)劃的本質(zhì)就是打表格,從一個小規(guī)模問題出發(fā),逐步得到大問題的解,并記錄過程。動態(tài)規(guī)劃依然是“空間換時間”思想的體現(xiàn)。
3、思考初始化
初始化是非常重要的,一步錯,步步錯,初始化狀態(tài)一定要設置對,才可能得到正確的結(jié)果。
角度 1:直接從狀態(tài)的語義出發(fā);
角度 2:如果狀態(tài)的語義不好思考,就考慮“狀態(tài)轉(zhuǎn)移方程”的邊界需要什么樣初始化的條件;
角度 3:從“狀態(tài)轉(zhuǎn)移方程”方程的下標看是否需要多設置一行、一列表示“哨兵”,這樣可以避免一些邊界的討論,使得代碼變得比較短。
4、思考輸出
有些時候是最后一個狀態(tài),有些時候可能會綜合所有計算過的狀態(tài)。
5、思考狀態(tài)壓縮
“狀態(tài)壓縮”會使得代碼難于理解,初學的時候可以不一步到位。先把代碼寫正確,然后再思考狀態(tài)壓縮。
狀態(tài)壓縮在有一種情況下是很有必要的,那就是狀態(tài)空間非常龐大的時候(處理海量數(shù)據(jù)),此時空間不夠用,就必須狀態(tài)壓縮。
這道題比較煩人的是判斷回文子串。因此需要一種能夠快速判斷原字符串的所有子串是否是回文子串的方法,于是想到了“動態(tài)規(guī)劃”。
“動態(tài)規(guī)劃”最關鍵的步驟是想清楚“狀態(tài)如何轉(zhuǎn)移”,事實上,“回文”是天然具有“狀態(tài)轉(zhuǎn)移”性質(zhì)的:
一個回文去掉兩頭以后,剩下的部分依然是回文(這里暫不討論邊界)。
依然從回文串的定義展開討論:
1、如果一個字符串的頭尾兩個字符都不相等,那么這個字符串一定不是回文串;
2、如果一個字符串的頭尾兩個字符相等,才有必要繼續(xù)判斷下去。
(1)如果里面的子串是回文,整體就是回文串;
(2)如果里面的子串不是回文串,整體就不是回文串。
即在頭尾字符相等的情況下,里面子串的回文性質(zhì)據(jù)定了整個子串的回文性質(zhì),這就是狀態(tài)轉(zhuǎn)移。因此可以把“狀態(tài)”定義為原字符串的一個子串是否為回文子串。
第 1 步:定義狀態(tài)
dp[i][j] 表示子串 s[i, j] 是否為回文子串。
第 2 步:思考狀態(tài)轉(zhuǎn)移方程
這一步在做分類討論(根據(jù)頭尾字符是否相等),根據(jù)上面的分析得到:
dp[i][j] = (s[i] == s[j]) and dp[i + 1][j - 1]
分析這個狀態(tài)轉(zhuǎn)移方程:
(1)“動態(tài)規(guī)劃”事實上是在填一張二維表格,i 和 j 的關系是 i <= j ,因此,只需要填這張表的上半部分;
(2)看到 dp[i + 1][j - 1] 就得考慮邊界情況。
邊界條件是:表達式 [i + 1, j - 1] 不構(gòu)成區(qū)間,即長度嚴格小于 2,即 j - 1 - (i + 1) + 1 < 2 ,整理得 j - i < 3。
這個結(jié)論很顯然:當子串 s[i, j] 的長度等于 2 或者等于 3 的時候,我其實只需要判斷一下頭尾兩個字符是否相等就可以直接下結(jié)論了。
如果子串 s[i + 1, j - 1] 只有 1 個字符,即去掉兩頭,剩下中間部分只有 11 個字符,當然是回文;
如果子串 s[i + 1, j - 1] 為空串,那么子串 s[i, j] 一定是回文子串。
因此,在 s[i] == s[j] 成立和 j - i < 3 的前提下,直接可以下結(jié)論,dp[i][j] = true,否則才執(zhí)行狀態(tài)轉(zhuǎn)移。
(這一段看暈的朋友,直接看代碼吧。我寫暈了,車轱轆話來回說。)
第 3 步:考慮初始化
初始化的時候,單個字符一定是回文串,因此把對角線先初始化為 1,即 dp[i][i] = 1 。
事實上,初始化的部分都可以省去。因為只有一個字符的時候一定是回文,dp[i][i] 根本不會被其它狀態(tài)值所參考。
第 4 步:考慮輸出
只要一得到 dp[i][j] = true,就記錄子串的長度和起始位置,沒有必要截取,因為截取字符串也要消耗性能,記錄此時的回文子串的“起始位置”和“回文長度”即可。
第 5 步:考慮狀態(tài)是否可以壓縮
因為在填表的過程中,只參考了左下方的數(shù)值。事實上可以壓縮,但會增加一些判斷語句,增加代碼編寫和理解的難度,丟失可讀性。在這里不做狀態(tài)壓縮。
下面是編碼的時候要注意的事項:總是先得到小子串的回文判定,然后大子串才能參考小子串的判斷結(jié)果。
思路是:
1、在子串右邊界 j 逐漸擴大的過程中,枚舉左邊界可能出現(xiàn)的位置;
2、左邊界枚舉的時候可以從小到大,也可以從大到小。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內(nèi)容