算法题目复习

153 阅读3分钟

算法题目

一、将一个数组旋转K步

1.输入一个数组[1,2,3,4,5,6,7]
2.k = 3,即旋转33.输入[5,6,7,1,2,3,4]

思路:
a.把未尾的元素挨个pop,然后unshift道数组前面(时间复杂度O(n^2),空间复杂度O(1))
function rotate(arr, k) {
    const len = arr.length
    if(!k || len === 0) return arr
    const step = Math.abs(k % len) // Math.abs取绝对值
    // O(n^2)
    for (let i = 0; i < step; i++) {
        const n = arr.pop() // 移除最后一个数组元素
        if (n) {
            arr.unshift(n) // 可向数组的开头添加一个或更多元素
        }
    }
    return arr
}
console.log(rotate([1,2,3,4,5,6,7], 3))


推荐:
b.把数组拆分,最后concat拼接到一起(时间复杂度O(1),空间复杂度O(n))
function rotate(arr, k) {
    const len = arr.length
    if(!k || len === 0) return arr
    const step = Math.abs(k % len) // Math.abs取绝对值
    // slice() 方法可从已有的数组中返回选定的元素。 
    // slice() 方法可提取字符串的某个部分,并以新的字符串返回被提取的部分。
    // 注意: slice() 方法不会改变原始数组。
    const part1 = arr.slice(-step)
    const part2 = arr.slice(0, len-step)
    const res = part1.concat(part2)
    return res
}
console.log(rotate([1,2,3,4,5,6,7], 3))

二、判断字符串是否括号匹配

1.一个字符串s可能包含{}()[]三种括号
2.判断s是否是括号匹配的
3.如(a{b}c)匹配,而{a(b或{a(b}c)}就不匹配

思路:
a.遇到左括号{([就压栈
b.遇到右括号})]就判断栈顶,匹配则出栈
c.最后判断length是不是为0

function isMatch(left, right) {
    if (left === '{' && right === '}') return true
    if (left === '[' && right === ']') return true
    if (left === '(' && right === ')') return true
    return false
}
function matchBracket(str) {
    const len = str.length
    if (len === 0) return true
    const stack = []

    const leftSymbols = '{[('
    const rightSymbols = '}])'

    for (let i = 0; i < len; i++) {
        const s = str[i]
        if (leftSymbols.includes(s)) {
            // 左括号,压栈
            stack.push(s)
        } else if (rightSymbols.includes(s)) {
            // 右括号,判断栈顶(是否出栈)
            const top = stack[stack.length - 1]
            if (isMatch(top, s)) {
                stack.pop()
            } else {
                return false
            }
        }
    }
    return stack.length === 0
}
console.log(matchBracket('{a(b[c])}'))

三、用JS实现快速排序

思路:
1.找到中间位置midValue
2.遍历数组,小于midValue放在left,否则放在right
3.继续递归,最后concat拼接,返回

细节:
1.获取midValue的两种方式
2.使用splice,会修改原数组
3.使用slice,不会修改原数组--更加推荐
4.math.floor 返回小于等于x的最大整数Math.floor(1.6) = 1

推荐:
function quilckSort(arr) {
    const len = arr.length
    if(len === 0) return arr
    const midIndex = Math.floor(len / 2)
    const midValue = arr.splice(midIndex, 1)[0]
    const left = []
    const right = []
    // 注意这里不能直接用len,用arr.length,因为splice改变原数组
    for (let i = 0; i < arr.length; i++) {
        const n = arr[i]
        if (n < midValue) {
            // 小于midValue.则放在left
            left.push(n)
        } else {
            //大于midValue.则放在right
            right.push(n)
        }
    }
   return quilckSort(left).concat([midValue], quilckSort(right))
}


function quilckSort(arr) {
    const len = arr.length
    if (len === 0) return arr
    const midIndex = Math.floor(length / 2)
    const midValue = arr.slice(midIndex, midIndex + 1)[0]
    const left = []
    const right = []
    for (let i = 0; i < len; i++) {
        if (i !== midIndex) {
            const n = arr[i]
            if (n < midValue) {
                left.push(n)
            } else {
                right.push(n)
            }
        }
    }
    return quilckSort(left).concat([midValue],quilckSort(right))
} 
console.log(quilckSort([1,3,6,4,8,5]))

四、用JS实现二分查找

思路:
a.递归-代码逻辑更加清晰
function binarySearch(arr, target, startIndex, endIndex) {
    const len = arr.length
    if (len === 0) return -1
    // 开始和结束的范围
    if (startIndex == null) startIndex = 0
    if (endIndex == null) endIndex = len - 1
    // 如果start和end相遇,则结束
    if (startIndex > endIndex) return - 1
    // 中间位置
    const midIndex = Math.floor((startIndex + endIndex) / 2)
    const midValue = arr[midIndex]
    if (target < midValue) {
        // 目标值较小,则继续在左侧查找
        return binarySearch(arr, target, startIndex, midIndex - 1)
    } else if (target > midValue) {
        // 目标值较大,则继续在右侧查找
        return binarySearch(arr, target, midIndex + 1, endIndex)
    } else {
        // 相等,返回
        return midIndex
    }
}
console.log(binarySearch([1,2,3,4,5,6,19,30,50], 1))

推荐
b.非递归-性能更好 
function binarySearch(arr, target) {
    const len = arr.length
    if (len === 0) return -1
    let startIndex = 0 //开始位置
    let endIndex = len - 1 // 结束位置
    while (startIndex <= endIndex) {
        const midIndex = Math.floor((startIndex+endIndex) / 2)
        const midValue = arr[midIndex]
        if (target < midValue) {
            //目标值较小,则继续在左侧查找
            endIndex = midIndex - 1
        } else if (target > midValue) {
            // 目标值较大,则继续在右侧查找
            startIndex = midIndex + 1
        } else {
            // 相等,返回
            return midIndex
        }
    }
    return -1
}
console.log(binarySearch([1,2,3,4,5,6,19,30,50], 1))

五、给一个数组,找出其中和为n的两个元素

1.有一个递增的数组[1,2,4,7,11,15]和一个n=15
2.数组中有两个数,和是n。即4 + 11 === 15

思路:
1.嵌套循环,找到一个数,然后去遍历下一个数,求和,判断

function findTowNumbers(arr, n) {
    const res = []
    const len = arr.length
    if (len === 0) return res
    for (let i = 0; i < len - 1; i++) {
        const n1 = arr[i]
        let flag = false // 是否得到了结果
        for (let j = i + 1; j < len; j++) {
            const n2 = arr[j]
            if (n1 + n2 === n) {
                res.push(n1)
                res.push(n2)
                flag = true
                break
            }
        }
        if (flag) break
    }
    return res
}
console.log(findTowNumbers([1,2,4,7,11,15], 15))

推荐:
2.利用递增(有序)的特性
a.随便找两个数
b.如果和大于n,则需要向前寻找
c.如果和小于n,则需要向后寻找---二分法

function findTowNumbers(arr, n) {
   const res = []
   const len = arr.length
   if (len === 0) return res 
   let i = 0 // 头
   let j = len - 1 //尾
   while (i < j) {
       const n1 = arr[i]
       const n2 = arr[j]
       const sum = n1 + n2
       if (sum > n) {
           // sum大于n,则j要向前移动
           j--
       } else if (sum < n) {
           // sum小于n,则i要向后移动
           i++
       } else {
           // 相等
           res.push(n1)
           res.push(n2)
           break
       }
   }
   return res
}
console.log(findTowNumbers([1,2,4,7,11,15], 15))

六、斐波那契数列 '0 1 1 2 3 5 8 13 21 34'

1.用JS计算斐波那契数列的第n个值
f(0) = 0
f(1) = 1
f(n) = f(n - 1) + f(n - 2)
思想:

1.递归 - 大量的重复计算
function fibonacci(n) {
    if (n <= 0) return 0
    if (n === 1) return 1
    return fibonacci(n - 1) + fibonacci(n - 2)
}
console.log(fibonacci(9))


推荐
2.不用递归,用循环,记录中间结果
function fibonacci(n) {
    if (n <= 0) return 0
    if (n === 1) return 1
    let n1 = 1 // 记录n-1的结果
    let n2 = 0 // 记录n-2的结果
    let res = 0
    for (let i = 2; i <= n; i++) {
        res = n1 + n2
        // 记录中间结果
        n2 = n1
        n1 = res
    }
    return res
}
console.log(fibonacci(3))

青蛙跳台阶
一只青蛙,一次可跳1级,也可跳2级
问:青蛙跳到n级台阶,总共有多少种方式
思路:
1.要跳到1级台阶,就1种方式f(1) = 1
2.要跳到1级台阶,就2种方式f(2) = 2
1.要跳到n级台阶,f(n) = f(n-1) + f(n-2)

七、将数组中的0移动到未尾

1.如输入[1,0,3,0,11,0],输入[1,3,11,0,0,0]
2.只移动0,其他顺序不变
3.必须在原数组进行操作

思路:
1.遍历
1.遍历数组,遇到0则push到数组未尾
2.用splice截取掉当前元素

function moveZero(arr) {
    const len = arr.length
    if (len === 0) return
    let zeroLen = 0
    for (let i = 0; i < len - zeroLen; i++) {
        if (arr[i] === 0) {
            arr.push(0)
            arr.splice(i, 1) // 本身就有O(n)
            i-- // 数组截取了一个元素,i要递减,否则连续0就会有错误
            zeroLen++
        }
    }
    return arr
}
console.log(moveZero([1,22,0,0,4,0,5,0]))

2.双指针
1.定义j指向第一个0,i指向j后面的第一个非0
2.交换i和j的值,继续向后移动

function moveZero(arr) {
     const len = arr.length
     if (len === 0) return
     let i
     let j = -1 // 指向第一个0
     for (i = 0; i < len; i++) {
         if (arr[i] === 0) {
             // 第一个0
             if (j < 0) {
                 j = i
             }
         }
         if (arr[i] !== 0 && j >= 0) {
             // 交换
             const n = arr[i]
             arr[i] = arr[j]
             arr[j] = n
             j++
         }
     }
     return arr
}
console.log(moveZero([0,0,0,2,3,4,0,4,0,6]))

八、字符串中连续最多的字符,以及次数

1.如,输入'abbcccddeeee1234',计算得到
2.连续最多的字符是'e', 4次

思路:
1.嵌套循环,找出每个字符的连续次数,并记录

function findContinuousChar(str) {
    const res = {
        char: '',
        len: 0
    }
    const len = str.length
    if (len === 0) return res
    let tempLen = 0 // 临时记录当前连续字符的长度
    for (let i = 0; i < len; i++) {
        tempLen = 0 // 重置
        for (let j = i; j < len; j++) {
            if (str[i] === str[j]) {
               tempLen++
            }
            if (str[i] !== str[j] || j === len - 1) {
                // 不相等,或者已经到了最后一个元素,要去判断最大值
                if (tempLen > res.len) {
                    res.char = str[i]
                    res.len = tempLen
                }
                if(i < len - 1) {
                    i = j - 1 //跳步
                }
                break
            }
        }
    }
    return res
}
console.log(findContinuousChar('abbcccddeee1234'))

推荐
2.双指针 
1.定义指针i和j,j不动,i继续移动
2.如果i和j的值一直相等,则i继续移动
3.直到i和j的值不想等,记录处理,让j追上i,继续第一步

function findContinuousChar(str) {
    const res = {
        char: '',
        len: 0
    }
    const len = str.length
    if (len === 0) return res
    let tempLen = 0 // 临时记录当前连续字符的长度
    let j = 0
    for (let i = 0; i < len; i++) {
        if (str[i] === str[j]){
            tempLen++
        }
        if (str[i] !== str[j] || i === len - 1) {
            // 不想等,或者i到了字符串的未尾
            if (tempLen > res.len) {
                res.char = str[j]
                res.len = tempLen
            }
            tempLen = 0 // reset
            if (i < len - 1) {
                j = i // 让j追求i
                i-- // 细节
            }
        }
    }
    return res
}
console.log(findContinuousChar('abbcccddee1234'))

九、求1-10000之间的所有对称数(回文)

例如:0,1,2,11,22,101,232,1221..
思路:
1.数字转换为字符串,再转换为数组
2.数组reverse,再join为字符串
3.前后字符串进行对比

function findPalidromeNumbers(n) {
    const res = []
    if (n <= 0) return res 
    for (let i = 1; i <= n; i++) {
        // 转换为字符串,转换为数组,再反转,比较
        const s = i.toString()
        if (s === s.split('').reverse().join('')){
             res.push(i)
        }
    }
    return res
}
console.log(findPalidromeNumbers(20))

字符串头尾比较
1.数字转换为字符串
2.字符串头尾字符比较
3.也可以用栈,像括号匹配,但要注意奇偶数

function findPalidromeString(str) {
    const res = []
    if (str <= 0) return res
    for (let i = 0; i <= str; i++) {
        const s = i.toString()
        const len = s.length
        // 字符串头尾比较
        let flag = true
        let startIndex = 0 // 字符串开始
        let endIndex = len - 1 // 字符串结束
        while (startIndex < endIndex) {
            if(s[startIndex] !== s[endIndex]) {
                flag = false
                break
            } else {
                // 继续比较
                startIndex++
                endIndex--
            }
        } 
        if (flag) res.push(i)
    }
    return res
}
console.log(findPalidromeString(200))

生成翻转数
1.使用%和Math.floow生成反转数
2.前后数字进行对比
function findPalidromeString(n) {
    const res = []
    if (n <= 0) return res
    for (let i = 1; i <= n; i++) {
        let n = i
        let rev = 0 // 存储翻转数
        // 生成翻转数
        while (n > 0) {
            rev = rev * 10 + n % 10
            n = Math.floor(n / 10)
        }
        if (i == rev) res.push(i)
    }
    return res
}
console.log(findPalidromeString(200))

十、数字千分位格式化

1.将数字千分位格式化,输出字符串
2.如输入数字12050100,输出字符串12,050,100
3.注意:逆序判断

思路:
1.转换为数组,reverse,每3位拆分

function format(n) {
    s = Math.floor(n) // 只考虑整数
    const arr = s.split('').reverse()
    return arr.reduce((prev, val, index) => {
        if (index % 3 === 0) {
            if(prev) {
                return val + ',' + prev
            } else {
                return val
            }
        } else {
            return val + prev
        }
    }, '')
}

2.使用正则表达式

function format (num) {  
    const reg=/\d{1,3}(?=(\d{3})+$)/g;   
    return (num + '').replace(reg, '$&,');  
}

3.使用字符串拆分
const n = 120501000
function format(n) {
  n = Math.floor(n)
  let s = n.toString()
  let res = ''
  let len = s.length
  for (let i = len - 1; i >= 0; i--) {
      let j = len - i
      if (j % 3 === 0) {
          if (i === 0) {
            res = s[i] + res
          } else {
            res = ',' + s[i] + res
          }

      } else {
          res = s[i] + res
      }
  }
  return res
}
console.log(format(n), 'format(n)')

十一、切换字母大小写

1.输入一个字符串,切换其中字母的大小写
2.如输入字符串12aBc34,输入字符串12AbC34
思路:
1.正则表达式

function switchLetterCase(s) {
    let res = ''
    const len = s.length
    if (len === 0) return res 
    const reg1 = /[a-z]/
    const reg2 = /[A-Z]/
    for (let i = 0; i < len; i++) {
        const n = s[i]
        if (reg1.test(n)) {
            res += n.toUpperCase()
        } else if (reg2.test(n)) {
            res += n.toLowerCase()
        } else {
            res += n
        }
    }
    return res
}
console.log(switchLetterCase('12aBc34'))

2.通过ASCII码判断

function switchLetterCase(s) {
    let res = ''
    const len = s.length
    if (len === 0) return res 
    for (let i = 0; i < len; i++) {
        const n = s[i]
        const code = n.charCodeAt(0)
        if (code >= 65 && code <= 90) {
            res += n.toLowerCase()
        } else if (code >= 97 && code <= 122) {
            res += n.toUpperCase()
        } else {
            res += n
        }
    }
    return res
}
console.log(switchLetterCase('12aBc34'))

十二、数组中只出现一次的数字

function filterArr(arr) {
    // 这里写下你的代码
    let array = []
    let res = {} 
    let len = arr.length
    for (let i = 0; i < len; i++) {
       if (arr[i] in res) {
          res[arr[i]] += 1
       } else {
        res[arr[i]] = 1
       }
    }
   for (let key in res) {
       if (res[key] === 1) {
           array.push(key)
       }
   }
   return array
}
console.log(filterArr([1, 2, 3, 4, 2, 3, 4, 1, 5, 6, 23, 32, 1]))


function filterArr(arr) {
   let len = arr.length
   let res = []
   for (let i = 0; i < len; i++) {
       let count = 0;
       for (let j = 0; j < len; j++) {
           if (arr[i] === arr[j]) {
               count++
           }
       }
       if (count === 1) {
           res.push(arr[i])
       }
   }
   return res
}
console.log(filterArr([1, 2, 3, 4, 2, 3, 4, 1, 5, 6, 23, 32, 1]))


function filterArr(arr) {
     let res = []
     let len = arr.length
     for (let i = 0; i < len; i++) {
         if (arr.indexOf(arr[i]) === arr.lastIndexOf(arr[i])) {
             res.push(arr[i])
         }
     }
     return res
}
console.log(filterArr([1, 2, 3, 4, 2, 3, 4, 1, 5, 6, 23, 32, 1]))

十三、js实现无序数组中的两数之和,并返回它们的索引

let array = [1,2,3,4,5,6,7]
function add(arr, target) {
    const len = arr.length
    let res = []
    for (let i = 0; i < len; i++) {
        for (j = i; j < len; j++) {
           if(arr[i] + arr[j] === target) {
               res = [i, j]
           }
        }
    }
    return res
}
console.log(add(array, 3))

十四、sleep函数

function sleep(timer){
   return new Promise((resolve, reject) => {
       setTimeout(resolve, timer)
   })
}
sleep(3000).then(function () {
    console.log(3)
})


1s后输出1 2s后输出2 3s后输出3
const log = (target) => {
    console.log(target)
}
const sleep = (timer) => {
    return new Promise(resolve => setTimeout(resolve, timer))
}
const main = async() => {
    await sleep(1000)
    log(1)
    await sleep(2000)
    log(2)
    await sleep(3000)
    log(3)
}
main()

十五、用setTimeout实现setInterval和setInterval实现setTimeout

function newSetInterval(timer, target) {
    setTimeout(() => {
        for (let i = 1; i <= target; i++) {
            console.log(i)
        }
        newSetInterval(timer, target)
    }, timer)
}
newSetInterval(3000, 5)


function newSetTimeout(timer, target) {
    let time = null
    timer = setInterval(() => {
        for (let i = 0; i <= target; i++) {
            console.log(i)
        }
        clearInterval(timer)
    }, timer)
}
newSetTimeout(3000, 5)

十六、JS数组取交集、并集

数组取交集、并集
indexOf() 方法可返回某个指定的字符串值在字符串中首次出现的位置。
如果没有找到匹配的字符串则返回 -1

let a = [1,2,3], b = [2,3,4,5]
let interSection = a.filter(item => b.indexOf(item) > -1)
console.log(interSection, 'interSection')

let union = a.concat(b.filter(item => a.indexOf(item) === -1))
console.log(union,'union')

let a = [1,2,3], b = [2,3,4,5]
let interSection = a.filter(item => new Set(b).has(item))
console.log(interSection, 'interSection')

let union = Array.from(new Set([...a, ...b]))
console.log(union)

数组对象取交集、并集
const c = [{id: 1, name: 'jack'}, {id: 2, name: 'bob'}]
const d = [{id: 2, name: 'bob'}, {id: 3, name: 'tom'}]
function jiaoji(arr1,arr2) {
    const res = []
    const len1 = arr1.length
    const len2 = arr2.length
    for (let i = 0; i < len1; i++) {
        for (let j = 0; j < len2; j++) {
            if (arr1[i].id === arr2[j].id) {
                res.push(arr2[j])
            }
        }
    }
    return res
}
console.log(jiaoji(c,d))

findIndex() 方法返回传入一个测试条件(函数)符合条件的数组第一个元素位置。
findIndex() 方法为数组中的每个元素都调用一次函数执行:
当数组中的元素在测试条件时返回 true 时, findIndex() 返回符合条件的元素的索引位置,之后的值不会再调用执行函数。
如果没有符合条件的元素返回 -1
注意: findIndex() 对于空数组,函数是不会执行的。
注意: findIndex() 并没有改变数组的原始值。
function bingji(arr1,arr2) {
    let res = [...arr1, ...arr2]
    const len1 = arr1.length
    const len2 = arr2.length
    for (let i = 0; i < len1; i++) {
        for (let j = 0; j < len2; j++) {
            if (arr1[i].id === arr2[j].id) {
                res.splice(res.findIndex(item => arr1[i].id === item.id), 1)
            }
        }
    }
    return res
}
console.log(bingji(c,d))