這篇文章介紹Leetcode1到10題的解決思路和相關代碼。html
問題描述:給定一個整數數組,返回兩個數字的索引,使它們加起來等於一個特定的目標。node
例子:python
Given nums = [2, 7, 11, 15], target = 9, Because nums[0] + nums[1] = 2 + 7 = 9, return [0, 1].
常規方法:使用雙重循環,第一重從左往右固定索引,計算須要查找的結果,第二層循環從固定索引出發依次向右查找第一層計算的結果。時間複雜度\(O(n^2)\), 空間複雜度\(O(1)\).c++
def twoSum(nums: List[int], target: int) -> List[int]: nums_len = len(nums) for ind1 in range(nums_len): value = target - nums[ind1] for ind2 in range(ind1+1, nums_len): if value == nums[ind2]: return [ind1, ind2]
行程哈希表(第一次行程遍歷nums生成字典,第二次遍歷nums找結果): 首先,構建dict<num, index>的字典(哈希表),存入對應的值和索引,遍歷map, 計算target-num, 利用哈希表常數時間的尋址,只要在字典中找到的索引不與當前索引同樣,即找到結果。時間複雜度\(O(n)\), 空間複雜度\(O(n)\).git
def twoSum(nums: List[int], target: int) -> List[int]: nums_dict = {value: index for index, value in enumerate(nums)} for i, num in enumerate(nums): find_value = target - num if find_value in nums_dict and nums_dict[find_value] != i: return [i, nums_dict[find_value]]
單行程哈希表(遍歷一次nums):與上面一種的區別在於不先將全部的值和索引都放入map中,在遍歷中依次放入,少了一次遍歷的時間,速度更快佔用內存更小。時間複雜度\(O(n)\), 空間複雜度\(O(n)\)。正則表達式
def twoSum(nums: List[int], target: int) -> List[int]: nums_dict = {} for i, num in enumerate(nums): find_value = target - num if find_value in nums_dict: return [i, nums_dict[find_value]] nums_dict[num] = i
運行時間與內存佔用比較:算法
方法 | 時間 | 內存 |
---|---|---|
常規 | 3244ms | 13.9Mb |
雙行程 | 56ms | 15Mb |
單行程 | 40ms | 14.1Mb |
問題描述:給定兩個非空鏈表,表示兩個非負整數。這些數字以相反的順序存儲,它們的每一個節點都包含一個數字。將這兩個數字相加並以鏈表的形式返回。能夠假設這兩個數字不包含任何前導零,除了數字0自己。express
例子:segmentfault
Input: (2 -> 4 -> 3) + (5 -> 6 -> 4) Output: 7 -> 0 -> 8 Explanation: 342 + 465 = 807.
方法:創建一個新的ListNode,依次對l2和l3的每一個元素進行相加,這裏重要的是對進位的判斷,還要考慮兩個ListNode的長度不同,以及長度不同,最後相加的時候是否有進位,有進位則還要考慮沒有結束的ListNode與進位的相加,相加完是否還有進位。數組
class ListNode: def __init__(self, x): self.val = x self.next = None class Solution: def addTwoNumbers(self, l1, l2): """ :type l1: ListNode :type l2: ListNode :rtype: ListNode """ head = l3 = ListNode(0) #至關於雙指針指同一個listNode,l3構建head的next, 會在後面發生變化 carry_flag = 0 while( l1 and l2 ): #循環依次按位相加,直到至少一個listnode爲空 l3.next = ListNode(0) l3 = l3.next temp = l1.val + l2.val + carry_flag if temp < 10: l3.val = temp carry_flag = 0 else: l3.val = temp - 10 carry_flag = 1 l1 = l1.next l2 = l2.next if l1: #若是l1非空,將l1剩餘的listnode賦給l3.next,至關於l3的高位數 l3.next = l1 carry_flag, l3 = self.control_carry(carry_flag, l3) #讓l3.next與carry_flag進行運算,直到進位符爲0或者l3爲空退出循環, 返回l3和進位符。 if l2: l3.next = l2 carry_flag, l3 = self.control_carry(carry_flag, l3) if not l1 and not l2:# l1和l2同時爲空,而且有進位,讓l3.next爲1 if carry_flag == 1: l3.next = ListNode(1) if carry_flag == 1: # 例如,l2剩餘的部分賦給l3.next, 但carry_flag爲1,而且在control_carry中一爲1,比較高位都是9,最終進位爲1,須要添加最高位爲1. l3.next = ListNode(1) return head.next def control_carry(self, carry_flag, l3): while(carry_flag): if l3.next: l3 = l3.next else: break temp1 = l3.val + carry_flag if temp1 < 10: l3.val = temp1 carry_flag = 0 else: l3.val = temp1 - 10 carry_flag = 1 return carry_flag, l3
Times = 76ms, memory_usage = 13.2Mb
問題描述: 給定一個字符串,在不重複字符的狀況下找出最長子字符串的長度。
例子:
Input: "abcabcbb" Output: 3 Explanation: The answer is "abc", with the length of 3. Input: "bbbbb" Output: 1 Explanation: The answer is "b", with the length of 1. Input: "pwwkew" Output: 3 Explanation: The answer is "wke", with the length of 3. Note that the answer must be a substring, "pwke" is a subsequence and not a substring.
個人方法:循環遍歷字符串的每一個字符,對每一個字符相加,新加入的字符要查找前面的字符串是否存在新加入的字符,不存在繼續下一個字符,存在比較當前最大長度。Times = 76ms, memory_usage = 13.2Mb
def lengthOfLongestSubstring(self, s: str) -> int: max_length = 0 longest_sub_str = '' for i, elem in enumerate(s): longest_sub_str += elem if len(longest_sub_str) > 1: if elem in longest_sub_str[:-1]: if max_length < len(longest_sub_str) - 1: max_length = len(longest_sub_str) - 1 same_char_index = longest_sub_str[:-1].find(longest_sub_str[-1]) longest_sub_str = longest_sub_str[same_char_index+1:] if len(longest_sub_str) > max_length: return len(longest_sub_str) else: return max_length
更好的方法:使用哈希表
int lengthOfLongestSubstring(string s) { //創建ascii碼的字典,字典的鍵表明字符,字典的值表明字符所在的索引,初始爲0 int m[256] = {0}, res = 0, left = 0; for (int i = 0; i < s.size(); ++i) { //if中第一個條件判斷當前s[i]是否出現過,第二個條件是當出現重複字符時,更換left的位置爲重複的字符的前一個索引 if (m[s[i]] == 0 || m[s[i]] < left) { res = max(res, i - left + 1); } else { left = m[s[i]]; } m[s[i]] = i + 1; } return res; }
下面這個與上面相似,用到了unordered_map構建字典:
int lengthOfLongestSubstring(string s) { int res = 0, left = 0, i = 0, n = s.size(); unordered_map<char, int> m; for (int i = 0; i < n; ++i) { left = max(left, m[s[i]]); m[s[i]] = i + 1; res = max(res, i - left + 1); } return res; }
問題描述: 有兩個大小分別爲m和n的排序數組nums1和nums2。求兩個排序數組的中值。總的運行時複雜度應該是O(log(m+n))。您能夠假設nums1和nums2不能同時爲空。
例子:
nums1 = [1, 3] nums2 = [2] The median is 2.0 nums1 = [1, 2] nums2 = [3, 4] The median is (2 + 3)/2 = 2.5
方法:創建一個新的列表,長度是輸入兩個列表長度之和,兩個列表依次倒序比較,大的放入新列表的相應位置,當其中至少一個列表爲空時,退出循環,還得將那個列表爲空最後pop出的數與非空列表進行比較,找到小於那個書即終止,將剩餘的數賦值給新列表。Times = 64ms, memory_usage = 13.4Mb
class Solution: def getMiddleValue(self, nums, nums_len): if nums_len % 2 == 0: mid_idx = nums_len // 2 return (nums[mid_idx] + nums[mid_idx - 1])/2.0 else: return nums[(nums_len - 1) // 2]/1.0 def dealTheRest(self, res, nums, num1, num2, lens): while num1 > num2 and nums: res[lens - 1] = num1 num1 = nums.pop() lens -= 1 if nums: res[lens - 1] = num2 res[lens - 2] = num1 res[0:lens - 2] = nums else: if num1 > num2: res[lens - 1] = num1 res[lens - 2] = num2 else: res[lens - 1] = num2 res[lens - 2] = num1 return res def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float: if not nums1: return self.getMiddleValue(nums2, len(nums2)) if not nums2: return self.getMiddleValue(nums1, len(nums1)) lens = len(nums1) + len(nums2) res = [0]*lens num1 = nums1.pop() num2 = nums2.pop() if not nums1 and not nums2: return (num1 + num2)/2.0 while nums1 and nums2: if num1 > num2: res[lens - 1] = num1 num1 = nums1.pop() else: res[lens - 1] = num2 num2 = nums2.pop() lens -= 1 if nums1: res = self.dealTheRest(res, nums1, num1, num2, lens) if nums2: res = self.dealTheRest(res, nums2, num2, num1, lens) return self.getMiddleValue(res, len(res))
還能夠進一步優化,不須要將新的list所有填滿,判斷列表長度之和是否爲偶數,是則新列表長度爲2,不然長度爲1,只存儲中間的值,存儲完即返回,後面的值無需比較,這種方法在大規模數據下更快佔用內存更小。在提交後想到的,有興趣的能夠本身動手試試。
另外在Leetcode上發現更簡單的方法,使用系統自帶的sorted函數,簡潔很快。Times = 56ms
def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float: num = sorted(nums1 + nums2) leng = len(num) median = 0 if ( (leng%2) == 1): median = num[(leng-1)//2] else: median = (num[leng//2] + num[leng//2 - 1]) / 2 return median
給定一個字符串s,找出s中最長的迴文子串。能夠假設s的最大長度爲1000。
例子:
Input: "babad" Output: "bab" Note: "aba" is also a valid answer. Input: "cbbd" Output: "bb"
方法1:暴力搜索,兩個循環肯定左右索引\(i,j\),判斷中間\([i, j)\)表明的字符串是不是迴文,與最大回文比較,長就替換,時間複雜度過高\(O(n^3)\). Times = 8904ms
class Solution: def longestPalindrome(self, s: str) -> str: longestPStr = '' for i in range(len(s), 0, -1): for j in range(len(s)): if s[j:i] == s[j:i][::-1]: if len(s[j:i]) > len(longestPStr): longestPStr = s[j:i] if len(longestPStr) > i: return longestPStr return longestPStr
方法2:以當前索引爲中間,往外擴張判斷字符串是否爲迴文數,不符合迴文數退出內層循環,取最大字符串。參考自答案。這個方法佔用了O(n)的空間,可使用\([begin, end)\)降至\(O(1)\). Times = 932ms.
class Solution: def longestPalindrome(self, s: str) -> str: res = '' for i in range(len(s)): odd = self.palindromeAt(s, i, i) even = self.palindromeAt(s, i, i+1) res = max(res, odd, even, key=len) return res def palindromeAt(self, s, l, r): while l >= 0 and r < len(s) and s[l] == s[r]: l -= 1 r += 1 return s[l+1:r]
方法3: 這種方法比較巧妙,當想s增長一個字符時,最長的迴文數可能加1,加2或者不動,因此處理的方式就是創建一個最長迴文數長度maxLen,跟蹤這個長度,當前索引爲i,判斷\(s[i-maxLen-1:i+1]\)(對應'abba')和\(s[i-maxLen:i+1]\)(對應'aba')是否爲迴文數,對最大maxlen加上對應的長度。參考自答案. Times = 88ms
class Solution: def longestPalindrome(self, s): if len(s)==0: return 0 maxLen=1 start=0 for i in xrange(len(s)): if i-maxLen >=1 and s[i-maxLen-1:i+1]==s[i-maxLen-1:i+1][::-1]: start=i-maxLen-1 maxLen+=2 continue if i-maxLen >=0 and s[i-maxLen:i+1]==s[i-maxLen:i+1][::-1]: start=i-maxLen maxLen+=1 return s[start:start+maxLen]
方法4:很是巧妙,時間複雜度達到\(O(n)\),這個方法的名字是==Manacher‘s Algorithm==,也叫馬拉車方法,方法創建了一個數組,數組第i個元素表示以 i 爲中心的最長迴文的半徑,利用迴文數的對稱性,在一個大的迴文數後面的索引能夠利用對稱性映射前面而大大簡化計算。算法細節描述請參考博客,講得很是好,牆裂推薦,如下代碼來自博客。
class Solution { public: string longestPalindrome(string s) { if (s.size() < 2) return s; string t = "$#"; for (int i = 0; i < s.size(); ++i){ t += s[i]; t += '#'; } vector<int> p(t.size(), 0); int mx = 0, id = 0, resLen = 0, resCenter = 0; for (int i = 1; i < t.size(); ++i){ p[i] = mx > i ? min( p[2 * id - i], mx - i ) : 1; while (t[ i + p[i] ] == t[ i - p[i] ]) ++p[i]; if (mx < i + p[i]){ mx = i + p[i]; id = i; } if (resLen < p[i]){ resLen = p[i]; resCenter = i; } } return s.substr((resCenter - resLen) / 2, resLen - 1); } };
描述:字符串「PAYPALISHIRING」在給定行數上以之字形書寫,以下所示:
P A H N A P L S I I G Y I R
而後逐行讀取:「PAHNAPLSIIGYIR」編寫代碼,該代碼將接受一個字符串,並在給定行數的狀況下進行轉換:
string convert(string s, int numRows);
例子:
Input: s = "PAYPALISHIRING", numRows = 3 Output: "PAHNAPLSIIGYIR" Input: s = "PAYPALISHIRING", numRows = 4 Output: "PINALSIGYAHRPI" Explanation: P I N A L S I G Y A H R P I
方法:首先審好題,觀看結果解釋,是以旋轉翻轉Z的形狀進行排列的,根據這一性質,就能夠找到字符按週期排列規則,生成對應行數的vector
class Solution: def convert(self, s: str, numRows: int) -> str: if numRows == 1: return s dict = {i:'' for i in range(numRows)} law_len = 2 * numRows - 2 for i, e in enumerate(s): temp = i % law_len if temp > numRows - 1: temp = (numRows - 1) - (temp - (numRows - 1)) dict[temp] += e res = '' for i in range(numRows): res += dict[i] return res
問題描述:給定一個32位帶符號整數,對其進行反數運算. 超過int的最大值或者最小值,返回相應最大值或最小值
例子:
Input: 123 Output: 321 Input: -123 Output: -321 Input: 120 Output: 21
方法1:使用循環對輸入整數按10取餘,取餘後取整除以10,而後餘數循環乘10,直到取整除覺得0中止循環,獲得最後反轉的數。Times = 72ms, memory_usage = 13.3Mb
注意:在python3中,-123%10是不等於3的,這點是個坑,因此python3實現中加入了負號的判斷。
class Solution: def reverse(self, x: int) -> int: a = 0 max = 2**31 if x==0 or x > max-1 or x < -max: return 0 if x < 0: x = -x flag_neg = True else: flag_neg = False while(x != 0): a = a*10 + x%10 x //= 10 if a < max-1 and a > -max: return a if not flag_neg else -a else: return 0
方法2:更加粗暴,直接判斷整數是否爲負數,標識符用字符來表示,是負數則'-',不是則空字符,直接將整數轉成字符串反轉加上前面的標識符符號,後面判斷越界便可, 更快. Times = 56ms, memory_usage = 13.3Mb
class Solution: def reverse(self, x: int) -> int: if x==0: return 0 max = 2**31 if x > max-1 or x < -max: return 0 a = 0 if x < 0: x = -x flag_neg = '-' else: flag_neg = '' a = int(flag_neg + str(x)[::-1]) if a < max-1 and a > -max: return a else: return 0
問題描述: 實現將字符串轉換爲整數的atoi。
函數首先丟棄儘量多的空白字符,直到找到第一個非空白字符。而後,從這個字符開始,取一個可選的初始正負號,後跟儘量多的數字,並將它們解釋爲一個數值。
字符串能夠包含構成整數的字符以後的附加字符,這些字符將被忽略,而且對函數的行爲沒有影響。
若是不能執行有效的轉換,則返回一個零值。
Input: "42" Output: 42 Input: " -42" Output: -42 Explanation: The first non-whitespace character is '-', which is the minus sign. Then take as many numerical digits as possible, which gets 42. Input: "4193 with words" Output: 4193 Explanation: Conversion stops at digit '3' as the next character is not a numerical digit. Input: "words and 987" Output: 0 Explanation: The first non-whitespace character is 'w', which is not a numerical digit or a +/- sign. Therefore no valid conversion could be performed. Input: "-91283472332" Output: -2147483648 Explanation: The number "-91283472332" is out of the range of a 32-bit signed integer. Thefore INT_MIN (−231) is returned.
方法:首先判斷字符的長度,長度爲0,返回0,長度爲1,判斷是否爲數字,不爲數字,返回0,還要判斷整個str是否都是空格,是則返回0;而後找到第一個不是空格的索引,在這個索引下依次遞加查詢數字,在查詢以前要判斷第一個字母是否爲負號或者正號,創建了一個符號標誌,而後依次生成數字,終止條件是發現字符不是數字,另外,這裏爲了防止溢出,在c++中使用long類型進行數字的計算,在循環的過程當中會判斷是否超過了整形的最大值,超過返回最大值或者最小值。
class Solution: def myAtoi(self, str: str) -> int: if len(str) == 0 or not str.strip(): return 0; if len(str) == 1 and not str.isdigit(): return 0 ls = list(str.strip()) if not ls[0].isdigit() and ls[0] != '-' and ls[0] != '+': return 0 elif ls[0] == '-': flag = '-' begin = 1 elif ls[0] == '+': flag = '+' begin = 1 else: begin = 0 flag = '+' res = '' for i in range(begin, len(ls)): if ls[i].isdigit(): res += ls[i] else: break if not res: return 0 if flag == '-': return max(-2**31, -int(res)) else: return min(2**31 - 1, int(res))
問題描述:給定一個整數,判斷是不是迴文數
Input: 121 Output: true Input: -121 Output: false Explanation: From left to right, it reads -121. From right to left, it becomes 121-. Therefore it is not a palindrome. Input: 10 Output: false Explanation: Reads 01 from right to left. Therefore it is not a palindrome.
方法: 數值判斷,兩種狀況,第一種數的位數是偶數,如1881,能夠除10取餘再乘以10的方法對低位數進行逆序,最後判斷18=18,第二種數的位數是奇數,如18281,一樣按照上面方法,最後判斷18=182/10,循環條件是逆序的數大於剩餘的數即中止。
class Solution { public: bool isPalindrome(int x) { if (x < 0 || (x % 10 == 0 && x != 0)) return false; int res = 0; while(x > res){ res = res * 10 + x % 10; x /= 10; } return x == res || x == res/10; } };
問題描述:給定一個輸入字符串和一個模式,實現正則表達式匹配並支持'.'和'*'.
'.'匹配任何單個字符。'*'匹配前一個元素的零個或多個. 匹配應該覆蓋整個輸入字符串(而不是部分)。
例子:
Input: s = "aa" p = "a" Output: false Explanation: "a" does not match the entire string "aa". Input: s = "aa" p = "a*" Output: true Explanation: '*' means zero or more of the precedeng element, 'a'. Therefore, by repeating 'a' once, it becomes "aa". Input: s = "ab" p = ".*" Output: true Explanation: ".*" means "zero or more (*) of any character (.)". Input: s = "aab" p = "c*a*b" Output: true Explanation: c can be repeated 0 times, a can be repeated 1 time. Therefore it matches "aab". Input: s = "mississippi" p = "mis*is*p*." Output: false
方法:基於動態規劃,若是\(s[0..i)\)匹配\(P[0..j]\), 定義狀態\(P[i][j]\)爲真, 不然爲假。狀態方程爲:
a. P[i][j] = P[i - 1][j - 1], if p[j - 1] != ‘*’ && (s[i - 1] == p[j - 1] || p[j - 1] == ‘.’); b. P[i][j] = P[i][j - 2], if p[j - 1] == ‘*’ and the pattern repeats for 0 times; c. P[i][j] = P[i - 1][j] && (s[i - 1] == p[j - 2] || p[j - 2] == ‘.’), if p[j - 1] == ‘*’ and the pattern repeats for at least 1 times.
將三種狀況合在一塊兒,獲得下面的解決方案。
class Solution: def isMatch(self, s: str, p: str) -> bool: m, n = len(s), len(p) dp = [[False for _ in range(n+1)] for _ in range(m+1)] dp[0][0] = True for i in range(0, m+1): for j in range(1, n+1): if p[j - 1] == '*': dp[i][j] = dp[i][j - 2] or (i > 0 and (s[i - 1] == p[j - 2] or p[j -2] == '.') and dp[i - 1][j]) else: dp[i][j] = i > 0 and dp[i - 1][j - 1] and (s[i - 1] == p[j - 1] or p[j - 1] == '.') return dp[m][n]