常見的手寫面試題: 方法實現、算法

實現簡易版方法

call
  • 參數: 一個或多個參數;第一個爲對象,後面若干爲數據。
  • 執行方式: 馬上執行。
  • 實現原理: 在對象上添加一個屬性,屬性的值爲當前對象,這樣能夠改變當前對象的指向了。

舉例子node

function showName() {
    console.log(name);
}
let person = {
    name: 'mochixuan',
    age: 20
}
person.temp = showName;
person.temp(); //調用
複製代碼

實現正則表達式

Function.prototype.call1 = function () {
    // 數組解構,獲取參數
    let [thisArgs,...args] = [...arguments];
    thisArgs = thisArgs || window;
    // 使用Symbol能夠防止原有屬性被替換。
    const tempArg = Symbol('call1');
    thisArgs[tempArg] = this;
    let result;
    if (args == undefined) {
        result = thisArgs[tempArg]()
    } else {
        result = thisArgs[tempArg](...args)
    }
    delete thisArgs[tempArg]
    return result;
}
複製代碼
apply
  • 參數: 參數一個或多個參數,第一個爲對象,後面爲數組的數據。
  • 執行方式: 馬上執行。
  • 實現原理: 在對象上添加一個屬性,屬性的值爲當前對象,這樣能夠改變當前對象的指向了。
Function.prototype.apply1 = function (thisArgs,args) {
    thisArgs = thisArgs || window;
    const tempArg = Symbol('apply1');
    thisArgs[tempArg] = this;
    let result;
    if (args == undefined) {
        result = thisArgs[tempArg]()
    } else {
        result = thisArgs[tempArg](...args)
    }
    delete thisArgs[tempArg]
    return result;
}
複製代碼
bind
  • 參數: 參數一個或多個參數,第一個爲對象,後面爲的數據。
  • 執行方式: 返回函數,函數被調用後執行。
  • 實現原理: 生成一個函數,函數內部使用call改變this指向。
  • 注意事項: 生成的bind函數,若是使用new進行實例化,this仍是指向原來的對象。
Function.prototype.bind1 = function (thisArg) {
    if (typeof this !== 'function') return new TypeError(this+'must be function')
    const arg1 = Array.prototype.slice.call(arguments,1);
    const self = this;
    const bound = function (args) {
        const arg2 = Array.prototype.slice.call(arguments);
        const allArgs = arg1.concat(arg2);
        if (this instanceof bound) {
            if (self.prototype) {
                function TempFC() {}
                TempFC.prototype = self.prototype;
                bound.prototype = new TempFC();
            }
            return self.apply(this,allArgs);
        } else {
            return self.apply(thisArg,allArgs);
        }
    }
    return bound;
}
複製代碼
深拷貝
  • 實現原理: 遞歸。
  • 注意事項: 1. 反正循環引用形成死循環。2. Date、正則表達式、函數要進行特殊處理這裏就不考慮了。
  • 建議項目開發使用:lodash裏的方法。

死循環例子算法

let a = {x: 1,y: 2};
a.z = a;
複製代碼
// Map: Map對象保存鍵值對,相似於數據結構字典;與傳統上的對象只能用字符串當鍵不一樣,Map對象可使用任意值當鍵
// WeakMap: 對象保存鍵值對,與Map不一樣的是其鍵必須是對象,由於鍵是弱引用,在鍵對象消失後自動釋放內存.
function deepCopy(data,map = new WeakMap()) {
    if (!data || typeof data !== 'object') return data; // null也是object

    const result = Array.isArray(data) ? [] : {};

    // 解決死循環問題
    if (map.has(data)) {
        return map.get(data);
    } else {
        map.set(data,result);
    }

    for (let key in data) {
        if (key != null && data.hasOwnProperty(key)) { //原型鏈上的可枚舉屬性,去除原型鏈上的數據
            if (typeof data === 'object') {
                result[key] = deepCopy(data[key],map);
            } else {
                result[key] = data[key];
            }
        }
    }

    return result;
}
複製代碼
淺比較: PureComponent裏用到了
  • 實現原理: 對象則比較第一層數據,基本數據類型直接比較,對象比較引用。
// 該方法會忽略掉那些從原型鏈上繼承到的屬性
const hasOwnProperty = Object.prototype.hasOwnProperty 
function is(x, y) {
    // === 嚴格判斷適用於對象和原始類型。可是有個例外,就是NaN和正負0。
    if (x === y) {
        //這個是個例外,爲了針對0的不一樣,譬如 -0 === 0 : true
        // (1 / x) === (1 / y)這個就比較有意思,能夠區分正負0, 1 / 0 : Infinity, 1 / -0 : -Infinity
        return x !== 0 || y !== 0 || 1 / x === 1 / y
    } else {
        // 這個就是針對上面的NaN的狀況: parseInt('abc')  // NaN
        return x !== x && y !== y
    }
}


function shallowEqual(objA, objB) {

    // Object.is()
    if (is(objA, objB)) {
        return true;
    }

    // null 也是對象
    //下面這個就是,若是objA和objB其中有個不是對象或者有一個是null, 那就認爲不相等。
    //不是對象,或者是null.咱們能夠根據上面的排除來猜測是哪些狀況:
    //有個不是對象類型或者有個是null,那麼咱們就直接返回,認爲他不一樣。其主要目的是爲了確保兩個都是對象,而且不是null。
    if (typeof objA !== 'object' || objA === null || typeof objB !== 'object' || objB === null) {
        return false
    }

    var keysA = Object.keys(objA);
    var keysB = Object.keys(objB);

    if (keysA.length !== keysB.length) {
        return false;
    }

    //這裏只比較了對象A和B第一層是否相等
    for (var i = 0; i < keysA.length; i++) {
        if (!hasOwnProperty.call(objB, keysA[i]) || !is(objA[keysA[i]], objB[keysA[i]])) {
            return false;
        }
    }

    return true;
}
複製代碼

常見算法

冒泡排序
  • 原理: 比較相鄰的元素。若是第一個比第二個大,就交換他們兩個.
  • 時間複雜度: O(n^2)
function bubbleSort(items) {
    for (let i = 0 ; i < items.length - 1; i++) {
        for (let j = 0 ; j < items.length - 1 - i ; j++) {
            if (items[j] > items[j+1]) {
                let temp = items[j];
                items[j] = items[j+1];
                items[j+1] = temp;
            }
        }
    }
    return items;
}
複製代碼
選擇排序
  • 規則: 在要排序的一組數中,選出最小的一個數與第一個位置的數交換;而後在剩下的數當中再找最小的與第二個位置的數交換,如此循環到倒數第二個數和最後一個數比較爲止
  • 時間複雜度O(n^2)
  • 交換次數比冒泡排序少,性能上要好於冒泡排序
function selectSort(items) {
    for (let i = 0 ; i < items.length ; i++) {
        let tempIndex = i;
        for (let j = i+1 ; j < items.length ; j++) {
            if (items[j] < items[tempIndex]) {
                tempIndex = j;
            }
        }
        if (i != tempIndex) {
            let temp = items[i];
            items[i] = items[tempIndex];
            items[tempIndex] = temp;
        }
    }
    return items;
}
複製代碼
快速排序
  • 原理: 二分法,遞歸實現,每次獲得一個正確的位置。
  • 時間複雜度: O(nlogn).
function quick(items,low,high) {
    if (low == null && high == null) {
        low = 0;
        high = items.length - 1;
    }
    if (low >= high) return items;
    let middle = getMiddle(items,low,high);
    if (middle > low) quick(items,low,middle-1);
    if (middle < high) quick(items,middle+1,high)
    return items;
}

function getMiddle(items,start,end) {
    let temp = items[start];
    while (start < end) {
        while (start < end && items[end] >= temp) --end;
        items[start] = items[end];
        while (start < end && items[start] <= temp)  ++start;
        items[end] = items[start];
    }
    items[start] = temp;
    return start;
}
複製代碼
二叉樹查找: 最大值、最小值、固定值
  • 原理: 遍歷
  • 時間複雜度
function queryMinMaxFix(node,fix,result) {

    if (result == null) result = {};

    if (node != null) {
        if (result.min == null) result.min = node.value;
        if (result.max == null) result.max = node.value;
        if (result.fix == null) result.fix = [];

        if (node.value < result.min) result.min = node.value;
        if (node.value > result.max) result.max = node.value;
        if (node.value == fix) result.fix.push(node);

        if (node.left != null) queryMinMaxFix(node.left, fix, result);
        if (node.right != null) queryMinMaxFix(node.right, fix, result);
    }

    return result;
}
複製代碼
二叉樹遍歷
  • 原理: 遞歸
function traversal(node,tempOrderTraversal) {
    if (node != null) {
        // tempOrderTraversal.push(node.value) 前序遍歷
        if (node.left != null) {
            preOrderTraversal(node.left,tempOrderTraversal)
        }
        // tempOrderTraversal.push(node.value) 中序遍歷
        if (node.right != null) {
            preOrderTraversal(node.right,tempOrderTraversal)
        }
        // tempOrderTraversal.push(node.value) 後序遍歷
    }
}
複製代碼

不能使用遞歸時,則使用棧就是JS的數組push、pop數組

// 非遞歸遍歷
var kthSmallest = function(root, k) {
    const tempArr = [];
    let result;
    tempArr.push(root);
    while (tempArr.length > 0) {
        result = tempArr.pop();
        if (result.value == k) break;
        if (result.left != null) tempArr.push(result.left);
        if (result.right != null) tempArr.push(result.right);
    }
    return result;
};

複製代碼
二叉樹的最大深度
  • 最大深度是從根節點到最遠葉節點的最長路徑上的節點數
  • 原理: 遍歷
var maxDepth = function(root) {
    let nodeArrs = [];
    if (root != null) nodeArrs.push(root);
    
    let result = 0;
    while(nodeArrs.length > 0) {
      result++;  
      let temp = [];  
      for(let i = 0 ; i < nodeArrs.length ; i++) {
          if (nodeArrs[i].left != null) temp.push(nodeArrs[i].left);
          if (nodeArrs[i].right != null) temp.push(nodeArrs[i].right);
      } 
      nodeArrs = temp;  
   }     
   return result;
};
複製代碼
給予鏈表中的任一節點,把它刪除掉
  • 編寫一個函數來刪除單鏈表中的節點(尾部除外),只容許訪問該節點
var deleteNode = function(node) {
    if (node.next != null) {
        node.val = node.next.val;
        node.next = node.next.next
    }
};
複製代碼
鏈表倒敘
  • 原理: 借用臨時數據。
  • 時間複雜度: n
function reserveLink(head) {
    let result = null;
    let temp = null;
    while (head) {
        temp = head.next;
        head.next = result;
        result = head;
        head = temp;
    }
    return result;
}
複製代碼
如何判斷一個單鏈表有環
  • 原理:使用兩個臨時指針,一個指針每次加一.next,一個每次加二.next.next,當它們相等時就是有環,通常會在一次循環後相遇若是有環的化。
  • 時間複雜度: n
  • 常看法法: Set存儲每一個Node節點,has存在時則閉環,不存在則沒必要環
function hasRing(root) {
    if(root == null) return false;
    let oneNode = root;
    let twoNode = root;
    while(twoNode) {
        if (twoNode.value === oneNode.value) {
            return true;
        }
        oneNode = oneNode.next;
        if (twoNode.next != null) {
            twoNode = twoNode.next.next;
        }
    }
    return false;
}
複製代碼
給定一個有序數組,找出兩個數相加爲一個目標數
  • 原理: 用兩個指針。通常手寫出現比較多的是三個數相加等於一個數
  • 時間複雜度: n
let data = [1,3,5,9,11];
let target = 8;
output: 3,5 或者是下標
複製代碼
function(data,target) {
    let i = 0;
    let j = data.length - 1;
    for( ; i < j ;) {
        if (data[i] + data[j] === target) {
            break;
        } else if (data[i] + data[j] > target) {
            j--;
        } else {
            j++;
        }
    }
    return [i,j];
}
複製代碼
找出一個無序數組中出現超過一半次數的數字
  • 原理: 超過一半有個特色,假設超過通常的那個數爲X,則X>n/2,能夠抵消法,同性相加,異性相抵。
function (data) {
    let x = data[0];
    let total = 0;
    for(let i = 0 ; i < data.length ; i++) {
        if (x === data[i]) {
            total = total + 1;
        } else {
            total = total - 1;
        }
        
        if (total === 0 && i < data.length - 1) {
            x = data[i+1];
        }
    }
    return x;
}
複製代碼

後續慢慢再加

相關文章
相關標籤/搜索