数据结构与算法-线性表练习-Day4

459 阅读9分钟

题目1

将2个递增的有序链表合并为一个有序链表; 要求结果链表仍然使用两个链表的存储空间,不另外占⽤其他的存储空间. 表中不允许有重复的数据

关键词:

  • 递增有序链表
  • 不允许有重复数据
  • 保留递增关系(后插法)
  • 不占用额外的存储空间指的是不能开辟新节点

La:1->3->4->9   
Lb:1->2->5->8   
结果:
Lc:1->2->3->4->5->8->9

算法思想

  1. 假设待合并的链表为La和Lb,合并后的新表使用头指针Lc(Lc的表头结点设为La的表头结点)指向. Pa 和 Pb 分别是La,Lb的工作指针.初始化为相应链表的首元结点

  2. 从首元结点开始比较, 当两个链表La 和Lb 均未到达表尾结点时,依次摘取其中较小值重新链表在Lc表的最后.

  3. 如果两个表中的元素相等,只摘取La表中的元素,删除Lb表中的元素,这样确保合并后表中无重复的元素;

  4. 当一个表达到表尾结点为空时,非空表的剩余元素直接链接在Lc表最后.

  5. 最后释放链表Lb的头结点;

代码

void MergeList(LinkList *La, LinkList *Lb, LinkList *Lc){
    LinkList pa = (*La)->next;
    LinkList pb = (*Lb)->next;
    //释放结点用
    LinkList temp;
    *Lc = *La;
    LinkList pc = *Lc;
    while(pa && pb) {
        if(pa->data < pb->data) {
            //如果pa<pb,pc->next指向pa,pa,pc后移一位
            pc->next = pa;
            pa = pa->next;
            pc = pc->next;
        }else if(pa->data > pb->data) {
            //如果pa>pb,pc->next指向pb,pb,pc后移一位
            pc->next = pb;
            pb = pb->next;
            pc = pc->next;
        }else {
            //如果pa=pb,pc->next指向pa,释放pb当前的结点,pa,pb,pc同时后移一位,
            pc->next = pa;
            pa = pa->next;
            pc = pc->next;
            temp = pb;
            pb = pb->next;
            free(temp);
        }
    }
    //循环结束后,pa和pb最少有一个为NULL,pc->next指向非空的结点或者NULL
    pc->next = pa?pa:pb;
    //释放b链表的头结点
    free(*Lb);
}

题目2

已知两个链表A和B分别表示两个集合,其元素递增排列。设计⼀个算法,用于求出A与B的交集,并存储在A链表中。
例如 : La = {2,4,6,8}; Lb = {4,6,8,10}; 输出La = {4,6,8}。

关键词:

  • 递增
  • 交集
  • 存储在A链表

算法思想

  1. 假设待合并的链表为La和Lb,合并后的新表使用头指针Lc(Lc的表头结点设为La的表头结点)指向. Pa 和 Pb 分别是La,Lb的工作指针.初始化为相应链表的首元结点
  2. 从首元结点开始比较, 当两个链表La 和Lb 均未到达表尾结点时.
  3. 如果两个表中的元素相等,只摘取La表中的元素,删除Lb表中的元素;
  4. 如果其中一个表中的元素较小,删除此表中较小的元素. 此表的工作指针后移;
  5. 当链表La和Lb有一个先到达表尾结点为空时,依次删除另一个非空表中的所有元素
  6. 释放链表lb;

代码

void Intersection(LinkList *La, LinkList *Lb, LinkList *Lc){
    LinkList pa = (*La)->next;
    LinkList pb = (*Lb)->next;
    //删除结点用
    LinkList temp;
    *Lc = *La;
    LinkList pc = *Lc;
    while(pa && pb) {
        //如果pa、pb不相等,删除对应的结点,并后移一位
        if(pa->data < pb->data) {
            temp = pa;
            pa = pa->next;
            free(temp);
        }else if(pa->data < pb->data) {
            temp = pb;
            pb = pb->next;
            free(temp);
        }else {
            //如果pa=pb,删除pb结点,pc->next指向pa, pb,pb,pc后移一位
            pc->next = pa;
            pc = pc->next;
            pa = pa->next;
            temp = pb;
            pb = pb->next;
            free(temp);
        }
    }
    //pc->next置空
    pc->next = NULL;
    //释放pa、pb剩余的结点
    while(pa) {
        temp = pa;
        pa = pa->next;
        free(temp);
    }
    while(pb) {
        temp = pb;
        pb = pb->next;
        free(temp);
    }
    //释放头结点*Lb
    free(*Lb);
}

题目3

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

关键词

  • 逆序
  • 不开辟新的存储空间

算法思想

思路1

记录当前结点的后继结点后,将当前结点利用前插法重新插入当前链表

void Inverse(LinkList *L){
    LinkList cur = (*L)->next;
    LinkList temp;
    //前插法初始状态*L->next = NULL
    (*L)->next = NULL;
    while(cur) {
        temp = cur->next;
        cur->next = (*L)->next;
        (*L)->next = cur;
        cur = temp;
    }
}

思路2:
记录当前结点的后继结点后,翻转当前结点和它的前驱的顺序。

void Inverse_1(LinkList *L){
    LinkList next;
    LinkList pre = NULL;
    LinkList cur = (*L)->next;
    while(cur) {
        //记录next
        next = cur->next;
        //翻转与前驱的关系
        cur->next = pre;
        //前驱和当前结点均后移一位
        pre= cur;
        cur = next;
    }
    //cur为空时,代表链表到底了,此时将头结点指向最后一个结点,实现翻转
    (*L)->next = pre;
}

题目4

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

关键词

  • 递增有序
  • 删除区间内的结点

算法思路

  • 遍历当前链表,记录小于mink的结点pre
  • 删除区间内的结点
  • 将前驱pre的next指向大于maxk的结点q
void DeleteMinMax(LinkList *L, int mink, int maxk){
    LinkList temp;
    LinkList p = (*L)->next;
    LinkList pre = (*L);
    //找到小于mink的最后一个结点pre
    while(p && p->data < mink) {
        pre = p;
        p = p->next;
    }
    //删除区间内的结点
    while(p && p->data <= maxk) {
        temp = p;
        p = p->next;
        free(temp);
    }
    //连接前驱和后继结点
    pre->next = p;
}

题目5

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

例如: pre[10] = {0,1,2,3,4,5,6,7,8,9}, n = 10,p = 3; pre[10] = {3,4,5,6,7,8,9,0,1,2}

算法思想

思路1

  1. 先将n个数据原地逆置[9,8,7,6,5,4,3,2,1,0];
  2. 将n个数据拆解成[9,8,7,6,5,4,3] [2,1,0]
  3. 将前n-p个数据和后p个数据分别原地逆置; [3,4,5,6,7,8,9] [0,1,2]
void Reverse(int *pre,int left ,int right){
    //pre数组中left-right之间的数据逆序
    int i = left;
    int j = right;
    int temp;
    while(i < j) {
        temp = pre[i];
        pre[i] = pre[j];
        pre[j] = temp;
        i++;
        j--;
    }
}

void LeftShift(int *pre,int n,int p){
    //逆序整个数组
    Reverse(pre, 0, n-1);
    //逆序前n-p个元素
    Reverse(pre, 0, n-p-1);
    //逆序后p个元素
    Reverse(pre, n-p, n-1);
}

思路2

假设old为索引inew为转换之后索引i位置的值在转换前的数组中的索引。通过观察题意可以算出oldnew之间的关系:new = (old + n + p) % n;
例如索引1转换之后放入索引4的值,4=(1+10+3)%10。又因为0<p<n转换之后位置必然发生改变。通过(n-1)次交换之后,就可以得到正确的数据。

void leftMove(int* pre, int n, int p) {
    int old = 0;
    int count = 0;
    int new, temp;
    while(count < n-1) {
        new = (old + n + p) % n;
        temp = pre[old];
        pre[old] = pre[new];
        pre[new] = temp;
        old = new;
        count++;
    }
}

题目6

已知⼀个整数序列A = (a0,a1,a2,...an-1),其中0<=ai<=n, 0<=i<=n。 若存在ap1= ap2 = ...=apm = x,且m>n/2,则称x为A的主元素。

例如,A=(0,5,5,3,5,7,5,5),则5是主元素; 若B=(0,5,5,3,5,1,5,7),则B中没有主元素。

假设A中的n个元素保存在⼀个一维数组中,请设计⼀个尽可能⾼效的算法,找出数组元素中的主元素,若存在主元素则输出该元素,否则输出-1

关键词

  • 主元素,出现次数>n/2

算法思想

根据消除法,如果是主元素,序列中不同元素两两相消之后必定至少还会存在一个。 例如{1,4,4,4,2,4,5},最后会剩余{4}
当然这是一个充分不必要条件,两两相消之后存在的未必一定是主元素。例如{1,2,3,4,5,6,6}。因此最后还需要进行校验。

int getTemp(int* a, int length){
    //默认主元素为a[0],此时计数为1
    int count = 1;
    int temp = a[0];
    for(int i = 1; i < length; i++) {
        //如果相等则计数+1
        if(temp == a[i]) {
            count++;
        }else {
            //如果不相等,计数-1
            if(count > 0) {
                count--;
            }else {
            //计数为0时,更换主元素候选人
                temp = a[i];
                count = 1;
            }
        }
    }
    return temp;
}

int getMain(int *a, int length) {
    int temp = getTemp(a, length);
    int count = 0;
    for(int i = 0; i < length; i++) {
        if(a[i] == temp) count++;
    }
    if(count > length/2) return temp;
    return -1;
}

题目7

⽤单链表保存m个整数,结点的结构为(data, link), 且|data|<=n(n为正整数)。现在要去设计一个时间复杂度尽可能高效的算法。对于链表中的data绝对值相等的结点,仅保留第⼀次出现的结点,删除其余绝对值相等的结点。

例如,链表A = {21,-15,15,-7,15},删除后的链表A={21,-15,-7}。

关键词

  • 保留第一次出现的结点
  • 删除绝对值相等

思路 在要求时间复杂度高效的情况下,可以利用空间换时间的方法开辟一份空间来保存每个结点绝对值出现的次数。

void DeleteEqualNode(LinkList *L,int n){
    int *p = calloc(n, sizeof(int));
    LinkList pre = *L;
    LinkList cur = (*L)->next;
    LinkList temp;
    while(cur) {
        if(p[abs(cur->data)] == 0) {
            p[abs(cur->data)] = 1;
            //pre和cur都后移一位
            pre = cur;
            cur = cur->next;
        }else {
            //释放当前cur,cur后移一位,pre->next指向新的cur
            temp = cur;
            cur = cur->next;
            pre->next = cur;
        }
    }
}