1. Linear Search (Sequential Search)
目的:有一個未知長度的陣列,要在這串陣列中找尋某個數字,如果他存在的話他的 index 是多少 ?
- It's an algorithm that sequentially checks(一步一步地) each element of the list until a match is found or the whole list has been searched.
- Probably the easily algorithm we'll learn in the course.
- In our coding practice, we'll try to find a number inside an array. If the number could be found, then return the index. If not, then return -1.
Pseudocode for Linear Search
Linear_Search(array, n) :
for i from 0 to array.length-1:
if (array[i] === n):
return i
return -1
Code for Linear Search
function linearSearch(arr, n) {
for (let i = 0; i < arr.length; i++) {
if (arr[i] === n) {
console.log("Found number " + n + " at index " + i)
return i
}
}
console.log('can not find' + n)
return -1
}
linearSearch(numbers, 33)
Overview of Linear Search
- Worst case performance:O(n) -> 要找的在陣列的最後一個位置 ie index = arr.length-1
- Best case performance:O(1) -> 要找的在陣列的第一個位置,ie index = 0
- Average performance:O(n/2)
2. Binary Search
對於 sorted array,更好的找法是將這個 array 拆成一半,一半裡面再去比較要找的數字在左半邊還是右半邊。
- Binary search is a search algorithm that finds the position of a target value within a sorted array.
- More efficient than linear search, but only works with sorted data set.
- In our coding practice, we'll have a sorted array, and one number we want to find inside the sorted array. If the number could be found, then return the index. If not, then return -1.
Pseudocode for Binary Search
先去抓出這個 sorted array 的第一格(min)與最後一格(max)。
Binary_Search(array, n) :
min = 0
max = array.length - 1
while min < = max :
middle = (min + max) /2
if num > array[middle]
min = middle + 1
else if num < array[middle]
max = middle - 1
else
return middle
return -1
Code for Binary Search
最多需要步數:log 以 2 為底,指數是陣列的長度
function binarySearch(array, number) {
let min = 0
let max = array.length - 1
let step = 0
while(min <= max) {
step ++
let middle = Math.floor((min + max)/2)
if (array[middle] < number) {
min = middle + 1
} else if (array[middle] > number) {
max = middle -1
} else if (array[middle] === number) {
console.log(`Found it after ${step} steps.`)
console.log(`Found number ${number} at position ${middle}`)
return middle
}
}
console.log(`Not found number ${number} in the sorted array`)
return -1
}
Overview of Binary Search
- Worst case performance:O(log(n)) -> f(n) = log 以 2 為底,指數是 n,又 big O 不在意底數,所以就會變成 O(log(n))
- Best case performance:O(1) -> 要找的在陣列的中央
- Average performance:O(log(n)) -> 絕大多數情況下都是 log 以 2 為底,指數是 n
3. General guides to algorithm Design
Implement some human thinking in algorithms.
Don't make the computers do dumb calculations or stupid things just because computers can do it.
用人腦的方式想想,先不要用複雜的迴圈解。
Intersection Problem
外層有迴圈(長度 n)、內層也有迴圈(長度 m),所以 intersection 的演算法他的 big O 是 n * m
function intersection(arr1, arr2) {
let result = []
for (let i=0; i<arr1.length; i++) {
for (let j=0; j<arr2.length; j++) {
if (arr1[i] === arr2[j]) {
result.push(arr1[i])
}
}
}
console.log('result', result)
return result
}
Counter
- This is a general skill when doing algorithm design. Counter is not a formal name. Name is different everywhere but the idea is the same.
- Using the counter object will reduce the complexity of algorithms.
以 Intersection Problem 為例子
時間複雜度:O(n + m)
function intersection(arr1, arr2) {
let result = []
let arr3 = arr1.concat(arr2)
let counter = {}
for (let i = 0; i < arr3.length; i++) {
// 計分板無法找到 arr[i] 的值
if (!counter[arr3[i]]) {
counter[arr3[i]] = 1
} else { // 計分板可以找到 arr[i]
counter[arr3[i]] ++
}
}
console.log(counter)
// loop over the counter
for (let property in counter) {
if (counter[property] >=2 ) {
result.push(property)
}
}
console.log('result', result)
return result
}
intersection([15, 3, 6, 8, 24, 16], [11, 3, 9, 6, 15, 8])
Frequency Problem
先將 array 換成 string => arr.split('')
- 在 counter1 裡面的每個 property,如果在 counter2 裡面不存在 -> return false
- counter1 property 的值與 counter2 property s的值不一樣 -> return false
- 通過上述 property test -> return true
function sameFrequency(str1, str2) {
let arr1 = str1.split('')
let arr2 = str2.split('')
let counter1 = {}
let counter2 = {}
if (arr1.length !== arr2.length) return false
for (let i=0; i<arr1.length; i++) {
if (!counter1[arr1[i]]) {
counter1[arr1[i]] = 1
} else {
counter1[arr1[i]] ++
}
}
for (let i=0; i<arr2.length; i++) {
if (!counter2[arr2[i]]) {
counter2[arr2[i]] = 1
} else {
counter2[arr2[i]] ++
}
}
for (let property in counter1) {
if (!counter2[property]) {
return false
}
if (counter2[property] !== counter1[property]) {
return false
}
}
return true
}
Average Pair
時間複雜度:O(n^2) -> 因為 nested array
接著會學習 pointer 幫我們將 O(n^2) -> O(n)
function averagePair(arr, avg) {
let result = []
for (let i=0; i<arr.length-1; i++) {
for (let j=i+1; j<arr.length; j++) {
if ((arr[i] + arr[j])/2 === avg) {
result.push([arr[i], arr[j]])
}
}
}
console.log(result)
return result
}
Pointer
- This is a general skill when doing algorithm design. Pointer is not a formal name. Name is different everywhere but the idea is the same.
- It helps reduce the complexity of algorithms.
時間複雜度:O(n)
function averagePair(arr, avg) {
let left = 0
let right = arr.length - 1
let result = []
while(right > left) {
let tempAvg = (arr[left] + arr[right])/2
if (tempAvg > avg) {
right --
} else if (tempAvg < avg) {
left ++
} else {
result.push([arr[left], arr[right]])
right --
left ++
}
}
return result
}
Palindrome
function isPalindrome(str) {
let left = 0
let right = str.length -1
while (left <= right) {
if (str[left] !== str[right]) {
return false
}
left ++
right --
}
return true
}
Subsequence Problem (⚝⚝⚝)
function isSubsequence(str1, str2) {
if (str1.length === 0) return true
let pointer1 = 0
let pointer2 = 0
while (pointer2 < str2.length) {
if (str1[pointer1] === str2[pointer2]) {
pointer1 ++
}
if (pointer1 >= str1.length) {
return true
}
pointer2 ++
}
return false
}
Sliding Window (⚝)
window 的 size 也可以不固定
怎麼找 index number 的終點 ? 從最後一格開始看,最末格的 index = arr.length - 1,若 size 是 n 就往前推 n - 1 格,所以 index = arr.length - n
function maxSum(arr, size) {
let maxValue = -Infinity
if (size > arr.length) return null
for (let i = 0; i <= arr.length - size; i++) { // i 是 Sliding Window 第一個 pointer
let attempt = 0
for (let j = i; j < i + size; j++) { // j 表示 arr 的 index number
attempt += arr[j]
}
if (attempt > maxValue) {
maxValue = attempt
}
}
return maxValue
}
function minSum(arr, size) {
let minValue = Infinity
if (size > arr.length) {
return null
}
for (let i = 0; i <= arr.length - size; i++) {
let temp = 0
for (let j = i; j < i + size; j++) {
temp += arr[j]
}
if (temp < minValue) {
minValue = temp
}
}
return minValue
}
各自算每個 window 的總和並不是個好方法。
第一個 window 與 第二個 window 的差別在於第一個值與最後一個值,中間 7, 3 都是重複的。
Improved Maxsum
function maxSum(arr, size) {
let maxValue = 0
if (size > arr.length) {
return null
}
// 先拿第一個 sliding window 的總和
for (let i = 0; i < size; i++) {
maxValue += arr[i]
}
let tempValue = maxValue
for (let j = size; j < arr.length; j++) {
tempValue = tempValue + arr[j] - arr[j - size]
if (tempValue > maxValue) {
maxValue = tempValue
}
}
return maxValue
}
Improved Minsum
function minSum(arr, size) {
let minSum = 0
if (size > arr.length) {
return null
}
for (let i = 0; i < size; i++) {
minSum += arr[i]
}
let temp = minSum
for (let i = size; i < arr.length; i++) {
temp += arr[i] - arr[i - size]
if (temp < minSum) {
minSum = temp
}
}
return minSum
}
Coding Practice - Min Sub Array (⚝)
提示:sliding window、pointer
- start、end 這兩個 pointer 都指向第一個數字
- 若沒有大於 sum,就將 end 往後移動一格,直到各數字加總大於 sum -> 找到第一個 sub array -> 算出它的長度
- 降低這個 sub array 的長度 -> 將 start 往後移一格,若 start 到 end 的各數字加總小於 sum -> end 就再往後移動一格
function minSubArray(arr, sum) {
let start = 0
let end = 0
let total = 0
let minLength = Infinity
while(start < arr.length) {
if (total < sum && end < arr.length) {
total += arr[end]
end ++
} else if (total >= sum) {
let currentLength = end - start
if (minLength > currentLength) {
minLength = currentLength
}
total -= arr[start]
start ++
} else if (end >= arr.length) {
break
}
}
if (minLength === Infinity) {
console.log('can not find subarray that can sum up to the given number')
return -1
} else {
console.log(minLength)
return minLength
}
}
Unique Letter Substring (⚝⚝⚝)
提示:sliding window、counter
針對這串字 thisishowwedoit
- 準備 start, end 兩個 pointer 與 counter
- 若沒有重複,end 就會繼續指向下一格
- 若 counter 裡面有重複,移動 start,若 counter 裡面還有重複 start 就繼續往後移一格
function uniqueLetterString(str) {
let start = 0
let end = 0
let counter = {}
let maxLength = -Infinity
while(end < str.length) {
if (counter[str[end]]) {
counter[str[start]] --
start ++
} else {
counter[str[end]] = 1
end ++
if ((end - start) > maxLength) {
maxLength = end - start
}
}
}
if (maxLength === -Infinity) {
console.log('cant not find unique letter sub string')
return null
}
console.log('maxLength', maxLength)
return maxLength
}
Largest Product (⚝)
提示:sliding window、pointer
function largestProduction(n) {
let currentProduct
let maxProduct = -Infinity
let left = 0
let right = n - 1
while (right < thousandDigits.length) {
currentProduct = 1
for (let i = left; i <= right; i++) {
currentProduct *= thousandDigits[i]
}
if (currentProduct > maxProduct) {
maxProduct = currentProduct
}
right ++
left ++
}
return maxProduct
}
Recursion
學習動機:排列的演算法會大量使用遞迴
- Def:a function that calls itself.
- Recursion is using a data structure called stack. When we're calling a function inside another function, we're on the call stack.
- Recursion is also a mathematical relation in sequences. 遞迴是數列的一種關係
function rs(n) {
console.log(`inside function rs(${n})`)
if (n === 1) {
return 10
} else {
return rs(n - 1) + 15
}
}
(⚝) console.log(rs(3)) 執行流程
- rs(3) 因為 3 不等於 1 所以會執行 return -> rs(2) + 15 -> + 15 還沒做,rs(2) 就被執行了
- 同理,rs(2) -> rs(1) + 15 -> + 15 還沒做,rs(1) 就被執行了
- rs(1) return 10
- 回到 rs(2) -> rs(1) + 15 = 10 + 15 = 25
- 回到 rs(3) -> rs(2) + 15 = 25 + 15 = 40
Fibonacci Sequence
function fs(n) {
if (n === 0) return 0
if (n === 1) return 1
return fs(n-1) + fs(n-2)
}
Array of Arrays (⚝⚝⚝)
let result = []
function collector(arr) {
for (let i = 0; i < arr.length; i++) {
if (Array.isArray(arr[i])) {
collector(arr[i])
} else {
result.push(arr[i])
}
}
return result
}
let arrs = [[[["a", [["b", ["c"]], ["d"]]], [["e"]], [[["f", "g", "h"]]]]]];
function collector(arr1) {
let result = []
helper(arr1)
return result
function helper(arr2) {
for (let i = 0; i < arr2.length; i++) {
if (Array.isArray(arr2[i])) {
helper(arr2[i])
} else {
result.push(arr2[i])
}
}
}
}