📜  门| GATE-IT-2004 |第86章(1)

📅  最后修改于: 2023-12-03 14:58:32.778000             🧑  作者: Mango

门| GATE-IT-2004 |第86章

简介

GATE-IT-2004是印度计算机协会在2004年组织的一项全国性计算机科学考试,是印度考取计算机相关学位课程的重要指标之一。本题目位于第86章,测试程序员对数据结构和算法的熟练程度。

题目描述

给定一个由n个整数组成的数组a[1...n],设计一个算法,找到所有满足i < j < k同时满足a[i] < a[k] < a[j]的(i,j,k)的总数。时间复杂度应该为O(nlogn)或O(n),空间复杂度应该为O(n)。

解题思路

首先,可以使用暴力枚举的方法,将每一个i,j,k情况都枚举一遍,判断是否符合条件,但是时间复杂度达到O(n^3),显然会TLE。

接着,可以使用一种类似于归并排序的方法,通过在归并的同时计算结果。对于每个区间[l,r],首先求出左半边[1,mid]和右半边[mid+1,r]的答案,然后再求出跨越[left,mid]和[mid+1,right]的答案。最后将上述三个数加起来就是该区间的答案。对于符合条件的数对(i,j,k),假设i和k已经来自左半边,j已经来自右半边,考虑如何统计满足条件的(i,j,k)数目。设p和q是左右半边都是从小到大排序后的位置指针。初始时,p指向最左边,q指向fist+1位置。此时考虑当前q能与p形成多少个数对。如果a[q]>=a[p],那么p可以向右移动到中间点,此时j∈(mid,q]都是符合条件的数,k∈[p,mid]也都是符合条件的数对。如果a[q]<a[p],那么q继续向右移动。此时将移动之前,q的前面所有元素都和a[q]可以形成数对,因为这些元素都比a[q]小,其中i都来自左半边,j都来自右半边,k能在左半边中找到。

代码实现
public class GATE_IT_2004 {
    public static void main(String[] args) {        
        int[] a = { 6, 2, 5, 4, 3, 1 };        
        int res = getCount(a, 0, a.length - 1);        
        System.out.println("符合条件的数目为:" + res);    
    }

    /*
     * 递归计算包含当前区间a[left...right]的答案
     */
    private static int getCount(int[] a, int left, int right) {
        if (left >= right) {
            return 0;
        }
        int mid = (left + right) >> 1; // 中断点
        int res = getCount(a, left, mid) + getCount(a, mid + 1, right); // 递归计算左右两半的答案

        // 记录左半边和右半边排序后的新数列
        int[] tmp = new int[right - left + 1];
        int p = left, q = mid + 1, k = 0;
        while (p <= mid && q <= right) {
            // 如果a[q]>=a[p],那么p可以向右移动到中间点
            // 此时j∈(mid,q]都是符合条件的数,k∈[p,mid]也都是符合条件的数对
            if (a[q] >= a[p]) {
                for (int i = mid + 1; i <= q; i++) {
                    int l = p, r = mid;
                    while (l <= r) {
                        int y = (l + r) >> 1;
                        if (a[y] < a[i]) {
                            l = y + 1;
                        } else {
                            r = y - 1;
                        }
                    }
                    res += mid - l + 1; // 统计跨越j和k的数量
                }
                tmp[k++] = a[p++];
            } else {
                // a[q]<a[p],那么q继续向右移动
                // 此时将移动之前,q的前面所有元素都和a[q]可以形成数对
                // 因为这些元素都比a[q]小,其中i都来自左半边,j都来自右半边,k能在左半边中找到
                for (int i = p; i <= mid; i++) {
                    int l = mid + 1, r = q;
                    while (l <= r) {
                        int y = (l + r) >> 1;
                        if (a[y] > a[i]) {
                            r = y - 1;
                        } else {
                            l = y + 1;
                        }
                    }
                    res += r - mid; // 统计跨越j和k的数量
                }
                tmp[k++] = a[q++];
            }
        }
        while (p <= mid) {
            tmp[k++] = a[p++];
        }
        while (q <= right) {
            tmp[k++] = a[q++];
        }
        // 将新的数列赋值回原数组
        for (int i = 0; i < tmp.length; i++) {
            a[left + i] = tmp[i];
        }
        return res;
    }
}