【小象算法Java版】第一节:链表(上)

leetcode206. 反转链表

传送门

题目描述

反转一个单链表。

示例:

输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL

解题思路

思路一:使用双指针

  • 定义两个指针: pre 和 cur ;pre 在后 cur 在前。(pre .next = cur)
  • 每次让 cur的 next 指向 pre ,使用临时tmp 保存cur.next不然会找不到后边,这样就实现一次局部反转
  • 局部反转完成之后,pre 和 cur 同时往前移动一个位置
  • 循环上述过程,直至 cur 到达链表尾部

在这里插入图片描述

实现代码

public ListNode reverseList(ListNode head) {
        if (head == null) {
            return head;
        }
        ListNode cur = head;
        ListNode pre = null;
        while (cur != null) {
            ListNode tmp = cur.next;
            cur.next = pre;
            pre = cur;
            cur = tmp;
        }
        return pre;
    }

思路二:递归实现
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

public ListNode reverseList(ListNode head) {
        if (head == null || head.next == null) {
            return head;
        }
        ListNode p = reverseList(head.next);
        head.next.next = head;
        head.next = null;
        return p;
    }

思路三:

public ListNode reverseList(ListNode head) {
        ListNode newHead = null;// 指向新链表头节点的指针
        while (head != null) {
            ListNode temp = head.next;//备份head的next 不然后边会断掉
            head.next = newHead;// 更新head.next
            newHead = head;// 移动 newHead
            head = temp;// 遍历链表
        }
        return newHead;// 返回新链表头节点
    }

leetcode92.反转链表

题目描述

反转从位置 m 到 n 的链表。请使用一趟扫描完成反转。

说明:
1 ≤ m ≤ n ≤ 链表长度。

示例:

输入: 1->2->3->4->5->NULL, m = 2, n = 4
输出: 1->4->3->2->5->NULL

解题思路

在这里插入图片描述
在这里插入图片描述
步骤一:找到逆置的头节点和尾结点,并记录前驱和后继。
步骤二:逆置n - m
步骤三:连接起来
具体见代码解释

代码实现

public ListNode reverseBetween(ListNode head, int m, int n) {
//        输入: 1->2->3->4->5->NULL, m = 2, n = 4
//        输出: 1->4->3->2->5->NULL
        //如果头节点为空直接返回
        if (head == null) {
            return null;
        }
        // 当 m == n 则不需要翻转直接返回头节点
        if (m - n == 0) {
            return head;
        }
        // 计算需要反转的长度   2-4 总共3个元素长度
        int len = n - m + 1;
        
        ListNode second = head;//该节点记录n位置节点(4)
        ListNode first = head;//该节点记录m位置节点(2)
        ListNode first_pre = null;//该节点用于保存m位置节点的前驱

        while (m-- > 1) {
            first_pre = first;
            first = first.next;
        }// 循环走到m位置节点,并且找到前驱节点    first->2
        while (n-- > 1) {
            second = second.next;
        }// 循环走到n位置节点  second->4
        ListNode second_next = second.next;// 保存 n 位置的后继节点 5
// 3        3   2  1
        ListNode tt = null;//用来保存first的前驱
        
        // 循环翻转
        while (len-- > 0) {
            tt = first;
            ListNode temp = first.next;//保存first的后继  也就是3 节点
            first.next = second_next;//2->5
            second_next = first;//second_next 往前走,指向2
            first  = temp;// first指向 3
        }
        // 循环结束4->3->2->5->NULL
        //注意first结束后指向的temp 是 节点5位置,也就是需要tt 节点保存没有更新之前,tt是指向4的
        

        // 如果m == 1 则first_pre == null 则直接返回tt即可
        
        if (first_pre == null) {
            return tt;
        }
        // 不为空则将 之前保存的前驱节点first_pre指向tt,返回头节点
        first_pre.next = tt;
        return head;
    }

示意图就先不画了。如有需要后边补充。

还有个思路就是从4开始连,这个里边就需要递归比较麻烦。

leetcode876. 链表的中间结点

传送门

题目描述

给定一个头结点为 head 的非空单链表,返回链表的中间结点。

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

示例 1:

输入:[1,2,3,4,5]
输出:此列表中的结点 3 (序列化形式:[3,4,5])
返回的结点值为 3(测评系统对该结点序列化表述是 [3,4,5])。
注意,我们返回了一个 ListNode 类型的对象 ans,这样:
ans.val = 3, ans.next.val = 4, ans.next.next.val = 5, 以及 ans.next.next.next = NULL.

示例 2:

输入:[1,2,3,4,5,6]
输出:此列表中的结点 4 (序列化形式:[4,5,6])
由于该列表有两个中间结点,值分别为 34,我们返回第二个结点。

解题思路

思路一:单指针两次遍历
第一遍统计长度,第二遍再去找N/2.
这种方法我就不在说了。
思路二:快慢指针
定义两个指针,一个快指针fast,一个慢指针slow。同时走,fast一次走两步,slow一次走一步,那么当fast为空或者fast.next为空时,slow就是寻找的中间节点。

代码实现

 public ListNode middleNode(ListNode head) {
        ListNode fast = head;
        ListNode slow = head;
        while (fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;
        }
        return slow;

    }

leetcode160 相交链表

题目描述

编写一个程序,找到两个单链表相交的起始节点。(传送门

如下面的两个链表:

在这里插入图片描述

在节点 c1 开始相交。
示例 1:

在这里插入图片描述

输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3
输出:Reference of the node with value = 8
输入解释:相交节点的值为 8 (注意,如果两个链表相交则不能为 0)。从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,0,1,8,4,5]。在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。

示例 2:

在这里插入图片描述

输入:intersectVal = 2, listA = [0,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1
输出:Reference of the node with value = 2
输入解释:相交节点的值为 2 (注意,如果两个链表相交则不能为 0)。从各自的表头开始算起,链表 A 为 [0,9,1,2,4],链表 B 为 [3,2,4]。在 A 中,相交节点前有 3 个节点;在 B 中,相交节点前有 1 个节点。

示例 3:

在这里插入图片描述

输入:intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB = 2
输出:null
输入解释:从各自的表头开始算起,链表 A 为 [2,6,4],链表 B 为 [1,5]。由于这两个链表不相交,所以 intersectVal 必须为 0,而 skipA 和 skipB 可以是任意值。
解释:这两个链表不相交,因此返回 null。

解题思路

方法一:Set集合

  • 把第一个链表的节点全部存放到集合set中
  • 遍历第二个链表的每一个节点,判断在集合set中是否存在,如果存在就直接返回这个存在的结点。如果遍历完了,在集合set中还没找到,说明他们没有相交,直接返回null即可

方法二:

  • 步骤一:计算headA和headB链表的长度,和较长链表多出的长度

在这里插入图片描述

  • 步骤二:较长链表的指针先走,差值,这样两者就对齐了

在这里插入图片描述

  • 步骤三:一起走,当两节点相同时,就找到了,否则返回null
    在这里插入图片描述

方法三:双指针

  • 指针 pA 指向 A 链表,指针 pB 指向 B 链表,依次往后遍历
  • 如果 pA 到了末尾,则 pA = headB 继续遍历
  • 如果 pB 到了末尾,则 pB = headA 继续遍历
  • 相遇时就找到了(因为这样下来两指针走的长度是一样的)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

代码实现

方法一:Set

public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
    //创建集合set
    Set<ListNode> set = new HashSet<>();
    //先把链表A的结点全部存放到集合set中
    while (headA != null) {
        set.add(headA);
        headA = headA.next;
    }

    //然后访问链表B的结点,判断集合中是否包含链表B的结点,如果包含就直接返回
    while (headB != null) {
        if (set.contains(headB))
            return headB;
        headB = headB.next;
    }
    //如果集合set不包含链表B的任何一个结点,说明他们没有交点,直接返回null
    return null;
}


方法二:

public ListNode getIntersectionNode1(ListNode headA, ListNode headB) {
        if (headA == null || headB == null) {
            return null;
        }
        // 步骤一
        ListNode temp = headA;
        int lenA = 0;
        while (temp != null) {
            temp = temp.next;
            lenA++;
        }
        temp = headB;
        int lenB = 0;
        while (temp != null) {
            temp = temp.next;
            lenB++;
        }
        
        // 步骤二:
        if (lenA > lenB) {
            while (lenA - lenB > 0) {
                headA = headA.next;
            }

        }else {
            while (lenB - lenA > 0) {
                headB = headB.next;
            }
        }

        //步骤三
        while (headA != null) {
            if (headA == headB) {
                return headA;
            }
            headA = headA.next;
            headB = headB.next;
        }
        return null;
    }

方法三:

public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        if (headA == null || headB == null) {
            return null;
        }
        ListNode pa = headA;
        ListNode pb = headB;
        while (pa != pb) {
            if (pa == null) {
                pa = headB;
            }else {
                pa = pa.next;
            }
            if (pb == null) {
                pb = headA;
            }else {
                pb = pb.next;
            }
        }
        return pa;
    }

leetcode141.环形链表

题目描述

给定一个链表,判断链表中是否有环。(传送门

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

如果链表中存在环,则返回 true 。 否则,返回 false 。
示例 1:

在这里插入图片描述

输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。

示例 2:

在这里插入图片描述

输入:head = [1,2], pos = 0
输出:true
解释:链表中有一个环,其尾部连接到第一个节点。

示例 3:

在这里插入图片描述

输入:head = [1], pos = -1
输出:false
解释:链表中没有环。

解题思路

方法一:Set
方法二:快慢指针

实现代码

方法一:

public boolean hasCycle(ListNode head) {
        Set<ListNode> set = new HashSet<>();
        while (head != null) {
            if (set.contains(head)) {
                return true;
            }
            set.add(head);
            head = head.next;
        }
        return false;
    }

方法二:

public boolean hasCycle(ListNode head) {
        if (head == null) {
            return false;
        }
        ListNode slow = head;
        if (head.next == null) {
            return false;
        }
        ListNode fast = head.next;
        while (fast != null && fast.next !=null) {
            if (slow == fast) {
                return true;
            }
            slow = slow.next;
            fast = fast.next.next;
        }
        return false;
    }

leetcode142.环形链表II

题目描述

给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。(传送门

为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。注意,pos 仅仅是用于标识环的情况,并不会作为参数传递到函数中。

说明:不允许修改给定的链表。

示例 1:

在这里插入图片描述

输入:head = [3,2,0,-4], pos = 1
输出:返回索引为 1 的链表节点
解释:链表中有一个环,其尾部连接到第二个节点。

解题思路

方法一:set集合
方法二:快慢指针

在这里插入图片描述
同上一个题一样,相遇的时候就能判断他是有环的。
在这里插入图片描述

代码实现

方法一:Set

public boolean hasCycle(ListNode head) {
        Set<ListNode> set = new HashSet<>();
        while (head != null) {
            if (set.contains(head)) {
                return true;
            }
            set.add(head);
            head = head.next;
        }
        return false;
    }

方法二:快慢指针

public ListNode detectCycle(ListNode head) {
        ListNode fast = head;
        ListNode slow = head;
        ListNode meet = null;
        while (fast != null && fast.next != null) {
            slow = slow.next;
            fast = fast.next.next;
            if (fast == slow) {
                meet = fast;
                break;
            }
        }

        while (meet != null) {
            if (head == meet) {
                return head;
            }
            meet = meet.next;
            head = head.next;
        }
        return null;
    }

leetcode203. 移除链表元素

传送门

题目描述

删除链表中等于给定值 val 的所有节点。

示例:
输入: 1->2->6->3->4->5->6, val = 6
输出: 1->2->3->4->5

解题思路

方法:哨兵节点(在这里我先不使用傀儡节点,使用傀儡节点问题也就简单了)
首先这个要删除的节点位置有几种情况:
prev代表前驱节点:cur = head;
cur代表当前节点:cur = head.next;

情况一:要删除的节点在链表中间,不在开头
在这里插入图片描述
在这里我们只需要循环遍历在cur 不为空的时候。如果遇到要删除的节点,让prev指向cur的下一个节点即可,然后更新cur这样就完成删除。

pre.next = cur.next;
cur = cur.next;

情况二:当要删除的一个或多个节点位于链表的头部时
在这里插入图片描述
我们这时需要用循环处理一下头节点,一直更新head使得头节点不是你要删除的节点,这样问题就转换为情况一了。

代码实现

第一种:不使用傀儡节点

 public ListNode removeElements(ListNode head, int val) {
        if (head == null) {
            return head;
        }
        while (head.val == val) {
            head = head.next;
            if (head == null) {
                return head;
            }
        }
        ListNode cur = head.next;
        ListNode pre = head;
        while (cur != null) {
            if (cur.val == val) {
                pre.next = cur.next;
                cur = cur.next;
            }else {
                cur = cur.next;
                pre = pre.next;
            }
        }
        return head;
    }

第二种:使用傀儡节点

public ListNode removeElements(ListNode head, int val) {
        ListNode kind = new ListNode(0);
        kind.next = head;
        ListNode cur = head;
        ListNode prev = kind;
        while (cur != null) {
            if (cur.val == val) {
                prev.next = cur.next;
                cur = cur.next;
            }else {
                prev = prev.next;
                cur = cur.next;
            }
        }
        return kind.next;
    }
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值