蓝桥杯-修改数组

148 阅读3分钟

1、题目描述

给定一个长度为 N 的数组 a=[A_1,A_2,...,AN]a=[A\_1,A\_2,...,A_N],数组中有可能有重复出现的整数。

现在小明要按以下方法将其修改为没有重复整数的数组。小明会依次修改A_2,A_3,...,ANA\_2,A\_3,...,A_N

当修改AiA_i时,小明会检查AiA_i是否在中出A_1Ai1A\_1\sim A_i-1现过。如果出现过,则小明会给 AiA_i加上 1 ;如果新的A_i仍在之前出现过,小明会持续给 加AiA_i 1 ,直 到 AiA_i没有在A_1Ai1A\_1\sim A_i-1中出现过。

ANA_N也经过上述修改之后,显然A数组中就没有重复的整数了。

现在给定初始的 A 数组,请你计算出最终的 A 数组。

输入描述

第一行包含一个整数 N

第二行包含N个整数A_1,A_2,...,ANA\_1,A\_2,...,A_N

其中,1≤N10510^5,1≤*AiA_i*≤10610^6

输出描述

输出 N 个整数,依次是最终的A_1,A_2,...,ANA\_1,A\_2,...,A_N

输入输出样例

示例

输入

5
2 1 1 3 4

输出

2 1 3 4 5

运行限制

  • 最大运行时间:1s
  • 最大运行内存: 256M

2、解题思路

2.1 思路一:暴力法(超时)

用一个exist[]数组标记输入的x是否已经被使用过exist[x]=1,若已经被使用过,则执行x++。这个思路倒是很清晰,就是超时了。

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StreamTokenizer;
import java.util.ArrayList;


/**
 * 暴力:用一个数组exist标记输入的x是否被使用过(exist[x]=1),若已经被使用过,则执行x++
 */
public class Main {
    public static int[] exist=new int[1000010];
    public static StreamTokenizer st=new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
    public static void main(String[] args) throws IOException {
        int n = nextInt();
        ArrayList<Integer> list = new ArrayList<>();
        for (int i = 1; i <=n ; i++) {
            int x = nextInt();
            while(exist[x]==1){
                x++;
            }
            exist[x]=1;
            list.add(x);
        }
        list.forEach(x->{
            System.out.print(x+" ");
        });
    }
    public static int nextInt() throws IOException{
        st.nextToken();
        return (int)st.nval;
    }
}

2.2 思路二:并查集(AC)

假设我们当前的输入为2 1 1 3 4,我们使用一个father[]数组保存每个节点的父节点,使用a数组保存最终每个位置上的结果。

以输入[2,1,1,3,4]为例,初始的时候,我们让每个节点都指向自己。

//并查集初始化 
for (int i = 1; i <=MAXN; i++) {
      father[i]=i;
	}

没输入一个数字,我们就去查找它的根节点,把他根节点的值当作这个位置的最终结果。

比如,

先输入2,查2的根节点为2,则最终结果:[2],然后将[2,3]合并,让2指向3,如下图:

image-20230331213222862

这样做的目的是,在下一次还输入2的时候,直接把2的根节点值3当作本次的最终结果。

再输入1,查1的根节点,1的根节点还是1,所以此时最终结果为:[2,1],然后将1和(1+1=2)执行合并,让1的根节点指向2的根节点,此时结果如下图:

image-20230331213536838

接着输入1,查找1的根节点为3,直接把3当作本次的结果,则此时最终结果为:[2,1,3],然后执行3和(3+1=4)的合并,结果如下右图

image-20230331214820637

本来应该为作图,但是由于路径压缩操作的存在,最终的结果为右图。

接着输入3,查找3的根节点为4,则此时的最终结果为:[2,1,3,4],然后执行4和(4+1=5)的合并,结果如下图:

image-20230331214904881

接着输入4,查找4的根节点为5,所以此时的最终结果:[2,1,3,4,5],然后执行5和(5+1=6)的合并就行,到这里已经结束了,就不再画出5和6的合并结果了。

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StreamTokenizer;
import java.util.Arrays;
/**
 * 并查集
 */
public class Final {
    public static final int MAXN=1000000;
    public static int n;
    //并查集数组,father[i]
    public static int[] father=new int[1000010];
    public static int[] a=new int[1000010];//保存结果
    public static StreamTokenizer st=new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
    public static void main(String[] args) throws IOException {
        n=nextInt();
        init();
        for (int i = 1; i <=n; i++) {
            int x=nextInt();
            int root =find(x);
            a[i]=root;  //保存结果
            union(root,root+1);
        }
        Arrays.stream(a).skip(1).limit(n).forEach(x->{
            System.out.print(x+" ");
        });
    }
    //并查集初始化
    public static void init(){
        for (int i = 1; i <=MAXN; i++) {
            father[i]=i;
        }
    }
    //查找父节点:路径压缩,否则会超时
    public static int find(int x){
        if(father[x]==x){
            return x;
        }else{
            //注意在不断查找根节点的过程中要路径压缩
             father[x]=find(father[x]);
             return father[x];
        }
    }
    public static void union(int x,int y){
        int father_x = find(x); //找到x的祖先
        int father_y = find(y); //找到y的祖先
        father[father_x]=father_y;//让x的祖先指向y的祖先
    }
    public static int nextInt() throws IOException{
        st.nextToken();
        return (int)st.nval;
    }
}

image-20230331215052076

image-20230331215107412