小白学java链表算法题——第一关(白银挑战)

目录​​​​​​​

1.两个链表的第一个公共子节点(看图二)

2.LeeCode234题目

3.1LeeCode21题目

 3.2合并K个链表

3.3LeeCode1699题目

4.1LeeCode876题目

4.2倒数第K个元素

 4.3旋转链表LeeCode61

 5.1删除特定结点LeeCode203

 5.2删除特定结点LeeCode19

5.3.1删除重复元素,重复元素只保留一个LeetCoede83

5.3.2删除重复元素,重复元素都不要LeetCoede82


1.两个链表的第一个公共子节点(看图二)

方法一:通过Hash辅助查找


public static ListNode findFirstCommonNodeByMap(ListNode pHead1, ListNode pHead2) {
    if (pHead1 == null || pHead2 == null) { // 如果其中一个链表为空,则不存在公共节点,直接返回null
        return null;
    }
    ListNode current1 = pHead1; // 创建一个指针current1,指向链表pHead1的头节点
    ListNode current2 = pHead2; // 创建一个指针current2,指向链表pHead2的头节点

    HashMap<ListNode, Integer> hashMap = new HashMap<ListNode, Integer>(); // 创建一个HashMap用于存储链表节点和出现次数的映射关系
    while (current1 != null) { // 遍历链表pHead1
        hashMap.put(current1, null); // 将当前节点加入到HashMap中
        current1 = current1.next; // 移动current1指针到下一个节点
    }

    while (current2 != null) { // 遍历链表pHead2
        if (hashMap.containsKey(current2)) // 如果HashMap中包含了当前节点
            return current2; // 返回当前节点,即为两个链表的第一个公共节点
        current2 = current2.next; // 移动current2指针到下一个节点
    }

    return null; // 不存在公共节点,返回null
}

 方法2:通过集合来辅助查找

public static ListNode findFirstCommonNodeBySet(ListNode headA, ListNode headB) {
    Set<ListNode> set = new HashSet<>(); // 创建一个HashSet用于存储链表节点
    while (headA != null) { // 遍历链表headA
        set.add(headA); // 将当前节点加入到HashSet中
        headA = headA.next; // 移动headA指针到下一个节点
    }

    while (headB != null) { // 遍历链表headB
        if (set.contains(headB)) // 如果HashSet中包含了当前节点
            return headB; // 返回当前节点,即为两个链表的第一个公共节点
        headB = headB.next; // 移动headB指针到下一个节点
    }
    return null; // 不存在公共节点,返回null
}

方法3:通过栈

public static ListNode findFirstCommonNodeByStack(ListNode headA, ListNode headB) {
    Stack<ListNode> stackA = new Stack(); // 创建一个栈stackA,用于存储链表headA的节点
    Stack<ListNode> stackB = new Stack(); // 创建一个栈stackB,用于存储链表headB的节点

    while (headA != null) { // 遍历链表headA
        stackA.push(headA); // 将当前节点压入栈stackA
        headA = headA.next; // 移动headA指针到下一个节点
    }

    while (headB != null) { // 遍历链表headB
        stackB.push(headB); // 将当前节点压入栈stackB
        headB = headB.next; // 移动headB指针到下一个节点
    }

    ListNode preNode = null; // 定义一个节点preNode,用于存储公共节点的前一个节点
    while (stackB.size() > 0 && stackA.size() > 0) { // 当两个栈都非空时循环
        if (stackA.peek() == stackB.peek()) { // 如果栈顶节点相同
            preNode = stackA.pop(); // 将栈A的栈顶节点出栈并赋值给preNode
            stackB.pop(); // 将栈B的栈顶节点出栈
        } else { // 如果栈顶节点不同,说明上一个节点就是最后一个公共节点
            break;
        }
    }
    return preNode; // 返回preNode,即为两个链表的第一个公共节点的前一个节点
}

 方法4:通过序列拼接

public static ListNode findFirstCommonNodeByCombine(ListNode pHead1, ListNode pHead2) {
    if (pHead1 == null || pHead2 == null) { // 如果其中一个链表为空,直接返回null
        return null;
    }
    ListNode p1 = pHead1; // 定义指针p1指向链表pHead1的头节点
    ListNode p2 = pHead2; // 定义指针p2指向链表pHead2的头节点

    while (p1 != p2) { // 当p1和p2不相等时循环
        p1 = p1.next; // p1指针后移一位
        p2 = p2.next; // p2指针后移一位

        if (p1 != p2) { // 如果p1和p2不相等
            if (p1 == null) { // 如果p1已经到达链表末尾
                p1 = pHead2; // 将p1指针重置为链表pHead2的头节点
            }
            if (p2 == null) { // 如果p2已经到达链表末尾
                p2 = pHead1; // 将p2指针重置为链表pHead1的头节点
            }
        }
    }
    return p1; // 返回p1,即为两个链表的第一个公共节点
}

2.LeeCode234题目

public boolean isPalindrome(ListNode head) {
    ListNode temp = head; // 创建一个临时节点,指向链表的头节点
    Stack<Integer> stack = new Stack(); // 创建一个栈,用于存储链表节点的值

    // 将链表节点的值依次压入栈中
    while (temp != null) {
        stack.push(temp.val);
        temp = temp.next;
    }

    // 从头节点开始遍历链表
    while (head != null) {
        // 如果当前节点的值与栈顶元素不相等,说明链表不是回文的,返回false
        if (head.val != stack.pop()) {
            return false;
        }
        head = head.next; // 继续遍历下一个节点
    }

    return true; // 当遍历完整个链表时,没有发现不相等的值,说明链表是回文的,返回true
}

3.1LeeCode21题目

public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
    ListNode newHead = new ListNode(-1); // 创建一个新的头节点,并初始化为-1
    ListNode res = newHead; // 创建一个结果节点,用于保存合并后的链表

    // 遍历两个链表,直到其中一个链表为空
    while (list1 != null || list2 != null) {
        if (list1 != null && list2 != null) { // 如果两个链表都不为空
            if (list1.val < list2.val) { // 如果list1的值小于list2的值
                newHead.next = list1; // 将list1连接到新链表的下一个节点
                list1 = list1.next; // 移动list1指针到下一个节点
            } else if (list1.val > list2.val) { // 如果list1的值大于list2的值
                newHead.next = list2; // 将list2连接到新链表的下一个节点
                list2 = list2.next; // 移动list2指针到下一个节点
            } else { // 如果list1的值等于list2的值
                newHead.next = list2; // 将list2连接到新链表的下一个节点
                list2 = list2.next; // 移动list2指针到下一个节点
                newHead = newHead.next; // 移动新链表的指针到下一个节点
                newHead.next = list1; // 将list1连接到新链表的下一个节点
                list1 = list1.next; // 移动list1指针到下一个节点
            }
            newHead = newHead.next; // 移动新链表的指针到下一个节点
        } else if (list1 != null && list2 == null) { // 如果list1不为空而list2为空
            newHead.next = list1; // 将list1连接到新链表的下一个节点
            list1 = list1.next; // 移动list1指针到下一个节点
            newHead = newHead.next; // 移动新链表的指针到下一个节点
        } else if (list1 == null && list2 != null) { // 如果list1为空而list2不为空
            newHead.next = list2; // 将list2连接到新链表的下一个节点
            list2 = list2.next; // 移动list2指针到下一个节点
            newHead = newHead.next; // 移动新链表的指针到下一个节点
        }
    }
    
    return res.next; // 返回合并后的链表的头节点
}

 3.2合并K个链表

public ListNode mergeKLists(ListNode[] lists){
        ListNode res =null;
        for (ListNode list:list){
            res=mergeTwoList(res,list);
        }
        return res;
    }

3.3LeeCode1699题目

class Solution {
    public ListNode mergeInBetween(ListNode list1, int a, int b, ListNode list2) {
        ListNode pre1 = list1;  // 第一个链表中的前一个节点
        ListNode post1 = list1; // 第一个链表中的后一个节点
        ListNode post2 = list2; // 第二个链表的头节点
        int i = 0; // 计数器,用于记录pre1的移动次数
        int j = 0; // 计数器,用于记录post1的移动次数
        
        // 移动pre1和post1直到达到指定位置a和b
        while (pre1 != null && post1 != null && j < b) {
            if (i != a - 1) { // 如果i不等于a-1,则继续向后移动pre1
                pre1 = pre1.next;
                i++;
            }
            if (j != b) { // 如果j不等于b,则继续向后移动post1
                post1 = post1.next;
                j++;
            }
        }
        
        post1 = post1.next; // 跳过第一个链表中的一段节点
        
        // 移动post2到第二个链表的末尾
        while (post2.next != null) {
            post2 = post2.next;
        }
        
        pre1.next = list2; // 将第一个链表的pre1节点指向第二个链表的头节点
        post2.next = post1; // 将第二个链表的末尾节点指向第一个链表的post1节点
        
        return list1; // 返回合并后的链表
    }
}

4.1LeeCode876题目

​​​​​​​力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台备战技术面试?力扣提供海量技术面试资源,帮助你高效提升编程技能,轻松拿下世界 IT 名企 Dream Offer。icon-default.png?t=N7T8https://leetcode.cn/problems/middle-of-the-linked-list/solutions/1646119/by-jyd-aphd/

class Solution {
    public ListNode middleNode(ListNode head) {
        ListNode slow = head; // 慢指针,每次移动一步
        ListNode fast = head; // 快指针,每次移动两步

        while (fast != null && fast.next != null) {
            slow = slow.next; // 慢指针向前移动一步
            fast = fast.next.next; // 快指针向前移动两步
        }

        return slow; // 返回中间节点
    }
}

4.2倒数第K个元素

要求:输入一个链表,输出该链表中倒数第k个结点,本题从1开始计数,即链表的尾结点是倒数第1个结点。

示例:

给定一个链表:1->2->3->4->5,和k=2

返回链表4->5.

这里也可以使用快慢指针,我们先将fast向后遍历到第k+1个结点,slow仍然指向链表的第一个结点,此时的指针fast与slow二者之间,刚好间隔k个结点,之后两个指针同步向后走,当fast走到链表的尾部空结点时,slow指针刚好指向链表的倒数第k个结点

  public ListNode1.ListNode getKthFormEnd(ListNode1.ListNode head,int k){
        ListNode1.ListNode fast=head;
        ListNode1.ListNode slow=head;
        while (fast != null && k > 0){
            fast=fast.next;
            k--;
        }
        while (fast != null){
            fast=fast.next;
            slow=slow.next;
        }
        return slow;
    }

 4.3旋转链表LeeCode61

示例

(1)思路一:将整个链表反转成{5,4,3,2,1},然后将前k和前N-k两个分别反转

(2)思路二:先用双指针策略找到倒数K的位置,也就是{1,2,3}和{4,5}两个序列,再将它们拼接成{4,5,6,1,2,3}就可以了

因为k有可能大于链表长度,所以首先获取一下链表长度len,如果k==0,则不用反转,直接返回头结点。否则:

1.快指针先走k步

2.慢指针和快指针一起走

3.快指针走到链表尾部时,慢指针所在位置刚好是要断开的地方。把快指针指向的结点练到头部,慢指针指向的结点断开和下一结点的联系。

4.返回结束时慢指针指向结点的下一结点。

代码:

class Solution {
    public ListNode rotateRight(ListNode head, int k) {
        if (head == null || k == 0) {
            return head;
        }
        
        ListNode temp = head;
        int len = 1;
        while (temp.next != null) {
            temp = temp.next;
            len++;
        }
        temp.next = head; // 将链表头尾相连,形成一个环
        
        int stepsToNewHead = len - k % len;
        for (int i = 0; i < stepsToNewHead; i++) {
            temp = temp.next;
        }
        
        ListNode newHead = temp.next;
        temp.next = null; // 断开环
        
        return newHead;
    }
}

 5.1删除特定结点LeeCode203

public ListNode removeElements(ListNode head, int val) {
    // 创建一个虚拟头节点,值为0
    ListNode dummyHead = new ListNode(0);
    dummyHead.next = head; // 将虚拟头节点指向原链表的头节点
    ListNode cur = dummyHead; // 使用cur指针来遍历链表

    // 遍历链表
    while (cur.next != null) {
        if (cur.next.val == val) { // 如果当前节点的下一个节点的值等于目标值val
            cur.next = cur.next.next; // 则将当前节点的下一个节点跳过,指向下下个节点,即删除了目标值的节点
        } else {
            cur = cur.next; // 否则,继续遍历下一个节点
        }
    }

    return dummyHead.next; // 返回处理后的链表(虚拟头节点的下一个节点即为新的头节点)
}

 5.2删除特定结点LeeCode19

方法一:计算链表长度

首先从头结点开始对链表进行一次遍历,得到链表的长度L。随后我们再从头结点开始对链表进行一次遍历,当遍历到第L-n+1个结点,他就是我们需要删除的结点。代码如下:

class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        // 创建一个虚拟头结点,方便处理边界情况
        ListNode dummy = new ListNode(0);
        dummy.next = head;
        
        // 获取链表长度
        int length = getLength(head);
        
        // 定义一个指针 cur,初始指向虚拟头结点
        ListNode cur = dummy;
        
        // 移动 cur 指针到倒数第 n+1 个节点
        for (int i = 1; i < length - n + 1; ++i) {
            cur = cur.next;
        }
        
        // 删除倒数第 n 个节点
        cur.next = cur.next.next;
        
        // 返回修改后的链表头结点
        ListNode ans = dummy.next;
        return ans;
    }
    
    // 计算链表的长度
    public int getLength(ListNode head) {
        int length = 0;
        while (head != null) {
            ++length;
            head = head.next;
        }
        return length;
    }
}

方法二:双指针

定义first和second两个指针,firt先走N步,然后second再开始走,当first走到队尾的时候,second就是我们要的结点。代码如下:

public ListNode removeNthFromEnd(ListNode head, int n) {
    // 创建一个虚拟头结点,方便处理边界情况
    ListNode dummy = new ListNode(0);
    dummy.next = head;
    
    // 定义两个指针,分别为 first 和 second
    ListNode first = head; // first 指针先向前移动 n 步
    ListNode second = dummy; // second 指针初始化为虚拟头结点
    
    // 移动 first 指针到第 n 个节点
    for(int i = 0; i < n; i++){
        first = first.next;
    }
    
    // 同时移动 first 和 second 指针,直到 first 到达链表末尾
    while(first != null){
        first = first.next;
        second = second.next;
    }
    
    // 删除倒数第 n 个节点
    second.next = second.next.next;
    
    // 返回修改后的链表头结点
    ListNode ans = dummy.next;
    return ans;
}

5.3.1删除重复元素,重复元素只保留一个LeetCoede83

public ListNode deleteDuplicates(ListNode head) {
    // 如果链表为空,直接返回
    if (head == null) {
        return head;
    }
    
    ListNode cur = head; // 使用cur指针遍历链表,从头节点开始

    while (cur.next != null) { // 循环直到当前节点的下一个节点为空(即到达链表末尾)
        if (cur.val == cur.next.val) { // 如果当前节点的值等于下一个节点的值,表示有重复元素
            cur.next = cur.next.next; // 跳过当前节点的下一个节点,指向下下个节点,即删除了重复的节点
        } else {
            cur = cur.next; // 否则,继续遍历下一个节点
        }
    }
    
    return head; // 返回处理后的链表
}

5.3.2删除重复元素,重复元素都不要LeetCoede82

public ListNode deleteDuplicates(ListNode head) {
    // 如果链表为空,直接返回
    if (head == null) {
        return head;
    }
    
    ListNode dummy = new ListNode(0, head); // 创建一个虚拟头节点(dummy),值为0,并将其指向原链表的头节点
    ListNode cur = dummy; // 使用cur指针来遍历链表

    while (cur.next != null && cur.next.next != null) {
        // 如果当前节点的下一个节点和下下个节点的值相同
        if (cur.next.val == cur.next.next.val) {
            int x = cur.next.val; // 记录重复的值
            // 循环跳过所有值为x的节点
            while (cur.next != null && cur.next.val == x) {
                cur.next = cur.next.next; // 跳过当前节点,指向下一个节点
            }
        } else {
            cur = cur.next; // 否则,继续遍历下一个节点
        }
    }
    
    ListNode res = dummy.next; // 获取处理后的链表(虚拟头节点的下一个节点即为新的头节点)
    return res;
}

  • 9
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值