《算法通关村第一关——链表经典问题之LeetCode刷题笔记》

一、两个链表的第一个公共子节点问题

1.第一步,定义链表

struct ListNode {
    int val;
    struct ListNode* next;
} ;

 struct ListNode* createNode(int x) {
   struct ListNode* newNode = (struct ListNode*)malloc(sizeof(struct ListNode));
    newNode->val = x;
    newNode->next = NULL;
    return newNode;
}

具体题目: 输入两个链表,找出它们的第一个公共节点。

例如下面的两个链表:

两个链表的头结点都是已知的,相交之后成为一个单链表,但是相交的位置未知,并且相交之前的结点数也是未知的,请设计算法找到两个链表的合并点。 

一个屡试不爽的思路:将常用数据结构和常用算法思想都想一遍,看看哪些能解决问题。

1)用集合的方法来做

这里用 java 的代码实现

public ListNode findFirstCommonNodeBySet(ListNode headA, ListNode headB) {
    Set<ListNode> set = new HashSet<>();  // 创建一个空的Set对象set,用于存储链表节点
    
    while (headA != null) {  // 循环将链表headA的节点添加到set中
        set.add(headA);
        headA = headA.next;  // 将headA指向下一个节点
    }

    while (headB != null) {  // 循环遍历链表headB的节点
        if (set.contains(headB))  // 如果set中含有当前节点
            return headB;  // 返回当前节点,即找到第一个公共节点
        headB = headB.next;  // 将headB指向下一个节点
    }
    
    return null;  // 如果没有找到公共节点,则返回null
}

函数findFirstCommonNodeBySet用于在两个链表中查找第一个公共节点。

代码中使用了Set接口的实现类HashSet来存储链表节点。

首先将链表headA的所有节点添加到set中,然后遍历链表headB的节点,如果set中包含当前节点,则说明找到了第一个公共节点,返回该节点。

如果遍历完整个链表headB仍未找到公共节点,则返回null。

二、判断链表是否为回文序列

LeetCode234,这也是一道简单,但是很经典的链表题,判断一个链表是否为回文链表。

示例1:
输入: 1->2->2->1
输出: true
进阶:你能否用 O(n) 时间复杂度和 O(1) 空间复杂度解决此题?

做这题,刚开始我就在想各种数据结构,得到的一种思路是这个链表元素,一份压入栈中,一份压入队中,分别出栈,出队,比较两者元素值。有一个不相等,就不是回文链表。

看了知识库里面的答案,其实不用压入队,遍历就行。

同时还有优化方法:

一边遍历一边压栈,得到链表的总长度。第二遍比较的时候,只比较一半。

(因为假设是回文链表)

public boolean isPalindrome(ListNode head) {
    ListNode temp = head;  // 创建一个指针temp,用于遍历链表
    Stack<Integer> stack = new Stack();  // 创建一个空的栈对象stack,用于存储链表节点的值

    // 将链表节点的值压入栈中
    while (temp != null) {  // 循环遍历链表
        stack.push(temp.val);  // 将节点的值压入栈中
        temp = temp.next;  // 将temp指向下一个节点
    }

    // 判断链表是否回文
    while (head != null) {  // 循环遍历链表
        if (head.val != stack.pop()) {  // 如果链表节点的值不等于栈顶元素
            return false;  // 说明该链表不是回文链表,返回false
        }
        head = head.next;  // 将head指向下一个节点
    }

    return true;  // 如果整个链表遍历完之后都没有发现不相等的情况,则说明该链表是回文链表,返回true
}

在代码中,我们使用stack.pop()方法弹出栈顶的元素,并与链表节点值进行比较。栈顶元素会随着每次弹出而改变。

三、合并两个有序列表

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

输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]

示例 2:

输入:l1 = [], l2 = []
输出:[]

示例 3:

输入:l1 = [], l2 = [0]
输出:[0]

话不多说,附上代码

class Solution {
    public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
        // 如果 list1 为空,则直接返回 list2
        if (list1 == null){
            return list2;
        }
        // 如果 list2 为空,则直接返回 list1
        if (list2 == null){
            return list1;
        }
        else if (list1.val < list2.val){          
            // 当 list1 的值小于 list2 的值时,
            // 将 list1 的下一个节点与 list2 进行合并,并将结果作为 list1 的下一个节点
            list1.next =  mergeTwoLists(list1.next, list2);
            // 返回合并后的 list1
            return list1;
        }
        else {
            // 当 list1 的值大于等于 list2 的值时,
            // 将 list2 的下一个节点与 list1 进行合并,并将结果作为 list2 的下一个节点
            list2.next = mergeTwoLists(list1, list2.next);
            // 返回合并后的 list2
            return list2;
        }
    }
}

这段代码实现了合并两个有序链表的功能。根据链表节点的值大小,递归地将两个链表中较小节点连接在一起,形成新的有序链表。

递归方法用的很巧妙,以我自己的理解是节点的迭代恰好与递归的特性相符,可惜现阶段对递归的理解的=还没有很透彻...

四、合并两个链表

给你两个链表 list1 和 list2 ,它们包含的元素分别为 n 个和 m 个。

请你将 list1 中下标从 a 到 b 的全部节点都删除,并将list2 接在被删除节点的位置。

下图中蓝色边和节点展示了操作后的结果:

请你返回结果链表的头指针。 

这道题的思路并不难,就是需要注意一些细节。

思路:可以把题目看成是链表的中间插入

细节:要通过遍历来找到插入的位置在哪,代码中用到了 pre1 和 post1 来找到需要插入的节点

list2 也 用到了 post2 来遍历链表

总的来说,思路并不难,需要你对链表的插入较为熟悉

代码如下:

class Solution {
    public ListNode mergeInBetween(ListNode l1, int a, int b, ListNode l2) {
        // 创建指针 pre1 指向链表 l1 的起始节点
        ListNode pre1 = l1;
        // 创建指针 post1 指向链表 l1 的起始节点
        ListNode post1 = l1;
        // 创建指针 post2 指向链表 l2 的起始节点
        ListNode post2 = l2;
        // 初始化变量 i 和 j 为 0,用于计数
        int i = 0, j = 0;

        // 遍历链表 l1,找到 pre1 指针的位置,停止条件是找到区间的前一个节点 a-1 或者遍历到链表末尾
        while (pre1 != null && i < a - 1) {
            pre1 = pre1.next;  // 向后移动 pre1 指针
            i++;  // i 计数器加一
        }

        // 遍历链表 l1,找到 post1 指针的位置,停止条件是找到区间的最后一个节点 b 或者遍历到链表末尾
        while (post1 != null && j < b) {
            post1 = post1.next;  // 向后移动 post1 指针
            j++;  // j 计数器加一
        }

        // 将 pre1 的下一个节点指向 l2,完成区间 [a, b] 的切断
        pre1.next = l2;

        // 遍历链表 l2,找到 post2 的末尾节点
        while (post2.next != null) {
            post2 = post2.next;  // 向后移动 post2 指针
        }

        // 将 post2 的下一个节点指向 post1 的下一个节点,连接剩余部分
        post2.next = post1.next;

        // 返回合并后的链表 l1
        return l1;
    }
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值