首页 > 技术文章 > 第十届蓝桥杯省赛JavaA组——修改数组(并查集)

pengsay 2021-03-28 09:16 原文

题目:

【问题描述】给定一个长度为 N 的数组 A = [A1, A2, · · · AN],数组中有可能有重复出现的整数。
现在小明要按以下方法将其修改为没有重复整数的数组。小明会依次修改A2, A3, · · · , AN。当修改
Ai 时,小明会检查 Ai 是否在 A1 ∼ Ai−1 中出现过。如果出现过,则小明会给 Ai 加上 1 ;如果新
的 Ai 仍在之前出现过,小明会持续给 Ai 加 1 ,直到 Ai 没有在 A1 ∼ Ai−1 中出现过。当 AN 也
经过上述修改之后,显然 A 数组中就没有重复的整数了。现在给定初始的 A 数组,请你计算出最终的 A 数组。
【输入格式】
第一行包含一个整数 N。
第二行包含 N 个整数 A1, A2, · · · , AN 。
【输出格式】
输出 N 个整数,依次是最终的 A1, A2, · · · , AN。
【样例输入】
5
2 1 1 3 4
【样例输出】
2 1 3 4 5
【数据规模】 1<=N<=100000; 1<=Ai<=1000000;

 

分析:一开始就像暴力试试,结果发现不行,后面就写了个二叉树,用了下二分查找,发现也还是不行,哈哈哈,最后想的头都快炸了,自己网上百度查了相关并查集的算法,还是没弄懂这题,这也是第一次写并查集的题目。于是就去直接搜了下网上网友写的代码,一看直呼牛逼,竟然还有这种奇妙的算法,真是令自己大开眼界,虽然不是自己写的,但是看了十几二十分钟终于看懂了这个用并查集解的题目,心里也很开心。好了不多说,先上我自己的暴力代码和二叉树实现的二分代码

暴力代码:

 1 public class AlterArray {
 2 
 3     private static int[] arr;
 4     private static boolean[] isExist = new boolean[1000000];
 5     public static void main(String[] args) {
 6         Scanner input = new Scanner(System.in);
 7         int n = input.nextInt();
 8         arr = new int[n];
 9         for (int i = 0; i < n; i++) {
10             arr[i] = input.nextInt();
11         }
12 
13         // 第一个数默认已经存在
14         isExist[arr[0]] = true;
15         // 从第一个数开始遍历
16         for (int i = 1; i < arr.length; i++) {
17             while (isExist[arr[i]]) {
18                 arr[i]++;
19             }
20             isExist[arr[i]] = true;
21         }
22 
23         for (int i = 0; i < arr.length; i++) {
24             System.out.printf("%d ", arr[i]);
25         }
26     }
27 }

二分代码:

 1 public class AlterArray2 {
 2     private static int[] arr;
 3     private static Node root;
 4     public static void main(String[] args) {
 5         Scanner input = new Scanner(System.in);
 6         int n = input.nextInt();
 7         arr = new int[n];
 8 
 9         arr[0] = input.nextInt();
10         root = new Node(arr[0], null, null);
11         for (int i = 1; i < n; i++) {
12             int a = input.nextInt();
13             // 查找
14             while (BinarySortTreeUtil.find(root, a)) {
15                 a++;
16             }
17             arr[i] = a;
18             BinarySortTreeUtil.add(root, arr[i]);
19         }
20 
21         for (int i = 0; i < arr.length; i++) {
22             System.out.printf("%d ", arr[i]);
23         }
24     }
25 }
26 
27 /**
28  * 二叉排序树
29  */
30 class BinarySortTreeUtil {
31 
32     public static void add(Node root, int data) {
33        if (!find(root, data)) {
34            addNode(root, data);
35        }
36     }
37 
38     /**
39      * 添加元素
40      * 如果要添加的元素跟二叉树中的某一个元素相等则不添加
41      * @param root
42      * @param data
43      */
44     private static void addNode(Node root, int data) {
45         if (data < root.data) {
46             if (root.left == null) {
47                 // 如果左孩子为空,则直接赋值给左孩子
48                 root.left = new Node(data, null, null);
49             } else {
50                 // 左孩子
51                 addNode(root.left, data);
52             }
53         } else if (data > root.data){
54             if (root.right == null) {
55                 // 如果右孩子为空,则直接赋值给右孩子
56                 root.right = new Node(data, null, null);
57             } else {
58                 // 右孩子
59                 addNode(root.right, data);
60             }
61         }
62     }
63 
64     /**
65      * 二分查找
66      * @param data
67      * @return
68      */
69     public static boolean find(Node root, int data) {
70         if (root == null) {
71             return false;
72         }
73         if (data == root.data) {
74             return true;
75         } else if (data < root.data) {
76             return find(root.left, data);
77         } else {
78             return find(root.right, data);
79         }
80     }
81 }
82 
83 class Node {
84     int data;
85     Node left;
86     Node right;
87 
88     public Node(int data, Node left, Node right) {
89         this.data = data;
90         this.left = left;
91         this.right = right;
92     }
93 }

思路:网友的代码中用了一个长度为1000003的辅助数组f,f数组的作用是存储每个数应该存储的位置,这个数组一开始初始化为每个数都是存储在自己这个数本身的位置,而后会随着循环的执行,这个数组的某些内容可能会在不停的变化。例如:在某个数第一次出现之时,第一步find方法就会直接返回这个数,然后再对这个数进行下一个存放的位置进行查找(意思就是如果这个数现在已经出现了一次,后面再出现这个数的话,那么就要从该数的下一个位置去寻找它应该存放的位置),find方法的作用就是找到指定数它应该存放的位置,而此时位置对应的数字就是当前位置该存放的数。(再次声明一下:f[i] = val 代表的是i位置下次应该放的数为val

网友的AC代码:

 1 import java.util.*;
 2 
 3 /**
 4  * @Author LZP
 5  * @Date 2021/3/18 17:37
 6  * @Version 1.0
 7  * <p>
 8  * 并查集是什么
 9  * <p>
10  * 并查集是一种用来管理元素分组情况的数据结构。并查集可以高效地进行如下操作。
11  * 不过需要注意并查集虽然可以进行合并操作,但是却无法进行分割操作
12  * 1、查询元素a和元素b是否属于同一组
13  * 2、合并元素a和元素b所在的组
14  */
15 public class AlterArray3 {
16     static int[] a = new int[100010];
17 
18     /**
19      * f[i] = val 代表的是i应该放的位置是在val处
20      */
21     static int[] f = new int[1000010];
22     static int n;
23 
24     /**
25      * 查找还没被取过的值
26      * @param x
27      * @return
28      */
29     static int find(int x) {
30         if (f[x] != x) {
31             f[x] = find(f[x]);
32         }
33         // 如果f[x] == x ,说明x是第一次出现,就直接返回
34         return f[x];
35     }
36 
37     public static void main(String[] args) {
38         Scanner scanner = new Scanner(System.in);
39         n = scanner.nextInt();
40         for (int i = 1; i <= n; i++) {
41             a[i] = scanner.nextInt();
42         }
43         scanner.close();
44         // 1.初始化,即把每个点所在的集合初始化为自身
45         for (int i = 1; i <= 1000003; i++) {
46             f[i] = i;
47         }
48         // 2.查找a[i]处应有的值,每次取完都把原位置的值设为下一位置的值
49         for (int i = 1; i <= n; i++) {
50             a[i] = find(a[i]);
51             /*
52                 如果a[i]这个数下次再出现,就让它去找a[i]所在位置的下一个能放的位
53                 置,如果下一个位置已经被放了,那么再去找下一个,就这样递归的找下去,
54                 直到找到能放的位置
55                 这里每次+1,就是印证了题目的要求,每次往后找,而不会返回来往前去找
56              */
57             f[a[i]] = find(a[i] + 1);
58         }
59         for (int i = 1; i <= n; i++) {
60             System.out.print(a[i] + " ");
61         }
62     }
63 }

推荐阅读