两种高阶排序+四道简单力扣题带你走近“分而治之”

1,459 阅读5分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

开篇

这是我的第二篇发布在掘金文章,课设和考试都结束了,总想着记录点什么,最近正在学习js算法,三月份的春招快来了hhh。废话不多说直接开始吧。

分而治之 是什么?

分而治之是算法思想的一种方法,或者说是一种思想,我们可以利用这种思想设计很多很多的算法。它将一个问题分解 成为很多个小问题,递归解决小问题,再将小问题合并以解决原来的问题。耐心看完,相信一定会有收获

简单来说就是三个步骤

三个核心步骤

  • 递归

带入上述三个核心步骤我们来看看一个经常出现在面试题中的一个排序题- 归并排序

两个排序

归并排序

动画展示

Animation.gif 图中相同颜色的柱体为一个组,归并排序的核心思想就是保证局部有序,将有序的组再进行合并成为一个更大的有序组,直到所有的子元素都在同一个组内。

  • 分 : 把数组一分为二
  • 解 : 递归地对两个子数组进行归并排序
  • 合 : 合并有序数组 代码展示
Array.prototype.mergeSort = function () {
	const rec = (arr) => {
		if (arr.length == 1) return arr  // 递归终止条件,直到每个组只有一个元素(保证有序)
		const mid = arr.length >> 1 //这里的作用等价于: Math.floor(arr.length/2)
		const left = arr.slice(0, mid) //1.将数组一分为二
		const right = arr.slice(mid, arr.length)
		const orderLeft = rec(left) //2.递归调用
		const orderRight = rec(right)
		const res = []
		while (orderRight.length || orderLeft.length) { //3.合并
			if (orderLeft.length && orderRight.length) {
                        // 两个数组都有值的情况下比较队头大小,小的先出队
				res.push(
					orderLeft[0] < orderRight[0] ? orderLeft.shift() : orderRight.shift()
				) 
			} else if (orderLeft.length) {
				res.push(orderLeft.shift())
			} else {
				res.push(orderRight.shift())
			}
		}
		return res
	}
        //这里是在Array的原型上添加的mergeSort方法,this代指每个array实例,也就是数组
	const res = rec(this)
        // 对结果进行浅拷贝,赋值给this
	res.forEach((v, i) => {
		this[i] = v
	})
}

将n个元素从中间切开,分成两部分。(左边可能比右边多1个数) 将步骤1分成的两部分,再分别进行递归分解。直到所有部分的元素个数都为1。 从最底层开始逐步合并两个排好序的数列。这就是归并排序,也是分而治之思想的一种应用

快速排序

动画展示

快排动画.gif 每一次选中基准柱,保证基准的左边都小于基准,基准的右边大于基准,这样确保了基准是有序的,再对两边的数组进行相同的操作,直到每个柱子都在它应该在的地方(排序后的地方)

  • 分 : 选择一个基准(随机,一般选数组的第一个),按照基准把数组分为两个子数组
  • 解 : 递归的对子数组进行快速排序
  • 合 : 对两个数组进行合并 代码展示
Array.prototype.quickSort = function () {
	const rec = (arr) => {
		if (arr.length <= 1) return arr //递归终止条件,数组已经是有序()
		const left = []
		const right = []
		const mid = arr[0]
		for (let i = 1; i < arr.length; i++) { //2.分 将小于基准的元素放在left,大于基准的元素放在right
			if (arr[i] < mid) { 
				left.push(arr[i]) 
			} else {
				right.push(arr[i])
			}
		}
		return [...rec(left), mid, ...rec(right)] //2.对基准左右的数组进行递归
 	}
	const res = rec(this)
	res.forEach((v, i) => (this[i] = v))
}

快排的思想就是: 通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。

四道简单力扣题

  • [目标] - - > 认真看完下面四道简单的力扣题,并能动手尝试一番。

1-lc374.猜数字大小

题目描述

image.png

题目详解

这道题简单来说就是,给定我们一个n,去通过guess方法猜测pick(最终答案),guess方法返回-1,表示答案比我们猜测的数字要小,返回1,表示答案比我们猜测数字要大,返回1,则猜对了。

做题三部曲

类似于快排的实现,这道题我们可以直接使用二分查找来解决问题

  • 分: 计算中间元素,分割数组
  • 解: 根据guess返回的结果,递归的在较大或较小的数组进行二分搜索
  • 合: 这个题不需要合并操作,因为在子数组中搜索到pick的值就直接返回了

代码展示

/** 
 * Forward declaration of guess API.
 * @param {number} num   your guess
 * @return 	            -1 if num is lower than the guess number
 *			             1 if num is higher than the guess number
 *                       otherwise return 0
 * var guess = function(num) {}
 */
var guessNumber = function(n) {
    const rec = (low,high)=>{ // 递归函数
        const mid = Math.floor((low +high)/2)
        const res = guess(mid)
        if(res==0){ //找到结果直接返回
            return mid
        }else if(res==1){
            return rec(mid+1,high) // 答案比猜测的值大,缩小区间到【mid+1,high】
        }else {
            return rec(low,mid-1) // 答案比猜测的值小,缩小区间到【low,mid-1】
        }
    }
    return rec(1,n)
};

2-lc226翻转二叉树

题目描述

image.png

解题思路

  • 先翻转左右子树,再将子树换个位置

三部曲

  • 分:获取左右子树
  • 解:递归翻转左右子树
  • 合:将翻转后的左右子树换个位置放到根节点

代码展示

var invertTree = function(root) {
    const rec = (n)=>{
        if(!n) return //递归终止条件
        const temp = n.left //交换左右子树
        n.left = n.right
        n.right = temp
       if(n.left!=null){
            rec(n.left)
       }
       if(n.right!=null){
        rec(n.right)
       }
    }
    rec(root)
    return root
};

另一个版本(思路一致)

var invertTree = function(root) {
   if(!root) return null //终止递归条件
   return {
       val:root.val,
       left:invertTree(root.right),
       right:invertTree(root.left)
   }
};

3-lc100相同的树

题目展示

image.png

解题三部曲

  • 分: 获取树的左子树和右子树
  • 解(递归):递归的判断两个数的左子树和右子树是否相同
  • 合: 将上述结果合并,如果根节点的值也相同,树就相同 实现代码
var isSameTree = function (p, q) {
      if (p == null && q == null) {
        return true
      }
      if (p == null || q == null) {
        return false
      }
      if (p.val != q.val) {
        return false
      }
      return isSameTree(p.left, q.left) && isSameTree(p.right, q.right)
    };

4-lc101对称二叉树

题目描述

image.png

解题三部曲

将问题转化为: 左右子树是否镜像

  • 分: 获取两棵树的左子树和右子树
  • 解:递归地判断树1的左子树和树2的右子树是否镜像,树1的右子树和树2的左子树是否镜像
  • 合: 如果上述都成立,且根节点也相同,两棵树就满足镜像要求

实现代码

var isSymmetric = function(root) {
    if(!root) return true
    const isMirror = (l,r)=>{
        if(!l&&!r) return true
        if(l&&r&&l.val==r.val&&isMirror(l.left,r.right)&&isMirror(l.right,r.left)){
            return true
        }
        return false
    }
    return isMirror(root.left,root.right)
};

总结

记住三个步骤 : 分 -> 递归 -> 合并 全文的精华就在这里了。感觉总结太水了

重点在于思想的掌握学会一道题解决一类一部分的题hhh。

我是邵小白,一个正在不断学习的大三前端小白,文章中如果有不对的地方欢迎评论,谢谢。