文章將會分紅上中下三部分,包含一些常見面試算法題,大部分算法題來自於《劍指offer》,在此對此書的做者表示感謝,還有一部分來自於本人的收集。題目解法有多種,望大蝦多多評論探討或指正node
題述: 一個數組中,每一行都按照從左至右遞增的順序排序,每一列按照從上到下遞增的順序排序。完成:輸入一個這樣的二維數組和一個整數,判斷數組是否含有這個整數git
思路:數組是有序的,能夠根據規律減小遍歷次數。從右上角開始遍歷,若是這個數不等,那麼能夠根據目標數與這個數的大小能夠去掉一些行/列的遍歷github
function findNumInSortedArray(arr, num) {
if (!Array.isArray(arr) || typeof num != 'number' || isNaN(num)) {
return;
}
let rows = arr.length;
let columns = arr[0].length;
let row = 0;
let column = columns -1;
while(row < rows && column >=0 ){
if (arr[row][column] == num) {
return true;
} else if (arr[row][column] > num) {
column --;
} else {
row ++ ;
}
}
return false;
}
複製代碼
題述:實現一個函數,將字符串中的每一個空格替換成%20。如輸入'we arr happy', 則輸出'we%20are%20happy'面試
思路:可使用正則替換與遍歷替換兩種方式算法
//使用正則
function replaceStr(str){
if (typeof str !== 'string') {
console.log('str is not string');
return;
}
return str.replace(/\s/g, '%20')
}
//使用遍歷替換,須要遍歷str,識別空格而後替換字符串
function replaceStr2(str) {
if (typeof str !== 'string') {
console.log('str is not string');
return;
}
let strArr = [];
let len = str.length;
let i = 0;
while(i < len) {
if (str[i] === ' ' ) {
strArr[i] = '%20';
} else {
strArr[i] = str[i];
}
}
return strArr.join('');
}
複製代碼
題述:輸入一個鏈表的頭結點,從尾到頭打印每一個節點的值數組
思路:能夠將鏈表翻轉,再打印,但會破壞鏈表的結構。還能夠用棧存儲節點,而後打印緩存
function displayLinkList(head) {
let stack = [];
let node = head;
while(node) {
stack.push(node);
node = node.next;
}
for (let len = stack.length - 1; len >=0 ; len--) {
console.log(stack[i].ele);
}
}
複製代碼
題述:輸入某二叉樹的前序遍歷和中序遍歷的結果,請重建出該二叉樹。假設輸入的前序遍歷和中序遍歷的結果中都不含重複的數字。例如輸入前序遍歷序列{1,2,4,7,3,5,6,8}和中序遍歷序列{4,7,2,1,5,3,8,6},則重建二叉樹並返回。bash
思路:在二叉樹的前序遍歷中,第一個數字老是樹的根節點的值,在中序遍歷中,根結點的值在序列的中間。找根節點,肯定左右子樹,而後遞歸循環,關鍵是依次掛載'根'節點(肯定其在左仍是右)。前序肯定根節點,中序肯定左右節點app
//節點定義
function TreeNode(ele) {
this.ele = ele;
this.right = null;
this.left = null;
}
function constructBinaryTree(preOrders, inOrders) {
if (!inOrders.length) {
return null;
}
let rootIndex = 0;
let l_preOrders = [];
let l_inOrders = [];
let r_preOrders = [];
let r_inOrders = [];
//肯定根節點
let head = new TreeNode(preOrders[0]);
for (let i = 0; i < inOrders.length; i++ ) {
if (preOrders[0] === inOrders[i]) {
rootIndex = i;
}
}
//肯定左右子節點樹
for (let i = 0; i < rootIndex; i++) {
l_preOrders.push(preOrders[ i + 1]);
l_inOrders.push(inOrders[i]);
}
for (let i = rootIndex + 1; i < inOrders.length; i ++ ) {
r_preOrders.push(preOrders[i]);
r_inOrders.push(inOrders[i]);
}
head.left = constructBinaryTree(l_preOrders, l_inOrders);
head.right = constructBinaryTree(r_preOrders, r_inOrders);
return head;
}
function getTreeFromPreInOrders(preOrders, inOrders) {
if (Array.isArray(preOrders) && Array.isArray(inOrders)) {
return constructBinaryTree(preOrders, inOrders);
}
console.error('preOrders or inOrders is no Array');
}
複製代碼
棧:先進後出, 隊列:先進先出函數
題述:用兩個棧實現隊列
思路:棧a的數據所有依次放到棧b,那麼原先早進入棧a的數據會出如今棧b棧頂的位置, 那麼隊列的出隊,至關於棧b的出棧,隊列的入隊,至關於棧a的入棧。當棧b爲空時,將棧a的數據所有出棧到棧b
let stack_a = [];
let stack_b = [];
function push (node ) {
stack_a.push(node);
}
function pop () {
if (stack_b.length === 0 ) {
for (let i = 0, len = stack_a.length; i < len; i ++ ) {
stack_b.push(stack_a.pop());
}
}
return stack_b.pop();
}
複製代碼
題述:使用兩個隊列實現棧
思路:兩個隊列,拿一個隊列作存儲區,有數據的隊列依次出隊數據到緩存隊列,那麼當有數據的隊列出到最後一個數據時,便是須要出棧的數據。入棧的數據入隊到有數據的隊列,若是兩個爲空,任取一個入隊
let queue_a = [];
let queue_b = [];
function push(node) {
if (queue_a.length && queue_b.length) {
return console.log('wrong !');
}
if (queue_a.length) {
queue_a.push(node);
} else if (queue_b.length) {
queue_b.push(node);
} else {
queue_a.push(node);
}
}
function pop() {
if (queue_a.length && !queue_b.length) {
for (let i = 0, len = queue_a.length; i < len; i++) {
if (i == len -1) {
return queue_a.shift();
} else {
queue_b.push(queue_a.shift());
}
}
} else if (!queue_a.length && queue_b.length) {
for (let i = 0, len = queue_b.length; i < len; i++) {
if (i == len -1) {
return queue_b.shift();
} else {
queue_a.push(queue_b.shift());
}
}
} else if (queue_a.length && queue_b.length) {
console.log('wrong!');
} else {
return null;
}
return null;
}
複製代碼
題述:把一個數字最開始的若干個元素搬到數組的末尾,稱之爲數組的旋轉。輸入一個遞增排序的數組的一個旋轉,輸出旋轉數組的最小元素。例如數組{3,4,5,1,2}爲{1,2,3,4,5}的一個旋轉,該數組的最小值爲1
思路:遞增有序找最值,能夠嘗試二分法。數組第一個元素確定會比最後一個元素大,選擇中間元素,與末尾元素比較,若是大於末尾元素則表示最小元素在右區間,不然在左區間
function findMinFromRotateArr(arr) {
if (!Array.isArray(arr)) {
return console.error('wrong!')
}
let start = 0;
let end = arr.length - 1;
while((end - start) > 1) {
let mid = Math.floor(((end + start)) / 2) ;
if (arr[mid] >= arr[end]) {
start = mid;
} else {
end = mid;
}
}
return arr[end];
}
複製代碼
題述:當n = 0,f(n) = 0;當n = 1, f(n) = 1;當n > 1, f(n) = f(n-1) + f(n-2)。如今要求輸入一個整數n,請你輸出斐波那契數列的第n項
思路:斐波那契數列是一個經典數學題。能夠採用遞歸與循環方式解決,注意遞歸下,若是n比較大時,會產生很大內存消耗
//遞歸解法
function fibonacci(n) {
if (n <= 0) {
return 0;
}
if(n == 1) {
return 1;
}
return fibonacci(n - 2) + fibonacci(n-1);
}
//循環解法
function fabonacci(n) {
if (n <= 0) {
return 0;
}
if(n == 1) {
return 1;
};
let fn_2 = 0;
let fn_1 = 1;
let fn = 0;
for (let i = 2; i <= n; i++) {
fn = fn_1 + fn_2;
fn_2 = fn_1;
fn_1 = fn;
}
return fn;
}
複製代碼
斐波那契變題1 題述:一隻青蛙一次能夠跳上1級臺階,也能夠跳上2級。求該青蛙跳上一個n級的臺階總共有多少種跳法。
思路:n個臺階跳法-> f(n), 假如其第一次跳一級,那麼接下來是跳法是f(n-1),假如第一次跳2級,那麼跳法是f(n-2)。那麼f(n) = f(n-1) + f(n-2),就是一個斐波那契數列
斐波那契變題2 題述:題述:[] 這是 2x1的矩形,能夠橫着或者豎着擺放,那麼其覆蓋 8*2x1這樣的小矩形有多少種擺法
//大矩形:[][][][][][][][]
// [][][][][][][][]
複製代碼
思路:若是豎着擺,那麼會佔去1列,若是橫着擺,一種擺法會佔去2列,那麼從8列的矩形,第一次擺放的時候,要麼豎着擺,接着覆蓋7列矩形,要麼橫着擺,接着覆蓋6列矩形。從而能夠抽象成 f(8) = f(7) + f(6)。仍是一個斐波那契問題
js中的位運算:&(與), |(或) , ~(非) ,^(異或), <<(左移), >>(右移), >>>(無符號右移)
題述:輸入一個整數,輸出該數二進制表示中1的個數。其中負數用補碼錶示。
思路:可使用右移與位與運算。判斷整數的二進制數的最右側數是否是1(和1與),而後右移,直至爲0
//缺陷版:
//缺陷在於不能針對負數狀況。由於帶符號的數字,其二進制最高位有一個數字爲符號標誌,負數爲1
function numOf1(n) {
if(n.toString().indexOf('.') != -1) {
return console.error('n is not a int');
}
let num = 0;
while(n) {
if (n & 1) {
num ++ ;
}
n = n >> 1;
}
return num;
}
//改進:將1進行左移與i比較,這樣來判斷i二進制各個位是否是1
//若是是32位存儲,那麼會循環32次
function numOf1(n){
if(n.toString().indexOf('.') != -1) {
return console.error('n is not a int');
}
let nums = 0;
let flag = 1;
while(flag) {
if(flg & n) {
nums ++;
}
flag = flag << 1;
}
return nums;
}
//究極版:這個的原理是 一個二進制與其減去1的二進制進行位與運算後,產生的數與原先的二進制數相比,
//從右邊看會少去一個1。問題能夠簡化到二進制數有多少個1,就會進行以上多少次的循環,這個是效率最高的
function numsOf1(n) {
if(n.toString().indexOf('.') != -1) {
return console.error('n is not a int');
}
let nums = 0;
while(n) {
nums ++ ;
n = (n - 1) & n;
}
return nums;
}
複製代碼
題述:給定一個double類型的浮點數base和int類型的整數exponent。求base的exponent次方。不使用庫函數
思路:解題的第一反應是用for循環累加乘積,但可能忽略一些狀況:輸入的0值與負整數次冪。還有如何減小遍歷次數
function power(base, exponent) {
if (base == 0 && exponent < 0) {
return console.error('base should not be 0');
}
let absExponent = exponent < 0 ? -exponent : exponent;
let result = 1;
for (let i = 1; i <= absExponent; i++) {
result *= base;
}
if (exponent < 0) {
result = 1 / result;
}
return result;
}
//使用遞歸減小乘積次數
//使用位與運算可判斷奇偶, 整數右移一位可取數除2的整數
//能夠經過互乘減小運算次數,如 數的8次方是數的4次冪的2次冪,數的4次冪是數的2次冪的2次冪 ...
function power (base, exponent) {
if (exponent == 0) {
return 1;
}
if (exponent == 1) {
return base;
}
let result = power(base, exponent >> 1);
result *= result;
//爲奇數
if (exponent & 1 == 1) {
result *= base;
}
return result;
}
複製代碼
題述:定義一個刪除節點的函數,傳參爲頭結點與待刪除節點,要求時間複雜度爲O(1)。
思路:常規鏈表刪除,會循環遍歷到待刪除節點,而後將其前一個節點指向其後一個節點。可是每次刪除須要遍歷,時間複雜度爲O(n)。若是直接將待刪除節點的下一個節點的值賦予給待刪除節點,而後刪除這個下一個節點,不是就至關於刪除了麼。
function deleteNode(headNode, deleteNode) {
if (!headNode || !deleteNode) {
return ;
}
//刪除的節點是頭結點
if (headNode == deleteNode) {
headNode = null;
}
//刪除的節點是尾節點
else if (deleteNode.next == null) {
let node = headNode;
while(node.next != deleteNode) {
node = node.next;
}
node.next = null;
deleteNode = null;
}
//刪除的節點是中間節點
else {
let nextNode = delete.next;
deleteNode.ele = nextNode.ele;
deleteNode.next = nextNode.next;
nextNode = null;
}
}
//總體時間:[(n-1)O(1) + O(n)]/n -> O(1)
複製代碼
題述:輸入一個整數數組,實現一個函數來調整該數組中數字的順序,使得全部的奇數位於數組的前半部分,全部的偶數位於位於數組的後半部分。
思路:常規下能夠遍歷數組,若是數是偶數,能夠將數拿出放到數組最後面,其後面的數字前移一位。同時也可使用兩個指針,一個指向數組頭p1,一個指向數組尾p2,若是p1指向偶數,p2指向奇數,則雙方對調,這樣會出現4種狀況,依次處理便可。
function reOrderArray(arr)
{
// write code here
if (!Array.isArray(arr)) {
return ;
};
let start = 0;
let end = arr.length - 1;
while(start <= end) {
let isOddS = arr[start] & 1;
let isEvenE = !(arr[end] & 1);
if (isOddS && !isEvenE) {
start ++;
} else if (isOddS && isEvenE) {
start ++;
end --;
} else if(!isOddS && isEvenE) {
end --;
} else {
let temp = arr[start];
arr[start] = arr[end];
arr[end] = temp;
start ++ ;
end --;
}
}
return arr;
}
複製代碼
題述:輸入一個鏈表,輸出該鏈表中倒數第k個結點。
思路:通常想法能夠第一次遍歷鏈表獲得其長度,而後倒數第k個節點,那麼則是第n+1-k個節點,而後第二次遍歷鏈表便可得出,這樣的缺點是須要遍歷鏈表兩次。遍歷一次鏈表的作法:取兩個指針,一個指針指向頭節點,另一個指針指向第k-1個節點,而後兩個指針同時遍歷,當第二個指針指向鏈表尾的時候,那麼第一個指針會指向導數第k個節點
//注意邊界狀況:頭結點爲空,節點數小於k個,k不大於0
function findKthToTial (head, k) {
if (!head || k <= 0) {
return null;
}
let startNode = head;
let endNode = head;
for (let i = 0; i < k - 1; i++) {
if (!endNode.next) {
return null;
}
endNode = endNode.next;
}
while(endNode.next) {
startNode = startNode.next;
endNode = endNode.next;
}
return startNode;
}
複製代碼
題述:輸入一個鏈表,反轉鏈表後,輸出新鏈表的表頭。
思路:遍歷鏈表,將下一個節點指向前一個節點
function resverseList(head) {
if (!head) {
return null;
}
if (head.next == null) {
return head;
}
let node = head;
let nextNode = null;
let reservedNode = null;
let newHead = head;
while (node.next) {
nextNode = node.next;
reservedNode = nextNode.next;
nextNode.next = newHead;
node.next = reservedNode;
newHead = nextNode;
}
return newHead;
}
複製代碼
題述:輸入兩個單調遞增的鏈表,輸出兩個鏈表合成後的鏈表,固然咱們須要合成後的鏈表知足單調不減規則
思路:依次去取兩個鏈表的節點進行比較
function mergeLinkList(head1, head2) {
if (head1 == null) {
return head2;
}
if (head2 == null) {
return head1;
}
let mergeHead = null;
if (head1.ele < head2.ele ) {
mergeHead = head1;
mergeHead.next = mergeLinkList(haed1.next, head2);
} else {
mergeHead = head2;
mergeHead.next = mergeLinkList(head1, head2.next);
}
return mergeHead;
}
複製代碼
輸入兩顆二叉樹A和B,判斷B是否是A的子結構。
思路:先找A包含B的根節點,而後根據該節點比較左右子樹
//樹節點定義
function Node(ele) {
this.ele = ele;
this.left = null;
this.right = null;
}
//判斷樹A有樹B
function hasSubTree(pRootA, pRootB) {
if(pRootA == null || pRootB == null) {
return false;
}
let result = false;
if (pRootA.ele === pRootB.ele) {
result = doesTreeAHaveTreeB(pRootA, pRootB);
}
if (!result) {
result = hasSubTree(pRootA.left, pRootB);
}
if (!result) {
result = hasSubTree(pRootA.right, pRootB)
}
return result;
}
function doesTreeAHaveTreeB(pRootA, pRootB) {
//先要判斷 pRootB
if (pRootB == null) {
return false;
}
if(pRootA == null) {
return true;
}
if (pRootA.ele != pRootB.ele) {
return false;
}
return doesTreeAHaveTreeB(pRootA.left, pRootB.left) && doesTreeAHaveTreeB(pRootA.right, pRootB.right)
}
複製代碼
題述:完成一個函數,輸入一個二叉樹,該函數輸出它的鏡像
思路:進行前序遍歷,對於非葉子節點,有兩個節點,則將其對換
function mirror(root) {
if (root == null) {
return ;
}
let temp = root.left;
root.left = root.right;
root.right = temp;
if (root.left) {
mirror(root.left);
}
if (root.right) {
mirror(root.right);
}
}
複製代碼
題述:/輸入一個矩陣,按照從外向裏以順時針的順序依次打印出每個數字,例如,若是輸入以下矩陣: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 則依次打印出數字1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,10。
思路:關鍵在於循環打印的條件在於 列數 > 開始打印的列數x2 ,並且 行數 > 開始打印的行數x2
function printMatrix (arr) {
if (!Array.isArray(arr)) {
return;
}
let rows = arr.length;
let columns = arr[0].length;
let start = 0;
while( columns > start * 2 && rows > start * 2) {
printMatrixInCicle(arr, columns, rows, start);
start ++ ;
}
}
function printMatrixInCicle (arr, columns, rows, start) {
let endX = columns - 1 - start;
let endY = rows -1 - start;
//從左到右打印一行
for (let i = start; i <= endX; ++i) {
console.log(arr[start][i]);
}
//從上到下打印一列
if (start < endY) {
for (let i = start + 1; i <= endY; ++ i) {
console.log(arr[endY][i]);
}
}
//從右向左打印一行
if (start < endX && start < endY) {
for (let i = endX -1 ; i >= start; --i) {
console.log(arr[endY][i]);
}
}
//從下到上打印一行
if (start < endX && start < endY - 1) {
for (let i = endY -1 ; i >= start + 1; --i) {
console.log(arr[i][start]);
}
}
}
複製代碼
各位觀衆老爺,若是以爲能夠的話,biaozhiwang.github.io star下