郑州轻工业ACM 2269: minval

2269: minval

时间限制: 3 Sec 内存限制: 256 MB
提交: 762 解决: 111
[提交][状态][讨论版][命题人:外部导入]
题目描述
有两个长度为N的序列A和B,在A和B中各任取一个数相加可以得到N2个和,求这N2个和中最小的N个。

输入
第一行输入一个正整数N(1<=N<=100000);

第二行N个整数Ai且Ai<=10^9;第三行N个整数Bi且Bi<=10^9。

输出
输出仅一行,包含n个整数,从小到大输出这n个最小的和,相邻数字之间用空格隔开。

样例输入
5
1 3 2 4 5
6 3 4 1 7
样例输出
2 3 4 4 5

题目分析

显而易见,这个题目是一个卡时间的题目,A、B两个数组的范围都是10^9,简单模拟肯定不行,需要整点套路什么的

第一种方案:

(1)首先将A,B两个数组进行排序,然后将A[0]+B[i]求和放到num[]数组中,且num[]数组是有序的;
(2)然后进行两层for循环遍历,将a[i]+b[j] < num[n-1] 插入到num[] 数组中,并且保持有序(其中是插入排序的思想);

结果肯定是超时,如果这样简单就过了,岂不是很僵硬

package algorithm.sort;
import java.util.Arrays;
import java.util.Scanner;
/**
 * @ClassName: SortTest1.java
 * @Description: 郑州轻工业ACM 2269: minval
 * @author: ccq
 * @date: 2018年4月23日 下午10:05:32
 */
public class SortTest1 {
    public static void main(String[] args) {
        int[] num;// 目标数组
        int[] a;  // A数组
        int[] b;  // B数组
        int n;    // 数组A、B的长度
        Scanner input = new Scanner(System.in);
        while (input.hasNext()) {
            n = input.nextInt();
            num = new int[n];
            a = new int[n];
            b = new int[n];
            for (int i = 0; i < n; i++) {
                a[i] = input.nextInt();
            }
            for (int i = 0; i < n; i++) {
                b[i] = input.nextInt();
            }
            // 分别对A、B进行排序
            Arrays.sort(a);
            Arrays.sort(b);
            // 初始化有序数组num[]
            for (int i = 0; i < n; i++) {
                num[i] = a[0] + b[i];
            }
            // 两层for循环
            for (int i = 1; i < n; i++) {
                // 如果a[i]+b[0] >= num[n-1]的时候说明以后所有a[i]+b[j]一定是大于num[n-1],
                // num[n-1] 是此时数组最大的数,所以没有必要在进行遍历,直接break即可
                if (a[i] + b[0] >= num[n - 1]) {
                    break;
                }
                for (int j = 0; j < n; j++) {
                    if (a[i] + b[j] >= num[n - 1]) {
                        break;
                    }
                    // 使用二分查找查询离a[i]+b[j]最近的值的位置
                    int index = search(num, a[i] + b[j], 0, n - 1);
                    // 将a[i]+b[j] 插入到num[] 数组中,同时保持有序
                    insertVal(num, a[i] + b[j], index, n - 1);
                }
            }
            printNum(num, n);
        }
        input.close();
    }

    /**
     * 二分查找
     * @param num   目标数组
     * @param v     二分查找的值
     * @param low   
     * @param high
     * @return
     */
    public static int search(int[] num, int v, int low, int high) {
        while (low <= high) {
            int mid = (low + high) / 2;
            if (v == num[mid]) {
                return mid;
            } else if (v > num[mid]) {
                low = mid + 1;
            } else {
                high = mid - 1;
            }
        }
        return high;
    }

    /**
     * 将X值插入到index+1的位置,从数组的最后n-1的位置移动
     * @param num   目标数组
     * @param x     插入的值
     * @param index 目标位置
     * @param k     数组指针
     */
    public static void insertVal(int[] num, int x, int index, int k) {
        for (; k > index + 1; k--) {
            num[k] = num[k - 1];
        }
        num[k] = x;
    }
    /**
     * 打印输出
     * @param num
     * @param n
     */
    public static void printNum(int[] num, int n) {
        System.out.print(num[0]);
        for (int i = 1; i < n; i++) {
            System.out.print(" " + num[i]);
        }
        System.out.println();
    }
}

再次分析一波

虽然在循环中进行优化,对于复杂的数据,代码的时间复杂度应该接近O(N^3)
为啥???两层for循环O(N^2),虽然二分查找的时间复杂度是O(logN),但是数值插入的时间是O(N)的,所以最后的时间复杂度O(N^3),对于第一种方案肯定是超时,果不其然,提交了一下OJ,超时刚刚的!
对于两层for循环是不可能避免的,因为需要计算a[i]+b[j]的值,但是对于num[]的维护可以进行优化的, 如果可以把num[]中插入值的操作优化一下,代码应该是可以过的
首先想到的是用堆,什么是堆尼?自己百度吧,很简单,对于大根堆的调整时间复杂度是O(logN) 也就是说可以优化到时间复杂度到O(N^2logN)

方案二

(1)首先将A,B两个数组进行排序,然后将A[0]+B[i]求和放到num[]数组中,且num[]数组是有序的,构造成从大到小的数组,同时它还是一个大根堆,好巧啊
(2)然后进行两层for循环遍历,将a[i]+b[j] < num[0],使num[0]=a[i]+b[j],进行大根堆的调整
(3)最后排序输出
美滋滋,这一回肯定可以过!!!
哈哈,顺利水过

package algorithm.sort;
import java.util.Arrays;
import java.util.Scanner;
/**
 * @ClassName: SortTest2.java
 * @Description: 郑州轻工业ACM 2269: minval
 * @author: ccq
 * @date: 2018年4月23日 下午10:05:32
 */
public class SortTest2 {
    public static void main(String[] args) {
        int[] num;// 目标数组
        int[] a; // A数组
        int[] b; // B数组
        int n; // 数组A、B的长度
        Scanner input = new Scanner(System.in);
        while (input.hasNext()) {
            n = input.nextInt();
            num = new int[n];
            a = new int[n];
            b = new int[n];
            for (int i = 0; i < n; i++) {
                a[i] = input.nextInt();
            }
            for (int i = 0; i < n; i++) {
                b[i] = input.nextInt();
            }
            // 分别对A、B进行排序
            Arrays.sort(a);
            Arrays.sort(b);
            // num 大根堆
            for (int i = 0; i < n; i++) {
                num[n - i - 1] = a[0] + b[i];
            }
            // 两层for循环
            for (int i = 1; i < n; i++) {
                // 如果a[i]+b[0] >= num[n-1]的时候说明以后所有a[i]+b[j]一定是大于num[n-1],
                // num[n-1] 是此时数组最大的数,所以没有必要在进行遍历,直接break即可
                if (a[i] + b[0] >= num[0]) {
                    break;
                }
                for (int j = 0; j < n; j++) {
                    if (a[i] + b[j] >= num[0]) {
                        break;
                    }
                    num[0] = a[i] + b[j];
                    // 对0位置进行堆调整
                    heapAdjust(num, n, 0);
                }
            }
            Arrays.sort(num);
            printNum(num, n);
        }
        input.close();
    }
    /**
     * 将数组a进行大根堆调整
     * @param a
     * @param len
     * @param i
     */
    public static void heapAdjust(int[] a, int len, int i) {
        int max = i;
        int curr = i;
        while (max < len) {
            int lchild = (curr << 1) + 1;
            int rchild = (curr << 1) + 2;
            if (lchild < len && a[max] < a[lchild]) {
                max = lchild;
            }
            if (rchild < len && a[max] < a[rchild]) {
                max = rchild;
            }
            if (curr == max) {
                break;
            }
            swap(a, max, curr);
            curr = max;
        }
    }
    public static void swap(int[] arr, int i, int j) {
        if (i != j) {
            int temp = arr[i];
            arr[i] = arr[j];
            arr[j] = temp;
        }
    }
    /**
     * 打印输出
     * 
     * @param num
     * @param n
     */
    public static void printNum(int[] num, int n) {
        System.out.print(num[0]);
        for (int i = 1; i < n; i++) {
            System.out.print(" " + num[i]);
        }
        System.out.println();
    }
}

方案三

当然,java的api还是很强大的,JDK中优先队列对堆进行了封装实现,也就是:PriorityQueue 这个玩意,我试试效果咋样
再次顺利水过!

package algorithm.sort;
import java.util.Arrays;
import java.util.Comparator;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.Scanner;
/**
 * 
 * @ClassName: SortTest3.java
 * @Description: 郑州轻工业ACM 2269: minval
 * @author: ccq
 * @date: 2018年4月23日 下午10:05:32
 *
 */
public class SortTest3 {
    public static void main(String[] args) {
        int[] num;// 目标数组
        int[] a; // A数组
        int[] b; // B数组
        int n; // 数组A、B的长度
        Queue<Integer> queue;
        Scanner input = new Scanner(System.in);
        while (input.hasNext()) {
            n = input.nextInt();
            num = new int[n];
            a = new int[n];
            b = new int[n];
            // 自定义的比较器,可以让我们自由定义比较的顺序  Comparator<Integer> cmp;
            // 生成最大堆使用e2-e1,生成最小堆使用e1-e2,
            queue = new PriorityQueue<Integer>(n,new Comparator<Integer>() {
                @Override
                public int compare(Integer e1, Integer e2) {
                    return e2 - e1; 
                }
            });
            for (int i = 0; i < n; i++) {
                a[i] = input.nextInt();
            }
            for (int i = 0; i < n; i++) {
                b[i] = input.nextInt();

            }
            // 分别对A、B进行排序
            Arrays.sort(a);
            Arrays.sort(b);
            for (int i = 0; i < n; i++) {
                queue.add(a[0] + b[i]);
            }
            // 两层for循环
            for (int i = 1; i < n; i++) {
                // 如果a[i]+b[0] >= num[n-1]的时候说明以后所有a[i]+b[j]一定是大于num[n-1],
                // num[n-1] 是此时数组最大的数,所以没有必要在进行遍历,直接break即可
                if (a[i] + b[0] >= queue.peek()) {
                    break;
                }
                for (int j = 0; j < n; j++) {
                    if (a[i] + b[j] >= queue.peek()) {
                        break;
                    }
                    queue.poll();
                    queue.add(a[i] + b[j]);
                }
            }
            for (int i = n - 1; i >= 0; i--) {
                num[i] = queue.poll();
            }
            printNum(num, n);
        }
        input.close();
    }

    /**
     * 打印输出
     * 
     * @param num
     * @param n
     */
    public static void printNum(int[] num, int n) {
        System.out.print(num[0]);
        for (int i = 1; i < n; i++) {
            System.out.print(" " + num[i]);
        }
        System.out.println();
    }
}

运行结果

这里写图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值