在CSDN上写的第一篇博客

AcWing 3775 数组补全

给定一个1\sim n的排列f_1,f_2,\dots,f_n

已知,对于1\leq i \leq n,f_i \neq i始终成立。

现在,因为一些原因,数组中的部分元素丢失了。

请你将数组丢失的部分补全,要求数组在补全后仍然是一个1\sim n的排列,并且对于1 \le i \le n,f_i \ne i均成立。

输入格式

第一行包含整数T,表示共有T组测试数据。

每组数据第一行包含一个整数n

第二行包含n个整数f_1,f_2,\dots ,f_n。如果f_i=0,则表示f_i已经丢失,需要补全。

输出格式

每组数据一行,输出补全后的f数组,整数之间空格隔开。

如果方案不唯一,则输出任意合理方案即可。

数据范围

1\le T\le 100,
2\le n \le 2\times 10^5,
0\le f_i \le n,至少两个f_i0
同一测试点内所有n的和不超过2\times 10^5
数据保证有解。

方法1

使用Set存下所有存在的数,再遍历一次1\sim n找到Set中不存在的数,存入List中,这样就找到了元素中缺失的数字。将其从小到大排列,并将其按尾到头依次放到原数组中值为0的位置。这样就保证了最多只有一个数字满足f_i=i(填入的数字是降序的,而数组序号是升序的)。再遍历一次填好后的数组,找到f_i=i的位置,若这个位置是原数组缺失数字的第一个位置,那么将其与缺失数字的最后一个位置交换,否则就将其与第一个位置交换,这样就保证了数组中没有满足f_i=i的数。

import java.util.*;

class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int t = sc.nextInt();
        while(t-- > 0) {
            int n = sc.nextInt();
            int[] a = new int[n+1];
            Set<Integer> set = new HashSet<>();
            for(int i = 1;i <= n;i++) {
                a[i] = sc.nextInt();
                set.add(a[i]);
            }
            List<Integer> num = new ArrayList<>();
            for(int i = 1;i <= n;i++) {
                if(!set.contains(i)) {
                    num.add(i);
                }
            }
            Collections.sort(num);
            int l = 0,r = 0;
            for(int i = 1,j = num.size() - 1;i <= n;i++) {
                if(a[i] == 0) {
                    a[i] = num.get(j);
                    j--;
                    if(j == num.size() - 2) {
                        r = i;
                    }
                    if(j == -1) {
                        l = i;
                    }
                }
            }
            for(int i = 1;i <= n;i++) {
                if(a[i] == i) {
                    if(i == l) {
                       int temp = a[i];
                       a[i] = a[r];
                       a[r] = temp;
                    } else {
                        int temp = a[i];
                        a[i] = a[l];
                        a[l] = temp;
                    }
                }
            }
            for(int i = 1;i <= n;i++) {
                System.out.print(a[i] + " ");
            }
            System.out.println();
        }
    }
}

方法2:环图

使用两个数组分别存一个数i的下一个节点p[i]和上一个节点q[i],再用一个数组存下一个数是否被加入环中。当递推一个节点的往下的最后一个节点和往上的最后一个节点是同一个节点时,那么这是一个完整的环,此时就需要将其余的所有p[i]=0的节点存入一个新的环中;否则就将所有的孤立节点存入到这个不完整的环中,构成一个完整的环。

import java.util.*;

class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int t = sc.nextInt();
        while(t-- > 0) {
            int n = sc.nextInt();
            int[] p = new int[n+10];   //p[i]=j表示i的下一个节点是j
            int[] q = new int[n+10];   //p[i]=j表示i的上一个节点是j,即j节点指向i
            boolean[] st = new boolean[n+10];   //每个节点是否已经加入到环图中
            boolean flag = false;   
            
            for(int i = 1;i <= n;i++) {
                p[i] = sc.nextInt();
                q[p[i]] = i;
            }
            
            for(int i = 1;i <= n;i++) {
                if(st[i] || p[i] == 0) {   //i已经加入到环中,或者p[i]=0
                    continue;
                }
                st[i] = true;
                int x = i,y = i;
                while(p[x] != 0 && !st[p[x]]) { //将p[x]加入环中
                    x = p[x];
                    st[x] = true;
                }
                while(q[y] != 0 && !st[q[y]]) {  //将q[y]加入环中
                    y = q[y];
                    st[y] = true;
                }
                if(p[x] == y) {   //已经是一个完整的环
                    continue; 
                }
                if(!flag) {   //将所有孤立的节点(p[j]=0,q[j]=0)加入该环中,形成完整的环
                    flag = true;
                    for(int j = 1;j <= n;j++) {
                        if(p[j] == 0 && q[j] == 0) {
                            st[j] = true;
                            p[x] = j;
                            x = j;
                        }
                    }
                }
                p[x] = y;
            }
            if(!flag) {    //将其余的节点(p[i]=0)的节点加入其它的环中
                int x = 0,y = 0;
                for(int i = 1;i <= n;i++) {
                    if(p[i] == 0) {
                        if(x == 0 && y == 0) {
                            x = i;
                            y = i;
                        } else {
                            p[x] = i;
                            x = i;
                        }
                    } 
                }
                p[x] = y;
            }
            for(int i = 1;i <= n;i++) {
                System.out.print(p[i] + " ");
            }
            System.out.println();
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值