前端算法入门之路(九)(Algorithm算法杂谈)

229 阅读3分钟

计数排序

  1. 统计每一项出现的次数
  2. 根据统计的次数循环输出对应的值
  3. 应用于值域有限的排序场景中

基数排序

  1. 统计低16位每个数字出现的次数,求其前缀和后在temp里排序
  2. 统计高16位每个数字出现的次数,求其前缀和后将temp的数在原数组排序
  3. 基数排序是利用前两步排序的数据之间的稳定性实现最终排序效果

拓扑排序

  1. 统计所有元素的入度和对应关系,将入度为0的元素入队
  2. 队列每出队一个元素,将指向的元素入度减一,若该元素入度为0则入队
  3. 队列弹出元素即是拓扑序列

LeetCode肝题

    1. 数组的相对排序
// 统计arr1里面所有数字出现的次数,根据arr2的顺序排列,最后把剩余的加进去
var relativeSortArray = function(arr1, arr2) {
    let cut = new Array(1001).fill(0)
    for(let i of arr1) cut[i]++
    let k = 0
    for(let i = 0; i < arr2.length; i++) {
        for(let j = 0; j < cut[arr2[i]]; j++) {
            arr1[k++] = arr2[i]
        }
        cut[arr2[i]] = 0
    }
    for(let i in cut) {
        if (cut[i] == 0) continue
        for(let j = 0; j < cut[i]; j++) arr1[k++] = i
    }
    return arr1
};
    1. 最大间距
// 先统计低16位每个数字出现的次数记录到cut中,对cut求前缀和,此时cut[i]-1就是nums里的i在temp排序后的位置
// 第二步统计高16位每个数字出现的次数重新记录到cut中,对cut求前缀和,此时cut[i]-1就是temp里的i在nums排序后的位置
// 标准的基数排序题,排序之后计算每个元素差值取最大值
var maximumGap = function(nums) {
    if (nums.length < 2) return 0
    let cut = new Array(65536).fill(0), temp = new Array(nums.length).fill(0)
    for(let i of nums) cut[i % 65536] += 1
    for(let i = 1; i < cut.length; i++) cut[i] += cut[i - 1]
    for(let i = nums.length - 1; i >=0 ; --i) temp[--cut[nums[i] % 65536]] = nums[i]
    cut.fill(0)
    for(let i of temp) cut[parseInt(i / 65536)] += 1
    for(let i = 1; i < 65536; i++) cut[i] += cut[i - 1]
    for(let i = temp.length - 1; i >= 0; --i) nums[--cut[parseInt(temp[i] / 65536)]] = temp[i]
    let ans = 0
    for(let i = 1; i < nums.length; i++) {
        ans = Math.max(ans, nums[i] - nums[i - 1])
    }
    return ans
};
    1. H 指数
// 对原数组排序,从数组结尾遍历满足当前位置的元素大于等于倒数的位数,即h
var hIndex = function(citations) {
    citations = citations.sort((a,b) => a - b)
    let h = 1, len = citations.length
    while(h <= len && citations[len - h] >= h) ++h
    return h-1
};
    1. 课程表
// 计算拓扑序
var canFinish = function(numCourses, prerequisites) {
    let indeg = Array(numCourses).fill(0), g = Array(numCourses), q = [], cut = 0
    for(let i = 0; i < g.length; i++) g[i] = []
    for(let item of prerequisites) {
        indeg[item[0]] += 1
        g[item[1]].push(item[0])
    }
    for(let i = 0; i < indeg.length; i++) {
        if (indeg[i] == 0) q.push(i)
    }
    while(q.length > 0) {
        let top = q.shift()
        cut++
        for(let i of g[top]) {
            indeg[i] -= 1
            if (indeg[i] == 0) q.push(i)
        }
    }
    return cut == numCourses
};
    1. 课程表 II
// 标准的拓扑排序
var findOrder = function(numCourses, prerequisites) {
    let indeg = Array(numCourses).fill(0), g = Array(numCourses), q = [], ans = []
    for(let i = 0; i < g.length; i++) g[i] = []
    for(let item of prerequisites) {
        indeg[item[0]] += 1
        g[item[1]].push(item[0])
    }
    for(let i = 0; i < indeg.length; i++) {
        if (indeg[i] == 0) q.push(i)
    }
    while(q.length > 0) {
        let top = q.shift()
        ans.push(top)
        for(let i of g[top]) {
            indeg[i] -= 1
            if (indeg[i] == 0) q.push(i)
        }
    }
    return ans.length == numCourses ? ans : []
};
    1. 合并区间
// 先根据第一项排序,然后遍历判断两端的端点值是否是包含关系
var merge = function(intervals) {
    intervals = intervals.sort((a, b) => a[0] - b[0])
    let ans = [intervals[0]]
    for(let i = 1; i < intervals.length; i++) {
        let len = ans.length - 1
        if (intervals[i][0] <= ans[len][1] && intervals[i][1] > ans[len][1]) {
            ans[len][1] = intervals[i][1]
        } 
        if (intervals[i][0] > ans[len][1]) {
            ans.push(intervals[i])
        }
    }
    return ans
};
    1. 删除被覆盖区间
// 思路同上,排序的时候先按照第一项从小到大排序,第一项相同的按照第二项从大到小排序,遍历一下排除掉被覆盖的区间
var removeCoveredIntervals = function(intervals) {
    intervals = intervals.sort((a, b) => {
        if (a[0] == b[0]) return b[1] - a[1]
        return a[0] - b[0]
    })
    let ans = [intervals[0]]
    for(let i = 1; i < intervals.length; i++) {
        let len = ans.length - 1
        if(intervals[i][0] >= ans[len][0] && intervals[i][1] <= ans[len][1]) {
            continue
        }
        ans.push(intervals[i])
    }
    return ans.length
};
  1. 491.递增子序列
// 赋予递归函数一个明确的意义:
// 寻找从nums的第k位与其之后的位置的组合放入buff中并添加到结果数组中
var getResult = function(nums, k, buff, ans) {
    if (buff.length > 1) ans.push(JSON.parse(JSON.stringify(buff)))
    buff.push(0)
    let map = {}
    for(let i = k; i < nums.length; i++) {
        if (map[nums[i]]) continue
        if (buff.length == 1 || nums[i] >= buff[buff.length - 2]) {
            buff[buff.length - 1] = nums[i]
            map[nums[i]] = 1
            getResult(nums, i + 1, buff, ans)
            buff.pop()
        }
    }
}
var findSubsequences = function(nums) {
    let ans = []
    getResult(nums, 0, [], ans)
    return ans
};
  1. 面试题 04.12. 求和路径
// 赋予递归函数一个明确的意义:
// pathSum:寻找和为sum的路径数量,等于从根节点开始寻找路径数量 + 左子树寻找到的路径数量 + 右子树寻找的路径数量
// getPathSum:从当前节点寻找和为sum的路径数量,如果当前节点等于sum值,说明找到一条路径,然后返回左子树和为sum-root.val的路径+右子树和为sum-root.val的路径+当前找到的路径数1或0
var getPathSum = function(root, sum) {
    if(!root) return 0
    const val = sum - root.val, a = root.val == sum ? 1 : 0
    return a + getPathSum(root.left, val) + getPathSum(root.right, val)
}
var pathSum = function(root, sum) {
    if (!root) return 0
    return getPathSum(root, sum) + pathSum(root.left, sum) + pathSum(root.right, sum)
};