前端算法入门之路(八)(归并排序 - 从二路到多路)

208 阅读2分钟

归并排序

  1. 处理左边 得到左边的信息
  2. 处理右边 得到右边的信息
  3. 完成合并过程 得到横跨两边的信息

手撕归并排序

// 将数组分成左右两边,直到元素个数为1
// 定义一段存储空间temp用于存放排序后的目标数组,俩指针p1、p2分别指向左右两边的数组
// 将p1、p2指向的较小的值放入temp数组中,指针向后移动
// 最后将temp的值按顺序放入arr中,完成排序
function merge_sort(arr, l, r) {
    if (l >= r) return
    let mid = (l + r) >> 1
    merge_sort(arr, l, mid)
    merge_sort(arr, mid + 1, r)
    let temp = Array(r - l + 1), k = 0, p1 = l, p2 = mid + 1
    while (p1 <= mid || p2 <= r) {
        if (p2 > r || (p1 <= mid && arr[p1] <= arr[p2])) {
            temp[k++] = arr[p1++]
        } else {
            temp[k++] = arr[p2++]
        }
    }
    for(let i = l; i <= r; i++) arr[i] = temp[i - l]
}

LeetCode肝题

  1. 剑指 Offer 51. 数组中的逆序对
// 归并排序的过程中,数组中的逆序对等于左边区间的逆序对+右边区间的逆序对+横跨左右区间的逆序对
// 横跨左右的逆序对就是在合并的时候右边区间进入temp时左侧区间剩余的值(mid-p1+1)
var temp = []
var countReversePairs = function(nums, l, r) {
    if (l >= r) return 0;
    let mid = (l + r) >> 1, ans = 0;
    ans += countReversePairs(nums, l, mid);
    ans += countReversePairs(nums, mid + 1, r);
    let k = l, p1 = l, p2 = mid + 1;
    while((p1 <= mid) || (p2 <= r)) {
        if ((p2 > r) || (p1 <= mid && nums[p1] <= nums[p2])) {
            temp[k++] = nums[p1++]
        } else {
            temp[k++] = nums[p2++]
            ans += (mid - p1 + 1)
        }
    }
    for(let i = l; i <= r; i++) nums[i] = temp[i]
    return ans
}
var reversePairs = function(nums) {
    while (temp.length < nums.size) temp.push(0);
    return countReversePairs(nums, 0, nums.length - 1)
};
    1. 合并K个升序链表
// 遍历lists将每个链表放入优先队列,每次弹出最小的节点放到虚拟节点ret后面,最后返回ret.next
var mergeKLists = function(lists) {
    let q = new MinPriorityQueue({priority: p => p.val}), ret = new ListNode()
    for (let i = 0; i < lists.length; i++) {
        if (!lists[i]) continue
        q.enqueue(lists[i])
    }
    let p = ret
    while(!q.isEmpty()) {
        let top = q.dequeue()
        if (top.element.next) q.enqueue(top.element.next)
        p.next = top.element
        p = p.next
    }
    return ret.next
};
    1. 排序链表
var mergeSort = function(head, n) {
    if (!head || !head.next) return head;
    let l = n >> 1, r = n - l;
    let lp = head, rp = lp, p
    for (let i = 1; i < l; i ++) rp = rp.next
    p = rp
    rp = rp.next
    p.next = null
    lp = mergeSort(lp, l)
    rp = mergeSort(rp, r)
    let ret = new ListNode()
    p = ret
    while(lp || rp) {
        if (rp == null || (lp && lp.val <= rp.val)) {
            p.next = lp
            lp = lp.next
            p = p.next
        } else {
            p.next = rp
            rp = rp.next
            p = p.next
        }
    }
    return ret.next;

}
var sortList = function(head) {
    let n = 0, p = head;
    while (p) {
        n += 1
        p = p.next;
    } 
    return mergeSort(head, n);
};
    1. 两棵二叉搜索树中的所有元素
var sortTree = function(root, arr) {
    if (!root) return []
    root.left && sortTree(root.left, arr)
    arr.push(root.val)
    root.right && sortTree(root.right, arr)
    return arr
}
var getAllElements = function(root1, root2) {
    let arr_left = sortTree(root1, []), arr_right = sortTree(root2, [])
    let i = 0, j = 0, ans = []
    while(i < arr_left.length || j < arr_right.length) {
        if (j >= arr_right.length || (i < arr_left.length && arr_left[i] <= arr_right[j])) {
            ans.push(arr_left[i])
            i++
        } else {
            ans.push(arr_right[j])
            j++
        }
    }
    return ans
};
    1. 区间和的个数
// 将题意转换成前缀和数组,求sum[j] - sum[i]在lower和upper之间并且j>i
// 在归并排序的过程中可以求解,等于左边区间和个数+右边区间和个数+横跨左右区间和的个数
var countTwoPart = function(sum, l1, r1, l2, r2, lower, upper) {
    let ans = 0, k1 = l1, k2 = l1
    for(let j = l2; j <= r2; j++) {
        let a = sum[j] - upper
        let b = sum[j] - lower
        while(k1 <= r1 && sum[k1] < a) k1 += 1
        while(k2 <= r1 && sum[k2] <= b) k2 += 1
        ans += k2 - k1
    }
    return ans
}
var margeSort = function(sum, l, r, lower, upper) {
    if (l >= r) return 0
    let mid = (l + r) >> 1, ans = 0
    ans += margeSort(sum, l, mid, lower, upper)
    ans += margeSort(sum, mid + 1, r, lower, upper)
    ans += countTwoPart(sum, l, mid, mid + 1, r, lower, upper)
    let k = l, p1 = l, p2 = mid + 1
    while(p1 <= mid || p2 <= r) {
        if (p2 > r || (p1 <= mid && sum[p1] <= sum[p2])) {
            temp[k++] = sum[p1++]
        } else {
            temp[k++] = sum[p2++]
        }
    }
    for(let i = l; i <= r; i++) sum[i] = temp[i]
    return ans
}
let temp
var countRangeSum = function(nums, lower, upper) {
    let sum = Array(nums.length + 1)
    temp = Array(nums.length + 1).fill(0)
    sum[0] = 0
    for (let i = 0; i < nums.length; i++) sum[i + 1] = nums[i] + sum[i]
    return margeSort(sum, 0, sum.length - 1, lower, upper)
};
    1. 最大子序和
// 求出前缀和数组,遍历前缀和数组用较大的值减去较小的值
var maxSubArray = function(nums) {
    let sum = [0], pre = 0
    for (let i = 0; i < nums.length; i++) sum.push(nums[i] + sum[i])
    let ans = sum[1]
    for (let i = 1; i < sum.length; i++) {
        ans = Math.max(sum[i] - pre, ans)
        pre = Math.min(sum[i], pre)
    }
    return ans
};
    1. 子数组和排序后的区间和
// 0,0、0,1、0,2、... 0,n-1,和1,1、1,2、1,3、... 1,n-1等排列肯定是有序的,
// 模仿归并的思路将每条序列对放入优先队列中,每次弹出最小值并把和值累加
var rangeSum = function(nums, n, left, right) {
    let q = new MinPriorityQueue({priority: p => p.sum})
    for (let i = 0; i < n; i++) {
        q.enqueue({i: i, j: i, sum: nums[i]})
    }
    let ans = 0, mod = 1000000000+7
    for (let i = 0; i < right; i++) {
        let top = q.dequeue().element
        if (top.j < n - 1) q.enqueue({i: top.i, j: top.j + 1, sum: top.sum + nums[top.j + 1]})
        if (i >= left - 1) ans += top.sum
    }
    return ans % mod
};
  1. 面试题 04.08. 首个共同祖先
// 赋予递归函数一个明确的意义:寻找节点值等于p或q的节点
// 如果当前节点是p或者q说明就是首个共同祖先
// 否则去左右子树寻找p或q,如果都能找到说明就是首个共同祖先
// 如果只能找到一个说明找到的那个就是首个共同祖先
var lowestCommonAncestor = function(root, p, q) {
    if (!root) return root
    if (root == p || root == q) return root
    let l = lowestCommonAncestor(root.left, p, q), r = lowestCommonAncestor(root.right, p, q)
    if (l && r) return root
    if (l) return l
    return r
};
    1. 层数最深叶子节点的和
// 每条子树递归,遇到更深的层级就把ans重置,等于更深的层级就累加
function getResult(root, k, result) {
    if (!root) return
    if (k == result.max_k) result.ans += root.val
    if (k > result.max_k) {
        result.max_k = k
        result.ans = root.val
    }
    getResult(root.left, k + 1, result)
    getResult(root.right, k + 1, result)
    return
}
var deepestLeavesSum = function(root) {
    let result = {
        max_k: 0,
        ans: 0
    }
    getResult(root, 1, result)
    return result.ans
};