小白学算法(8)区间合并+单链表

186 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第12天,点击查看活动详情

区间合并

803. 区间合并 - AcWing题库

给定 n 个区间 [l,r],要求合并所有有交集的区间。

注意如果在端点处相交,也算有交集。

输出合并完成后的区间个数。

例如:1,32,6 可以合并为一个区间 1,6

1.模板介绍

首先按照人的思维,就是判断下一个区间的左端点是否小于上一个区间的右端点,如上 [1,3],[2,6] ,此时2<3并且2>1,所以左端点为1,两个可以合并;

并且判断此时右端点,3<6,所以右端点为大的6;

// 将所有存在交集的区间合并
//此时返回值为void,参数是地址传递,所以最后直接将结果赋值即可
void merge(vector<PII> &segs)
{
    
    vector<PII> res;
    //先根据左端点进行排序
    sort(segs.begin(), segs.end());
    //给出左右端点的初始值,最小值。与他们比较的值都大于他们
    int st = -2e9, ed = -2e9;
    for (auto seg : segs)
        //判断当前右端点是否小于下一个区间的左端点,
        //如果小于 说明两个区间无法合并
        if (ed < seg.first)
        {
            if (st != -2e9) res.push_back({st, ed});
            //重新赋值 当前比较的端点
            st = seg.first, ed = seg.second;
        }
        //如果可以合并,那么判断最大的右端点
        else ed = max(ed, seg.second);
    //如果左端点不是初始值,那么赋值一个进去
    if (st != -2e9) res.push_back({st, ed});
​
    segs = res;
}

2.完整代码

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
const int MAX=1e5+10;
int main()
{
    //创建二维数组存放区间
    vector<pair<int,int>>A;
    int n;
    cin>>n;
    for(int i=0;i<n;i++)
    {
        int l,r;
        cin>>l>>r;
        A.push_back({l,r});
    }
    //进行排序,默认是按左端点排序
    sort(A.begin(),A.end());
    int r=1;
    //初始化最大右端点
    int maxr=A[0].second;
    for(int i=1;i<n;i++)
    {
        //如果下一个区间的左端点大于当前的最大右端点,那么这两个区间没有交集
        if(A[i].first>maxr)
        {
            r++;
            //重新赋值最大右端点
            maxr=A[i].second;
        }else
        {
            //否则说明有交集,那么判断当前的最大右端点是什么
            maxr=max(maxr,A[i].second);
        }
    }
    cout<<r;
    return 0;
}

单链表(加餐)

1.模板介绍

class Node{
    public:
    int val;
    Node* next;
};
​
静态单链表
// head存储链表头,e[]存储节点的值,ne[]存储节点的next指针,idx表示当前用到了哪个节点
int head, e[N], ne[N], idx;
​
// 初始化
void init()
{
    head = -1;
    idx = 0;
}
​
// 在链表头插入一个数a
void insert(int a)
{
    e[idx] = a, ne[idx] = head, head = idx ++ ;
}
​
// 将头结点删除,需要保证头结点存在
void remove()
{
    head = ne[head];
}

问题:为什么不用结构体构建链表

Node* node = new Node();,中间有一个 new 关键字来为新对象分配空间。

new的底层涉及内存分配,调用构造函数,指针转换等多种复杂且费时的操作。一秒大概能new1w次左右。

在平时的工程代码中,不会涉及上万次的new操作,所以这种结构是一种 见代码知意 的好结构。

但是在算法比赛中,经常碰到操作在10w级别的链表操作,如果使用结构体这种操作,是无法在算法规定时间完成的。

所以,在算法比赛这种有严格的时间要求的环境中,不能频繁使用new操作。也就不能使用结构体来实现数组。

826. 单链表 - AcWing题库

#include<iostream>
using namespace std;
const int N=1e5+10;
//head 作为头指针
//e[N] 存储数据域
//ne[N] 存储指针域
//idx 表示已经使用到第几个节点了
int head,e[N],ne[N],idx;
//初始化 head=-1 表示空;idx=0  表示当前无节点
void init()
{
    head=-1;
    idx=0;
}
//头插
void add_to_head(int x)
{
    //存入数据域
    e[idx]=x;
    //第一个添加的就成为了最后一个节点,ne[idx]=-1,指向空
    //接下来ne[idx]=idx++,所以从头到尾的 ne[idx]是从高向低的,ne[idx]越高就越靠近头节点
    ne[idx]=head;
    head=idx++;
}
//指定位置插入
void add(int k,int x)
{
    e[idx]=x;
    //idx指的当前使用第几个节点了,
    ne[idx]=ne[k];
    //因为使用到的idx个节点插到了k位置之前,要知道idx是顺序的,0 1 2 3 ,所以k的指针域就是idx+1了
    ne[k]=idx++;
}
//ne[k]指的是k指向的节点,所以ne[ne[k]]是k指向的下一个节点所指向的节点  eg: 1 2 3,1就是指向
void remove(int k)
{
    ne[k]=ne[ne[k]];
}
int main()
{
    init();
    int n;
    cin>>n;
    while(n--)
    {
        char x;
        int i,j;
        cin>>x;
        if(x=='H')
        {
            cin>>i;
            add_to_head(i);
        }else if(x=='I')
        {
            cin>>i>>j;
            //下标从0开始的
            add(i-1,j);
        }else
        {
            cin>>i;
            //如果是0 那么就删除头节点
            if(i==0)
            { 
               head=ne[head];
            }else
            {
                
                remove(i-1);
            }
​
        }
    }
    for(int i=head;i!=-1;i=ne[i])
    {
        cout<<e[i]<<" ";
    }
    return 0;
}