字节算法题目
1.无序数组,第k大的数 (快排,快排+判断: 轴+k和轴比对)
// =============== 解题1 =============== //// 力扣215题: https://leetcode-cn.com/problems/kth-largest-element-in-an-array/submissions/// 思路1: 快排 + 二分查找// 思路2: 第k次-最大顶堆的尾节点元素: 构建最大顶堆 + 循环k次 [基于回溯算法的优化]func findKthLargest(nums []int, k int) int {// return findKByquickSort(nums,k) // 快排解法return findKByMaxTopHeap(nums,k) // 最大顶堆解法}// 思路1: 快排 + 二分查找// @param []int num 无序数组// @param int k 第k大// @return int 第k大的数,num[len(num)-k]func findKByquickSort(num []int,k int) int {// 边界条件le := len(num)if 0 == le {return 0}// 快排var quickSort func(nums []int) []intquickSort = func(nums []int) []int {// 边界条件le2 := len(nums)if 0 == le2 { // ***易错点:return []int{}}mid := nums[0]l,r := []int{},[]int{}// for i := 0; i < le; i++ { // ***易错点:for i := 1; i < le2; i++ {// ***易错点: 3种case别漏了,<,=,>if mid >= nums[i] { // <=存左边l = append(l,nums[i])} else if mid < nums[i] { // >存右边r = append(r,nums[i])}}// 递归执行l = quickSort(l)r = quickSort(r)// 合并数组l = append(l,mid)l = append(l,r...) // 批量追加: go合并切片方法,同下for循环用法// for _,v := range r {// l = append(l,v)// }return l}res := quickSort(num)// 二分查找return res[le-k]}// 思路2: 第k次-最大顶堆(顶点值max=nums[0]): 循环k次,每次循环中 {构建最大顶堆 => max=maxTopHeap[0] => 依次追加到尾节点处}// 注: k=le-1时,和第一种快排时间复杂度相同// 几个易错点:// 1) for i := le; i > 0; i-- // ***易错点: 长度必须>=2,否则无需用maxTopHeap排序;// 2) nums[i-1],nums[le-1] = nums[le-1],nums[i-1] // ***易错点: 最大顶堆顶点为max,即max=topHeap[0],把max移到尾节点,nodeCount-1,继续排序,故交换 nums[i-1]和nums[0],而非nums[i-1]和nums[len(nums)-1]// @param []int num 无序数组// @param int k 第k大// @return int 第k大的数,num[len(num)-k]func findKByMaxTopHeap(nums []int,k int) int {// 边界条件le := len(nums)if 0 == le || k > le {return 0}// 循环构建maxTopHeap,共构建k次,(取最大值=>maxTopHeap,取最小值=>minTopHeap)// 循环次数为 数组长度,而非数组右边界;// 递减循环: 因为第 n 次排序 N 个数,则第 (n+1) 次排序 (N-1) 个数;for i := le; i > (le-k); i-- { // i > 1时(即le-k=1),则全部排序// 构建maxTopHeapbuildMaxTopHeap(nums,i)// 交换max(maxTopHeap顶点值) 和 当前位置(尾节点元素值)nums[i-1],nums[0] = nums[0],nums[i-1]}// fmt.Println("******sortArr==",nums)// fmt.Println("res==",nums[le-k])return nums[le-k] // 最大值nums[le-1],第k大值为nums[le-k]}// 构建最大顶堆maxTopHeap; [注意: 最大顶堆中for循环中递减的为元素范围长度length,而非下标值]// 思路: maxTopHeap特征(二叉树): currNode>=leftSonNodes && currNode>=rightSonNodes, 否则需对调currNode和sonNode位置//// 几个问题:// 1) 问题1: mid := (le-1)/2 是否可行?? [leng/2-1,leng/2都可以,能保证从中间root开始(因为同时遍历左右子树)即可]// 2) 问题2: 循环遍历时,为何不是i++? [因为未排序的元素越来越少]// 3) 问题3: 这2行代码运行结果相同,有何区别:// buildMaxTopHeap(nums,i) // O(logN),防止(已校验过的node)重复校验,建议使用// buildMaxTopHeap(nums,leng) // O(N)//// @param []int nums 集合(可看作: 树的节点集合)// @param int le (当前最大顶堆的,即排序元素的)长度// @param []int 排序后的最大顶堆(最大值在尾节点)func buildMaxTopHeap(nums []int,leng int) {// 边界条件 [可有可无,上一层已过滤]le := len(nums)if 0 == le {return}// 从中间root开始(因为同时遍历左右子树); i--因为未排序的元素越来越少mid := leng/2 - 1for i := mid; i >= 0; i-- {// root的左右子节点下标 (注: 树的左右子节点下标对应currIndex+1/currIndex+2,而非currIndex-1,currIndex+1)left := 2*i+1right := 2*i+2//## 校验并矫正 左/右子树,不满足maxTopHeap特性,则交换节点值// 左子树if left < leng && nums[left] > nums[i] {nums[left],nums[i] = nums[i],nums[left] // 交换节点值// 检验并矫正左子树的(左/右)子树if (2*left+1 < leng && nums[2*left+1] > nums[i]) || (2*left+2 < leng && nums[2*left+2] > nums[i]) {buildMaxTopHeap(nums,i) // O(logN),防止(已校验过的node)重复校验,建议使用// buildMaxTopHeap(nums,leng) // O(N)}}// 右子树if right < leng && nums[right] > nums[i] {nums[right],nums[i] = nums[i],nums[right]// 检验并矫正右子树的(左/右)子树if (2*right+1 < leng && nums[2*right+1] > nums[i]) || (2*right+2 < leng && nums[2*right+2] > nums[i]) {buildMaxTopHeap(nums,i) // O(logN),防止(已校验过的node)重复校验,建议使用// buildMaxTopHeap(nums,leng) // O(N)}}}}
2. 求2个节点的最近公共祖先,如: [8,10] => 2 , [6,10] => 3 ,[5,9] => 5
nums := []int{2,1,3,4,5,6,7,8,9,10}
21 34 5 6 78 9 10
// =============== 解题2 =============== //// 详见力扣236题: https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-tree/submissions/// 参考: https://mp.weixin.qq.com/s/9RKzBcr3I592spAsuMH45g/*** Definition for TreeNode.* type TreeNode struct {* Val int* Left *ListNode* Right *ListNode* }*/// 1.应用场景: git树,git的操作rebase/merge//// 2.框架:// 递归3要素: 解决的问题(作用)/递归的变量/递归截止条件(拿到结果后如何处理)// 本题解析: 寻找(最近)公共祖先node / 变化的节点root / 截止条件+返回lcaNode//// 3.思路:// 公共祖先, root,p,q,lcaNode=root/p/q 三者之一 =>// 最近公共祖先节点: 后序遍历(自底向上),保证"最近"(第一个公共祖先node就是lcaNode); [left,right,root];// ***注: 前序遍历是自顶向下//// 3种情况:// 1) p,q都在root下 (即都是root子节点,return root)// 2) p,q都不在root下 (即都不是root子节点,return nil)// 3) p,q其中有1个在root下 (即p/q其中1个是root子节点,return 非nil节点)// [注: 本题解中,lowestCommonAncestor缩写为lcaNode,即公共祖先节点]//// @param *TreeNode root 二叉树// @param *TreeNode p 节点1// @param *TreeNode q 节点2// @return *TreeNode lcaNode 最近公共祖先节点func lowestCommonAncestor(root, p, q *TreeNode) *TreeNode {// 递归截止条件 (边界条件)if nil == root {return nil}// root和p/q重合,即p/q为lcaNodeif p == root || q == root {return root}// 递归+逻辑: 后序遍历 => [left,right,root] => leftNode->rightNode->rootNodeleft := lowestCommonAncestor(root.Left,p,q)right := lowestCommonAncestor(root.Right,p,q)//### 3种情况, 递归过程中: p=leftNode,q=rightNode// 情况1: p/q都在root下 (即都是root子节点)if nil != left && nil != right {return root}// 情况2: p/q都不在root下 (即都不是root子节点)if nil == right && nil == left {return nil}// p/q其中有1个在root下 (即p/q其中1个是root子节点)if nil == left {return right}return left}
