【LeetCode每日一题】——685.冗余连接II

一【题目类别】

二【题目难度】

  • 困难

三【题目编号】

  • 685.冗余连接II

四【题目描述】

  • 在本问题中,有根树指满足以下条件的 有向 图。该树只有一个根节点,所有其他节点都是该根节点的后继。该树除了根节点之外的每一个节点都有且只有一个父节点,而根节点没有父节点。
  • 输入一个有向图,该图由一个有着 n 个节点(节点值不重复,从 1 到 n)的树及一条附加的有向边构成。附加的边包含在 1 到 n 中的两个不同顶点间,这条附加的边不属于树中已存在的边。
  • 结果图是一个以边组成的二维数组 edges 。 每个元素是一对 [ui, vi],用以表示 有向 图中连接顶点 ui 和顶点 vi 的边,其中 ui 是 vi 的一个父节点。
  • 返回一条能删除的边,使得剩下的图是有 n 个节点的有根树。若有多个答案,返回最后出现在给定二维数组的答案。

五【题目示例】

  • 示例 1:
    在这里插入图片描述
    输入:edges = [[1,2],[1,3],[2,3]]
    输出:[2,3]
  • 示例 2:
    在这里插入图片描述
    输入:edges = [[1,2],[2,3],[3,4],[4,1],[1,5]]
    输出:[4,1]

六【题目注意】

  • n == edges.length
  • 3 <= n <= 1000
  • edges[i].length == 2
  • 1 <= ui, vi <= n

七【解题思路】

  • 首先计算每个结点的入度,如果入度都为1,就当成无向图来算,那么就和冗余连接I式一样的,加入哪个边形成环了就直接返回即可。如果入度有为2的,就当成有向图来算,则有两个结点指向该结点,这两条边优先删除位于临边数组后边的一条边,若不能连通返回另一条,否则返回该条,具体做法是判断去掉某个边是否还能构成通路,如果可以构成通路,最后都指向根节点,那么返回后一条边,否则不能构成通路,返回前一条边

八【时间频度】

  • 时间复杂度: O ( N ) O(N) O(N)

九【代码实现】

  1. Java语言版
package Graph;

import java.util.Arrays;

public class p685_RedundantConnectionII {

    public static void main(String[] args) {
        int[][] edges = {
                {1, 2},
                {1, 3},
                {2, 3},
        };
        int[] res = findRedundantDirectedConnection(edges);
        System.out.println("res = " + Arrays.toString(res));
    }

    public static int[] findRedundantDirectedConnection(int[][] edges) {
        // 定义辅助数组,长度为结点个数 + 1
        int[] degrees = new int[edges.length + 1];
        // 定义标识位,判断入度是1还是2
        int flag = -1;
        // 给数组赋初值,并判断入读个数,如果入度个数为2,则flag置为当前结点
        for (int[] edge : edges) {
            degrees[edge[1]] += 1;
            if (degrees[edge[1]] == 2) {
                flag = edge[1];
                break;
            }
        }
        // 如果flag=-1,说明所有节点的入度都为1,那么就当作无向图做并查集
        if (flag == -1) {
            // 定义辅助数组,长度为结点个数 + 1
            int[] nums = new int[edges.length + 1];
            // 给数组赋初值,开始每个结点的父节点就是自己
            for (int k = 0; k < nums.length; k++) {
                nums[k] = k;
            }
            // 开始遍历每一条边
            for (int i = 0; i < edges.length; i++) {
                // 找到每一个边的第一个结点的父节点
                int f1 = findRoot(edges[i][0], nums);
                // 找到每一个边的第二个结点的父节点
                int f2 = findRoot(edges[i][1], nums);
                // 如果两个结点的父节点相等,说明这两个结点构成的边是重复的
                if (f1 == f2) {
                    return edges[i];
                }
                // 如果不相等,那么连接这两个边
                else {
                    nums[f1] = f2;
                }
            }
        }
        // 如果flag!=-1,则有两个结点指向该结点,这两条边优先删除位于临边数组后边的一条边,若不能连通返回另一条,否则返回该条
        else {
            // pre:前一条边,pre1:后一条边
            int pre = -1, pre1 = -1;
            // 定义辅助数组,长度为结点个数 + 1
            int[] nums = new int[edges.length + 1];
            int i, j, count = 0;
            // 给数组赋初值,开始每个结点的父节点就是自己
            for (i = 0; i < edges.length + 1; i++) {
                nums[i] = i;
            }
            for (i = 0; i < edges.length; i++) {
                if (edges[i][1] == flag) {
                    if (pre == -1) {
                        pre = i;
                    } else {
                        pre1 = i;
                        break;
                    }
                }
            }
            int f1 = 0, f2 = 0;
            for (i = 0; i < edges.length; i++) {
                // 跳过后一条边
                if (i == pre1) {
                    continue;
                }
                f1 = findRoot(edges[i][0], nums);
                f2 = findRoot(edges[i][1], nums);
                // 合并
                nums[f1] = nums[f2];
            }
            // 判断去掉这个边是否还能构成通路
            for (i = 1; i < nums.length; i++) {
                if (nums[i] == i) {
                    count++;
                }
            }
            // 如果可以构成通路,最后都指向根节点,那么返回后一条边,如果count=2,说明不能构成通路,返回前一条边
            if (count == 2) {
                return edges[pre];
            } else {
                return edges[pre1];
            }
        }
        return null;
    }

    public static int findRoot(int n, int[] nums) {
        // 当当前结点的父节点不是自己,将父节点赋值给当前结点,并返回
        while (nums[n] != n) {
            n = nums[n];
        }
        return n;
    }

}
  1. C语言版
#include<stdlib.h>
#include<stdio.h>
#include<stdbool.h>

#define MAX_GROUPS_COUNT 1001
int g_groups[MAX_GROUPS_COUNT];

/*初始化并查集数组*/
void InitGroups()
{
	for (size_t i = 0; i < MAX_GROUPS_COUNT; i++)
	{
		g_groups[i] = i;
	}
}

/*寻找父节点*/
int Find(int id)
{
	if (id == g_groups[id])
	{
		return id;
	}
	return g_groups[id] = Find(g_groups[id]);
}

/*算法主程序*/
int *findRedundantDirectedConnection(int **edges, int edgesSize, int *edgesColSize, int *returnSize)
{
	/*不合法参数*/
	if (edges == NULL || edgesColSize == NULL || returnSize == NULL)
	{
		return NULL;
	}
	/*定义结果数组*/
	int *result = (int *)malloc(sizeof(int) * 2);
	if (result == NULL)
	{
		return NULL;
	}
	/*初始化并查集*/
	InitGroups();
	int twoDegreeUv[2][2];
	int removeFirst = 0;
	int twoDegreeV = MAX_GROUPS_COUNT;
	int degrees[MAX_GROUPS_COUNT] = { 0 };
	int u, v, uGroupId, vGroupId;
	/*寻找2度结点*/
	for (int i = 0; i < edgesSize; i++)
	{
		u = edges[i][0];
		v = edges[i][1];
		degrees[v] += 1;
		if (degrees[v] == 2) {
			twoDegreeV = v;
			break;
		}
	}
	int saveDegreeCount = 0;
	for (int i = 0; i < edgesSize; i++)
	{
		u = edges[i][0];
		v = edges[i][1];
		if (v == twoDegreeV)
		{
			twoDegreeUv[saveDegreeCount][0] = u;
			twoDegreeUv[saveDegreeCount][1] = v;
			saveDegreeCount += 1;
		}
		if (v == twoDegreeV && saveDegreeCount == 2)
		{
			/*跳过第二组,检查是否有异常*/
			continue;
		}
		uGroupId = Find(u);
		vGroupId = Find(v);
		if (uGroupId == vGroupId)
		{
			removeFirst = 1;
			if (twoDegreeV != MAX_GROUPS_COUNT)
			{
				/*未删除第一组有问题,说明应该删除第一组*/
			}
			else
			{
				twoDegreeUv[0][0] = u;
				twoDegreeUv[0][1] = v;
				break;
			}
		}
		g_groups[vGroupId] = uGroupId;
	}
	if (removeFirst)
	{
		result[0] = twoDegreeUv[0][0];
		result[1] = twoDegreeUv[0][1];
	}
	else
	{
		result[0] = twoDegreeUv[1][0];
		result[1] = twoDegreeUv[1][1];
	}
	*returnSize = 2;
	return result;
}

/*主函数省略*/

十【提交结果】

  1. Java语言版
    在这里插入图片描述
  2. C语言版
    在这里插入图片描述
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

IronmanJay

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值