最近在程序员圈子里看到一个爆料,隔壁组来了个超级牛的人物——清华姚班的OD,2018年辽宁理科状元,简直太狠了!😱

你要说这人有没有背景,那绝对是有的,不是普通的背景,是那种能在大厂HR面前“镶金边”的背景。
清华姚班、理科状元,这几个字放在简历上,HR能直接给你开门,根本不需要多看一眼别的。大家都知道,大厂HR眼睛一亮,学历硬件就是他们最想要的“金字招牌”。

但是,很多人就疑惑了,像他这样的牛人,为什么还要进OD?你想啊,清华姚班、状元这样的履历,出去随便跳槽,大厂也都是抢着要。按理说,应该高高在上,直接进入某个技术岗,结果却去了OD,这不太符合常规吧?
但反过来想,只要他想,啥大厂进不去呀,可能就是单纯的目前不想进大厂吧~
算法题:翻转对
今天我们来聊聊一个非常有意思的算法题:翻转对。如果你在面试中遇到过“翻转对”这种题目,可能会稍微有点懵,因为它听起来好像有点复杂,其实它的本质是考察你如何高效地处理数组中的逆序对,进而提升你的算法和编程思维。
这个问题的描述一般是这样的:
给定一个数组,你需要计算出该数组中有多少对(i, j),使得i < j 且nums[i] > nums[j]。
翻转对,这名字听起来挺酷的,不是吗?就像“翻转的世界”,指的是在你碰到的两个元素中,前面那个应该大,后面那个应该小,但实际情况是——它们恰恰相反。这种情况称为“逆序对”或者翻转对。
说实话,算法题中最让人头疼的就是这种“排好序,结果反了”的问题——每次你想着前面的数据应该大,后面的应该小,却发现它们互换了位置。
OK,那咱们先从最暴力的做法开始分析,然后一步步走向高效解法,毕竟作为一个程序员,做题嘛,最怕的就是笨方法。
暴力解法
暴力解法就是直接用两个嵌套的for循环,暴力检查每一对(i, j)。简单来说,就是逐个比较所有元素,找出符合条件的“逆序对”。
func countReversePairs(nums []int) int {
count := 0
for i := 0; i < len(nums); i++ {
for j := i + 1; j < len(nums); j++ {
if nums[i] > nums[j] {
count++
}
}
}
return count
}
这里的思路很简单:nums[i]如果大于nums[j],那么这就是一个翻转对(逆序对)。但是,这个方法的时间复杂度是O(n^2),对于数据量大的情况,可能会很慢。毕竟,你得两两比较每一对,速度可想而知。
优化思路
接下来,我们要进行优化。暴力解法虽然直观,但显然不是最优解。为了提高效率,我们可以借助“归并排序”的思想来计算翻转对。
通过归并排序,我们可以在排序的过程中计算出逆序对的个数。归并排序本身是一个O(n log n)的算法,而在归并的过程中,我们可以同时统计逆序对的数量。
归并排序解法
当我们合并两个有序数组时,如果left[i] > right[j],那么说明left[i]和右侧数组right[j]及其后续元素都会形成翻转对。通过这种方式,我们可以在O(n log n)的时间复杂度内计算出翻转对的数量。
func countReversePairs(nums []int) int {
return mergeSort(nums, 0, len(nums)-1)
}
func mergeSort(nums []int, left, right int) int {
if left >= right {
return 0
}
mid := left + (right-left)/2
count := mergeSort(nums, left, mid) + mergeSort(nums, mid+1, right)
count += merge(nums, left, mid, right)
return count
}
func merge(nums []int, left, mid, right int) int {
temp := make([]int, right-left+1)
i, j, k := left, mid+1, 0
count := 0
// Count the reverse pairs during the merge step
for i <= mid {
for j <= right && nums[i] > nums[j] {
count += (mid - i + 1) // All elements from i to mid form a pair with nums[j]
j++
}
i++
}
// Merge the two halves
i, j = left, mid+1
for i <= mid && j <= right {
if nums[i] <= nums[j] {
temp[k] = nums[i]
i++
} else {
temp[k] = nums[j]
j++
}
k++
}
// Copy the remaining elements
for i <= mid {
temp[k] = nums[i]
i++
k++
}
for j <= right {
temp[k] = nums[j]
j++
k++
}
// Copy the merged array back to nums
copy(nums[left:right+1], temp)
return count
}
在这个解法中,mergeSort递归地将数组分成两部分,然后通过merge函数来计算逆序对的数量。在合并的过程中,我们每找到一个left[i] > right[j]的情况,就能直接统计出从left[i]到left[mid]范围内的所有元素都与right[j]形成了翻转对。
效率分析
这个算法的时间复杂度是O(n log n),比暴力解法O(n^2)要高效得多。空间复杂度为O(n),因为我们使用了一个临时数组来存储合并的结果。对于大规模的数据,这个算法表现得非常不错。
对编程、职场感兴趣的同学,大家可以联系我微信:golang404,拉你进入“程序员交流群”。
虎哥作为一名老码农,整理了全网最全《GO后端开发资料合集》。
资料包含了《IDEA视频教程》、《最全GO面试题库》、《最全项目实战源码及视频》及《毕业设计系统源码》,总量高达650GB。全部免费领取!全面满足各个阶段程序员的学习需求!

