判斷文本是否為回文
定義:如果將一個文本翻轉過來,能和原文本完全相等,那么就可以稱之為“回文”。
方法一(字符串、數組內置方法)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | /* * 判斷文字是否為回文 * @param {string|number} val 需要判斷的文字 * @return {boolean} bool 是否為回文 */ function isPalindrome1(val){ // 允許輸入字符串和數字和布爾值 if (typeof val !== 'string') val = val.toString(); let newVal = val.split('').reverse().join(''); return val === newVal; } isPalindrome1(121) // true isPalindrome1('yuzuy') // true |
// PS:方法簡單,但效率不高,會產生一個新的變量
方法二(循環)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | /* * 判斷文字是否為回文 * @param {string|number} val 需要判斷的文字 * @return {boolean} bool 是否為回文 */ function isPalindrome2(val){ val = val + ''; // 非字符串轉化為字符串 // 這里為什么 i <= j 呢?如果中間只有一個字符,是不需要比較的,它肯定等于它本身!!! for(let i = 0, j = val.length - 1; i < j; i++, j--){ if(val.charAt(i) !== val.charAt(j)){ return false; } } return true; } isPalindrome2(121) // true isPalindrome2('yuzuy') // true |
PS:網上還有其他解法,大多為以上兩種的變形。
反轉字符串
方法一(字符串、數組內置方法))
借用反轉字符串的方法
1 2 3 4 5 6 7 8 9 10 | /* * 反轉字符串 * @param {string} val 需要反轉的字符串 * @return {string} str 反轉后的字符串 */ function reverseVal1(val){ if (typeof val !== 'string') return; return val.split('').reverse().join(''); } |
方法二(循環)
循環系列
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | /* * 反轉字符串 * @param {string} val 需要反轉的字符串 * @return {string} str 反轉后的字符串 */ function reverseVal2(val){ if (typeof val !== 'string') return; let str = '', i = 0, len = val.length; while(i < len){ str += val.charAt(len - 1 - i); i++; } return str; } /* * 反轉字符串 * @param {string} val 需要反轉的字符串 * @return {string} str 反轉后的字符串 */ function reverseVal3(val){ if (typeof val !== 'string') return; let str = '', len = val.length; for(let i = len - 1; i >= 0; i--){ str += val.charAt(i) } return str; } |
測試:reverseVal(‘abc’) // ‘cba’
階乘
方法一(遞歸)
1 2 3 4 5 6 7 8 9 10 11 12 | /* * 階乘 * @param {number} n 需要求的階乘 * @return {number} 階乘值 */ function factorialize1(n){ if(typeof n !== 'number') throw new Error('參數必須為整整') if(n === 1) return 1; // 建議不要使用 arguments.callee,目前已經廢棄了。 return n * factorialize1(n - 1); } |
PS:上面代碼是一個階乘函數,計算n的階乘,最多需要保存n個調用記錄,復雜度 O(n) 。
遞歸非常耗費內存,因為需要同時保存成千上百個調用幀,很容易發生“棧溢出”錯誤(stack overflow)。
方法二(ES6尾調用優化)
(遞歸優化版)
1 2 3 4 5 6 7 8 9 10 11 12 | /* * 階乘 * @param {number} n 需要求的階乘 * @return {number} 階乘值 */ function factorialize2(n, total = 1){ if(typeof n !== 'number' || typeof total !== 'number') throw new Error('參數必須為整整') if(n === 1) return total; return factorialize2(n - 1, n * total) // f(3) => f(2, 3 * 2) => f(1, 6) => 6 } |
PS:ES6尾調用優化但對于尾遞歸來說,由于只存在一個調用幀,所以永遠不會發生“棧溢出”錯誤。
尾調用(Tail Call)是函數式編程的一個重要概念,本身非常簡單,一句話就能說清楚,就是指某個函數的最后一步是調用另一個函數。
方法三(循環)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | /* * 階乘 * @param {number} n 需要求的階乘 * @return {number} 階乘值 */ function factorialize3(n){ if(typeof n !== 'number') throw new Error('參數必須為整整') if(n === 1) return 1; let total = 1; while(n>1){ total = n * total; n--; } return total; } |
測試:factorialize1(3) // 6
隨機生成長度為n字符串
方法一
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | /* * 生成指定長度的隨機字符串 * @param {number} n 生成字符串個數 * @return {string} str 反轉后的字符串 */ function randomString1(n){ let str = 'abcdefghijklmnopqrstuvwxyz0123456789'; let tem = '', i = 0; // Math.random 函數產生值的范圍[0,1) while(i<n){ tem += str.charAt(Math.floor(Math.random() * str.length)) i++; } return tem; } |
PS:Math.round(Math.random()?(str.length - 1))
Math.ceil(Math.random()?(str.length - 1))
Math.floor(Math.random() * str.length)
這三種方式等價,都能生成[0, str.length-1]隨機數
方法二(進制轉化)
1 2 3 4 5 6 7 8 | /* * 生成指定長度的隨機字符串 * @param {number} n 生成字符串個數 * @return {string} 反轉后的字符串 */ function randomString2(n){ return Math.random().toString(36).substr(2).slice(0, n) } |
PS:該方法原理為隨機產生的數轉換為指定進制字符串
toString(n),n為[2,36],n<=10時只產生0-9也就是10進制數字
該方法有個缺點,產生字符串的長度有一定的限制。
方法三(隨機碼點)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | /* * 生成指定長度的隨機字符串 * @param {number} n 生成字符串個數 * @return {string} str 反轉后的字符串 */ function randomString3(n){ let str = ''; function randomChar(){ let l = Math.floor(Math.random() * 62); if(l < 10) return l; // 數字部分 0-9 if(l < 36) return String.fromCharCode(l + 55); // 大寫字母 return String.fromCharCode(l + 61); // 小寫字母 } while(str.length < n) str += randomChar(); return str; } |
PS:可以參考對于的ASCII碼表。
測試:randomString1(3) // ‘1sd’
數組去重
方法一(ES6的Set數據結構)
1 2 3 4 5 6 7 8 | /* * 數組去重 * @param {array} ary 需要去重的數組 * @return {array} 去重后的數組 */ function unique1(ary){ return [...new Set(ary)]; } |
方法二(對象的key唯一性)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | /* * 數組去重 * @param {array} ary 需要去重的數組 * @return {array} 去重后的數組 */ function unique2(ary){ let obj = {}, i = 0, len = ary.length; while(i < len){ if(!obj[ary[i]]){ obj[ary[i]] = true; // 如果不存在 } i++; } return Object.keys(obj); } |
PS:該方法存在一定問題,數組的元素全部被轉化為字符串,因為ES6之前對象的key只能是字符串。
會把數字1和字符串’1’,會被視為同一個值。
方法三(臨時數組判斷插入)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | /* * 數組去重 * @param {array} ary 需要去重的數組 * @return {array} 去重后的數組 */ function unique3(ary){ let tem = [], i = 0, len = ary.length; while(i < len){ // tem.indexOf() === -1 同理 !tem.includes(ary[i]) ? tem.push(ary[i]) : ''; i++; } return tem; } |
方法四(判斷首次出現的位置)
如果當前數組的第i項在當前數組中第一次出現的位置不是i,那么表示第i項是重復的,忽略掉。否則存入結果數組。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | /* * 數組去重 * @param {array} ary 需要去重的數組 * @return {array} 去重后的數組 */ function unique4(ary){ let tem = [ary[0]], len = ary.length; for(let i = 1; i < len; i++ ){ // 核心,首次的索引出現是否為當前的索引 if(ary.indexOf(ary[i]) === i) tem.push(ary[i]); } return tem; } |
方法五(排序后逐個比較插入)
給傳入數組排序,排序后相同值相鄰,然后遍歷時新數組只加入不與前一值重復的值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | /* * 數組去重 * @param {array} array 需要去重的數組 * @return {array} 去重后的數組 */ function unique5(array){ let ary = array.slice(); ary.sort(); let tem = [ary[0]]; for(let i = 0, len = ary.length; i < len; i++){ ary[i] !== tem[tem.length - 1] ? tem.push(ary[i]) : ''; } return tem; } |
PS:返回的數組順序發生了改變。
方法六()
獲取沒有重復的最右一值放入新數組(檢測到有重復值時終止當前循環同時進入頂層循環的下一輪判斷)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | /* * 數組去重 * @param {array} ary 需要去重的數組 * @return {array} 去重后的數組 */ function unique6(ary){ let tem = []; for(let i = 0, len = ary.length; i < len; i++){ for(let j = i + 1; j < len; j++){ if(ary[i] === ary[j]) j = ++i; } tem.push(ary[i]) } return tem; } |
測試:unique1([1, 2, 3, 2]) // [1, 2, 3]
出現次數最多的字符
方法一(對象key的唯一性進行累加)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | function maxNum1(str){ if(typeof(str) !== 'string') str = str.toString(); let obj = {}, maxChar = []; // 使用數組保存出現最多次的某些字符 str.split('').forEach( (val) => { if(!obj[val]){ let demo = obj[val] = 1; }else{ obj[val]++; } }) let maxCount = Math.max.apply(null, Object.values(obj)) // forEach方法總是返回 undefined 且 沒有辦法中止或者跳出 forEach 循環。 Object.entries(obj).forEach( item => { if(item[1] == maxCount){ maxChar.push(item[0]) } }) return maxChar; } |
測試:maxNum1(‘11223333’) // ‘3’
數組扁平化
實現方法:Array.prototype.flatten(depth),參數depth表示需要扁平化的層數,返回一個新的數組。
方法一(遞歸遍歷數組拼接)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | function flatten1(ary){ let tem = [], i = 0, len = ary.length; while(i < len){ if(Array.isArray(ary[i])){ // 遞歸進行上面步驟 // [].concat(...ary),它的參數可以為數組或值,作用為將數組或值連接成新數組。 tem = tem.concat(flatten1(ary[i])) }else{ tem.push(ary[i]); } i++; } return tem; } |
PS:可以處理多層數組。
方法二(reduce結合concat)
1 2 3 4 5 6 7 | function flatten2(ary){ return ary.reduce((pre, cur) => { return pre.concat(Array.isArray(cur) ? flatten2(cur) : cur) }, []) } |
PS:可以處理多層數組。
方法三(轉化為字符串)
1 2 3 4 5 | function flatten2(ary){ return ary.toString().split(',') } |
PS:返回的數組項將為字符串。
方法四(解構數組)
1 2 3 4 5 6 7 8 9 10 11 12 13 | function flatten4(ary){ let tem = [] ary.forEach(item => { if(Array.isArray(item)){ tem = tem.concat(...item); }else{ tem = tem.concat(item); } }) return tem; } |
PS:只能處理2維數組。
測試:getMaxProfit1([1, 2, 3, [4, 5, 6]]) // [1, 2, 3, 4, 5, 6]
數組中最大差值
方法一
1 2 3 | function getMaxProfit1(ary){ return Math.max.apply(null, ary) - Math.min.apply(null, ary); } |
測試:getMaxProfit1([1, 2, 3, 4]) // 3
斐波那契數列
這里我們只實現通項公式
方法一
1 2 3 4 5 6 7 | function fib1(n){ if(n === 1 || n === 2){ return 1; } return fib1(n - 1) + fib1(n - 2); } |
PS:時間復雜度為O(2^n),空間復雜度為O(n)
方法二
1 2 3 4 5 6 7 8 9 10 11 12 13 | function fib2(n){ let tem = [1, 1]; if(n === 1 || n === 2){ return 1; } // 數組索引從0開始,數列索引從1開始 for(let i = 2; i < n; i++){ tem[i] = tem[i-1] + tem[i-2]; } return tem[n-1]; } |
PS:時間復雜度為O(n),空間復雜度為O(n)
方法三
1 2 3 4 5 6 7 8 9 10 11 12 | function fib2(n){ let prev = 1, next = 1, res; for(let i = 2; i < n; i++){ res = prev + next; prev = next; next = res; } return res; } |
PS:時間復雜度為O(n),空間復雜度為O(1)
測試:fib2(3) // 2
判斷是否為質數(prime number)素數
質數:只能被1和自己整除且大于1的數。
合數:數大于1且因數多余2個(大于1的數質數的補集)。
1 2 3 4 5 6 7 8 9 10 11 12 | function isPrimeNumber1(n){ if(n < 2) return false; if(n === 2) return true; // 最小的質數 for(let i = 2; i < n; i++){ if(n % i === 0){ return false; } } return true; } |
測試:isPrimeNumber1(2) // true
最小公約數
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | function greatestCommonDivisor1(a, b){ if(a < 0 || b < 0) throw new Error('參數只能為正整數'); if(a < 2 || b < 2) return 1; let min = a, max = b, arymin = []; if(a > b) { min = b; max = a; } for(let i = 1; i <= min; i++){ if(min % i === 0){ arymin.push(i); console.log(1) } } arymin.reverse(); for(let j = 0, len = arymin.length; j < len; j++){ if(max % arymin[j] === 0){ return arymin[j]; } } } |
測試:greatestCommonDivisor1(5, 10) // 5
金額轉大寫
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | function money2Chinese(num) { if(typeof num) throw new Error('參數為數字') let strOutput = "" let strUnit = '仟佰拾億仟佰拾萬仟佰拾元角分' num += "00" const intPos = num.indexOf('.') if (intPos >= 0) { num = num.substring(0, intPos) + num.substr(intPos + 1, 2) } strUnit = strUnit.substr(strUnit.length - num.length) for (let i = 0; i < num.length; i++) { strOutput += '零壹貳叁肆伍陸柒捌玖'.substr(num.substr(i, 1), 1) + strUnit.substr(i, 1) } return strOutput.replace(/零角零分$/, '整').replace(/零[仟佰拾]/g, '零').replace(/零{2,}/g, '零').replace(/零([億|萬])/g, '$1').replace(/零+元/, '元').replace(/億零{0,3}萬/, '億').replace(/^元/, "零元"); } |
測試:money2Chinese(1234) // 壹仟貳佰叁拾肆元整