前言
前幾天逛 github 的時候看到一些前端的演算法題,自己做了一遍發現還挺有意思的,因此整理了一下收錄 daily-question 的 algorithm 資料夾中,後續會繼續增加,本文分享我整理的十個演算法題目。
題外話:其實給這篇文章起名字的時候不知道起什麼名字,看了下掘金命名的文章,整理了幾個模板:
- 你不知道系列 ——《你不知道的前端演算法》
- 滿足系列 —— 《前端演算法看這篇就足夠了》
- 靈魂系列 —— 《前端演算法之靈魂拷問》
- 你真的懂嗎系列 —— 《你真的懂前端演算法嗎?》
- 萬字長文建議收藏系列 —— 《(萬字長文,強烈建議收藏,錯過沒有)之前端演算法》
最後想想還是樸素一點,不做標題黨吧哈哈哈?
01-把數字轉換成中文
題目描述:
完成將 toChineseNum
, 可以將數字轉換成中文大寫的表示,處理到萬級別,例如 toChineseNum(12345)
,返回 一萬二千三百四十五
。
思路:
對八位以上和一下進行分類討論:
- 定義
getResult
方法處理八位數 - 處理八位數,切割成兩部分,以五位進行分析,如果超出五位,則後五位為一體,除下後五位的為一體。 如果沒有超出五位,則按照和後五位一樣的方式,轉換。最後處理特殊情況零。
- 處理八位數以上的,同樣切割成兩部分,後八位同 2 處理,剩下的用
getResult
處理後在後面補'億'
參考答案:
function toChineseNum(num) {
num += ''
let numLength = num.length
let numStr = '零一二三四五六七八九十'
let unitArr = ['', '十', '百', '千', '萬']
function getResult(str) {
let res = '';
if (str.length > 5) {
let first = str.slice(-5);
let second = str.slice(0, str.length - 5);
for (let i in second) {
res = res + numStr[second[i]] + unitArr[second.length - i];
}
for (let i in first) {
res = res + numStr[first[i]] + unitArr[first.length - i - 1];
}
} else {
let first = str.slice(-5);
for (let i in first) {
res = res + numStr[first[i]] + unitArr[first.length - i - 1];
}
}
res = res.replace(/零[零十百千]+/g, '零').replace(/零+$/g, '').replace(/零萬/g, '萬')
return res;
}
if (numLength > 8) {
return getResult(num.slice(0, numLength - 8)) + '億' + getResult(num.slice(-8))
}
return getResult(num)
}
console.log(toChineseNum(1000005600454456))
複製程式碼
02-數字新增逗號
題目描述:
完成函式 commafy
,它接受一個數字作為引數,返回一個字串,可以把整數部分從右到左每三位數新增一個逗號,如:12000000.11
轉化為 12,000,000.11
。
思路:
- 此題主要考察思路是否嚴謹,分為正數與負數,整數與非整數
- 加逗號主要是處理整數部分,每隔3位插入一個逗號,可以使用陣列的
splice()
插入逗號
參考答案:
function commafy (num) {
let numStr = num + '';
let arr = num < 0 ? numStr.slice(1).split('.') : numStr.split('.');
let a = arr[0].split(''); // 整數部分切割成陣列
for(let i = a.length - 3; i > 0; i=i-3) {
a.splice(i, 0, ',')
}
let res = arr[1] ? a.join('') + '.' + arr[1] : a.join('')
return num < 0 ? '-' + res : res;
}
console.log(commafy(12564654.456456)) // 12,564,654.456456
複製程式碼
03-16進位制顏色值轉RGB值
題目描述:
完成函式 hexToRGB,它的作用將 16 進位制顏色值轉換成 RGB 值:
hexToRGB('#F0F0F0') // => rgb(240, 240, 240)
hexToRGB('#9fc') // => rgb(153, 255, 204)
hexToRGB('無效顏色') // => null
複製程式碼
思路:
16 進位制轉十進位制如何計算。A,B,C,D,E,F,不區分大小寫這六個字母分別表示10,11,12,13,14,15 首先判斷是否是16進位制的顏色,特點以#號開頭,其餘是字母和數字,6位或者3位。 正則匹配如何只匹配3位數字或字母,或只匹配 6 位數字或字母
參考答案:
const hexToRGB = (hex) => {
if (!/(^\#([a-fA-F0-9]{3})$)|(^\#([a-fA-F0-9]{6})$)/g.test(hex)) return null
let allNumberStr = '0123456789abcdef' // 十六進位制的所有數字
let len = hex.slice(1).length;
let str = len === 6 ? hex.slice(1) : hex.slice(1)[0].repeat(2) + hex.slice(1)[1].repeat(2) + hex.slice(1)[2].repeat(2);
let arrStr = str.split('');
let newArrStr = arrStr.map((item, index) => {
return allNumberStr.indexOf((item + '').toLowerCase())
})
let num1 = newArrStr[0] * 16 + newArrStr[1];
let num2 = newArrStr[2] * 16 + newArrStr[3];
let num3 = newArrStr[4] * 16 + newArrStr[5];
return `rgb(${num1}, ${num2}, ${num3})`
}
console.log(hexToRGB('#fffaaa'))
複製程式碼
04-轉換駝峰命名
題目描述:
小科去了一家新的公司做前端主管,發現裡面的前端程式碼有一部分是 C/C++ 程式設計師寫的,他們喜歡用下劃線命名,例如: is_good。小科決定寫個指令碼來全部替換掉這些變數名。
完成 toCamelCaseVar 函式,它可以接受一個字串作為引數,可以把類似於 is_good 這樣的變數名替換成 isGood。
思路:
- 可以利用正則及字串方法
replace()
實現
參考答案:
const toCamelCaseVar = (variable) => {
variable = variable.replace(/[\_|-](\w)/g, function (all, letter) {
return letter.toUpperCase();
});
return variable.slice(0, 1).toLowerCase() + variable.slice(1)
}
console.log(toCamelCaseVar('Foo_style_css')) // fooStyleCss
console.log(toCamelCaseVar('Foo-style-css')) // fooStyleCss
複製程式碼
05-監聽陣列變化
題目描述:
在前端的 MVVM
框架當中,我們經常需要監聽資料的變化,而陣列是需要監聽的重要物件。請你完成 ObserverableArray
,它的例項和普通的陣列例項功能相同,但是當呼叫:push,pop,shift,unshift,splice,sort,reverse
這些方法的時候,除了執行相同的操作,還會把方法名列印出來。 例如:
const arr = new ObserverableArray()
arr.push('Good') // => 列印 'push',a 變成了 ['Good']
複製程式碼
注意,你不能修改 Array 的 prototype。還有函式 return 的值和原生的操作保持一致。
思路:
- 可以利用 es6的
Proxy
監聽實現實現
參考答案:
function ObserverableArray() {
return new Proxy([], {
get(target, propKey) {
const matArr = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'];
matArr.indexOf(propKey) > -1 && console.log(propKey);
return target[propKey]
}
})
}
const arr = new ObserverableArray()
arr.push('Good') // => 列印 'push',a 變成了 ['Good']
arr.push('Good2') // => 列印 'push',a 變成了 ['Good', 'Good2']
arr.unshift('Good2') // => 列印 'unshift',a 變成了 ['Good2','Good', 'Good2']
console.log(arr) // ['Good2','Good', 'Good2']
複製程式碼
06-驗證一個數是否是素數
思路:
素數也稱為質數,質數是指在大於1的自然數中,除了1和它本身以外不再有其他因數的自然數。有一下幾種情況:
-
如果這個數是 2 或 3,一定是素數;
-
如果是偶數,一定不是素數
-
如果這個數不能被 3 至它的平方根中的任一數整除,m 必定是素數。而且除數可以每次遞增2(排除偶數)
參考答案:
function isPrime(num){
if (typeof num !== 'number') {
throw new TypeError('num should be number')
}
if (num === 2 || num === 3) {
return true;
};
if (num % 2 === 0) {
return false;
};
let divisor = 3, limit = Math.sqrt(num);
while(limit >= divisor){
if (num % divisor === 0) {
return false;
}
else {
divisor += 2;
}
}
return true;
}
console.log(isPrime(30)); // false
console.log(isPrime(31)); // true
複製程式碼
07-斐波那契數列
什麼是斐波那契數列:
斐波那契數列(Fibonacci sequence),又稱黃金分割數列、因數學家列昂納多·斐波那契(Leonardoda Fibonacci)以兔子繁殖為例子而引入,故又稱為“兔子數列”,指的是這樣一個數列:1、1、2、3、5、8、13、21、34、……在數學上,斐波那契數列以如下被以遞推的方法定義:F(1)=1,F(2)=1, F(n)=F(n-1)+F(n-2)(n>=3,n∈N*)在現代物理、準晶體結構、化學等領域,斐波納契數列都有直接的應用,為此,美國數學會從 1963 年起出版了以《斐波納契數列季刊》為名的一份數學雜誌,用於專門刊載這方面的研究成果。
思路:
從上面的定義得出,斐波那契數列的三種情況:
F(1)=1
F(2)=1
F(n)=F(n-1)+F(n-2)(n>=3,n∈N\*)
試用遞迴實現
function fibonacci(n) {
if (n <= 0) {
return 0;
}
if (n == 1) {
return 1;
}
return fibonacci(n - 1) + fibonacci(n - 2);
}
console.time('fibonacci');
console.log(fibonacci(40)); // 102334155
console.timeEnd('fibonacci'); // 1106.791ms
複製程式碼
仔細分析該遞迴,你會發現 f(40) = f(39) + f(38) , f(39) = f(38) + f(37) , f(38) = f(37) + f(36) , 算 f(40) 和 f(39) 都會計算 f(38),多算了一遍 f(38),會有明顯的效率問題。這種是從上至下計算(40-1),我們換種思路,從下至上試試(1-40),首先根據 f(0)和 f(1)計算出 f(2),再根據 f(1)和 f(2)計算出 f(3)……以此類推就可以計算出第 n 項。時間複雜度 O(n)。:
function fibonacci(n) {
if (n <= 0) {
return 0;
}
if (n == 1) {
return 1;
}
let fiboOne = 1,
fiboTwo = 0,
fiboSum = 0;
for (let i = 2; i <= n; i++) {
fiboSum = fiboOne + fiboTwo;
fiboTwo = fiboOne;
fiboOne = fiboSum;
}
return fiboSum;
}
console.time('fibonacci');
console.log(fibonacci(40)); //102334155
console.timeEnd('fibonacci'); // 4.376ms
複製程式碼
可以看出,時間上少了將近 1000ms,數字越大,耗時差越大
參考答案:
function fibonacci(n) {
if (n <= 0) {
return 0;
}
if (n == 1) {
return 1;
}
let fiboOne = 1,
fiboTwo = 0,
fiboSum = 0;
for (let i = 2; i <= n; i++) {
fiboSum = fiboOne + fiboTwo;
fiboTwo = fiboOne;
fiboOne = fiboSum;
}
return fiboSum;
}
複製程式碼
08-兩數相加
題目描述:
請你用 javascript 實現兩個字串數字相加(大數相加)?
思路:
- 這道題考查兩個超過 js 最大數值的數相加,可運用小學數學加法規律實現. 個十百千萬...位相加,滿十進一。
參考答案:
function add(a, b) {
// 看看兩個字串長度相差多少,小的在前面補0, 如 10000 和 1, 補0後為 10000 和 00001
let leng = Math.abs(a.length - b.length);
if (a.length > b.length) {
b = Array(leng).join('0') + '0' + b;
} else if (a.length < b.length) {
a = Array(leng).join('0') + '0' + a;
}
// 將字串轉化為陣列並且倒裝,如同小學加法從個位開始算起
let textArrA = a.split('').reverse(),
textArrB = b.split('').reverse(),
resultArr = [];
// 對陣列進行迴圈
for (let i = 0; i < a.length; i++) {
// 求和,和小於10,則將和放進目標陣列,若大於10,將除以10將餘數放進目標陣列,然後textArrA陣列的下一位 + 1(textArrB陣列也可以,選一個即可)
let sum = parseInt(textArrA[i]) + parseInt(textArrB[i]);
// 這裡判斷是否是最高位數值相加,即i === a.length - 1, 如果是不用取餘直接放進去
if (parseInt(sum / 10) === 0 || i === a.length - 1) {
resultArr.push(sum);
} else {
resultArr.push(sum % 10);
textArrA[i + 1] = parseInt(textArrA[i + 1]) + 1;
}
}
// 最後將目標陣列倒裝一下,再轉成字串
return resultArr.reverse().join('');
}
console.log(add('1045747', '10')); // 1045757
複製程式碼
09-最大公約數&最小公倍數
最大公約數:能同時被兩數整除的最大數字
最小公倍數:能同時整除兩數的最小數字
思路:
- 獲取兩數孰大孰小,若是最大公約數,則從小值逐一遞減,找到第一個能被兩數同時整除的數即為最大公約數;若是最小公倍數,則從大值逐一遞乘,找到第一個能同時整除兩數的的數即為最小公倍數
參考答案:
// 最大公約數
function maxDivisor(num1, num2) {
let max = num1 > num2 ? num1 : num2,
min = num1 > num2 ? num2 : num1;
for (var i = min; i >= 1; i--) {
if (max % i == 0 && min % i == 0) {
return i;
}
}
}
console.log(maxDivisor(60, 30)); // 30
// 最小公倍數
function minDivisor(num1, num2) {
let max = num1 > num2 ? num1 : num2,
min = num1 > num2 ? num2 : num1,
result = 0;
// 這個迴圈,當兩數同為質數時,終止的最大條件值為 i = min
for (var i = 1; i <= min; i++) {
result = i * max;
if (result % max == 0 && result % min == 0) {
return result;
}
}
}
console.log(minDivisor(6, 8)); // 24
複製程式碼
10-驗證是否為迴文
什麼是迴文?
迴文指從左往右和從右往左讀到相同內容的文字。比如: aba,abba,level。
思路:
- 使用陣列方法生成倒裝的新字串與原字串對比
function isPalindrome(str) {
str = '' + str;
if (!str || str.length < 2) {
return false;
}
return (
Array.from(str)
.reverse()
.join('') === str
);
}
複製程式碼
- 通過倒序迴圈生成新字串與原字串對比
function isPalindrome(str) {
str = '' + str;
if (!str || str.length < 2) {
return false;
}
var newStr = '';
for (var i = str.length - 1; i >= 0; i--) {
newStr += str[i];
}
return str1 === str;
}
複製程式碼
- 以中間點為基點,從頭至中與從尾至中逐一字串進行對比,若有一個不同,則
return false
. 這種方法迴圈次數最少,效率最高
function isPalindrome(str) {
str = '' + str;
if (!str || str.length < 2) {
return false;
}
for (let i = 0; i < str.length / 2; i++) {
if (str[i] !== str[str.length - 1 - i]) {
return false;
}
}
return true;
}
複製程式碼