一直以來,前端開發的知識儲備在數據結構以及算法層面是有所暫缺的,可能歸根於咱們的前端開發的業務性質,可是我認爲任何的編程崗位都離不開數據結構以及算法。
所以,我做爲一名前端菜雞,打算作一個專欄,就是關於用JavaScript來解答算法題,會持續跟新,但願你們督促。同時,本人才疏學淺,文章內容可能有錯誤的地方,但願各位大神指出,謝謝。
no more bb, show me the code!前端
題目均來自樂扣(leetcode)node
給定一個整數數組 nums 和一個目標值 target,請你在該數組中找出和爲目標值的那 兩個 整數,並返回他們的數組下標。git
給定 nums = [2, 7, 11, 15], target = 9github
由於 nums[0] + nums[1] = 2 + 7 = 9
因此返回 [0, 1]正則表達式
這題不難,遍歷nums,用targer減去當前元素,獲得的元素若是在數組中,那就完事了。不過要注意統一元素不能用兩次算法
var twoSum = function(nums, target) { let idx1, idx2; nums.forEach((ele, index) => { let tempIdx = nums.indexOf(target - ele); if(tempIdx !== -1 && tempIdx !== index){ idx1 = index; idx2 = tempIdx; } }); return [idx1, idx2] };
給出兩個 非空 的鏈表用來表示兩個非負的整數。其中,它們各自的位數是按照 逆序 的方式存儲的,而且它們的每一個節點只能存儲 一位 數字。
若是,咱們將這兩個數相加起來,則會返回一個新的鏈表來表示它們的和。
您能夠假設除了數字 0 以外,這兩個數都不會以 0 開頭。編程
輸入:(2 -> 4 -> 3) + (5 -> 6 -> 4)
輸出:7 -> 0 -> 8
緣由:342 + 465 = 807數組
這題不難,不過稍微有點複雜,涉及到了鏈表,同時考擦了js大數的運算狀況。
先遍歷兩個鏈表得到對應的數字,而後相加,最後反推算出結果對應的鏈表便可。數據結構
function ListNode(val) { this.val = val; this.next = null; return { val: this.val, next: null } } function addBigNumber(a, b) { var res = '', temp = 0; a = a.split(''); b = b.split(''); while (a.length || b.length || temp) { temp += ~~a.pop() + ~~b.pop(); res = (temp % 10) + res; temp = temp > 9; } return res.replace(/^0+/, ''); } var addTwoNumbers = function(l1, l2) { let num1 = '', num2 = '', cur; cur = l1; while(cur){ num1 += cur.val.toString(); cur = cur.next; } cur = l2; while(cur){ num2 += cur.val.toString(); cur = cur.next; } num1 = num1.split('').reverse().join(''); num2 = num2.split('').reverse().join(''); let total; if(num1.length > 21 || num2.length > 21){ total = addBigNumber(num1, num2) } else{ total = Number(num1) + Number(num2) } total = total.toLocaleString().toString().split('').reverse().join('').replace(/,/g, '') console.log(num1, num2, total) let l3 = ListNode(total[0]); cur = l3; for(let i = 1; i < total.length; i++){ let node = ListNode(total[i]); cur.next = node; cur = node; } return l3; };
給定一個字符串,請你找出其中不含有重複字符的 最長子串 的長度。函數
輸入: "abcabcbb"
輸出: 3
解釋: 由於無重複字符的最長子串是 "abc",因此其長度爲 3。
輸入: "bbbbb"
輸出: 1
解釋: 由於無重複字符的最長子串是 "b",因此其長度爲 1。
輸入: "pwwkew"
輸出: 3
解釋: 由於無重複字符的最長子串是 "wke",因此其長度爲 3。
請注意,你的答案必須是 子串 的長度,"pwke" 是一個子序列,不是子串。
維護一個數組用於存放無重複子串,遍歷輸入的字符串,若當前字符不在無重複數組中,則添加,不然,無重複數組清空,並push當前字符。
同時要維護另一個最長無重複子串的數組。
var lengthOfLongestSubstring = function(s) { let max = 0, maxArr = [], oldArr= []; s.split('').forEach((ele, index) => { if(maxArr.indexOf(ele) === -1){ maxArr.push(ele) if(maxArr.length > max){ max = maxArr.length; } } else{ maxArr = [ele] let tempItem = oldArr.pop(); while(tempItem != ele){ maxArr.unshift(tempItem) tempItem = oldArr.pop(); } } oldArr = [...maxArr] }) return max; };
給定兩個大小爲 m 和 n 的有序數組 nums1 和 nums2。
請你找出這兩個有序數組的中位數,而且要求算法的時間複雜度爲 O(log(m + n))。
你能夠假設 nums1 和 nums2 不會同時爲空。
nums1 = [1, 3]
nums2 = [2]
則中位數是 2.0
nums1 = [1, 2]
nums2 = [3, 4]
則中位數是 (2 + 3)/2 = 2.5
將兩個數組合並而後排序,以後獲取中位數便可。問題在於限定時間複雜度爲 O(log(m + n))的狀況下,如何排序呢?
咱們這裏直接使用sort()方法,該方法底層原理是將多個排序集於一體,根據數組的長度不一樣選擇不一樣的排序方法,加上V8引擎的優化,綜合來講時間複雜度是能知足的。
好像有點偷雞摸狗的感受。。。
sort方法的源碼:Array API源碼,從710行開始看吧
var findMedianSortedArrays = function(nums1, nums2) { let num = nums1.concat(nums2); num = num.sort((a, b) => a - b); let mid = Math.floor(num.length / 2); if (num.length % 2 === 0) { return (num[mid-1] + num[mid])/2 } else { return num[mid] } };
給定一個字符串 s,找到 s 中最長的迴文子串。你能夠假設 s 的最大長度爲 1000。
輸入: "babad"
輸出: "bab"
注意: "aba" 也是一個有效答案。
輸入: "cbbd"
輸出: "bb"
這題要用動態規劃來作,先是判斷出全部長度爲1,2,3的子串是否迴文。
長度爲1,一定迴文。
長度爲2或者3,取決於首位字符是否相同。
長度大於3,取決於該子串去掉首位字符以後是否迴文,而且首位字符是否相同。
核心在於 dp[i][j] == dp[i+1][j-1] && s[i] === s[j]
var longestPalindrome = function(s) { let dp = []; for(let i = 0; i < s.length; i++){ dp[i] = []; } let max = -1, str = ''; for(let k = 0; k < s.length; k++){ // k爲所遍歷的子串長度 - 1,即左下標到右下標的距離 for(let i = 0; i + k < s.length; i++){ let j = i + k; // i爲子串開始的左下標,j爲子串開始的右下標 if(k == 0){ // 當子串長度爲1時,一定是迴文 dp[i][j] = true; } else if(k <= 2){ // 當子串長度爲2時,兩字符相同則符合迴文,長度爲3,首位字符相同則符合迴文 if(s[i] == s[j]){ dp[i][j] = true; } else{ dp[i][j] = false; } } else{ // 當子串長度超過3,取決於去掉頭尾以後的子串是否迴文而且首位字符是否相同 if(dp[i+1][j-1] && (s[i] == s[j])){ dp[i][j] = true; } else{ dp[i][j] = false; } } if(dp[i][j] && k > max){ max = k; str = s.substring(i, j + 1) } } } return str; };
將一個給定字符串根據給定的行數,以從上往下、從左到右進行 Z 字形排列。
好比輸入字符串爲 "LEETCODEISHIRING" 行數爲 3 時,排列以下:
L C I R E T O E S I I G E D H N
以後,你的輸出須要從左往右逐行讀取,產生出一個新的字符串,好比:"LCIRETOESIIGEDHN"。
請你實現這個將字符串進行指定行數變換的函數:
string convert(string s, int numRows);
輸入: s = "LEETCODEISHIRING", numRows = 3
輸出: "LCIRETOESIIGEDHN"
輸入: s = "LEETCODEISHIRING", numRows = 4
輸出: "LDREOEIIECIHNTSG"
解釋:
L D R E O E I I E C I H N T S G
這題目的結構有點怪,但也是有規律可循的,咱們發現這個」Z「的字符順序是這樣子的:垂直向下,斜向上,而後再垂直向下。
那其實咱們能夠直接將該結構簡化爲一個二維數組,去掉中間的空格,再一行一行地遍歷就能獲取到答案了。
如:
L D R E O E I I E C I H N T S G
能夠變成
L D R E O E I I E C I H N T S G
接着再一行一行讀,拼成字符串,即可。
var convert = function(s, numRows) { if(numRows == 1){ return s; } let arr = [], direction = 'down', line = 0, str = ''; for(let i = 0; i < numRows; i++){ arr[i] = []; } for(let i = 0; i < s.length; i++){ arr[line].push(s[i]); if(line == 0){ line++; direction = 'down' } else if(line == numRows - 1){ line--; direction = 'up' } else{ if(direction == 'down'){ line++; } else if(direction = 'up'){ line--; } } } for(let i = 0; i < numRows; i++){ str += arr[i].join(""); } return str; };
給出一個 32 位的有符號整數,你須要將這個整數中每位上的數字進行反轉。
注意:
假設咱們的環境只能存儲得下 32 位的有符號整數,則其數值範圍爲 [−231, 231 − 1]。請根據這個假設,若是反轉後整數溢出那麼就返回 0。
輸入: 123
輸出: 321
輸入: -123
輸出: -321
輸入: 120
輸出: 21
這題就很簡單了,不過要考慮好邊緣溢出狀況便可。
var MAX = Math.pow(2, 31) - 1 var MIN = -1 * Math.pow(2, 31) var reverse = function(x) { let str = x.toString().split(''), symbolFlag = false; if(str[0] == '-'){ symbolFlag = true; str.shift(); } str = str.reverse(); if(symbolFlag){ str.unshift('-'); } let num = Number(str.join('')) if(num < MIN || num > MAX){ return 0 } else{ return num } };
請你來實現一個 atoi 函數,使其能將字符串轉換成整數。
首先,該函數會根據須要丟棄無用的開頭空格字符,直到尋找到第一個非空格的字符爲止。
當咱們尋找到的第一個非空字符爲正或者負號時,則將該符號與以後面儘量多的連續數字組合起來,做爲該整數的正負號;假如第一個非空字符是數字,則直接將其與以後連續的數字字符組合起來,造成整數。
該字符串除了有效的整數部分以後也可能會存在多餘的字符,這些字符能夠被忽略,它們對於函數不該該形成影響。
注意:假如該字符串中的第一個非空格字符不是一個有效整數字符、字符串爲空或字符串僅包含空白字符時,則你的函數不須要進行轉換。
在任何狀況下,若函數不能進行有效的轉換時,請返回 0。
說明:
假設咱們的環境只能存儲 32 位大小的有符號整數,那麼其數值範圍爲 [−231, 231 − 1]。若是數值超過這個範圍,qing返回 INT_MAX (231 − 1) 或 INT_MIN (−231) 。
題目很長是吧,不要緊,咱們直接看示例。
輸入: "42"
輸出: 42
輸入: " -42"
輸出: -42
解釋: 第一個非空白字符爲 '-', 它是一個負號。
咱們儘量將負號與後面全部連續出現的數字組合起來,最後獲得 -42 。
輸入: "4193 with words"
輸出: 4193
解釋: 轉換截止於數字 '3' ,由於它的下一個字符不爲數字。
輸入: "words and 987"
輸出: 0
解釋: 第一個非空字符是 'w', 但它不是數字或正、負號。
所以沒法執行有效的轉換。
輸入: "-91283472332"
輸出: -2147483648
解釋: 數字 "-91283472332" 超過 32 位有符號整數範圍。
所以返回 INT_MIN (−231) 。
這題不難,可是有不少坑。首先咱們採起ASCII編碼的方式來判斷字符爲數字仍是英文仍是別的。
先去空白,去掉空白以後取第一個字符,判斷正負符號,如果英文直接返回0,若數字則不取。
從第二個字符開始遍歷,若不是數字則退出循環。
最後還要考慮溢出狀況。
const MIN = -1 * Math.pow(2, 31); const MAX = Math.pow(2, 31) - 1; var myAtoi = function(str) { str = str.trim(); let result = '', symbol = ''; let idx = 0; if(str.charCodeAt(0) === 45){ idx++; symbol = '-'; } else if(str.charCodeAt(0) === 43){ idx++; } else if(str.charCodeAt(0) < 48 || str.charCodeAt(0) > 57){ return 0; } for(let i = idx; i < str.length; i++){ if(str.charCodeAt(i) === 46){ break; } else if(str.charCodeAt(i) >= 48 && str.charCodeAt(i) <= 57){ result += str[i]; } else{ break } } result = symbol.toString() + result.toString(); if(Number(result) !== Number(result)){ return 0; } else if(Number(result) < MIN){ return MIN; } else if(Number(result) > MAX){ return MAX; } else{ return Number(result) } };
判斷一個整數是不是迴文數。迴文數是指正序(從左向右)和倒序(從右向左)讀都是同樣的整數。
輸入: 121
輸出: true
輸入: -121
輸出: false
解釋: 從左向右讀, 爲 -121 。 從右向左讀, 爲 121- 。所以它不是一個迴文數。
輸入: 10
輸出: false
解釋: 從右向左讀, 爲 01 。所以它不是一個迴文數
這題比較簡單,反轉對比便可
var isPalindrome = function(x) { let y = x.toString().split("").reverse().join(""); return x == y };
給你一個字符串 s 和一個字符規律 p,請你來實現一個支持 '.' 和 '*' 的正則表達式匹配。
'.' 匹配任意單個字符
'*' 匹配零個或多個前面的那一個元素
所謂匹配,是要涵蓋 整個 字符串 s的,而不是部分字符串。
說明:
s 可能爲空,且只包含從 a-z 的小寫字母。
p 可能爲空,且只包含從 a-z 的小寫字母,以及字符 . 和 *。
輸入:
s = "aa"
p = "a"
輸出: false
解釋: "a" 沒法匹配 "aa" 整個字符串。
輸入:
s = "aa"
p = "a*"
輸出: true
解釋: 由於 '*' 表明能夠匹配零個或多個前面的那一個元素, 在這裏前面的元素就是 'a'。所以,字符串 "aa" 可被視爲 'a' 重複了一次。
輸入:
s = "ab"
p = ".*"
輸出: true
解釋: "." 表示可匹配零個或多個('')任意字符('.')。
輸入:
s = "aab"
p = "cab"
輸出: true
解釋: 由於 '*' 表示零個或多個,這裏 'c' 爲 0 個, 'a' 被重複一次。所以能夠匹配字符串 "aab"。
輸入:
s = "mississippi"
p = "misisp*."
輸出: false
這題稍微有點複雜,咱們採用了遞歸方法將兩個字符串對比,每次只對比一個字符。
將當前遞歸p的下一個字符是否爲*進行分類比較:
①p的下一個字符是*
若s和p的當前字符相同或者p的當前字符爲.,則結果就取決於:
isMatch(s.slice(1), p) || isMatch(s.slice(1), p.slice(2)) || isMatch(s, p.slice(2))
若p的最後兩個字符爲.*就返回true
若不符合上面兩種狀況就將取決於
isMatch(s,p.slice(2))
②p的下一個字符不爲*
這種狀況就簡單了
若s和p的當前字符相同或者p的當前字符爲.,返回true
不然返回false
var isMatch = function(s, p) { if(s.length === 0 && p.length === 0){ return true; } if(s.length !== 0 && p.length === 0){ return false; } let str = s[0], pattern = p[0]; let isNextStart = p[1] === "*"; if(isNextStart){ if(str && (str === pattern || pattern === ".")){ return isMatch(s.slice(1), p) || isMatch(s.slice(1), p.slice(2)) || isMatch(s, p.slice(2)) } else if(pattern === "." && p.slice(2).length === 0){ return true } else{ return isMatch(s,p.slice(2)); } } else{ if(str && (str === pattern || pattern === ".")){ return isMatch(s.slice(1), p.slice(1)) } else{ return false; } } };
本文全部題目均來自樂扣(leetcode),作法不惟一,甚至可能還有所錯誤,但願各位大神指出,弟弟虛心學習。