算法之链表

`链表简单算法题

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

img

//方法一
struct ListNode *mergeTwoLists(struct ListNode* l1, struct ListNode* l2){
    if(!l1)
      return l2;
    if(!l2)
      return l1;
    struct ListNode *head=(struct ListNode*)malloc(sizeof(struct ListNode)),*t=head;  
    while(l1&&l2){
        if(l1->val<l2->val){
            t->next=l1;
            l1=l1->next;
        }
        else
        {
            t->next=l2;
            l2=l2->next;
        }
        t->next;
    }
    if(l1)
     t->next=l1;
    else if(l2)
      t->next=l2;
    return head->next;
}

//方法二:
//终止条件:当链表中有一个链表为NULL,则直接返回另外一个链表。
//递归方法:判断l1,l2头节点的大小,然后将较小的next指向其余节点的合并结果。
struct ListNode* mergeTwoLists(struct ListNode* l1, struct ListNode* l2){
    if (l1 == NULL)
    {
        return l2;
    }
    if (l2 == NULL)
    {
        return l1;
    }
    
    if(l1->val <= l2->val)
    {
        l1->next = mergeTwoLists(l1->next, l2);
        return l1;
    }
    else
    {
        l2->next = mergeTwoLists(l1, l2->next);
        return l2;
    }
}

2.给定一个头结点为 head 的非空单链表,返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点。(附leetcode 876)
//快慢指针
//思路:设立两个指针,一个一次走两步,一个一次走一步,当走两步的走到终点的时候,那个走一步的刚好走到中点
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode* middleNode(struct ListNode* head){
    struct ListNode *fast = head;
    struct ListNode *slow = head;
    while( (fast) && (fast->next)){
        slow = slow -> next;
        fast = fast -> next -> next;
    }
    return slow;
}
3.反转一个链表(附leetcode 206)
示例:

输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
//方法一:迭代,双指针法
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode* reverseList(struct ListNode* head){
    struct ListNode* cur = head;
    struct ListNode* pre = NULL;
    while (cur) {
        struct ListNode* tmp = cur->next;
        cur->next = pre;
        pre = cur;
        cur = tmp;
    }
    return pre;
}

//方法二:头插法
//思路:通过遍历head的每一个结点,用头插法的方式插入到head上。
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode* reverseList(struct ListNode* head){
	struct ListNode *p = head, *r;//p指针指向head第一个结点,r指针为防止不断链而设的
	head = NULL;//令链表为空
	while(p != NULL){
		r = p -> next;//指向p的下一个结点,防止工作指针p头插法导致链表断链
		p -> next = head;
		head = p;
		p = r;
	}
	return head;
}
//方法三:递归
struct ListNode* reverseList(struct ListNode* head){
    //当只有一个节点,它的反转就是本身
    if(!head || !head->next)  return head;
    //取出head节点,调用函数自身将head->next为头的链表反转,结果保存在res中
    struct ListNode *res = reverseList(head->next);
    //res的尾巴是head->next,所以需要将之前弹出的head接在此尾巴上
    head->next->next = head;
    head->next = NULL;
    return res;
}





4.实现一种算法,删除单向链表中间的某个节点(即不是第一个或最后一个节点),假定你只能访问该节点。
输入:单向链表a->b->c->d->e->f中的节点c
结果:不返回任何数据,但该链表变为a->b->d->e->f
//思路:
因为说了只能访问当前节点,所以不能简单的 前一个node->next = 当前node->next;free(node);
那么只能让 当前node = 后一个node,然后free(后一个node);
代码如下,记得别忘了free()哦
内存泄露,可没有你好果汁吃!!!

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
void deleteNode(struct ListNode* node) {
    struct ListNode* temp = node->next;
    node->val = node->next->val;//实现值的交换
    node->next = node->next->next;
    free(temp);
}




5.给你一个单链表的引用结点 head。链表中每个结点的值不是 0 就是 1。已知此链表是一个整数数字的二进制表示形式。

img

输入:head = [1,0,1]
输出:5
解释:二进制数 (101) 转化为十进制数 (5)
提示:
链表不为空。
链表的结点总数不超过 30。
每个结点的值不是 0 就是 1。
方法一:
设置一个计数器,首先遍历整个链表,通过计数器的值知道有多少个节点。然后重新遍历链表,此次遍历主要用来二进制到十进制的转换,根据之前的计数器的值作为各权位的幂,即可求出。
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
int getDecimalValue(struct ListNode* head){
    int j=0;
    int sum=0;
    struct ListNode *p;
    p=head;
    while(p!=NULL)
    {
        p=p->next;
        j++;
    }
    p=head;
    while(p!=NULL&&j>0)
    {
        sum+=((p->val)*pow(2,j-1));
        j--;
        p=p->next;
    }
    return sum;
}

方法二:
由于链表中从高位到低位存放了数字的二进制表示,因此我们可以使用二进制转十进制的方法,在遍历一遍链表的同时得到数字的十进制值。
int getDecimalValue(struct ListNode* head){
    struct ListNode *p=head;
    int ans=0;
    while(p!=NULL){
        ans=ans*2+p->val;
        p=p->next;

    }
    return ans;
}

6. 存在一个按升序排列的链表,给你这个链表的头节点 head ,请你删除所有重复的元素,使每个元素 只出现一次。返回同样按升序排列的结果链表。
方法一:一次遍历
思路:
由于给定的链表是排好序的,因此重复的元素在链表中出现的位置是连续的,因此我们只需要对链表进行一次遍历,就可以删除重复的元素。
具体地,我们从指针cur 指向链表的头节点,随后开始对链表进行遍历。如果当前 cur 与 cur.next 对应的元素相同,那么我们就将 cur.next 从链表中移除;否则说明链表中已经不存在其它与 cur 对应的元素相同的节点,因此可以将cur 指向 cur.next。
当遍历完整个链表之后,我们返回链表的头节点即可。
struct ListNode* deleteDuplicates(struct ListNode* head) {
    if (!head) {
        return head;
    }

    struct ListNode* cur = head;
    while (cur->next) {
        if (cur->val == cur->next->val) {
            cur->next = cur->next->next;
        } else {
            cur = cur->next;
        }
    }

    return head;
}


7.给定一个链表,判断链表中是否有环。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。
如果链表中存在环,则返回 true 。 否则,返回 false 。

img

方法一:快慢指针
思路:定义两个指针,一快一满。慢指针每次只移动一步,而快指针每次移动两步。初始时,慢指针在位置 head,而快指针在位置 head.next。这样一来,如果在移动的过程中,快指针反过来追上慢指针,就说明该链表为环形链表。否则快指针将到达链表尾部,该链表不为环形链表。

bool hasCycle(struct ListNode* head) {
if (head == NULL || head->next == NULL) {
    return false;
}
struct ListNode* slow = head;
struct ListNode* fast = head->next;
while (slow != fast) {
    if (fast == NULL || fast->next == NULL) {
        return false;
    }
    slow = slow->next;
    fast = fast->next->next;
}
return true;
}
8. 请判断一个链表是否为回文链表。(附leetcode .234)

示例 :

输入: 1->2
输出: false

输入: 1->2->2->1
输出: true
方法一:将值复制到数组中后用双指针法
思路:
确定数组列表是否回文很简单,我们可以使用双指针法来比较两端的元素,并向中间移动。一个指针从起点向中间移动,另一个指针从终点向中间移动。这需要 O(n)O(n) 的时间,因为访问每个元素的时间是 O(1)O(1),而有 nn 个元素要访问。

然而同样的方法在链表上操作并不简单,因为不论是正向访问还是反向访问都不是 O(1)O(1)。而将链表的值复制到数组列表中是 O(n)O(n),因此最简单的方法就是将链表的值复制到数组列表中,再使用双指针法判断。
一共为两个步骤:
1.复制链表值到数组列表中。
2.使用双指针法判断是否为回文。
第一步,我们需要遍历链表将值复制到数组列表中。我们用 currentNode 指向当前节点。每次迭代向数组添加 currentNode.val,并更新 currentNode = currentNode.next,当 currentNode = null 时停止循环。
执行第二步的最佳方法取决于你使用的语言。在 Python 中,很容易构造一个列表的反向副本,也很容易比较两个列表。而在其他语言中,就没有那么简单。因此最好使用双指针法来检查是否为回文。我们在起点放置一个指针,在结尾放置一个指针,每一次迭代判断两个指针指向的元素是否相同,若不同,返回 false;相同则将两个指针向内移动,并继续判断,直到两个指针相遇。
在编码的过程中,注意我们比较的是节点值的大小,而不是节点本身。正确的比较方式是:node_1.val == node_2.val,而 node_1 == node_2 是错误的。
bool isPalindrome(struct ListNode* head) {
    int vals[50001], vals_num = 0;
    while (head != NULL) {
        vals[vals_num++] = head->val;
        head = head->next;
    }
    for (int i = 0, j = vals_num - 1; i < j; ++i, --j) {
        if (vals[i] != vals[j]) {
            return false;
        }
    }
    return true;
}
方法三:快慢指针
思路:
避免使用 O(n)O(n) 额外空间的方法就是改变输入。
我们可以将链表的后半部分反转(修改链表结构),然后将前半部分和后半部分进行比较。比较完成后我们应该将链表恢复原样。虽然不需要恢复也能通过测试用例,但是使用该函数的人通常不希望链表结构被更改。
该方法虽然可以将空间复杂度降到 O(1)O(1),但是在并发环境下,该方法也有缺点。在并发环境下,函数运行时需要锁定其他线程或进程对链表的访问,因为在函数执行过程中链表会被修改。
算法
整个流程可以分为以下五个步骤:
找到前半部分链表的尾节点。
反转后半部分链表。
判断是否回文。
恢复链表。
返回结果。
执行步骤一,我们可以计算链表节点的数量,然后遍历链表找到前半部分的尾节点。
我们也可以使用快慢指针在一次遍历中找到:慢指针一次走一步,快指针一次走两步,快慢指针同时出发。当快指针移动到链表的末尾时,慢指针恰好到链表的中间。通过慢指针将链表分为两部分。
若链表有奇数个节点,则中间的节点应该看作是前半部分。
步骤二可以使用「206. 反转链表」问题中的解决方法来反转链表的后半部分。
步骤三比较两个部分的值,当后半部分到达末尾则比较完成,可以忽略计数情况中的中间节点。
步骤四与步骤二使用的函数相同,再反转一次恢复链表本身。
struct ListNode* reverseList(struct ListNode* head) {
    struct ListNode* prev = NULL;
    struct ListNode* curr = head;
    while (curr != NULL) {
        struct ListNode* nextTemp = curr->next;
        curr->next = prev;
        prev = curr;
        curr = nextTemp;
    }
    return prev;
}
struct ListNode* endOfFirstHalf(struct ListNode* head) {
    struct ListNode* fast = head;
    struct ListNode* slow = head;
    while (fast->next != NULL && fast->next->next != NULL) {
        fast = fast->next->next;
        slow = slow->next;
    }
    return slow;
}
bool isPalindrome(struct ListNode* head) {
    if (head == NULL) {
        return true;
    }
    // 找到前半部分链表的尾节点并反转后半部分链表
    struct ListNode* firstHalfEnd = endOfFirstHalf(head);
    struct ListNode* secondHalfStart = reverseList(firstHalfEnd->next);
    // 判断是否回文
    struct ListNode* p1 = head;
    struct ListNode* p2 = secondHalfStart;
    bool result = true;
    while (result && p2 != NULL) {
        if (p1->val != p2->val) {
            result = false;
        }
        p1 = p1->next;
        p2 = p2->next;
    }
    // 还原链表并返回结果
    firstHalfEnd->next = reverseList(secondHalfStart);
    return result;
}
9.给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头结点。(附lettcode.203)
方法二:迭代
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode* removeElements(struct ListNode* head, int val){
    /* 找到第一个不为目标值的节点 */
    while (head != NULL && head->val == val) {
        head = head->next;
    }
    /* 若都是目标值,则返回NULL */
    if (head == NULL) {
        return NULL;
    }
    /* 用于存储上一个节点的值 */
    struct ListNode* pre = head;
    while (pre->next != NULL) {
        if (pre->next->val == val) {
            pre->next = pre->next->next;
        } else {
            pre = pre->next;
        }
    }
    return head;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
循环链表是一种特殊类型的链表,其尾节点指向头节点形成一个循环。相比于普通链表,循环链表可以更方便地遍历链表或进行某些操作。对于数据结构与算法的循环链表的随堂实验,我无法直接引用提供的参考内容中的具体实验内容。但是,一般的循环链表实验可能会包括以下几个步骤: 1. 定义循环链表节点类。 2. 实现循环链表的插入操作,可以在链表的任意位置插入新的节点。 3. 实现循环链表的删除操作,可以删除链表中的指定节点。 4. 实现循环链表的查找操作,可以根据给定的条件查找链表中的节点。 5. 实现循环链表的遍历操作,可以按照特定的顺序遍历链表中的所有节点。 请根据具体的实验要求,参考引用内容中的链表定义与用法,进行相应的实验设计与实现。<span class="em">1</span><span class="em">2</span> #### 引用[.reference_title] - *1* [Python实现的数据结构与算法链表详解](https://download.csdn.net/download/weixin_38545117/13771586)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* [Python数据结构与算法链表定义与用法实例详解【单链表、循环链表】](https://download.csdn.net/download/weixin_38660295/13786240)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值