js常見演算法題

水果哥發表於2019-03-10

1.如下是tries樹的實現,請書寫結果。

var s = []
var arr = s
for (var i = 0; i < 3; i++) {

    var pusher = {
      value: 'item' + i  
    },tmp
    
    if (i !== 2) {
        tmp = []
        pusher.children = tmp
    }

    arr.push(pusher)
    arr = tmp // arr每次都等於上一次的children,js指標的移動,arr的指標從s移動到了tmp,快取了上次的children.
}
console.log(s[0]) // {value: "item0", children: [ {value: "item2"}]}

pusher.children = []
pusher = {value: 'item0',children: []  }
arr = [{value: 'item0',children: []  }]
arr = []
複製程式碼

2.分別簡述佇列、棧、堆的區別?

佇列是先進先出:就像一條路,有一個入口和一個出口,先進去的就先出去。

是後進先出:就像摞盤子一樣,摞在上邊的先被使用。

是在程式執行時,而不是在程式編譯時,申請某個大小的記憶體空間。即動態分配記憶體,對其訪問和對一般記憶體的訪問沒有區別。類似於錢包,可以用錢,用完了還回去。

  • 棧(Stack)是作業系統在建立某個程式時或者執行緒為這個執行緒建立的儲存區域。在程式設計中,例如C/C++中,所有的區域性變數都是從棧中分配記憶體空間,實際上也不是分配,只是從棧頂向上用就行,在退出函式的時候,只是修改棧指標就可以把棧中的內容銷燬,所以速度最快。

  • 堆(Heap)是應用程式在執行的時候請求作業系統分配給自己的記憶體,一般是申請/給與的過程。由於從作業系統管理的記憶體分配所以在分配和銷燬時都要佔用時間,所以用堆的效率低的多!但是堆的好處是可以做的很大,C/C++對分配的Heap是不初始化的。

總結:棧(stack)會自動分配記憶體空間,會自動釋放。堆(heap)動態分配的記憶體,大小不定也不會自動釋放。

3.基本型別和引用型別(接2)

基本型別:簡單的資料段,存放在棧記憶體中,佔據固定大小的空間。包括undefined、string、boolean、null、Number

引用型別:指那些可能有多個值構成的物件,儲存在堆記憶體中,包含引用型別的變數實際上儲存的不是變數本身,而是指向該物件的指標。

4.按值傳遞和按引用傳遞(接3)

從一個向另一個變數複製引用型別的值,複製的其實是指標,因此兩個變數最終指向同一個物件。即複製的事棧中的地址而不是堆中的物件。

從一個變數複製另一個變數的基本型別的值,會建立這個值的副本。

5.用JavaScript實現二分法查詢

二分法查詢,也稱折半查詢,是一種在有序陣列中查詢特定元素的搜尋演算法。查詢過程可以分為以下步驟:

(1)首先,從有序陣列的中間的元素開始搜尋,如果該元素正好是目標元素(即要查詢的元素),則搜尋過程結束,否則進行下一步。

(2)如果目標元素大於或者小於中間元素,則在陣列大於或小於中間元素的那一半區域查詢,然後重複第一步的操作。

(3)如果某一步陣列為空,則表示找不到目標元素。

二分查詢:A為已按‘升序排列’的陣列,x為要查詢的元素 返回目標元素的下標

function binarySearch(A, x) {
    var low = 0,high = A.length -1
    while (low <= high) {
        var mid = Math.floor((low + high) / 2)
        
        if (x == A[mid]) {
            return mid
        }
        
        if (x < A[mid]) {
            high = mid -1
        } else {
            low = mid + 1
        }
    }
    return -1
}
複製程式碼

6.用JavaScript實現陣列快速排序

關於快排演算法的詳細說明,可以參考阮一峰老是的文章快速排序“快速排序”的思想很簡單,整個排序過程只需要三步:

(1)在資料集之中,選擇一個元素作為“基準”(pivot)

(2)所有小於“基準”的元素,都移動到“基準”的左邊;所有大於“基準”的元素都移到“基準”的右邊

(3)對“基準”左邊和右邊的兩個子集,不斷重複第一步和第二步,知道所有子集只剩下一個元素為止。

方法一(儘可能不用js陣列方法):

function qSort(arr,low,high) {
    if(low < high) {
        var partKey = partition(arr,low,high)
        qSort(arr,low,partKey - 1)
        qSort(arr,partKey + 1, high)
    }
}
function partition(arr,low,high) {
    var key = arr[low]
    while(low < high) {
        while(low < high && arr[high] >= arr[key]) {
            high--
            arr[low] = arr[high]
        }
        
        while(low < high && arr[low] <= arr[key]) {
            low++
            arr[high] = arr[low]
        }
        arr[low] = key
        return low
    }
}
複製程式碼

方法二(使用js陣列方法):

function quickSort(arr) {
    if (arr.length <= 1) {
        return arr
    }
    var index = Math.floor(arr.length / 2)
    var key = arr.splice(index,1)[0]
    var left = [],right = []
    arr.forEach(function(v){
      v <= key ? left.push(v) : right.push(v)
    })
    return quickSort(left).concat([key],quickSort(right))
}
複製程式碼

方法三:遞迴法


複製程式碼

7.編寫一個方法,求一個字串的位元組長度

演算法題一般都是一成不變的

假設:一個英文字元佔用一個位元組,一箇中文字元佔用兩個位元組

function GetBytes(str) {
    var len = str.length
    var bytes = len
    for (var i = 0; i < len; i++) {
        if (str.charCodeAt(i) > 255) {
            bytes++
        }
    }
    return bytes
}
alert(GetBytes('你好,world'))
複製程式碼

8.找出下列正陣列的最大差值

輸入[20, 3, 11, 19, 16, 14]

竟然輸出 16,正確應該是17,原因待查

function getMaxProfit(arr) {
    var minPrice = arr[0] 
    var maxProfit = 0 
    
    for (var i = 0; i < arr.length; i++) {
        var currentPrice = arr[i]
        // 獲取最小值
        minPrice = Math.min(minPrice, currentPrice) 
        // 獲取當前值與最小值的差值
        var potentialProfit = Math.abs(currentPrice - minPrice)
        // 獲取差值中的最大值
        maxProfit = Math.max(maxProfit, potentialProfit)
        
    }
    return maxProfit
}
複製程式碼

9.判斷一個單詞是否是迴文

迴文:迴文指把相同的詞彙或句子,在下文中調換位置或者顛倒過來,產生首尾迴環的情況,叫做迴文,也叫回環。比如mamam 、redivider

function checkPalindrom(str) {
    return str == str.split('').reverse().join('')
}

// while loop
const isPalindromicB = (w) => {
    let len = w.length
    
    let start = Math.ceil(len / 2)
    
    while(start < len) {
        if (s[start] !== w[len - start - 1]) {
            return false
        }
        start++
    }
    return true
}
複製程式碼

10.如何消除一個陣列裡面重復的元素

基本陣列去重(最好)

Array.prototype.unique = function() {
    var result = []
    this.forEach(function(v){
        if(result.indexOf(v) < 0) {
            result.push(v)
        }
    })
    return result
}
複製程式碼

利用hash表去重,這是一種空間換時間的方法

Array.prototype.unique = function () {
    var result = [],hash = {}
    this.forEach(function(v) {
        if (!hash[v]) {
            hash[v] = true
            result.push(v)
        }
    })
    reutrn result
}
複製程式碼

上面的方法存在一個bug,對於陣列[1,2,'1','2',3],去重結果為[1,2,3],原因在於物件索引時會進行強制型別轉換,arr['1']和arr[1]得到的都是arr[1]的值,因此需做一些改變:

Array.prototype.unique = function () {
    var result = [],hash = {}
    this.forEach(function(v){
        var type = typeof(v) // 獲取元素型別
        hash[v] || (hash[v] = new Array())
        if (hash[v].indexOf(type) < 0) {
            hash[v].push(type) //儲存型別
            result.push(v)
        }
    })
    return result
}
複製程式碼

先排序後去重

Array.prototype.unique = function() {
    var result = [this[0]]
    this.sort()
    this.forEach(function(v){
      v != result[result.length -1] && result.push(v) // 僅與result最後一個元素比較
    })
}
複製程式碼

11.統計字串中字母個數或統計最多字母數

輸入: afijghiaaafsaadaes

輸出: a

統計字串中字母個數與統計最多字母數的思路相同

把每一道題的思路先寫出來

思路

  1. 統計字串中字母個數:我們需要遍歷所給的字串,先拿一個字元放到新空間中,然後再拿第二個,與第一個進行比較,如果相同則丟棄,如果不相同則放到新陣列中...,處理完則獲得了字串中的字母個數,將對應的長度返回即可。

  2. 統計最多字母數,並返回個數:比第一個多了個記錄對應個數的操作。找到相同的元素時,將對於的元素的計數器加1.找到最大的計數,並返回。

function findMaxDuplicateChar(str) {
    if (str.length == 1) {
        return str
    }
    let charObj = {}
    for(let i = 0; i< str.length; i++) {
        if (!charOjb[str.charAt(i)) {
            charObj[str.charAt(i)] = 1
        } else {
            charObj[str.charAt(i)] += 1
        }
    }
    let maxChar = ''
    maxValue = 1
    for(var k in charObj) {
        if (charObj[k] >= maxValue) {
            maxChar = k
            maxValue = charObj[k]
        }
    }
    return maxChar
}
複製程式碼

秒殺與陣列結合的個方法,找出陣列中相同的項,寫文章出來

12.隨機生成指定長度的字串 -. 整理Math的常用方法

function randomString(n) {
    let str = 'abcdefghijklmnopqrstuvwxyz9876543210'
    let tmp = '',i = 0, len = str.length
    
    for (i = 0; i < n; i ++) {
        tmp += str.charAt(Math.floor(Math.random() * len))
    }
    return tmp
}
複製程式碼

13.寫一個isPrime()函式,當其為質數時返回true,否則返回false.

最基礎的要把所有的偶數先排除掉。

質數:又稱素數。指整數在一個大於1的自然數中,除了1和此整數自身外,沒法被其他自然數整除的數。換句話說,只有兩個正因數(1和自己)的自然數即為素數。比1大但不是素數的數稱為合數。1和0既非素數也非合數。素數在數論中有著很重要的作用。質數的分佈規律是以36N(N+1)為單位,隨著N的增大,素數的個數以波浪形式漸漸增多。

首先,因為JavaScript不同於C或者Java,因此你不能信任傳遞來的資料型別。如果面試官沒有明確地告訴你,你應該詢問他是否需要做輸入檢查,還是不進行檢查直接寫函式。嚴格上說,應該對函式的輸入進行檢查。

第二點要記住,負數不是質數。同樣的,1和0也不是,因此,首先測試這些數字。此外,2是質數中唯一的偶數。沒有必要用一個迴圈來驗證4,6,8。再則,如果一個數字不能被2整除,那麼它不能被4,6,8等整除。因此,你的迴圈必須跳過這些數字。如果你測試輸入偶數,你的演算法將慢2倍(你測試雙倍數字)。可以採取其他一些更明智的優化手段,我這裡採用的是適用於大多數情況的。例如,如果一個數字不能被5整除,它也不會被5的倍數整除。所以,沒有必要檢測10,15,20等等。

最後一點,你不需要檢查比輸入數字的開方還要大的數字。我感覺人們會漏掉這一點,並且也不會因此而獲得消極的反饋。但是,展示出這一方面的知識會額外加分。

function isPrime(number) {
    if (typeof number !== 'number' || !Number.isInteger(number)) {
        return false
    }
    if (number < 2) {
        return false
    }
    if (number === 2) {
        return true
    } else if (number % 2 === 0) {
        return false
    }
    
    var squareRoot = Math.sqrt(number)
    for (var i = 3; i <= squareRoot; i += 2) {
        if (number % i === 0) {
            return false
        }
    }
    return true
}
複製程式碼

14.有100格臺階,可以跨1步可以跨2步。那麼一共有多少種走法

本質是斐波那契數列演算法(數學公式) 總會遇到自己不會的東西,不會了就去積累

方法一
function step() {
    this.n1 = 1
    this.n2 = 2
    this.total = 100
    this.getFunction = getFunction
}

var Stairs = new step()

function getFunction() {
    for (i = 2;i < this.tatal; i++) {
        res = this.n1 + this.n2
        this.n1 = this.n2
        this.n2 = res
    }
    return res
}
var totalStairs = Stairs.getFunction()
alert(totalStairs)

方法二
function fib (n) {
    let array = new Array(n + 1).fill(null)
    array[0] = 0
    array[1] = 1
    for (let i = 2; i <= n; i++) {
        array[i] = array[i - 1] + array[i - 2]
    }
    return array[n]
}
複製程式碼

15.連結串列與陣列的區別(資料結構)

  • 陣列 陣列是將元素在記憶體中連續存放,由於每個元素佔用記憶體相同,可以通過下標迅速訪問陣列中任何元素。但是如果要在陣列中增加一個元素。,需要移動大量元素,在記憶體中空出一個元素的空間,然後將要增加的元素放在其中。同樣的道理,如果想刪除一個元素,同樣需要移動大量元素去填掉被移動的元素。如果應用需要快速訪問資料,很少插入和刪除元素,就應該用陣列,
  • 連結串列 連結串列中的元素是不存在順序儲存的,而是通過存在元素中的指標聯絡在一起,每個節點包括兩個部分:一個是儲存資料元素的資料域,另一個是儲存下一個節點地址的指標。 如果要訪問連結串列中的一個元素,需要從第一個元素開始,一直找到需要的元素位置。但是增加和刪除一個元素對於連結串列資料結構就非常簡單了,只要修改元素中的指標就可以了。如果應用需要經常插入和刪除元素你就需要用連結串列。

記憶體儲存區別

  1. 陣列從棧中分配空間,對於程式設計師方便快速,但自由度小。
  2. 連結串列從堆中分配空間,自由度大,但申請管理比較麻煩。 邏輯結構區別
  3. 陣列必須實現定義固定的長度(元素個數),不能適應資料動態地增減的情況。當資料增加時,可能超出原先定義的元素個數;當資料減少時,造成記憶體浪費。
  4. 連結串列動態地進行儲存分配,可以適應資料動態增減的情況,且可以方便地插入、刪除資料項。(陣列中插入、刪除資料項時,需要移動其它資料項)

總結

  1. 存取方式上,陣列可以順序存取或隨機存取,而連結串列只能順序存取;

  2. 儲存位置上,陣列邏輯上相鄰的元素在物理邏輯上也相鄰,而連結串列不一定;

  3. 儲存空間上,連結串列由於帶有指標域,儲存密度不如陣列大;

  4. 按序號查詢時,陣列可以隨機訪問,時間複雜度為o(1),而連結串列不支援隨機訪問,平均需要o(n);

  5. 按值查詢時,若陣列無序。陣列和連結串列時間複雜度為o(n),但是當陣列有序時,可以採用折半查詢將時間複雜度降為o(logn);

  6. 插入和刪除時,陣列平均需要移動n/2個元素,而連結串列只需要修改指標即可;

  7. 空間分配方面: 陣列在靜態儲存分配情形下,儲存元素數量受限制,動態儲存分配情形下,雖然儲存空間可以擴充,但需要移動大量元素,導致操作效率降低,而且如果記憶體中沒有更大塊連續儲存空間將導致分配失敗;

    連結串列儲存的節點空間值在需要的時候申請分配,只要記憶體中有空間就可以分配,操作比較靈活高效;

  8. 經典資料結構涵蓋了多種抽象資料型別(ADT),其中包括棧、佇列、有序列表、排序表、雜湊表及分散表、樹、優先佇列、集合和圖等。對於每種情況,都可以選用陣列或某一種連結串列資料結構來實現其抽象資料型別(ADT)。由於陣列和連結串列幾乎是建立所有ADT的基礎,所以稱陣列與連結串列為基本資料結構。

相關文章