js实用算法
判斷文本是否為回文
定義:如果將一個文本翻轉過來,能和原文本完全相等,那么就可以稱之為“回文”。
方法一(字符串、數組內置方法)
| 123456789101112131415 | /** 判斷文字是否為回文* @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) // trueisPalindrome1('yuzuy') // true |
// PS:方法簡單,但效率不高,會產生一個新的變量
方法二(循環)
| 1234567891011121314151617181920 | /** 判斷文字是否為回文* @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) // trueisPalindrome2('yuzuy') // true |
PS:網上還有其他解法,大多為以上兩種的變形。
反轉字符串
方法一(字符串、數組內置方法))
借用反轉字符串的方法
| 12345678910 | /** 反轉字符串* @param {string} val 需要反轉的字符串* @return {string} str 反轉后的字符串*/function reverseVal1(val){if (typeof val !== 'string') return; return val.split('').reverse().join('');} |
方法二(循環)
循環系列
| 1234567891011121314151617181920212223242526272829303132333435 | /** 反轉字符串* @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’
階乘
方法一(遞歸)
| 123456789101112 | /** 階乘* @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尾調用優化)
(遞歸優化版)
| 123456789101112 | /** 階乘* @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)是函數式編程的一個重要概念,本身非常簡單,一句話就能說清楚,就是指某個函數的最后一步是調用另一個函數。
方法三(循環)
| 1234567891011121314151617 | /** 階乘* @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字符串
方法一
| 123456789101112131415161718 | /** 生成指定長度的隨機字符串* @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]隨機數
方法二(進制轉化)
| 12345678 | /** 生成指定長度的隨機字符串* @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進制數字
該方法有個缺點,產生字符串的長度有一定的限制。
方法三(隨機碼點)
| 1234567891011121314151617181920 | /** 生成指定長度的隨機字符串* @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-9if(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數據結構)
| 12345678 | /** 數組去重* @param {array} ary 需要去重的數組* @return {array} 去重后的數組*/function unique1(ary){return [...new Set(ary)];} |
方法二(對象的key唯一性)
| 123456789101112131415161718 | /** 數組去重* @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’,會被視為同一個值。
方法三(臨時數組判斷插入)
| 123456789101112131415161718 | /** 數組去重* @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項是重復的,忽略掉。否則存入結果數組。
| 12345678910111213141516 | /** 數組去重* @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;} |
方法五(排序后逐個比較插入)
給傳入數組排序,排序后相同值相鄰,然后遍歷時新數組只加入不與前一值重復的值。
| 123456789101112131415 | /** 數組去重* @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:返回的數組順序發生了改變。
方法六()
獲取沒有重復的最右一值放入新數組(檢測到有重復值時終止當前循環同時進入頂層循環的下一輪判斷)。
| 12345678910111213141516 | /** 數組去重* @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的唯一性進行累加)
| 123456789101112131415161718192021222324 | 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表示需要扁平化的層數,返回一個新的數組。
方法一(遞歸遍歷數組拼接)
| 123456789101112131415161718 | 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)
| 1234567 | function flatten2(ary){return ary.reduce((pre, cur) => {return pre.concat(Array.isArray(cur) ? flatten2(cur) : cur)}, []) } |
PS:可以處理多層數組。
方法三(轉化為字符串)
| 12345 | function flatten2(ary){return ary.toString().split(',')} |
PS:返回的數組項將為字符串。
方法四(解構數組)
| 12345678910111213 | 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]
數組中最大差值
方法一
| 123 | function getMaxProfit1(ary){return Math.max.apply(null, ary) - Math.min.apply(null, ary);} |
測試:getMaxProfit1([1, 2, 3, 4]) // 3
斐波那契數列
這里我們只實現通項公式
方法一
| 1234567 | function fib1(n){if(n === 1 || n === 2){return 1;}return fib1(n - 1) + fib1(n - 2);} |
PS:時間復雜度為O(2^n),空間復雜度為O(n)
方法二
| 12345678910111213 | 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)
方法三
| 123456789101112 | 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的數質數的補集)。
| 123456789101112 | 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
最小公約數
| 12345678910111213141516171819202122232425262728 | 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
金額轉大寫
| 12345678910111213141516 | 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) // 壹仟貳佰叁拾肆元整
總結
- 上一篇: 前端 crypto-js aes 加解密
- 下一篇: Highcharts的饼图大小的控制