04--链式结构算法题

455 阅读7分钟

习题一

将2个递增的有序链表合并为⼀个有序链表; 要求结果链表仍然使⽤两个链表的存储空间,不另外占⽤其他的存储空间. 表中不允许有重复的数据。例如:有La {1,2,3} , Lb {3, 6,9} ,最终得到Lc {1, 2, 3, 6, 9}。

1.题意分析

image.png

  • 1.提供的两个链表是有序递增的;
  • 2.不能额外开辟空间,申请内存和创建结点;
  • 3.不能有重复数据;
  • 4.最终得到的是保持递增的,无重复数据的有序链表。

2.代码实现

假如我们已经设计好了一个单向链表,关于单向链表的设计可以参考# 01--单向链表

void MergeList(LinkList *La, LinkList *Lb, LinkList *Lc){

    //目标:将2个递增的有序链表La,Lb 合并为一个递增的有序链表Lc
    LinkList pa,pb,pc,temp;

    //pa 是链表La的工作指针,pb 是链表Lb的工作指针, 初始化为首元结点;
    pa = (*La)->next;//指向首元结点,从首元结点开始遍历
    pb = (*Lb)->next;
    *Lc = pc = *La;//Lc是目标链表,指向La的头结点即可在不额外申请空间的前提下得到一个带头结点的空链表

    while (pa && pb) {
        if (pa->data < pb->data) {
            //取较小者La中的元素,将pa链接在pc的后面,pa指针后移
            pc->next = pa;//pa指向的结点加入新链表
            pc = pa;//pc指向链表末尾,方便下一次插入数据
            pa = pa->next;//pa指向pa的下一个结点,进入下一次比较
        }else if(pa->data > pb->data){
            //取较小者Lb的元素,将pb链接在pc后面, pb指针后移
            pc->next = pb;
            pc = pb;
            pb = pb->next;
        }else
        {
            //相等时取La中的元素,删除Lb的元素;
            pc->next = pa;
            pc = pa;
            pa = pa ->next;
            //pa和pb的数据相等,取了pa就要把pb释放
            temp = pb->next;
            free(pb);
            pb = temp;
        }
    }

    //将非空表的剩余元素之间链接在Lc表的最后
    pc->next = pa?pa:pb;

    //最终*Lb的头结点已经没有存在的必要了,所以要释放Lb的头结点
    free(*Lb);
}

习题二

题⽬2: 已知两个链表A和B分别表示两个集合.其元素递增排列. 设计⼀个算法,⽤于求出A与B的交集,并存储在A链表中;

1.题意分析

image.png

  • 1.提供的两个链表A和B是有序递增的;
  • 2.结果要存在链表A中;
  • 3.求A和B的交集,所以不能有重复数据;
  • 4.最终得到的是保持递增的,无重复数据的有序链表。

2.代码实现

void Intersection(LinkList *La, LinkList *Lb, LinkList *Lc){

    //目标: 求2个递增的有序链表La,Lb的交集, 使用头指针Lc指向带回;
    LinkList pa,pb,pc,temp;

    //pa 是链表La的工作指针,pb 是链表Lb的工作指针, 初始化为首元结点;La的头结点作为Lc的头结点;
    pa = (*La)->next;
    pb = (*Lb)->next;
    *Lc = pc = *La;
    
    while (pa && pb) {
        if (pa->data == pb->data) {
            //相等,交集并入结果链表中;
            //(1).取La中的元素,将pa链接到pc的后面,pa指针后移;
            pc->next = pa;
            pc = pa;
            pa = pa->next;

            //(2)删除Lb中对应相等的元素
            temp = pb;
            pb = pb->next;
            free(temp);
        }else if(pa->data < pb->data){
            //删除较小值La的元素;
            temp = pa;
            pa = pa->next;
            free(temp);
        }else{
            //删除较小值Lb中的元素
            temp = pb;
            pb = pb->next;
            free(temp);
        }
    }

    //Lb为空,删除非空表La中的所有元素
    while (pa) {
        temp = pa;
        pa = pa->next;
        free(temp);
    }

    //La为空,删除非空表Lb中的所有元素
    while (pb) {
        temp = pb;
        pb = pb->next;
        free(temp);
    }

    pc->next = NULL;
    free(*Lb);
}

习题三

设计⼀个算法,将链表中所有节点的链接⽅向原地旋转,即要求仅仅利⽤原表的存储  空间. 换句话说,要求算法空间复杂度为O(1);  例如:L={0,2,4,6,8,10}, 逆转后: L = {10,8,6,4,2,0};

1.题意分析

image.png

  • 1.将一个链表的顺序倒置,既反转链表;
  • 2.只能使用原链表的内存空间,不能开辟新的内存和创建新的结点;
  • 3.只能申请有限的辅助空间。

2.代码实现

可以使用头插法思想来实现,关于头插法可以看# 01--单向链表

void Inverse(LinkList *L){
    //目的: 逆转带头结点单链表L;
    LinkList p,q;

    //p指向首元结点,从首元结点开始遍历
    p = (*L)->next;

    //头结点的指针域置空
    (*L)->next = NULL;

    //遍历链表
    while (p != NULL) {
        //记录p的下一个结点
        q = p->next;

        //使用头插法将p插入链表
        p->next = (*L)->next;

        //*p 插入到头结点之后;
        (*L)->next = p;

        //处理下一个结点
        p = q;
    }
}

习题四

设计⼀个算法,删除递增有序链表中值⼤于等于mink且⼩于等于maxk(mink,maxk  是给定的两个参数,其值可以和表中的元素相同,也可以不同)的所有元素;

1.题意分析

image.png

  • 1.给定的是一个有序递增的链表;
  • 2.删除链表中数据处在某个区间内的数据,即当数据满足 mink = 4 maxk = 9 时,将4、6、8从链表中移除。

2.代码实现

void DeleteMinMax(LinkList *L, int mink, int maxk){

    //目标: 删除递增有序链表L中值大于等于mink 和小于等于maxk的所有元素
    LinkList p,q,pre;
    pre = *L;
    LinkList temp;

    //p指向首元结点
    p = (*L)->next;

    //1.查找第一值大于mink的结点
    while (p && p->data < mink) {
        //指向前驱结点
        pre = p;
        p = p->next;
    }

    //2.查找第一个值大于等于maxk的结点
    while (p && p->data<=maxk) {
        p = p->next;
    }

    //3.修改待删除的结点指针
    q = pre->next;//满足条件的第一个结点,即第一个大于等于mink的结点
    pre->next = p;
    //4.将mink和maxk范围内的结点删除
    while (q != p) {
        temp = q->next;
        free(q);
        q = temp;
    }
}

习题五

设将n(n>1)个整数存放到⼀维数组R中, 试设计⼀个在时间和空间两⽅⾯都尽可能⾼  效的算法;将R中保存的序列循环左移p个位置(0<p<n)个位置, 即将R中的数据由  (x0,x1,......,xn-1)变换为(xp,xp+1,...,xn-1,x0,x1,...,xp-1).

1.题意分析

  • 1.给定的是一个保存了整数的一维数组;
  • 2.将数组的元素移动p个位置。如:1、2、3、4、5移动3个位置后是:4、5、1、2、3;
  • 3.时间和空间复杂度低。

2.代码实现

数组逆序算法

void Reverse(int *pre,int left ,int right){
    //将数组R中的数据原地逆置

    //i等于左边界left,j等于右边界right;
    int i = left,j = right;

    int temp;
    
    //交换pre[i] 和 pre[j] 的值
    while (i < j) {
        //交换
        temp = pre[i];
        pre[i] = pre[j];
        pre[j] = temp;

        //i右移,j左移
        i++;
        j--;
    }
}

算法实现

void LeftShift(int *pre,int n,int p){
    //将长度为n的数组pre 中的数据循环左移p个位置
    if (p>0 && p<n) {
        //1. 将数组中所有元素全部逆置
        /*
        原数组:1、2、3、4、5
        逆序数组:5、4、3、2、1
        */
        Reverse(pre, 0, n-1);

        //2. 将前n-p个数据逆置
        /*
        p = 3时
        再逆序数组前p个元素得:3、4、5、2、1
        */
        Reverse(pre, 0, n-p-1);

        //3. 将后p个数据逆置
        /*
        p = 3时
        再逆置数组后n-p个元素得:3、4、5、1、2
        */
        Reverse(pre, n-p, n-1);
    }
}

习题六

已知⼀个整数序列A = (a0,a1,a2,...an-1),其中(0<= ai <=n),(0<= i<=n). 若存在ap1= ap2 = ...= apm = x,且m>n/2(0<=pk<n,1<=k<=m),则称x 为 A的主元素. 例如,A = (0,5,5,3,5,7,5,5),则5是主元素; 若B = (0,5,5,3,5,1,5,7),则A 中没有主元素,假设A中的n个元素保存在⼀个⼀维数组中,请设计⼀个尽可能⾼效的算法,找出数组元素中的主元素,若 存在主元素则输出该元素,否则输出-1.

1.题意分析

  • 1.求数组的主元素,即出现次数超过数组长度一半的元素;
  • 2.先找到出现次数最多的元素;
  • 3.再计算出现的次数;
  • 4.判断是否超过半数。

2.代码实现

int MainElement(int *A, int n){
    //目标: 求整数序列A中的主元素;
    //count 用来计数
    int count = 1;

    //key 用来保存候选主元素, 初始A[0]
    int key = A[0];//假设第一个元素就是候选元素

    //(1) 扫描数组,选取候选主元素,找到出现次数最多的元素
    for (int i = 1; i < n; i++) {
        //(2) 如果A[i]元素值 == key ,则候选主元素计数加1;
        if (A[i] == key) {
            count++;//相等时,出现次数+1
        }else{
            //(3) 当前元素A[i] 非候选主元素,计数减1;
            if(count >0){
                count--;//不相等时,出现次数-1
            }else{
               //(4) 如果count 等于0,则更换候选主元素,重新计数
                key = A[i];//当出现次数为0时,更换候选元素
                count = 1;
            }
        }
    }
    
    //如果count >0
    if (count >0){
        //(5)统计候选主元素的实际出现次数
        for (int i = count = 0; i < n; i++)
            if (A[i] == key) count++;
    }
    //(6)判断count>n/2, 确认key是不是主元素
    if (count > n/2) return key;

    else return -1; //不存在主元素
}

习题七

⽤单链表保存m个整数, 结点的结构为(data,link),且|data|<=n(n为正整数). 现在要去设计⼀个时间复杂度尽可能⾼效的算法. 对于链表中的data 绝对值相等的结点, 仅保留第⼀次出现的结点,⽽删除其余绝对值相等的结点.例如,链表A = {21,-15,15,-7,15}, 删除后的链表A={21,-15,-7};

1.题意分析

  • 1.链表数据按绝对值去重;
  • 2.算法可以“空间换时间”;
  • 3.链表中的最大数据的绝对值为n;
  • 4.可以为0~n的数据都申请一个标记位,记录其是否出现过。

2.代码实现

void DeleteEqualNode(LinkList *L,int n){
    //目标: 删除单链表中绝对值相等的结点;
    
    //为可能出现0~n个数据都申请一个标记位,记录其是否出现过,初始值为0,表示未出现过
    //1. 开辟辅助数组p.
    int *p = alloca(sizeof(int)*n);
    LinkList r = *L;

    //2.数组元素初始值置空
    for (int i = 0; i < n; i++) {
        *(p+i) = 0;
    }

    //3.指针temp 指向首元结点
    LinkList temp = (*L)->next;

    //4.遍历整个链表,直到temp = NULL;
    while (temp != NULL) {
        //5.如果该绝对值已经在结点上出现过,则删除该结点
        if (p[abs(temp->data)] == 1) {
            //临时指针指向temp->next
            r->next = temp->next;

            //删除temp指向的结点
            free(temp);

            //temp 指向删除结点下一个结点
            temp = r->next;
        }else
        {
            //6. 未出现过的结点,则将数组中对应位置置为1;
            p[abs(temp->data)] = 1;
            r = temp;

            //继续向后遍历结点
            temp = temp->next;
        }
    }
}