图可以用于看似无关的问题。假设卡片两面都有数字的问题,然后尝试使用一侧创建连续的数字序列。此问题导致如何将图形分为带周期的连通子集和无周期的连通子集的问题。
有已知的算法可以检测图形是否包含圆。在这里,我们对其进行修改以查找所有未连接到圆的节点。我们添加一个循环数组布尔值数组,所有初始值为false,这意味着没有节点连接到圆上。
然后,我们遍历节点,并应用常规算法确定在已知已连接到圆的节点上跳过的圆。每当我们找到连接到圆的新节点时,我们都会将该属性传播到所有已连接节点,并跳过已经指定为再次连接的节点。
主要函数是对“无向图中的检测循环”一文中的DFS算法的修改:
这样连接到一个循环的所有节点都在线性时间内被指定。
该算法已应用于最近挑战“锗”中的以下问题,该问题已被约3.5%的参与者解决。
假设我们有N张卡(100000> = N> = 1),并且在卡的两边都有一个数字(N是无用的,可以丢弃。以同样的精神,每辆车(n,m):n N允许我们将n向上放置并确保其在序列中的存在,而m仍然无用。
如果我们有一张卡片(nn):n
现在,我们通过以下方式将卡编码为图形。图节点应该从0到N-1编号。每个节点都有一组通向的边表示为一个向量,起始为一个空的边,而整体边的集合则是一个由整数(边)(N)的向量组成的向量。
(n,m):n,m N –丢弃。
(n,m):n N –添加到图的边缘(n-1,n-1)
现在很容易理解,图中的每个循环都可以用于图中的所有节点。一个示例:(1、2)(2、5),(5、1)–每个数字出现在两张牌上,只是沿循环进行,将任意数字一次向上一次,一次向下一次:
(1、2)– 1向上,2向下
(2,5)– 2向上,5向下
(5,1)– 5向上,1向下
如果上面已经有任何号码,则随后每个具有该号码的卡都必须向下排列该号码,因此可以在上方显示另一个号码。在图中,这意味着可以自由显示通过边连接到多个循环的任何数字。对于连接到循环的卡,将其连接的卡也是如此。因此,以某种方式连接到任何周期的任何数字/节点都不会带来任何问题。成对的相同卡(例如(1、2),(1、2))也会在图中产生一个小圆圈,双卡也是如此,例如3、3,这是一个节点与其自身连接的循环。
可以将未连接到任何周期的图形的所有部分拆分为未连接的较小图形。或多或少显而易见的是,任何这样的片段都有一个有问题的数字-该片段中最大的一个。我们使用类似但容易得多的算法来查找此类片段和片段中的所有最大值。最小的是Q –最小的数字,它不能是最小数(上面的Q)中连续覆盖范围的结尾。
除了循环数组之外,我们还添加了另一个数组:VisitedNonCyclic,这意味着该数字在通过非循环片段时已经满足。
非循环片段中的最大值是通过对函数的循环调用提取的:
请参考下面的代码中的findMax()。图形创建在以下代码的solution()中完成。
创建图后,解决方案只需调用isCyclic,然后调用:
// the main crux of the solution, see full code below
graph.isCyclic();
int res = 1 + graph.GetAnswer();
该解决方案受到近期相当艰难的“ Germanium 2018 Codility Challenge”的启发,并获得了金牌。
大意:
1.数字很大,但是我们要求从1到最后一个数字的连续序列,
但最多不超过10万个数字。所以,最大的可能就是十万,以此作为第一个假设
2.基于相同的考虑,最大数量不能大于N(数量)
3.我们也可以传递总数,并取最大数为NN。结果不能大于N或NN
4.考虑到这一点,我们可以将所有(x,200000)卡替换为(x,x)。两者都允许将x向上设置,因此x并不是问题。
5.两个数字都大于N或NN的卡只是被忽略或处置
6.如果两边的卡号相同,则表示卡号始终可以,不会是最小的问题,因此永远不会有答案。
7.如果有两张相同的卡片,例如(x,y)和(y,x),则意味着x和y都没有问题,因此不能作为答案
8.现在,我们介绍BIG的主要思想,即将卡片组转换为图形。图的旋涡编号从0到M-1,其中M是最后一个可能的数字(N,NN中的最小数)。每个节点都有一些相邻的节点,因此所有边的集合都是整数向量的向量
8.每个像(x,y)都等于或小于M的数字都进入边x-1,y-1,因此第一个节点会获得一个额外的相邻节点,反之亦然
9.由于卡(3,3)之类的作用,一些漩涡会相互连接。这是一个大小为1的循环案例。
10.如果有相同的卡,则某些涡流将通过两个或更多个边缘连接。这是一个大小为2的循环。
11.大小为1和2的循环具有不会出现任何问题的数字。但是现在我们意识到,任何长度的循环都是如此。例如,纸牌是2、3; 3,4; 4、10; 10,2;它们的所有数字都没有问题,只需将2、3、4、10放在上面。不利的一面是3、4、10、2。
12.现在,如果我们有一张牌x,y,并且知道x已被保证在另一个地方,我们可以将该牌x放下,y放高,并确保y在结果序列中。
12.因此,如果任何卡通过边缘连接到一个循环,则由于循环一切正常,因此不会出现问题,因此该编号也可以。
13.因此,如果图的节点的互连子集包含一个循环,则所有涡旋都不会出现任何问题。
因此,我们可以应用一种已知的算法来查找循环,如“无向图中的检测循环”一文中所述,并增加了传播
14.我们还可以在图中指定所有连接到循环的节点cycled []。我们可以传播财产
只是从某个循环开始,将其设置为循环,然后跳过跳过循环并在相邻循环上调用相同的函数,将其跳过。
15.使用已知算法的组合来检测无向图中的循环,并跳过已经循环的循环并传播“连接到循环”的属性,我们发现所有连接到循环的节点。所有这些节点都是安全的,它们不会成为问题
16.但是,保持未连接任何周期的节点,可以简化其问题,但可以将其切成没有公共边或节点的独立图形。
17.但是如何处理他们?它可以是一个线性分支,也可以是彼此交叉的分支。所有节点都不同。
18.经过最后的努力,人们可以理解,任何这样的集合只有一个有问题的数字-集合的最大值。可以证明,注意交叉只会使事情变得更好,但最大值保持不变。
19.因此,我们将所有非循环实体都切成相互连接的分离实体,并在每个实体中找到最大值。
20.然后我们找到它们的最小值,这是最终答案!
例子:
Input : A = [1, 2, 4, 3] B = [1, 3, 2, 3]
Output : 5.
Because the cards as they are provide 1, 2, 3, 4 but not 5.
Input : A = [4, 2, 1, 6, 5] B = [3, 2, 1, 7, 7],
Output: 4.
Because you can show 3 or 4 by the first card but not both 3 and 4. So, put 3, while 1 and 2 are by the following cards.
Input : A = [2, 3], B = [2, 3]
Output : 1. Because 1 is missing at all.
复杂:
- 预期的最坏情况下的时间复杂度为O(N);
- 预期的最坏情况下的空间复杂度为O(N);
最终执行
#include
#include
using namespace std;
class Graph {
private:
int V; // No. of vertices
vector > edges; // edges grouped by nodes
bool isCyclicUtil(int v, vector& visited, int parent);
vector cyclic;
vector maxInNonCyclicFragments;
int findMax(int v);
vector visitedNonCyclic;
void setPropagateCycle(int v);
public:
Graph(int V); // Constructor
void addEdge(int v, int w); // to add an edge to graph
// returns true if there is a cycle and
// also designates all connected to a cycle
bool isCyclic();
int getSize() const { return V; }
int GetAnswer();
};
Graph::Graph(int V)
{
this->V = V;
edges = vector >(V, vector());
visitedNonCyclic = cyclic = vector(V, false);
}
void Graph::addEdge(int v, int w)
{
edges[v].push_back(w);
edges[w].push_back(v);
}
void Graph::setPropagateCycle(int v)
{
if (cyclic[v])
return;
cyclic[v] = true;
for (auto i = edges[v].begin(); i != edges[v].end(); ++i) {
setPropagateCycle(*i);
}
}
bool Graph::isCyclicUtil(int v, vector& visited, int parent)
{
if (cyclic[v])
return true;
// Mark the current node as visited
visited[v] = true;
// Recur for all the vertices edgesacent to this vertex
vector::iterator i;
for (i = edges[v].begin(); i != edges[v].end(); ++i) {
// If an edgesacent is not visited, then
// recur for that edgesacent
if (!visited[*i]) {
if (isCyclicUtil(*i, visited, v)) {
setPropagateCycle(v);
return true;
}
}
// If an edgesacent is visited and not parent
// of current vertex, then there is a cycle.
else if (*i != parent) {
setPropagateCycle(v);
return true;
}
if (cyclic[*i]) {
setPropagateCycle(v);
return true;
}
}
return false;
}
bool Graph::isCyclic()
{
// Mark all the vortices as not visited
// and not part of recursion stack
vector visited(V, false);
// Call the recursive helper function
// to detect cycle in different DFS trees
bool res = false;
for (int u = 0; u < V; u++)
// Don't recur for u if it is already visited{
if (!visited[u] && !cyclic[u]) {
if (isCyclicUtil(u, visited, -1)) {
res = true;
// there was retur true originally
visited = vector(V, false);
}
}
return res;
}
int Graph::findMax(int v)
{
if (cyclic[v])
return -1;
if (visitedNonCyclic.at(v))
return -1;
int res = v;
visitedNonCyclic.at(v) = true;
for (auto& u2 : edges.at(v)) {
res = max(res, findMax(u2));
}
return res;
}
int Graph::GetAnswer()
{
// cannot be less than, after extract must add 1
int res = V;
for (int u = 0; u < V; u++) {
maxInNonCyclicFragments.push_back(findMax(u));
}
for (auto& u : maxInNonCyclicFragments) {
if (u >= 0)
res = min(res, u);
}
return res;
}
int solution(vector& A, vector& B)
{
const int N = (int)A.size();
const int MAX_AMOUNT = 100001;
vector present(MAX_AMOUNT, false);
for (auto& au : A) {
if (au <= N) {
present.at(au) = true;
}
}
for (auto& au : B) {
if (au <= N) {
present.at(au) = true;
}
}
int MAX_POSSIBLE = N;
for (int i = 1; i <= N; i++) {
if (false == present.at(i)) {
MAX_POSSIBLE = i - 1;
break;
}
}
Graph graph(MAX_POSSIBLE);
for (int i = 0; i < N; i++) {
if (A.at(i) > MAX_POSSIBLE && B.at(i) > MAX_POSSIBLE) {
continue;
}
int mi = min(A.at(i), B.at(i));
int ma = max(A.at(i), B.at(i));
if (A.at(i) > MAX_POSSIBLE || B.at(i) > MAX_POSSIBLE) {
graph.addEdge(mi - 1, mi - 1);
}
else {
graph.addEdge(mi - 1, ma - 1);
}
}
graph.isCyclic();
int res = 1 + graph.GetAnswer();
return res;
}
// Test and driver
#include
void test(vector& A, vector& B, int expected,
bool printAll = false)
{
int res = solution(A, B);
if (expected != res || printAll) {
for (size_t i = 0; i < A.size(); i++) {
cout << A.at(i) << " ";
}
cout << endl;
for (size_t i = 0; i < B.size(); i++) {
cout << B.at(i) << " ";
}
cout << endl;
if (expected != res)
cout << "Error! Expected: " << expected << " ";
else
cout << "Expected: " << expected << " ";
}
cout << " Result: " << res << endl;
}
int main()
{
vector VA;
vector VB;
int A4[] = { 1, 1, 1, 1, 1 };
int B4[] = { 2, 3, 4, 5, 6 };
VA = vector(A4, A4 + 1);
VB = vector(B4, B4 + 1);
test(VA, VB, 2, true);
int A0[] = { 1, 1 };
int B0[] = { 2, 2 };
VA = vector(A0, A0 + 2);
VB = vector(B0, B0 + 2);
test(VA, VB, 3);
int A[] = { 1, 2, 4, 3 };
int B[] = { 1, 3, 2, 3 };
VA = vector(A, A + 4);
VB = vector(B, B + 4);
test(VA, VB, 5);
int A2[] = { 4, 2, 1, 6, 5 };
int B2[] = { 3, 2, 1, 7, 7 };
VA = vector(A2, A2 + 5);
VB = vector(B2, B2 + 5);
test(VA, VB, 4);
int A3[] = { 2, 3 };
int B3[] = { 2, 3 };
VA = vector(A3, A3 + 2);
VB = vector(B3, B3 + 2);
test(VA, VB, 1);
return 0;
}
1
2
Expected: 2 Result: 2
Result: 3
Result: 5
Result: 4
Result: 1