【初阶数据结构篇】顺序表和链表算法题

文章目录

顺序表算法题

不熟悉顺序表的可以先了解一下
顺序表实现方法

移除元素

给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素。元素的顺序可能发生改变。然后返回 nums 中与 val 不同的元素的数量。

假设 nums 中不等于 val 的元素数量为 k,要通过此题,您需要执行以下操作:

  • 更改 nums 数组,使 nums 的前 k 个元素包含不等于 val 的元素。nums 的其余元素和 nums 的大小并不重要。
  • 返回 k

很容易想到的是用双指针进行遍历和赋值即可

c 复制代码
int removeElement(int* nums, int numsSize, int val) {
    int left = 0;
    for (int right = 0; right < numsSize; right++) {
        if (nums[right] != val) {
            nums[left] = nums[right];
            left++;
        }
    }
    return left;
}
  • 注意题目中说的是元素的顺序可以发生改变,即不需要保持元素的相对顺序,可以进行优化

    • 如果左指针 left 指向的元素等于 val,此时将右指针 right 指向的元素复制到左指针 left 的位置,然后右指针 right 左移一位。如果赋值过来的元素恰好也等于 val,可以继续把右指针 right 指向的元素的值赋值过来(左指针 left 指向的等于 val 的元素的位置继续被覆盖),直到左指针指向的元素的值不等于 val 为止。

      注意while结束条件不要少了等于

c 复制代码
int removeElement(int* nums, int numsSize, int val) {
    int left = 0, right = numsSize-1;
    while (left <= right) {
        if (nums[left] == val) {
            nums[left] = nums[right];
            right--;
        } else {
            left++;
        }
    }
    return left;
}

注意:返回的是元素的个数


删除有序数组中的重复项

给你一个 非严格递增排列 的数组 nums ,请你**原地** 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回 nums 中唯一元素的个数。

考虑 nums 的唯一元素的数量为 k ,你需要做以下事情确保你的题解可以被通过:

  • 更改数组 nums ,使 nums 的前 k 个元素包含唯一元素,并按照它们最初在 nums 中出现的顺序排列。nums 的其余元素与 nums 的大小不重要。
  • 返回 k

  • 和上一道题思路大致相同,题目说了是非严格递增序列,双指针法比较前后两个元素即可
c 复制代码
int removeDuplicates(int* nums, int numsSize) {
    int src = 0;
    int dest = 1;
    while (dest < numsSize) {
        if(nums[src]!=nums[dest])
        {
            nums[++src]=nums[dest];
        }
        dest++;
    }

    return ++src;
}

合并两个有序数组

给你两个按 非递减顺序 排列的整数数组 nums1nums2,另有两个整数 mn ,分别表示 nums1nums2 中的元素数目。

请你 合并 nums2nums1 中,使合并后的数组同样按 非递减顺序 排列。

**注意:**最终,合并后数组不应由函数返回,而是存储在数组 nums1 中。为了应对这种情况,nums1 的初始长度为 m + n,其中前 m 个元素表示应合并的元素,后 n 个元素为 0 ,应忽略。nums2 的长度为 n


  • 当然可以直接把第二个数组移到第一个数组尾部,然后进行排序
  • 使用qsort函数
c 复制代码
int cmp(int* a, int* b) {
    return *a - *b;
}

void merge(int* nums1, int nums1Size, int m, int* nums2, int nums2Size, int n) {
    for (int i = 0; i != n; ++i) {
        nums1[m + i] = nums2[i];
    }
    qsort(nums1, nums1Size, sizeof(int), cmp);
}

  • 三指针法,利用两个数组都是非递减排列
  • 因为排序好的数组仍然是非递减序列,所以我们的两个指针依次从尾部开始向前遍历,谁大把谁放到nums1的尾部 (若从前面开始需要新创建一个数组来存储元素最后再赋值给nums1)
  • 最后出循环的时候l2和l3只可能有一个小于0
    • 若是l2,说明nums2没有遍历完,需要将剩下的元素赋值给nums1
    • 若是l3,则直接返回nums1即可
c 复制代码
void merge(int* nums1, int nums1Size, int m, int* nums2, int nums2Size, int n) {
    int l1=nums1Size-1;
    int l2=m-1;
    int l3=n-1;
    while(l2>=0&&l3>=0)
    {
        if(nums1[l2]>nums2[l3])
        {
            nums1[l1]=nums1[l2];
            l2--;
        }
        else
        {
            nums1[l1]=nums2[l3];
            l3--;
        }
        l1--;

    }
    if(l2<0)
    {
        while(l1>=0)
        nums1[l1--]=nums2[l3--];
    }
}

链表算法题

不熟悉链表的可以先了解一下
单链表实现方法

移除链表元素

给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点


- 最直观的办法就是创建一个新链表

注:不是开辟空间的深拷贝,而只是定义了指向同一结点的指针

在最后需要先判断newtail是否为空**(否则链表为空链表时会报错)再将其中的next指针置为空(否则可能会出现循环)**

c 复制代码
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
typedef struct ListNode ListNode;
ListNode* removeElements(ListNode* head, int val) {
    ListNode* pcur = head;
    ListNode* newhead = NULL;
    ListNode* newtail = NULL;
    while (pcur) {
        if (pcur->val != val) {
            if (newhead == NULL) {
                newhead = newtail = pcur;
            } else {
                newtail->next = pcur;
                newtail = newtail->next;
            }
        }
        pcur = pcur->next;
    }
    if (newtail)
        newtail->next = NULL;
    return newhead;
}
  • 在leetcode上给出了递归的方法,链表的定义具有递归的性质,因此链表题目常可以用递归的方法求解。这道题要求删除链表中所有节点值等于特定值的节点,可以用递归实现。
c 复制代码
struct ListNode* removeElements(struct ListNode* head, int val) {
    if (head == NULL) {
        return head;
    }
    head->next = removeElements(head->next, val);
    return head->val == val ? head->next : head;
}

反转链表

给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。

  • 可以和上一道题一样创建新链表,不多赘述
  • 这里介绍一种比较巧妙的方法->三指针法
c 复制代码
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
 typedef struct ListNode ListNode;
struct ListNode* reverseList(struct ListNode* head) {
    if(head==NULL)
    return head;
    ListNode* n1,*n2,*n3;
    n1=NULL,n2=head,n3=head->next;
    while(n3){
        n2->next=n1;
        n1=n2;
        n2=n3;
        n3=n3->next;
    }
    n2->next=n1;
    return n2;
    
}

链表的中间结点

给你单链表的头结点 head ,请你找出并返回链表的中间结点。

如果有两个中间结点,则返回第二个中间结点


  • 快慢指针法
    • 注意为偶数时返回第二个结点
c 复制代码
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
 typedef struct ListNode ListNode;
struct ListNode* middleNode(struct ListNode* head) {
    ListNode* slow, *fast;
    slow=fast=head;
    while(fast&&fast->next)
    {
        slow=slow->next;
        fast=fast->next->next;
    }
    return slow;
}

合并两个有序链表

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

  • 和合并数组大致思路相同
  • 可以在创建新链表的一开始申请头结点(哨兵位),避免对于newtail和newhead为空的情况进行讨论
    • 记得最后释放空间!!!
c 复制代码
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
 typedef struct ListNode ListNode;
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) {
    ListNode* newhead,*newtail;
    newhead=newtail=(ListNode*)malloc(sizeof(ListNode));
    while(list1&&list2)
    {
        if(list1->val<list2->val)
        {
            newtail->next=list1;
            list1=list1->next;
        }
        else
        {
            newtail->next=list2;
            list2=list2->next;
        }
        newtail=newtail->next;
    }
    if(list1)
    {
        newtail->next=list1;
    }
    else
    {
        newtail->next=list2;
    }
    ListNode* ret=newhead->next;
    free(newhead);
    newhead=newtail=NUll;
    return ret;
}

链表分割

有一链表的头指针 ListNode* pHead,给一定值x,编写一段代码将所有小于x的结点排在其余结点之前,且不能改变原来的数据顺序,返回重新排列后的链表的头指针

  • 解题思路:创建两个链表,分别存放小于x的节点和大于等于x的节点,分别进行尾插
  • 最后别忘了把大链表的尾节点置为空,否则可能会出现死循环
  • 还是别忘了释放空间
c 复制代码
ListNode* partition(ListNode* pHead, int x) {
        if(pHead == NULL)
            return NULL;
        
        struct ListNode* lessHead, *lessTail,*greaterHead, *greaterTail;
        //创建链表表头
        lessHead = lessTail = (struct ListNode*)malloc(sizeof(struct ListNode));
        greaterHead = greaterTail = (struct ListNode*)malloc(sizeof(struct ListNode));
        struct ListNode* cur = pHead;
        while(cur)
        {
            //小于x的尾插到lessTail
            if(cur->val < x)
            {
                lessTail->next = cur;
                lessTail = lessTail->next;
            }
            //大于等于x的尾插到greaterTail
            else
            {
                greaterTail->next = cur;
                greaterTail = greaterTail->next;
            }
            cur = cur->next;
        }
        //链接两个链表
        lessTail->next = greaterHead->next;
        greaterTail->next = NULL;
        //获取表头
        pHead = lessHead->next;
        free(lessHead);
        free(greaterHead); 
        return pHead;
    }

链表的回文结构

对于一个链表,请设计一个时间复杂度为O(n),额外空间复杂度为O(1)的算法,判断其是否为回文结构。

给定一个链表的头指针A,请返回一个bool值,代表其是否为回文结构。保证链表长度小于等于900

  • 不推荐的解法,类似数组字符串的回文解法
    • 先把链表中的元素值全部保存到数组中,然后再判断数组是否为回文。不建议使用这种解法,因为如果没有告诉链表最大长度,则不能同此解法
c 复制代码
bool chkPalindrome(ListNode* A) {
        // write code here
        int a[900] = {0};
        ListNode* cur = A;
        int n = 0;
        //保存链表元素
        while(cur)
        {
            a[n++] = cur->val;
            cur = cur->next;
        }
        //判断数组是否为回文结构
        int begin = 0, end = n-1;
        while(begin < end)
        {
            if(a[begin] != a[end])
                return false;
            ++begin;
            --end;
        }
         
        return true;
    }
  • 解题思路:此题可以先找到中间节点,然后把后半部分逆置,最近前后两部分一一比对,如果节点的值全部相同,则即为回文。
    • 快慢指针找中间结点
    • 三指针逆置后半部分
c 复制代码
bool chkPalindrome(ListNode* A) {
		if (A == NULL || A->next == NULL)
			return true;
		ListNode* slow, *fast, *prev, *cur, *nxt;
		slow = fast = A;
		//找到中间节点
		while (fast && fast->next)
		{
			slow = slow->next;
			fast = fast->next->next;
		}
		prev = NULL;
		//后半部分逆置
		cur = slow;
		while (cur)
		{
			nxt = cur->next;
			cur->next = prev;
			prev = cur;
			cur = nxt;
		}
		//逐点比对
		while (A && prev)
		{
			if (A->val != prev->val)
				return false;
			A = A->next;
			prev = prev->next;
		}
		return true;
	}
相关推荐
码流之上27 分钟前
【一看就会一写就废 指间算法】设计电子表格 —— 哈希表、字符串处理
javascript·算法
快手技术2 小时前
快手提出端到端生成式搜索框架 OneSearch,让搜索“一步到位”!
算法
CoovallyAIHub1 天前
中科大DSAI Lab团队多篇论文入选ICCV 2025,推动三维视觉与泛化感知技术突破
深度学习·算法·计算机视觉
NAGNIP1 天前
Serverless 架构下的大模型框架落地实践
算法·架构
moonlifesudo1 天前
半开区间和开区间的两个二分模版
算法
moonlifesudo1 天前
300:最长递增子序列
算法
CoovallyAIHub1 天前
港大&字节重磅发布DanceGRPO:突破视觉生成RLHF瓶颈,多项任务性能提升超180%!
深度学习·算法·计算机视觉
CoovallyAIHub1 天前
英伟达ViPE重磅发布!解决3D感知难题,SLAM+深度学习完美融合(附带数据集下载地址)
深度学习·算法·计算机视觉
聚客AI2 天前
🙋‍♀️Transformer训练与推理全流程:从输入处理到输出生成
人工智能·算法·llm
大怪v2 天前
前端:人工智能?我也会啊!来个花活,😎😎😎“自动驾驶”整起!
前端·javascript·算法