AcWing 14. 不修改数组找出重复的数字(分治法)

如果不将思考题考虑在内的话,那么AcWing 13.找出数组中重复的数字中的方法1的代码是可以完全移植过来的。这里不再赘述。不过,由于这道题的样例里面不存在含有超出范围的数据以及全部数据均出现一次的情况,因此代码还可以进一步简化。

简化后的具体代码如下:

class Solution {
public:
    int duplicateInArray(vector<int>& nums) {
        int n=nums.size();
        vector<int> v(n, 0);
        //统计出现次数
        for(auto &e: nums){
            v[e]++;
        }
        //遍历统计结果,发现有满足条件的则返回
        for(int i = 0; i < n; i++){
            if(v[i] > 1) return i;
        }
        return 0;
    }
};

现在,我们来解决思考题。由于要求空间复杂度为O(1),因此不可以再开辟等量空间统计出现次数。

为了解决问题,我们先按照元素所在的区间划分为等长的子区间(注意,这里的区间并不是数组的区间,而是数组中元素取值范围的区间)。然后再遍历数组统计落在这两个子区间里面元素的个数。假设所有的元素均是互异的,那么落在两个子区间里面元素的个数应该恰好等于区间长度。当存在重复元素时,两个子区间中应至少有一个是统计个数大于区间长度的。此时,只要在出现这种情况的子区间中继续如上的操作,最终就能得到重复的元素。该算法原理类似二分查找,不过要注意二分查找的子区间是按照数组划分的。

具体代码如下:

class Solution {
public:
    int duplicateInArray(vector<int>& nums) {
        int lo = 1, hi = nums.size() - 1;
        while(lo <= hi){
            int sum = 0; //统计数组元素在左子区间范围中元素个数
            int mid = lo + (hi - lo) / 2; //将区间划分为[lo, mid]和[mid + 1, hi]
            for(auto e: nums){
                if(lo <= e && e <= mid) sum++;
            }
            if(sum <= (mid - lo + 1)) lo = mid + 1;
            else hi = mid - 1;
        }
        return lo;
    }
};

 

<think>好的,我现在需要帮助用户理解如何用分治法和归并排序计算逆序对的数量。首先,用户已经提到了参考引用中的内容,涉及到归并排序和逆序对的关系,以及分治法的应用。我需要将这些信息整合成一个清晰的步骤,并给出具体的实现方法。 首先,我得回忆一下逆序对的定义:在一个数组中,如果前面的数比后面的数大,这两个数就构成一个逆序对。例如,数组[3,1,2]中的逆序对是(3,1)(3,2),所以总共有2个逆序对。 接下来,归并排序和分治法如何帮助计算逆序对呢?归并排序的基本思想是将数组分成两半,分别排序后再合并。在合并的过程中,可以统计逆序对的数量。具体来说,当合并左右两个有序子数组时,如果左半部分的当前元素大于右半部分的当前元素,那么左半部分剩余的所有元素都与右半部分的当前元素构成逆序对。这样在合并的过程中就能统计出跨越左右两部分的逆序对数量,再加上左右各自内部的逆序对数量,总和就是整个数组的逆序对总数。 然后,我需要整理出具体的步骤,可能包括分解、递归求解、合并统计三个部分。分解阶段将数组分成左右两半;递归求解左右两半各自的逆序对数量;合并阶段在归并的过程中统计跨左右的逆序对,并将结果相加。 接下来要考虑代码实现。用户提供的示例是快速排序的Python代码,我需要给出类似的归并排序实现,并加入逆序对的计数。需要注意递归终止条件、左右子数组的递归调用、合并过程中的比较和计数。 此外,需要考虑时间复杂度和空间复杂度。归并排序的时间复杂度是O(n log n),这也是计算逆序对的时间复杂度,因为每次合并操作是线性时间,递归深度是log n层。空间复杂度是O(n),因为需要临时数组来合并。 可能还需要提到优化点,比如当数组已经有序时,可以提前终止某些步骤,或者如何处理重复元素的情况。过根据引用内容,用户可能更需要基础的方法,所以这部分可能需要深入,但可以作为扩展问题。 用户的问题中提到了分治法,所以需要强调分治的三个步骤:分解、解决、合并。在分解阶段将问题拆分为子问题,解决阶段递归处理子问题,合并阶段统计逆序对并合并结果。 现在,我需要确保代码正确,特别是合并过程中的逆序对计数部分。例如,当左半部分的元素大于右半部分的元素时,逆序对的数量应该是左半部分剩余元素的个数,而仅仅是加1。例如,在合并时,左数组当前元素比右数组当前元素大,那么左数组中该元素之后的所有元素都比右数组当前元素大,所以逆序对数量增加mid - i + 1,其中mid是左半部分的结束索引,i是左数组当前的位置。 最后,要检查引用是否正确对应。引用[1]介绍归并排序的基本概念,引用[2]提到分治法分解逆序对问题,引用[3]指出排序是消除逆序对的过程。因此,在回答中需要正确引用这些内容,并自然地插入到相应的位置。 可能用户会有的疑问包括:为什么归并排序适合计算逆序对?如何处理重复元素?有没有其他方法可以计算逆序对?这些问题可以作为相关问题提出,但用户的问题主要关注分治法和归并排序的方法,所以重点应放在这上面。 总结步骤: 1. 分治法分解数组。 2. 递归计算左右子数组的逆序对数量。 3. 合并时统计跨左右的逆序对数量。 4. 合并左右子数组,形成有序数组。 5. 总和即为总逆序对数。 确保代码正确无误,特别是合并部分的逻辑。例如,当左数组的元素大于右数组的元素时,累加逆序对的数量。</think>### 使用分治法和归并排序计算逆序对数量 #### 核心思想 逆序对指数组中满足 $i < j$ 且 $a[i] > a[j]$ 的元素对。**分治法**通过将数组分解为子问题,利用归并排序的合并过程统计跨左右子数组的逆序对数量,最终递归累加结果[^3]。 #### 实现步骤 1. **分解** 将数组从中间分为左右两半,递归计算左右子数组的逆序对数。 2. **合并与统计** 合并左右有序子数组时,若左半部分元素 $a[i]$ 大于右半部分元素 $a[j]$,则左半部分剩余元素(数量为 $mid - i + 1$)均与 $a[j]$ 构成逆序对,累加到总结果中[^2]。 3. **递归终止条件** 当子数组长度为1时,逆序对数为0。 #### Python实现 ```python def merge_sort_count(arr): if len(arr) <= 1: return arr, 0 mid = len(arr) // 2 left, cnt_left = merge_sort_count(arr[:mid]) right, cnt_right = merge_sort_count(arr[mid:]) merged, cnt_cross = merge(left, right) return merged, cnt_left + cnt_right + cnt_cross def merge(left, right): merged = [] i = j = cnt = 0 while i < len(left) and j < len(right): if left[i] <= right[j]: merged.append(left[i]) i += 1 else: merged.append(right[j]) j += 1 cnt += len(left) - i # 统计逆序对 merged += left[i:] merged += right[j:] return merged, cnt # 示例 arr = [3, 1, 2] sorted_arr, count = merge_sort_count(arr) print(f"逆序对数量: {count}") # 输出2 ``` #### 复杂度分析 - **时间复杂度**:$O(n \log n)$,与归并排序一致[^1] - **空间复杂度**:$O(n)$,用于合并过程的临时数组 #### 关键点 - 归并排序的合并阶段天然适合统计跨子数组的逆序对 - 分治法将问题分解为子问题,避免暴力枚举的 $O(n^2)$ 复杂度
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值