字节算法题目

1.无序数组,第k大的数 (快排,快排+判断: 轴+k和轴比对)

  1. // =============== 解题1 =============== //
  2. // 力扣215题: https://leetcode-cn.com/problems/kth-largest-element-in-an-array/submissions/
  3. // 思路1: 快排 + 二分查找
  4. // 思路2: 第k次-最大顶堆的尾节点元素: 构建最大顶堆 + 循环k次 [基于回溯算法的优化]
  5. func findKthLargest(nums []int, k int) int {
  6. // return findKByquickSort(nums,k) // 快排解法
  7. return findKByMaxTopHeap(nums,k) // 最大顶堆解法
  8. }
  9. // 思路1: 快排 + 二分查找
  10. // @param []int num 无序数组
  11. // @param int k 第k大
  12. // @return int 第k大的数,num[len(num)-k]
  13. func findKByquickSort(num []int,k int) int {
  14. // 边界条件
  15. le := len(num)
  16. if 0 == le {
  17. return 0
  18. }
  19. // 快排
  20. var quickSort func(nums []int) []int
  21. quickSort = func(nums []int) []int {
  22. // 边界条件
  23. le2 := len(nums)
  24. if 0 == le2 { // ***易错点:
  25. return []int{}
  26. }
  27. mid := nums[0]
  28. l,r := []int{},[]int{}
  29. // for i := 0; i < le; i++ { // ***易错点:
  30. for i := 1; i < le2; i++ {
  31. // ***易错点: 3种case别漏了,<,=,>
  32. if mid >= nums[i] { // <=存左边
  33. l = append(l,nums[i])
  34. } else if mid < nums[i] { // >存右边
  35. r = append(r,nums[i])
  36. }
  37. }
  38. // 递归执行
  39. l = quickSort(l)
  40. r = quickSort(r)
  41. // 合并数组
  42. l = append(l,mid)
  43. l = append(l,r...) // 批量追加: go合并切片方法,同下for循环用法
  44. // for _,v := range r {
  45. // l = append(l,v)
  46. // }
  47. return l
  48. }
  49. res := quickSort(num)
  50. // 二分查找
  51. return res[le-k]
  52. }
  53. // 思路2: 第k次-最大顶堆(顶点值max=nums[0]): 循环k次,每次循环中 {构建最大顶堆 => max=maxTopHeap[0] => 依次追加到尾节点处}
  54. // 注: k=le-1时,和第一种快排时间复杂度相同
  55. // 几个易错点:
  56. // 1) for i := le; i > 0; i-- // ***易错点: 长度必须>=2,否则无需用maxTopHeap排序;
  57. // 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]
  58. // @param []int num 无序数组
  59. // @param int k 第k大
  60. // @return int 第k大的数,num[len(num)-k]
  61. func findKByMaxTopHeap(nums []int,k int) int {
  62. // 边界条件
  63. le := len(nums)
  64. if 0 == le || k > le {
  65. return 0
  66. }
  67. // 循环构建maxTopHeap,共构建k次,(取最大值=>maxTopHeap,取最小值=>minTopHeap)
  68. // 循环次数为 数组长度,而非数组右边界;
  69. // 递减循环: 因为第 n 次排序 N 个数,则第 (n+1) 次排序 (N-1) 个数;
  70. for i := le; i > (le-k); i-- { // i > 1时(即le-k=1),则全部排序
  71. // 构建maxTopHeap
  72. buildMaxTopHeap(nums,i)
  73. // 交换max(maxTopHeap顶点值) 和 当前位置(尾节点元素值)
  74. nums[i-1],nums[0] = nums[0],nums[i-1]
  75. }
  76. // fmt.Println("******sortArr==",nums)
  77. // fmt.Println("res==",nums[le-k])
  78. return nums[le-k] // 最大值nums[le-1],第k大值为nums[le-k]
  79. }
  80. // 构建最大顶堆maxTopHeap; [注意: 最大顶堆中for循环中递减的为元素范围长度length,而非下标值]
  81. // 思路: maxTopHeap特征(二叉树): currNode>=leftSonNodes && currNode>=rightSonNodes, 否则需对调currNode和sonNode位置
  82. //
  83. // 几个问题:
  84. // 1) 问题1: mid := (le-1)/2 是否可行?? [leng/2-1,leng/2都可以,能保证从中间root开始(因为同时遍历左右子树)即可]
  85. // 2) 问题2: 循环遍历时,为何不是i++? [因为未排序的元素越来越少]
  86. // 3) 问题3: 这2行代码运行结果相同,有何区别:
  87. // buildMaxTopHeap(nums,i) // O(logN),防止(已校验过的node)重复校验,建议使用
  88. // buildMaxTopHeap(nums,leng) // O(N)
  89. //
  90. // @param []int nums 集合(可看作: 树的节点集合)
  91. // @param int le (当前最大顶堆的,即排序元素的)长度
  92. // @param []int 排序后的最大顶堆(最大值在尾节点)
  93. func buildMaxTopHeap(nums []int,leng int) {
  94. // 边界条件 [可有可无,上一层已过滤]
  95. le := len(nums)
  96. if 0 == le {
  97. return
  98. }
  99. // 从中间root开始(因为同时遍历左右子树); i--因为未排序的元素越来越少
  100. mid := leng/2 - 1
  101. for i := mid; i >= 0; i-- {
  102. // root的左右子节点下标 (注: 树的左右子节点下标对应currIndex+1/currIndex+2,而非currIndex-1,currIndex+1)
  103. left := 2*i+1
  104. right := 2*i+2
  105. //## 校验并矫正 左/右子树,不满足maxTopHeap特性,则交换节点值
  106. // 左子树
  107. if left < leng && nums[left] > nums[i] {
  108. nums[left],nums[i] = nums[i],nums[left] // 交换节点值
  109. // 检验并矫正左子树的(左/右)子树
  110. if (2*left+1 < leng && nums[2*left+1] > nums[i]) || (2*left+2 < leng && nums[2*left+2] > nums[i]) {
  111. buildMaxTopHeap(nums,i) // O(logN),防止(已校验过的node)重复校验,建议使用
  112. // buildMaxTopHeap(nums,leng) // O(N)
  113. }
  114. }
  115. // 右子树
  116. if right < leng && nums[right] > nums[i] {
  117. nums[right],nums[i] = nums[i],nums[right]
  118. // 检验并矫正右子树的(左/右)子树
  119. if (2*right+1 < leng && nums[2*right+1] > nums[i]) || (2*right+2 < leng && nums[2*right+2] > nums[i]) {
  120. buildMaxTopHeap(nums,i) // O(logN),防止(已校验过的node)重复校验,建议使用
  121. // buildMaxTopHeap(nums,leng) // O(N)
  122. }
  123. }
  124. }
  125. }

2. 求2个节点的最近公共祖先,如: [8,10] => 2 , [6,10] => 3 ,[5,9] => 5

nums := []int{2,1,3,4,5,6,7,8,9,10}

  1. 2
  2. 1 3
  3. 4 5 6 7
  4. 8 9 10
  1. // =============== 解题2 =============== //
  2. // 详见力扣236题: https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-tree/submissions/
  3. // 参考: https://mp.weixin.qq.com/s/9RKzBcr3I592spAsuMH45g
  4. /**
  5. * Definition for TreeNode.
  6. * type TreeNode struct {
  7. * Val int
  8. * Left *ListNode
  9. * Right *ListNode
  10. * }
  11. */
  12. // 1.应用场景: git树,git的操作rebase/merge
  13. //
  14. // 2.框架:
  15. // 递归3要素: 解决的问题(作用)/递归的变量/递归截止条件(拿到结果后如何处理)
  16. // 本题解析: 寻找(最近)公共祖先node / 变化的节点root / 截止条件+返回lcaNode
  17. //
  18. // 3.思路:
  19. // 公共祖先, root,p,q,lcaNode=root/p/q 三者之一 =>
  20. // 最近公共祖先节点: 后序遍历(自底向上),保证"最近"(第一个公共祖先node就是lcaNode); [left,right,root];
  21. // ***注: 前序遍历是自顶向下
  22. //
  23. // 3种情况:
  24. // 1) p,q都在root下 (即都是root子节点,return root)
  25. // 2) p,q都不在root下 (即都不是root子节点,return nil)
  26. // 3) p,q其中有1个在root下 (即p/q其中1个是root子节点,return 非nil节点)
  27. // [注: 本题解中,lowestCommonAncestor缩写为lcaNode,即公共祖先节点]
  28. //
  29. // @param *TreeNode root 二叉树
  30. // @param *TreeNode p 节点1
  31. // @param *TreeNode q 节点2
  32. // @return *TreeNode lcaNode 最近公共祖先节点
  33. func lowestCommonAncestor(root, p, q *TreeNode) *TreeNode {
  34. // 递归截止条件 (边界条件)
  35. if nil == root {
  36. return nil
  37. }
  38. // root和p/q重合,即p/q为lcaNode
  39. if p == root || q == root {
  40. return root
  41. }
  42. // 递归+逻辑: 后序遍历 => [left,right,root] => leftNode->rightNode->rootNode
  43. left := lowestCommonAncestor(root.Left,p,q)
  44. right := lowestCommonAncestor(root.Right,p,q)
  45. //### 3种情况, 递归过程中: p=leftNode,q=rightNode
  46. // 情况1: p/q都在root下 (即都是root子节点)
  47. if nil != left && nil != right {
  48. return root
  49. }
  50. // 情况2: p/q都不在root下 (即都不是root子节点)
  51. if nil == right && nil == left {
  52. return nil
  53. }
  54. // p/q其中有1个在root下 (即p/q其中1个是root子节点)
  55. if nil == left {
  56. return right
  57. }
  58. return left
  59. }