小黄的刷题之路(十)——码题集OJ赛-双向链表和列表实现数环碰撞

一、题目

在这里插入图片描述
在这里插入图片描述


二、分析思路

2.1 审题和理解

我们把题目看了一遍之后,先总结出以下重要的几点:

  • 初始有n个孤立的数字,分别是 1 ∼ n 1 \sim n 1n,两个孤立的数字发生碰撞会产生一个环,比如 9 → 7 9 \rightarrow 7 97会产生上面示例图中最后的那个环

  • 把环写出来需要把环里面最小的数字放在前面,比如只能写成【7-9】不能写成【9-7】,还有示例里的输出也是这样的

  • 环和环之间也可能发生碰撞, x → y x \rightarrow y xy表示两个环上发生碰撞融合的点,比如有环 R 1 R_1 R1=【1-2】和 R 2 R_2 R2=环【3-4-5】,发生碰撞 1 → 4 1 \rightarrow 4 14碰撞融合的过程相当于,把 R 1 R_1 R1从数字1处断开, R 2 R_2 R2把环解开捋直成链,把4接在1后面,最后一整条链再圈成环

2.2 思路

C++双向链表

经过上一步的分析以及题目的暗示,我们很容易可以想到要用双向链表这个数据结构来表示环。碰撞就是两个双向链表的相连

struct point {
    struct point* pre;//指向上一个数字
    int value;
    bool flag;//用于输出
    struct point* next;//指向下一个数字
};

一开始所有的泡泡环都只有一个数字,我们先来初始化一下,创建一个指针数组a,数组里每一个元素都是一个指向结构体point的指针,即a[i]指向初始的带数字 i 的泡泡环

struct point* a[10000001]; //指针数组,数组的元素是指向每个数字的指针
for (int i = 1; i <= n; i++) 
{
    a[i] = (struct point*)malloc(sizeof(struct point));//分配内存空间
    a[i]->value = i;
    a[i]->flag = 0 //0表示还未输出过,1表示已经打印过了
    a[i]->next = a[i]->pre = a[i];//重点语句
}

接着我们来看具体如何实现泡泡环的碰撞

在这里插入图片描述

到这里其实就基本完成了,只剩下输出打印所有泡泡环,题目要求从泡泡环里数字最小的地方开始输出,这个简单我们只需要for(int i=1;i<=n;i++)遍历a[i],a[i]指向的数字是从小到大,本身保证了题目要求,每一个泡泡环的第一个数字一定是当前最小的。比如1->2->3->4这个泡泡环,肯定是第一次遍历 i=1,从a[1]开始,根据next一直输出,直到再次回到a[1]。由于2 3 4已经在第一个泡泡环里了,后面遍历到a[2],a[3],a[4]直接跳过就行,这个时候就需要用到上面设置的flag了。

for(int i = 1; i <= n; i++)
    if(a[i]->flag == 0)//0表示还未输出
    {
        p1 = a[i];
        do{
            cout << p1->value << " ";//初始化时a[i]->value = i
            a[p1->value]->flag = 1;//1表示该value已经被输出过,后面遍历到的时候跳过
            p1 = p1->next;
        }while(p1 != a[i]);
        cout << endl;S
    }

python列表

这道题也能用python列表实现,建立一个二维列表b,b里有n+1个元素,代表初始时的n个孤立泡泡环,泡泡环用列表来表示,方向是从左到右的方向

b = [[0],[1],[2],[3],[4],[5],...,[n-1],[n]]

b[i]表示数字 i 的泡泡环,b[0]闲置不用

碰撞 x → y x \rightarrow y xy为了方便看出哪个在前/左,哪个在后/右,写成 l → r l \rightarrow r lr

直接用题目给的例子来理解

b = [[0],[1],[2],[3],[4],[5],[6],[7],[8],[9]]

先来看一开始最简单的碰撞,比如 1 → 2 1 \rightarrow 2 12,会得到[1 , 2],相当于在b[1]中1的位置后面插入2,即b[1]=[1,2],那么此时b[2]就不再是孤立的泡泡环了,数字2已经在b[1]这个环里了。为了表示数字2在b[1]这个泡泡环里面,我们令b[2] = 1 本来b[2]=2表示 2 位于b[2],是一个孤立泡泡环,而现在 2 经过碰撞位于b[1]了。

b = [[0],[1,2],1,[3],[4],[5],[6],[7],[8],[9]]

接着假设发生碰撞 6 → 4 6 \rightarrow 4 64,会得到[6 , 4],相当于在b[6]中6的位置后面插入4,得到b[6] = [6 , 4],和上面一样同理。但是有一个不同的是,我们不打算就让b[6] = [6 , 4],而b[4]=6。而是改成b[4]=[6 , 4],b[6]=4。因为后面输出的时候,我们更加希望如果b[i]是列表,那么b[i]代表的泡泡环中,i 就是环中最小的数字。这样输出的时候只需要找到数字 i 的索引,从此处开始输出列表

# 如果b[i]是列表,那么b[i]代表的泡泡环中,i就是环中最小的数字
for i in range(1, n+1):
    if isinstance(b[i], list): 
        idx = b[i].index(i)
        print(*(b[i][idx:] + b[i][:idx]), sep=' ')

那么b就变成下面这样子,b[6]=4,表示指向b[4]那个泡泡环

b = [[0],[1,2],1,[3],[6,4],[5],4,[7],[8],[9]]

如果是两个泡泡环的碰撞,道理和上面一样,只是改成在列表中插入列表。可以利用切片巧妙地实现。

另外注意如果b[i]不是列表而是数字,如果b[2]=1和b[6]=4,发生碰撞时就需要检查b[i]是不是列表,如果检查发现b[i]的数据类型是int,那就需要根据这个int数找到它所在的泡泡环

l, r = map(int, input().split(' '))
id_l = l # 用来表示数字l所在的泡泡环下标,一开始数字i就在孤立泡泡环b[i]里
id_r = r # 用来表示数字r所在的泡泡环下标
while isinstance(b[id_l], int): # 找到数字l所在的环b[id_l]
    id_l = b[id_l]
while isinstance(b[id_r], int): # 找到数字r所在的环b[id_r]
    id_r = b[id_r]

三、代码实现

3.1、C++实现

#include<bits/stdc++.h> 
#include<iostream>
#include<vector>
using namespace std;
struct point {
    struct point* pre;
    int value;
    bool flag;
    struct point* next;
};
int main()
{
    int n, m, x, y; 
    cin >> n >> m; 
    struct point* a[10000001]; //指针数组,数组的元素是指向每个数字的指针
    struct point* p1=NULL, * p2=NULL; 
    // 初始化
    for (int i = 1; i <= n; i++) {
        a[i] = (struct point*)malloc(sizeof(struct point));//分配内存空间
        a[i]->value = i; 
        a[i]->next = a[i]->pre = a[i];//重点语句
        a[i]->flag = 0;//0表示还没打印,1表示已打印(暂时不用关注这句话)
    }
    // 碰撞
    for (int i = 0; i < m; i++) {
        cin >> x >> y;
        p1 = a[x]->next; p2 = a[y]->pre;//记录断点,以便后面重新接上
        a[x]->next = a[y];
        a[y]->pre = a[x];
        p2->next = p1; p1->pre = p2;
    }
    // 输出每一个泡泡
    for(int i = 1; i <= n; i++)
    {
        if(a[i]->flag == 0)//0表示还未输出
        {
            p1 = a[i];
            do{
                cout << p1->value << " ";//初始化时a[i]->value = i
                a[p1->value]->flag = 1;//1表示该value已经被输出过,后面遍历到的时候跳过
                p1 = p1->next;
            }while(p1 != a[i]);
            cout << endl;
        }
    }
    for (int i = 0; i < n; i++)free(a[i]);//释放内存空间
    return 0;
}

3.2、python代码实现

n, m = map(int,input().split(' '))
b = [[i]for i in range(n+1)]
# print(b)
for _ in range(m):
    l, r = map(int, input().split(' '))
    id_l = l # 碰撞方
    id_r = r # 被碰撞方
    while isinstance(b[id_l], int): # 找到数字l所在的环b[id_l]
        id_l = b[id_l]
    while isinstance(b[id_r], int): # 找到数字r所在的环b[id_r]
        id_r = b[id_r]

    cut_l = b[id_l].index(l) + 1 # 碰撞位置
    cut_r = b[id_r].index(r) # 碰撞位置
    b[id_l][cut_l:cut_l] = (b[id_r][cut_r:] + b[id_r][:cut_r])

    maxv = max(l, r)
    if id_l < id_r:  
        b[id_r] = id_l 
        b[maxv] = id_l
    else:# id_r < id_l
        b[id_r] = b[id_l]  
        b[id_l] = id_r
        b[maxv] = id_r
        	
    # print(b)
for i in range(1, n+1):
    if isinstance(b[i], list):
        idx = b[i].index(i)
        print(*(b[i][idx:] + b[i][:idx]), sep=' ') # 巧妙用法

题目示例的运行过程:

在这里插入图片描述

四、总结

  • 这道题比较容易想,可以很明显地看出需要使用双向链表来模拟泡泡环,最主要的是模拟碰撞,双向链表注意插入的细节问题,指针next和pre域的改变顺序不能乱

  • 突发奇想又尝试了用python实现,不得不说,想的时候容易,实现起来还是很费时间,而且很明显逻辑和思路没有C++双向链表那么清晰易懂。所以写成文字说明还是有难度的,希望大家看得懂,不过这种实现思路还是很有趣的

  • 最终尝试了把两种代码放到平台上跑,最终发现python的运行时间明显比C++长,仔细看了一眼代码也发现C++版本很典型的空间换时间,而python则相反,用时间换空间,而码题集上给的内存空间是很充足的,相比之下时间的限制比较大,所以也是为后面写题的时候提供了点思路:如果后面运行超时,可以尝试牺牲空间换时间


⭐如有不足之处,请多指教!⭐

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值