筆者按照目錄刷題,對於每一道題,力爭使用效率最高(時間複雜度最低)的算法,並所有經過C++代碼實現AC。(文中計算的複雜度都是最壞狀況複雜度)
由於考慮到大部分讀者已經在Leetcode瀏覽過題目了,因此每道題都按照 解題思路 -> 實現代碼 -> 問題描述 的順序進行講解。
(筆者目前已刷 40 題,已更新解法 10 題,最近一段時間會頻繁更新)能夠點擊下方連接,直達gitbook:
https://codernie.gitbooks.io/leetcode-solutions/content/
也歡迎你們關注個人微信公衆號(大雄的學習人生),有問題能夠隨時後臺回覆我,多多探討。node
嵌套兩層循環:第一層:i 從 0 到 n - 2;第二層:j 從 i + 1 到 n - 1;判斷 nums[i] + nums[j] == target ,若是成立則是正確答案git
從 0 到 n - 1 依次遍歷,利用map存放每個數值的下標,在map中尋找是否有使(nums[i] + x == target)成立的x的存在,若是存在則返回i和它的下標(即myMap[ target - nums[i] ])。算法
複雜度分析:由於只遍歷了一次數組,map每次的查詢的時間複雜度爲O(logN)因此總體複雜度爲O(N*logN)、若是這裏使用hash_map能夠將查詢複雜度下降到O(1),從而使得總體複雜度爲O(N),可是hash_map不是標準的C++庫,因此這裏沒有使用。express
// 1. Two Sum vector<int> twoSum(vector<int>& nums, int target) { map<int, int> myMap; vector<int> result; for (int i = 0; i < nums.size(); i++) { if (myMap.find(target - nums[i]) == myMap.end()) { myMap[nums[i]] = i; } else { result = {myMap[target - nums[i]], i}; break; } } return result; }
Given an array of integers, return indices of the two numbers such that they add up to a specific target.數組
You may assume that each input would have _exactly _one solution, and you may not use the_same_element twice.微信
Example:ide
Given nums = [2, 7, 11, 15], target = 9, Because nums[0] + nums[1] = 2 + 7 = 9, return [0, 1].
從左到右遍歷鏈表,依次相加,每個位置生成一個新的結點便可。學習
時間複雜度:O( max( len(l1), len(l2) ) )this
考慮邊界條件:spa
1.進位的的處理:carry表示進位,當最後一位還有進位時,即便 l1 和 l2 均爲NULL的狀況下,還須要生成一個新的結點,因此while的條件中加入了 carry != 0 判斷項。
2.返回頭結點:當頭結點爲NULL的時候記錄頭結點,而且讓p等於頭結點;後續狀況讓 p->next 等於新的結點,並讓 p 指向 p->next。
// 2. Add Two Numbers ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) { ListNode *head = NULL, *p; int carry = 0, sum; while (l1 != NULL || l2 != NULL || carry != 0) { sum = 0; if (l1 != NULL) { sum += l1->val; l1 = l1->next; } if (l2 != NULL) { sum += l2->val; l2 = l2->next; } sum += carry; carry = sum / 10; sum %= 10; ListNode *newNode = new ListNode(sum); if (head == NULL) { head = newNode; p = newNode; } else { p->next = newNode; p = p->next; } } return head; }
You are given two non-empty linked lists representing two non-negative integers. The digits are stored inreverse orderand each of their nodes contain a single digit. Add the two numbers and return it as a linked list.
You may assume the two numbers do not contain any leading zero, except the number 0 itself.
Example
Input: (2 -> 4 -> 3) + (5 -> 6 -> 4) Output: 7 -> 0 -> 8 Explanation: 342 + 465 = 807.
從每一個點遍歷、依次遍歷這個點後的每個點、經過遍歷該點以前的點判斷該點是否出現過。聽上去有點拗口,代碼在下方,這個暴力方法Leetcode也能夠AC,可是不推薦使用。
頭標記指向當前最長無重複字符串的頭部,尾標記指向其尾部。經過一個 map 來記錄出現過的字符最後出現的位置。
依次遍歷數組,若是當前字符已出現過,則讓頭標記指向其最後出現過的位置的後一個位置。而後每次經過頭、尾標記計算當前無重複字符串的長度,並與已知最大值做比較。這裏查詢map的複雜度爲 O(logN),遍歷的複雜度爲 O(N),所以總體複雜度爲 O(N*logN)。若是這裏使用hash_map能夠將查詢複雜度下降到O(1),從而使得總體複雜度爲O(N),可是hash_map不是標準的C++庫,因此這裏沒有使用。
// 3. Longest Substring Without Repeating Characters // 暴力解法 int lengthOfLongestSubstring_bruteForce(string s) { int res = 0, sum; for (int i = s.size() - 1; i >= 0; i--) { sum = 1; for (int j = i - 1; j >= 0; j--) { bool flag = true; for (int k = i; k > j; k--) { if (s[j] == s[k]) { flag = false; break; } } if (flag) { sum++; } else { break; } } res = max(res, sum); } return res; } // 頭尾標記法 int lengthOfLongestSubstring(string s) { map<char, int> myMap; int res = 0; for (int i = 0, j = 0; j < s.size(); j++){ if (myMap.find(s[j]) != myMap.end()) { i = max(i, myMap[s[j]] + 1); } myMap[s[j]] = j; res = max(res, j - i + 1); } return res; }
Given a string, find the length of thelongest substringwithout repeating characters.
Examples:
Given"abcabcbb"
, the answer is"abc"
, which the length is 3.
Given"bbbbb"
, the answer is"b"
, with the length of 1.
Given"pwwkew"
, the answer is"wke"
, with the length of 3. Note that the answer must be asubstring,"pwke"
is asubsequenceand not a substring.
這道題咋一看像二分查找,可是仔細看題,發現有兩個有序數組,並且不是讓咱們找一個特定的數,而是要找兩個數組合並後的中位數,這樣一看就比較難了,也難怪歸類爲hard類別。這道題除了一下介紹的二分查找法,還有兩個數組分別進行二分查找的方法,不過代碼量相對更多(也多是由於筆者水平不夠致使代碼量過大),並且看了下面的二分查找法後,感嘆於該算法做者的腦洞,因此在這裏只介紹該種方法。
將兩個數組合並,而後進行快排,中間的數即中位數。因爲題目說了複雜度不能超過O(log(m + n)),因此這個方法固然回Time Limit Excess,因此咱們得探究一種更高效的解法。
首先分別把兩個數組分紅兩邊,大概爲下面這種形式:(A表示nums1, B表示nums2)
left_part | right_part A[0], A[1], ..., A[i-1] | A[i], A[i+1], ..., A[m-1] B[0], B[1], ..., B[j-1] | B[j], B[j+1], ..., B[n-1]
由於有序數列的性質,咱們知道只要咱們知足如下兩個條件,咱們就能夠找到中位數了:
條件一:len(A_left) + len(B_left) = len(A_right) + len(B_right)
條件二:max[A_left, B_left] <= min[A_right, B_right]
爲了使問題簡單化,咱們先只考慮 m + n 爲偶數的狀況下,只要知足上述兩個條件,咱們的中位數就等於左邊的最大值加上右邊的最小值除以二。
爲了知足條件一,咱們只要令** i + j == m + n - i - j (+ 1)**便可(這裏加一是爲了以後考慮 m + n 爲奇數的狀況)
而爲了知足條件二,根據有序數組的性質,咱們知道只須要知足 A[i - 1] <= B[j] 且 B[j - 1] <= A[i] 便可。
接下來開始咱們的算法探究:
假設咱們首先隨機選擇一個 i (這裏 0 <= i < m),因此咱們根據條件一,能夠求得 j = (m + n + 1) / 2 - i;
爲了知足條件二,咱們開始分別比較 A[i - 1] 與 B[j] 和 B[j - 1] 與 A[i]:
不難知道可能會有四種狀況:
那咱們如何縮小和擴大咱們的 i 呢,那就是採用二分查找的方式啦,首先將 i 置爲數組A的中間下標,若是須要增大,則把其設爲上半區的中間下標,反之則設爲下半區的中間下標,因此這種搜索方式的時間複雜度和二分查找的時間複雜度同樣,爲了使時間複雜度儘可能的小,咱們使A成爲長度更小的那個數組,若是初始A比B長,咱們則交換它們的位置。
考慮邊界條件:
1.若是不存在知足條件二的狀況會怎麼樣呢?也就是 i 走到了數組A的盡頭,依舊無法知足條件二,這個時候咱們不難知道若是 i 走到數組A的最左端,那麼它必定是在不斷地經歷狀況4,而這時 A[0] > B[j],那麼咱們不難知道這時left_part的最大值就是B[j - 1];反之咱們也能夠推出 i 到了A的最右端、j 到了B的最左端或者最右端的狀況。
2.m + n 爲奇數。這個時候咱們不難推出 left_part 的最大值就是中位數。
3.A或B爲空數組,由於當數組空的時候沒法對數組進行下標訪問,因此咱們在進行二分查找前就應該對該狀況進行特殊處理,處理方式也是很簡單的啦。
// 4. Median of Two Sorted Arrays double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) { // 使得 nums1 短於 nums2 int m = nums1.size(); int n = nums2.size(); if (m > n) { vector<int> temp = nums1; nums1 = nums2; nums2 = temp; m = m + n; n = m - n; m = m - n; } // 考慮數組長度爲0的邊界狀況 if (m == 0) { if (n == 0) { return 0; } else { if (n % 2 == 1) { return nums2[n / 2]; } else { return (double)(nums2[n / 2] + nums2[n / 2 - 1]) / 2; } } } int iMin = 0, iMax = m, sizeSum = (m + n + 1) / 2, i, j; while (iMin <= iMax) { i = (iMax + iMin) / 2; j = sizeSum - i; if (nums2[j - 1] > nums1[i] && i < iMax) { iMin = i + 1; } else if (nums1[i - 1] > nums2[j] && i > iMin) { iMax = i - 1; } else { int maxLeft, minRight; if (i == 0) { maxLeft = nums2[j - 1]; } else if (j == 0) { maxLeft = nums1[i - 1]; } else { maxLeft = max(nums1[i - 1], nums2[j - 1]); } if ((m + n) % 2 == 1) { return maxLeft; } if (i == m) { minRight = nums2[j]; } else if (j == n) { minRight = nums1[i]; } else { minRight = min(nums1[i], nums2[j]); } return (double)(maxLeft + minRight) / 2; } } return 0; }
There are two sorted arrays nums1 and nums2 of size m and n respectively.
Find the median of the two sorted arrays. The overall run time complexity should be O(log (m+n)).
Example 1:
nums1 = [1, 3] nums2 = [2] The median is 2.0
Example 2:
nums1 = [1, 2] nums2 = [3, 4] The median is (2 + 3)/2 = 2.5
字符串有 n(n-1)/2 個子串,對每一個子串進行檢測,看其是不是迴文子串。所以複雜度爲 O(N^3)。
把字符串的每一個點分別當成迴文子串的中間點,開始往兩端擴展,不斷檢測,直到兩端不相等爲止。所以複雜度爲 O(N^2)。
用 dp[i][j] 表示下標爲 i 開頭 j 結尾的子串是不是迴文子串。
轉移方程:dp[i][j] = (dp[i + 1][j - 1] && s[i] == s[j]) 【含義:當且僅當子串首尾兩端相等,且去除首尾兩端依舊是迴文串時,該子串纔會是迴文串】
初始條件:對於每一個長度爲1的子串 dp[i][i] 都爲迴文串;對於每一個長度爲2的子串 dp[i][i + 1],當其首尾兩端相等時,其爲迴文串,不然不是。
// 5. Longest Palindromic Substring (動態規劃) string longestPalindrome(string s) { int length = s.size(); if (length == 0) return s; int resI = 0, resJ = 0; bool dp[length + 1][length + 1]; for (int i = 0; i <= length; i++) dp[i][i] = true; for (int i = 0; i < length; i++) { if (s[i] == s[i + 1]) { dp[i][i + 1] = true; if (resJ - resI < 1) { resI = i; resJ = i + 1; } } else { dp[i][i + 1] = false; } } for (int gap = 2; gap < length; gap++) { for (int i = 0; i + gap < length; i++) { int j = i + gap; if (s[i] == s[j] && dp[i + 1][j - 1]) { dp[i][j] = true; if (resJ - resI < j - i) { resI = i; resJ = j; } } else { dp[i][j] = false; } } } return s.substr(resI, resJ - resI + 1); }
Given a string s, find the longest palindromic substring in s. You may assume that the maximum length of s is 1000.
Example 1:
Input: "babad" Output: "bab" Note: "aba" is also a valid answer.
Example 2:
Input: "cbbd" Output: "bb"
這道題倒沒有特別的方法,就按照題目意思來模擬 Z 字形便可,用一個字符串數組來存放每一行的字符串,最後進行拼接便可。
考慮邊界條件:
當numRows等於1的時候,由於point沒法增長也沒法減少,因此沒辦法共用後面的代碼,考慮到numRows等於1的時候,答案就是原字符串,因此這裏直接返回s便可。
// 6. ZigZag Conversion string convert(string s, int numRows) { if (numRows == 1) return s; string res; bool shouldIncrease = true; string strArr[numRows]; int point = 0; for (char c : s) { strArr[point] += c; if (point == numRows - 1) { shouldIncrease = false; } else if (point == 0) { shouldIncrease = true; } if (shouldIncrease) { point++; } else { point--; } } for (string str: strArr) { res += str; } return res; }
The string"PAYPALISHIRING"
is written in a zigzag pattern on a given number of rows like this: (you may want to display this pattern in a fixed font for better legibility)
P A H N A P L S I I G Y I R
And then read line by line:"PAHNAPLSIIGYIR"
Write the code that will take a string and make this conversion given a number of rows:
string convert(string s, int numRows);
Example 1:
Input: s = "PAYPALISHIRING", numRows = 3 Output: "PAHNAPLSIIGYIR"
Example 2:
Input: s = "PAYPALISHIRING", numRows = 4 Output: "PINALSIGYAHRPI" Explanation: P I N A L S I G Y A H R P I
挨個遍歷,不斷把末位數賦給新的值便可。
考慮邊界條件:
當結果溢出時返回0,因此爲了避免讓中間值溢出,採用 long 類型來保存結果。
// 7. Reverse Integer int reverse(int x) { long result = 0, longX = abs((long)x); while (longX > 0) { result = result * 10 + longX % 10; longX /= 10; } result = (x > 0) ? result : -result; if (result > INT32_MAX || result < INT32_MIN) { return 0; } else { return (int)result; } }
Given a 32-bit signed integer, reverse digits of an integer.
Example 1:
Input: 123 Output: 321
Example 2:
Input: -123 Output: -321
Example 3:
Input: 120 Output: 21
Note:
Assume we are dealing with an environment which could only store integers within the 32-bit signed integer range: [−2^31, 2^31 − 1]. For the purpose of this problem, assume that your function returns 0 when the reversed integer overflows.
遍歷字符串而後進行分狀況討論:( isInit 表示數字是否已經開始,經過 isInit 的值判斷是否爲開頭,若是爲 true 表示不是開頭)
(1) 空格:若是爲開頭空格則continue,不然跳出循環
(2) 正負號:若是爲開頭正負號則設置isNeg的值,不然跳出循環
(3) 數字:將 isInit 置爲true,累加結果
(4) 其餘符號:跳出循環
考慮邊界條件:
當結果溢出時根據正負返回 INT32_MAX 或者 INT32_MIN,因此爲了避免讓中間值溢出,採用 long 類型來保存結果。
// 8. String to Integer (atoi) int myAtoi(string str) { long result = 0; bool isInit = false; bool isNeg = false; for (char c : str) { if (c == ' ') { if (isInit) { break; } else { continue; } } else if (c == '-' || c == '+') { if (!isInit) { isInit = true; } else { break; } isNeg = (c == '-'); } else if (c >= 48 && c <= 57) { isInit = true; result = result * 10 + (c - 48); if (result > INT32_MAX) { return isNeg ? INT32_MIN : INT32_MAX; } } else { break; } } return (int)(isNeg ? -result : result); }
Implementatoi
which converts a string to an integer.
The function first discards as many whitespace characters as necessary until the first non-whitespace character is found. Then, starting from this character, takes an optional initial plus or minus sign followed by as many numerical digits as possible, and interprets them as a numerical value.
The string can contain additional characters after those that form the integral number, which are ignored and have no effect on the behavior of this function.
If the first sequence of non-whitespace characters in str is not a valid integral number, or if no such sequence exists because either str is empty or it contains only whitespace characters, no conversion is performed.
If no valid conversion could be performed, a zero value is returned.
Note:
' '
is considered as whitespace character.Example 1:
Input: "42" Output: 42
Example 2:
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.
Example 3:
Input: "4193 with words" Output: 4193 Explanation: Conversion stops at digit '3' as the next character is not a numerical digit.
Example 4:
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.
Example 5:
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.
利用第七題的代碼,將數字反轉,判斷與原數字是否相等便可,這裏考慮到負數所有都不是迴文數字,因此直接返回false。
// 9. Palindrome Number int reverse(int x) { long result = 0, longX = abs((long)x); while (longX > 0) { result = result * 10 + longX % 10; longX /= 10; } result = (x > 0) ? result : -result; if (result > INT32_MAX || result < INT32_MIN) { return 0; } else { return (int)result; } } bool isPalindrome(int x) { if (x < 0) { return false; } else { return (x == reverse(x)); } }
Determine whether an integer is a palindrome. An integer is a palindrome when it reads the same backward as forward.
Example 1:
Input: 121 Output: true
Example 2:
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.
Example 3:
Input: 10 Output: false Explanation: Reads 01 from right to left. Therefore it is not a palindrome.
Follow up:
Coud you solve it without converting the integer to a string?
動態規劃思想解答這道題:
用 dp[i][j] 表示 s 的前 i 個字符組成的字符串和 p 的 前 j 個字符組成的字符串是否匹配。
轉移方程:
當 p[j - 1] == '*' 時:由於 * 能夠表示匹配零位或者多位,正則匹配這裏要作貪心考慮,分三種狀況,只要其中一種知足即爲true:
當 p[j - 1] != '*' 時,dp[i][j] 當且僅當 dp[i - 1][j - 1]爲true時,而且最後一位匹配成功時,才爲true。
初始狀態:
顯然,當 s 不爲空,p 爲空的時候dp[i][j] = false;
其次,當 s 爲空,p不爲空的時候,考慮到 * 能夠匹配零位,因此利用狀態轉移方程判斷其是否應該爲true。
// 10. Regular Expression Matching bool isMatch(string s, string p) { int n = s.size(); int m = p.size(); // initial bool dp[n + 1][m + 1]; for (int i = 0; i < n + 1; i++) { for (int j = 0; j < m + 1; j++) { dp[i][j] = false; } } // start dp[0][0] = true; for (int i = 1; i < n + 1; i++) { dp[i][0] = false; } for (int j = 1; j < m + 1; j++) { if (j % 2 == 0) { dp[0][j] = dp[0][j - 2] && p[j - 1] == '*'; } else { dp[0][j] = false; } } // trans bool compare; for (int i = 1; i < n + 1; i++) { for (int j = 1; j < m + 1; j++) { if (p[j - 1] != '*') { dp[i][j] = dp[i - 1][j - 1] && (s[i - 1] == p[j - 1] || p[j - 1] == '.'); } else { compare = (s[i - 1] == p[j - 2] || p[j - 2] == '.'); dp[i][j] = dp[i][j - 2] || (dp[i - 1][j - 2] && compare) || (dp[i - 1][j] && compare); } } } return dp[n][m]; }
Given an input string (s
) and a pattern (p
), implement regular expression matching with support for'.'
and'*'
.
'.' Matches any single character. '*' Matches zero or more of the preceding element.
The matching should cover theentireinput string (not partial).
Note:
s
could be empty and contains only lowercase lettersa-z
p
could be empty and contains only lowercase lettersa-z
, and characters like .
or *
.Example 1:
Input: s = "aa" p = "a" Output: false Explanation: "a" does not match the entire string "aa".
Example 2:
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".
Example 3:
Input: s = "ab" p = ".*" Output: true Explanation: ".*" means "zero or more (*) of any character (.)".
Example 4:
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".
Example 5:
Input: s = "mississippi" p = "mis*is*p*." Output: false