LeetCode【链表】

链表的基础知识

单向链表

 清空元素 – clear()

	public void clear() {
		size = 0;
		first = null;
	}

添加元素 - add(int index, E element)

public void add(int index,E element){
    if(index==0){
        first=new Node<>(element,first);
    }else {
        Node <E> prev=node(index-1);
        prve.next=new Node<>(element,prev.next);
    }
    size++;
}

删除元素 - remove(int index)

	public E remove(int index) {
		/*
		 * 最好:O(1)
		 * 最坏:O(n)
		 * 平均:O(n)
		 */
		rangeCheck(index);
		
		Node<E> node = first;
		if (index == 0) {//当待删除节点是第一个节点的时候
			first = first.next;
		} else {
			//1.先找到待删除节点的前一个节点
			Node<E> prev = node(index - 1);
			//2.将要删除1的节点的值取出来
			node = prev.next;
			//3.将前一个节点指向待删除节点的下一个节点
			prev.next = node.next;//等价于pre.next=prev.next.next
		}
		size--;
		//4.返回待删除节点的值
		return node.element;
	}

删除元素 – 注意0位置

返回查找元素的下标

	public int indexOf(E element) {
		Node<E> node = first;
		if (element == null) {//如果链表为空
			for (int i = 0; i < size; i++) {
				if (node.element == null)
					return i;
				
				node = node.next;
			}
		} else {//element!=null
			for (int i = 0; i < size; i++) {
				if (element.equals(node.element))//将传入的元素和遍历的元素进行比较
					return i;
				
				node = node.next;
			}
		}
		return ELEMENT_NOT_FOUND;//return -1;
	}

重写toString方法

	public String toString() {
		StringBuilder string = new StringBuilder();
		string.append("size=").append(size).append(", [");
		//方法一
		Node<E> node = first;
		for (int i = 0; i < size; i++) {
			if (i != 0) {
				string.append(", ");
			}
			
			string.append(node.element);
			
			node = node.next;
		}
		string.append("]");
		/*	方法二:	
		Node<E> node1 = first;
		while (node1 != null) {
			node1 = node1.next;
		}
		 */

		return string.toString();
	}

双向链表(LinkedList)

节点定义

	private static class Node<E> {
		E element;
		Node<E> prev;
		Node<E> next;
		public Node(Node<E> prev, E element, Node<E> next) {
			this.prev = prev;
			this.element = element;
			this.next = next;
		}

		@Override
		public String toString() {
			StringBuilder sb = new StringBuilder();

			if (prev != null) {
				sb.append(prev.element);
			} else {
				sb.append("null");
			}

			sb.append("_").append(element).append("_");

			if (next != null) {
				sb.append(next.element);
			} else {
				sb.append("null");
			}

			return sb.toString();
		}
	}

 双向链表 – 只有一个元素

获取index位置对应的节点对象
	private Node<E> node(int index) {
		rangeCheck(index);

		if (index < (size >> 1)) {//如果要查找元素的下标小于链表长度的一半,则从前往后找
			//将从第一个元素开始查找
			Node<E> node = first;
			for (int i = 0; i < index; i++) {
				//不断往下遍历
				node = node.next;
			}
			return node;
		} else {//如果要查找元素的下标大于链表长度的一半,则从后往前找
			//将从最后一个元素开始查找
			Node<E> node = last;
			/*记住最后一个元素下标为size-1*/
			for (int i = size - 1; i > index; i--) {
				//不断的往前遍历
				node = node.prev;
			}
			return node;
		}
	}
双向链表 – add(int index, E element)

	public void add(int index, E element) {
		rangeCheckForAdd(index);

		// size == 0
		// index == 0
		if (index == size) { // 往最后面添加元素
			Node<E> oldLast = last;
			last = new Node<>(oldLast, element, null);
			if (oldLast == null) { // 这是链表添加的第一个元素
				first = last;
			} else {
				oldLast.next = last;
			}
		} else {
			Node<E> next = node(index); 
			Node<E> prev = next.prev; 
			Node<E> node = new Node<>(prev, element, next);
			next.prev = node;
			
			if (prev == null) { // index == 0
				first = node;
			} else {
				prev.next = node;
			}
		}
		
		size++;
	}

双向链表 – remove(int index)

	public E remove(int index) {
		rangeCheck(index);
        
        //先找到要删除的元素
		Node<E> node = node(index);
		Node<E> prev = node.prev;
		Node<E> next = node.next;
		
        //当删除第一个节点的时候
		if (prev == null) { // index == 0
			first = next;
		} else {
			prev.next = next;
		}
        
        
		//删除最后一个节点
		if (next == null) { // index == size - 1
			last = prev;
		} else {
			next.prev = prev;
		}
		
		size--;
		return node.element;
	}

单向循环链表

 单向循环链表 – 只有1个节点

 节点定义

	private static class Node<E> {
		E element;
		Node<E> next;
		public Node(E element, Node<E> next) {
			this.element = element;
			this.next = next;
		}

		@Override
		public String toString() {
			StringBuilder sb = new StringBuilder();
			sb.append(element).append("_").append(next.element);
			return sb.toString();
		}
	}

单向循环链表 – add(int index, E element)

	public void add(int index, E element) {
		rangeCheckForAdd(index);

		if (index == 0) {//如果在第一个元素的位置插入
			Node<E> newFirst = new Node<>(element, first);
			// 拿到最后一个节点
			//如果链表中就只有一个节点的时候,自己的next指向自己
			Node<E> last = (size == 0) ? newFirst : node(size - 1);
			last.next = newFirst;
			first = newFirst;
		} else {
			Node<E> prev = node(index - 1);
			prev.next = new Node<>(element, prev.next);
		}
		size++;
	}

 单向循环链表 – remove(int index)

	public E remove(int index) {
		rangeCheck(index);

		Node<E> node = first;
		if (index == 0) {
			if (size == 1) {
				first = null;
			} else {
				Node<E> last = node(size - 1);
				first = first.next;
				last.next = first;
			}
		} else {
			Node<E> prev = node(index - 1);
			node = prev.next;
			prev.next = node.next;
		}
		size--;
		return node.element;
	}

双向循环链表

 双向循环链表 – 只有1个节点

 双向循环链表 – add(int index, E element)

	public void add(int index, E element) {
		rangeCheckForAdd(index);

		// size == 0
		// index == 0
		if (index == size) { // 往最后面添加元素
			Node<E> oldLast = last;
			//这里已经将要插入的节点的prev和next指向确定了
			last = new Node<>(oldLast, element, first);
			if (oldLast == null) { // 原来链表为空,这是链表添加的第一个元素
				first = last;
				//自己的next指向自己
				first.next = first;
				//自己的prev指向自己
				first.prev = first;
			} else {
				//原来链表的最后一个元素指向要插入的新元素
				oldLast.next = last;
				//原来链表的第一个节点的prev指向新插入的节点
				first.prev = last;
			}
		} else {//往链表中的任意位置插入
			//先找到要插入的节点位置上的节点
			Node<E> next = node(index);
			//记录下当前的next
			Node<E> prev = next.prev;
			Node<E> node = new Node<>(prev, element, next);
			next.prev = node;
			prev.next = node;

			if (next == first) { // index == 0
				first = node;
			}
		}

		size++;
	}
双向循环链表-remove(int index)

 

传入要删除节点的下标
	/**
	 * 传入要删除节点的下标
	 * @param index
	 * @return
	 */
	@Override
	public E remove(int index) {
		rangeCheck(index);
		return remove(node(index));
	}
传入要删除的node值
	/**
	 * 传入要删除的node值
	 * @param node
	 * @return
	 */
	private E remove(Node<E> node) {
		if (size == 1) {//当链表中只有一个元素的时候
			first = null;
			last = null;
		} else {
			Node<E> prev = node.prev;
			Node<E> next = node.next;
			prev.next = next;
			next.prev = prev;

			if (node == first) { // index == 0
				first = next;
			}

			if (node == last) { // index == size - 1
				last = prev;
			}
		}

		size--;
		return node.element;
	}

203.移除链表元素

203. 移除链表元素 - 力扣(LeetCode)

如果使用C,C++编程语言的话,不要忘了还要从内存中删除这两个移除的节点

如果使用java ,python的话就不用手动管理内存了

这里就涉及如下链表操作的两种方式:

  • 直接使用原来的链表来进行删除操作。
  • 设置一个虚拟头结点在进行删除操作。

直接使用原来的链表进行删除

移除头结点和移除其他节点的操作是不一样的,因为链表的其他节点都是通过前一个节点来移除当前节点,而头结点没有前一个节点

所以头结点如何移除呢,其实只要将头结点向后移动一位就可以,这样就从链表中移除了一个头结点

 

 设置一个虚拟头结点在进行删除操作。

可以设置一个虚拟头结点,这样原链表的所有节点就都可以按照统一的方式进行移除了

这里来给链表添加一个虚拟头结点为新的头结点,此时要移除这个旧头结点元素1。

这样是不是就可以使用和移除链表其他节点的方式统一了呢?

来看一下,如何移除元素1 呢,还是熟悉的方式,然后从内存中删除元素1。

最后呢在题目中,return 头结点的时候,别忘了 return dummyNode->next;, 这才是新的头结点

package leetcode.链表;

import org.w3c.dom.Node;


/**
 * @author lin
 * @creat 2022--12--20:09
 * https://leetcode.cn/problems/remove-linked-list-elements/
 *
 */
public class $_203移除元素 {
    public class ListNode {
        int val;
       ListNode next;
       ListNode() {

       }
        ListNode(int val) {
            this.val = val;
        }
        ListNode(int val, ListNode next) {
            this.val = val; this.next = next;
        }
}
    /**
     * 不添加虚拟节点方式
     * 时间复杂度 O(n)
     * 空间复杂度 O(1)
     * @param head
     * @param val
     * @return
     */
//方法一:不设虚拟结点
    public ListNode removeElements(ListNode head, int val) {
            while (head != null && head.val == val)
                head = head.next;
            ListNode prev = head;
            if (prev != null)
            {
                while (prev.next != null)
                {
                    if (prev.next.val == val)
                        prev.next = prev.next.next;
                    else
                        prev = prev.next;
                }
            }
            return head;
        }
//方法二:使用递归
    public ListNode removeElements2(ListNode head, int val) {
        if(head==null){//只有一个元素
            return head;
        }
        //当不至有一个元素的时候,就不断的指向下一个元素
        head.next=removeElements2(head.next,val);
        return head.val==val?head.next:head;
    }

    /**
     * 添加虚节点方式
     * 时间复杂度 O(n)
     * 空间复杂度 O(1)
     * @param head
     * @param val
     * @return
     */
    //3.使用虚拟头节点
    public ListNode removeElements3(ListNode head,int val){
        if(head==null){
            return head;
        }
        //因为删除可能使用到头节点,所以使用虚拟头节点,设置虚拟头节点为dummy head
        ListNode dummyHead=new ListNode(-1,head);
        ListNode pre=dummyHead;
        ListNode cur=head;
        while (cur!=null){
            if(cur.val==val){
                pre.next=cur.next;
            }else {
                pre=cur;
            }
            cur=cur.next;
        }
        //返回头节点
        return dummyHead;
    }
}

 707.设计链表

707. 设计链表 - 力扣(LeetCode)

删除链表节点: 

 

添加链表节点:

  设置一个虚拟头结点在进行删除操作。

获取第n个节点的值

头部插入节点

尾部插入节点

第n个节点前插入节点

删除第n个节点

package leetcode.链表;

/**
 * @author lin
 * @creat 2022--12--19:39
 */
//单链表
class ListNode {
    int val;
    ListNode next;
    ListNode(){}
    ListNode(int val) {
        this.val=val;
    }
}
class $_707_设计链表 {
    //size存储链表元素的个数
    int size;
    //虚拟头结点
    ListNode head;

    //初始化链表
    public $_707_设计链表() {
        size = 0;
        head = new ListNode(0);
    }

    //获取第index个节点的数值,注意index是从0开始的,第0个节点就是头结点
    public int get(int index) {
        //如果index非法,返回-1
        if (index < 0 || index >= size) {
            return -1;
        }
        ListNode currentNode = head;
        //包含一个虚拟头节点,所以查找第 index+1 个节点
        for (int i = 0; i <= index; i++) {
            currentNode = currentNode.next;
        }
        return currentNode.val;
    }

    //在链表最前面插入一个节点,等价于在第0个元素前添加
    public void addAtHead(int val) {
        addAtIndex(0, val);
    }

    //在链表的最后插入一个节点,等价于在(末尾+1)个元素前添加
    public void addAtTail(int val) {
        addAtIndex(size, val);
    }

    // 在第 index 个节点之前插入一个新节点,例如index为0,那么新插入的节点为链表的新头节点。
    // 如果 index 等于链表的长度,则说明是新插入的节点为链表的尾结点
    // 如果 index 大于链表的长度,则返回空
    public void addAtIndex(int index, int val) {
        if (index > size) {
            return;
        }
        if (index < 0) {
            index = 0;
        }
        size++;
        //找到要插入节点的前驱
        ListNode pred = head;
        for (int i = 0; i < index; i++) {
            pred = pred.next;
        }
        ListNode toAdd = new ListNode(val);
        toAdd.next = pred.next;
        pred.next = toAdd;
    }

    //删除第index个节点
    public void deleteAtIndex(int index) {
        if (index < 0 || index >= size) {
            return;
        }
        size--;
        if (index == 0) {
            head = head.next;
            return;
        }
        ListNode pred = head;
        for (int i = 0; i < index ; i++) {
            pred = pred.next;
        }
        pred.next = pred.next.next;
    }
}
//双链表
class ListNode{
    int val;
    ListNode next,prev;
    ListNode() {};
    ListNode(int val){
        this.val = val;
    }
}


class MyLinkedList {

    //记录链表中元素的数量
    int size;
    //记录链表的虚拟头结点和尾结点
    ListNode head,tail;

    public MyLinkedList() {
        //初始化操作
        this.size = 0;
        this.head = new ListNode(0);
        this.tail = new ListNode(0);
        //这一步非常关键,否则在加入头结点的操作中会出现null.next的错误!!!
        head.next=tail;
        tail.prev=head;
    }

    public int get(int index) {
        //判断index是否有效
        if(index<0 || index>=size){
            return -1;
        }
        ListNode cur = this.head;
        //判断是哪一边遍历时间更短
        if(index >= size / 2){
            //tail开始
            cur = tail;
            for(int i=0; i< size-index; i++){
                cur = cur.prev;
            }
        }else{
            for(int i=0; i<= index; i++){
                cur = cur.next;
            }
        }
        return cur.val;
    }

    public void addAtHead(int val) {
        //等价于在第0个元素前添加
        addAtIndex(0,val);
    }

    public void addAtTail(int val) {
        //等价于在最后一个元素(null)前添加
        addAtIndex(size,val);
    }

    public void addAtIndex(int index, int val) {
        //index大于链表长度
        if(index>size){
            return;
        }
        //index小于0
        if(index<0){
            index = 0;
        }
        size++;
        //找到前驱
        ListNode pre = this.head;
        for(int i=0; i<index; i++){
            pre = pre.next;
        }
        //新建结点
        ListNode newNode = new ListNode(val);
        newNode.next = pre.next;
        pre.next.prev = newNode;
        newNode.prev = pre;
        pre.next = newNode;

    }

    public void deleteAtIndex(int index) {
        //判断索引是否有效
        if(index<0 || index>=size){
            return;
        }
        //删除操作
        size--;
        ListNode pre = this.head;
        for(int i=0; i<index; i++){
            pre = pre.next;
        }
        pre.next.next.prev = pre;
        pre.next = pre.next.next;
    }
}

206.反转链表

206. 反转链表 - 力扣(LeetCode)

 使用双指针

    public ListNode reverseList2(ListNode head){
        ListNode pre=null;
        ListNode cur=head;
        ListNode temp=cur.next;
        while (cur!=null){
            temp=cur.next;//保存下一个节点,要不然找不到
            //将链表方向进行改变
            cur.next=pre;
            //将cur赋值给pre
            pre=cur;
            //将temp赋值给cur
            cur=temp;
        }
        //返回新链表的头节点,此时cur指向null
        return pre;
    }

使用递归

    public ListNode reverse(ListNode pre,ListNode cur){
        if (cur==null){//终止遍历的条件就是cur==null
            return pre;//返回新链表的头节点
        }
        ListNode temp=null;
        temp=cur.next;//记录下一个节点的位置
        //改变链表的方向
        cur.next=pre;
        //更新pre和cur的位置
        //pre=cur;
        //cur=temp;
        return reverse(cur,temp);
    }

    public ListNode reverseList(ListNode head) {
        //这里为什么传入的是null,head
        //因为初始化pre是指向头节点的前面,cur指向头节点
        return reverse(null,head);
    }

24.两两交换链表中的节点

24. 两两交换链表中的节点 - 力扣(LeetCode)

建议使用虚拟头结点,这样会方便很多,要不然每次针对头结点(没有前一个指针指向头结点),还要单独处理。 

 使用双指针

    public ListNode swapPairs(ListNode head) {
           //创建一个虚拟头节点
        ListNode dummyNode = new ListNode(0);
        //将头节点的next指向头节点
        dummyNode.next = head;
        //将定义一个临时变量来遍历
        ListNode prev = dummyNode;

        while (prev.next != null && prev.next.next != null) {//如果后面没有2个元素,则反转结束
            ListNode temp = head.next.next; // 缓存 next
            prev.next = head.next;          // 将 prev 的 next 改为 head 的 next
            head.next.next = head;          // 将 head.next(prev.next) 的next,指向 head
            head.next = temp;               // 将head 的 next 接上缓存的temp
            prev = head;                    // 步进1位
            head = head.next;               // 步进1位
        }
        return dummyNode.next;
    }

使用递归

    public ListNode swapPairs1(ListNode head) {
        if(head == null || head.next == null) return head;
        // 获取当前节点的下一个节点
        ListNode next = head.next;
        // 进行递归
        ListNode newNode = swapPairs(next.next);
        // 这里进行交换
        next.next = head;
        head.next = newNode;

        return next;
    }

19.删除链表的倒数第N个节点

19. 删除链表的倒数第 N 个结点 - 力扣(LeetCode)

双指针的经典应用,如果要删除倒数第n个节点,让fast移动n步,然后让fast和slow同时移动,直到fast指向链表末尾。删掉slow所指向的节点就可以了。

  1. 首先这里我推荐大家使用虚拟头结点,这样方便处理删除实际头结点的逻辑
  2. 定义fast指针和slow指针,初始值为虚拟头结点

定义fast指针和slow指针,初始值为虚拟头结点

 

fast首先走n + 1步 ,为什么是n+1呢,因为只有这样同时移动的时候slow才能指向删除节点的上一个节点(方便做删除操作)

 

fast和slow同时移动,直到fast指向末尾

  

删除slow指向的下一个节点

 


 让fast先走n+1步

    /**
     * 方法一:快指针向走n+1步
     * @param head
     * @param n
     * @return
     */
    public ListNode removeNthFromEnd(ListNode head, int n) {
        //创建虚拟节点
        ListNode dummyHead=new ListNode(0);
        //将虚拟链表和头节点连接起来
        dummyHead.next=head;

        //将快指针指向虚拟头节点
        ListNode fast=dummyHead;
        //将慢指针指向虚拟头节点
        ListNode slow=dummyHead;

        //因为快指针要比慢指针向走n+1个节点,所以先+1
        n++;

        //将快指针先走n+1步
        for (int i=0;i<n;i++){
            fast=fast.next;
        }
        //前面快指针先走完后,且当快指针不为空的时候,那快指针就要和慢指针一起走
        while (fast!=null){
            fast=fast.next;
            slow=slow.next;
        }

        //此时slow指向待删除节点的前一个节点
        slow.next=slow.next.next;
        return dummyHead.next;

    }

  让fast先走n步

    /**
     * 方法二:快指针向走n步
     * @param head
     * @param n
     * @return
     */
    public ListNode removeNthFromEnd1(ListNode head, int n) {
        //创建虚拟节点
        ListNode dummyHead=new ListNode(0);
        //将虚拟链表和头节点连接起来
        dummyHead.next=head;

        //将快指针指向虚拟头节点
        ListNode fast=dummyHead;
        //将慢指针指向虚拟头节点
        ListNode slow=dummyHead;

        //将快指针先走n步
        for (int i=0;i<n;i++){
            fast=fast.next;
        }

        //前面快指针先走完后,且当快指针不为空的时候,那快指针就要和慢指针一起走
        //因为快指针就快走n步,所以要判断的是fast.next是否为空,而不是fast==null
        while (fast.next!=null){
            fast=fast.next;
            slow=slow.next;
        }

        //此时slow指向待删除节点的前一个节点
        slow.next=slow.next.next;
        return dummyHead.next;

    }

160.相交链表

160. 相交链表 - 力扣(LeetCode)

简单来说,就是求两个链表交点节点的指针交点不是数值相等,而是指针相等。

为了方便举例,假设节点元素数值相等,则节点指针相等。

  •  目前curA指向链表A的头结点,curB指向链表B的头结点:

  • 我们求出两个链表的长度,并求出两个链表长度的差值,然后让curA移动到,和curB 末尾对齐的位置

 

 

  • 此时我们就可以比较curA和curB是否相同,如果不相同,同时向后移动curA和curB,如果遇到curA == curB,则找到交点。
  • 否则循环退出返回空指针。
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
         //分别创建2个指针来遍历链表
        ListNode curA=headA;
        ListNode curB=headB;
        //分别创建两个变量来记录2个链表的长度
        int lengthA=0;
        int lengthB=0;
        while (curA!=null){//求链表A的长度
            curA=curA.next;
            lengthA++;
        }
        while (curB!=null){//求链表B的长度
            curB=curB.next;
            lengthB++;
        }
        curA=headA;
        curB=headB;
        //让curA为最长链表的头,lengthA为其长度
        //如果本来链表A就为最长的链表,则不用进入
        if (lengthB>lengthA){
            //1.交换两个链表的长度
            int tempLength=lengthA;
            lengthA=lengthB;
            lengthB=tempLength;
            //2.交换两个链表
            ListNode tempNode=curA;
            curA=curB;
            curB=tempNode;
        }
        //求长度差
        int len=lengthA-lengthB;
        //让curA和curB在同一个起点,则将链表A不断的向下遍历
        while (len-->0){
            curA=curA.next;
        }
        //不断的遍历curA和curB,直到遇到相同就返回
        while (curA!=null){
            if (curA==curB){
                return curA;
            }
            //如果找不到curA==curB,则一直往下遍历
            curA=curA.next;
            curB=curB.next;
        }
        return null;
    }

142.环形2链表||

142. 环形链表 II - 力扣(LeetCode)

主要考察两知识点:

  • 判断链表是否环
  • 如果有环,如何找到这个环的入口

#判断链表是否有环

可以使用快慢指针法,分别定义 fast 和 slow 指针,从头结点出发,fast指针每次移动两个节点,slow指针每次移动一个节点,如果 fast 和 slow指针在途中相遇 ,说明这个链表有环。

为什么fast 走两个节点,slow走一个节点,有环的话,一定会在环内相遇呢,而不是永远的错开呢

首先第一点:fast指针一定先进入环中,如果fast指针和slow指针相遇的话,一定是在环中相遇,这是毋庸置疑的。

那么来看一下,为什么fast指针和slow指针一定会相遇呢?

可以画一个环,然后让 fast指针在任意一个节点开始追赶slow指针。

fast和slow各自再走一步, fast和slow就相遇了

这是因为fast是走两步,slow是走一步,其实相对于slow来说,fast是一个节点一个节点的靠近slow的,所以fast一定可以和slow重合。

如果有环,如何找到这个环的入口

此时已经可以判断链表是否有环了,那么接下来要找这个环的入口了。

假设从头结点到环形入口节点 的节点数为x。 环形入口节点到 fast指针与slow指针相遇节点 节点数为y。 从相遇节点 再到环形入口节点节点数为 z。

从头结点出发一个指针,从相遇节点 也出发一个指针,这两个指针每次只走一个节点, 那么当这两个指针相遇的时候就是 环形入口的节点

也就是在相遇节点处,定义一个指针index1,在头结点处定一个指针index2。

让index1和index2同时移动,每次移动一个节点, 那么他们相遇的地方就是 环形入口的节点。

那么 n如果大于1是什么情况呢,就是fast指针在环形转n圈之后才遇到 slow指针。

其实这种情况和n为1的时候 效果是一样的,一样可以通过这个方法找到 环形的入口节点,只不过,index1 指针在环里 多转了(n-1)圈,然后再遇到index2,相遇点依然是环形的入口节点。

    public ListNode detectCycle(ListNode head) {
        ListNode fast=head;
        ListNode slow=head;
        while (fast!=null && fast.next!=null){//因为fast一次走两步,所以要判断当前和下一个是否为空
            //如果不为空,则fast一次走2步,slow一次走1步
            fast=fast.next.next;
            slow=slow.next;
            //判断是否有环---他们一定在环中相遇
            if (slow==fast){
                ListNode index1=fast;//ListNode index1=slow;---用于记录fast和slow相遇的位置
                /*创建一个新的节点指向头节点,然后让index1和index2往前走,直到相遇点就是环的入口*/
                ListNode index2=head;
                while (index2!=index1){
                    //当index1和index2没有相遇时,两者都一直往前走
                    index1=index1.next;
                    index2=index2.next;
                }
                return index1;//return index2;
            }
        }
        return null;
    }

补充

在推理过程中,大家可能有一个疑问就是:为什么第一次在环中相遇,slow的 步数 是 x+y 而不是 x + 若干环的长度 + y 呢?

首先slow进环的时候,fast一定是先进环来了。

如果slow进环入口,fast也在环入口,那么把这个环展开成直线,就是如下图的样子:

可以看出如果slow 和 fast同时在环入口开始走,一定会在环入口3相遇,slow走了一圈,fast走了两圈。

重点来了,slow进环的时候,fast一定是在环的任意一个位置

那么fast指针走到环入口3的时候,已经走了k + n 个节点,slow相应的应该走了(k + n) / 2 个节点。

因为k是小于n的(图中可以看出),所以(k + n) / 2 一定小于n。

也就是说slow一定没有走到环入口3,而fast已经到环入口3了

这说明什么呢?

在slow开始走的那一环已经和fast相遇了

为什么fast不能跳过去呢? 在刚刚已经说过一次了,fast相对于slow是一次移动一个节点,所以不可能跳过去

2.两数相加

2. 两数相加 - 力扣(LeetCode)

迭代法

    /**
     * 使用非递归
     * @param l1
     * @param l2
     * @return
     */
    public ListNode addTwoNumbers1(ListNode l1, ListNode l2) {
        //记录l1.val+l2.val的值
        int total=0;
        //记录十位上的值
        int next1=0;
        //创建一个新的链表存放新值
        ListNode res=new ListNode();
        //创建一个临时遍历来遍历新链表
        ListNode cur=res;
        while (l1!=null && l2!=null){
            total=l1.val+l2.val+next1;
            //res记录下个位的值
            cur.next=new ListNode(total%10);
            //记录十位的值
            next1=total/10;
            l1=l1.next;
            l2=l2.next;
            //cur不断指向下一个节点
            cur=cur.next;
        }
        while (l1!=null){//此时l2==null
            total=l1.val+next1;
            cur.next=new ListNode(total%10);
            next1=total/10;
            l1=l1.next;
            cur=cur.next;
        }
        while (l2!=null){//此时l1==null
            total=l2.val+next1;
            cur.next=new ListNode(total%10);
            next1=total/10;
            l2=l2.next;
            cur=cur.next;
        }
        if (next1!=0){//此时l1.next和l2.next为0
            cur.next=new ListNode(next1);
        }
        return res.next;
    }

递归法 

    /**
     * 使用递归
     * @param l1
     * @param l2
     * @return
     */
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        //total记录和
        int total=l1.val+l2.val;
        //next1记录十位数的值
        int next1=total/10;
        //res记录下当前要加入新链表中的节点(也就是个位数)
        ListNode res=new ListNode(total%10);
        if (l1.next!=null || l2.next!=null ||next1!=0){
            if (l1.next!=null){
                l1=l1.next;
            }else {//l1.next==null
                l1=new ListNode(0);
            }
            if (l2.next!=null){
                l2=l2.next;
            }else {//l2.next==null
                l2=new ListNode(0);
            }
            l1.val=l1.val+next1;
            res.next=addTwoNumbers(l1,l2);
        }
        return res;
    }

61.旋转链表

61. 旋转链表 - 力扣(LeetCode)

 

    public ListNode rotateRight(ListNode head, int k) {
        if (head==null || head.next==null ){//判断当链表为空或者只有一个节点
            return head;
        }
        //创建一个临时节点
        ListNode cur=head;
        //保存链表长度--初始化为1
        int length=1;
        while (cur.next!=null){//使用cur不断遍历链表
            length++;
            cur=cur.next;
        }
        //此时的k是记录下每一个数应该移动的位数
        k=k%length;
        //使用两个指针分别指向新链表的尾节点和新链表的头节点
        //新链表的头节点--初始化指向头节点
        ListNode newHead=head;
        //指向新链表的尾节点--初始化指向头节点
        ListNode newTail=head;
        //创建一个变量来记录当前遍历到的旧链表的下标
        int index=0;
        while (newHead.next!=null){//如果
            if(index<k){//如果此时index小于要指向节点的下标,则要不断的向下遍历
                newHead=newHead.next;
                index++;
            }else {//index>=k的时候
                newHead=newHead.next;
                newTail=newTail.next;
                index++;
            }
        }
        //将原来的头节点赋值新头节点的下一个节点
        newHead.next=head;
        //将原来的头节点赋值给新的头节点
        head=newTail.next;
        //将新的尾节点的next置为空
        newTail.next=null;
        //返回头节点
        return head;
    }

82.删除排序链表中的重复元素

82. 删除排序链表中的重复元素 II - 力扣(LeetCode)

面试题 02.03 删除中间节点

面试题 02.03. 删除中间节点 - 力扣(LeetCode)

package leetcode.链表;

/**
 * @author lin
 * @creat 2022--12--10:49
 */
public class $_面试题_02_03_删除中间节点 {
             public class ListNode {
                 int val;
                ListNode next;
                 ListNode(int x) {
                     val = x;
                 }
            }

    public void deleteNode(ListNode node) {
        node.val=node.next.val;
        //不断遍历到下一个节点
        node.next=node.next.next;
    }
}

234.回文链表

234. 回文链表 - 力扣(LeetCode)

package leetcode.链表;

/**
 * @author lin
 * @creat 2023--01--15:01
 */
public class $_234_回文链表 {
        public class ListNode {
          int val;
          ListNode next;
          ListNode() {}
          ListNode(int val) {
              this.val = val;
          }
          ListNode(int val, ListNode next) {
              this.val = val;
              this.next = next;
          }
     }

    /**
     * 双指针+反转链表
     * @param head
     * @return
     */
    public boolean isPalindrome(ListNode head) {
        if (head == null) {
            return false;
        }
        ListNode fast = head;
        ListNode slow = head;
        // 快指针走两步,慢指针走一步,快指针走到尾巴,则慢指针走了刚好一般
        while (fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;
        }
        // 如果快指针不为null,则表示是奇数个
        if (fast != null) {
            slow = slow.next;
        }
        // 反转链表
        ListNode after = null;
        while (slow != null) {
            ListNode temp = slow.next;
            slow.next = after;
            after = slow;
            slow = temp;
        }
        fast = head;
        // 遍历正反链表,对比数据
        while (fast != null && after != null) {
            if (fast.val != after.val) {
                return false;
            }
            fast = fast.next;
            after = after.next;
        }
        return true;
    }
}

剑指 Offer 24.反转链表

剑指 Offer 24. 反转链表 - 力扣(LeetCode)

package leetcode.链表;

/**
 * @author lin
 * @creat 2023--01--18:11
 */
public class $_剑指_Offer_24_反转链表 {
        public class ListNode {
          int val;
          ListNode next;
          ListNode(int x) {
              val = x;
          }
      }

    public ListNode reverseList(ListNode head) {
            ListNode pre=null;
            ListNode cur=head;
            while (cur!=null){
                ListNode temp=cur.next;//保存下一个节点,要不然找不到
                //将链表方向进行改变
                cur.next=pre;
                //将cur赋值给pre
                pre=cur;
                //将temp赋值给cur
                cur=temp;
            }
            //返回新链表的头节点,此时cur指向null
            return pre;
        }
    }

剑指 Offer 52.两个链表的第一个公共节点

剑指 Offer 52. 两个链表的第一个公共节点 - 力扣(LeetCode)

package leetcode.链表;

/**
 * @author lin
 * @creat 2023--01--11:30
 */
public class $_剑指_Offer_52_两个链表的第一个公共节点 {
       public class ListNode {
          int val;
          ListNode next;
          ListNode(int x) {
              val = x;
              next = null;
          }
      }

    ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        if (headB==null || headA==null){
            return null;
        }
        ListNode n1=headA;
        ListNode n2=headB;
        while (n1!=n2){
            n1=n1==null ? headB:n1.next;
            n2=n2==null ? headA:n2.next;
        }
        return n1;
    }
}

 86.分隔链表

86. 分隔链表 - 力扣(LeetCode)

package leetcode.链表;

/**
 * @author lin
 * @creat 2023--01--12:18
 */
public class $_86_分隔链表 {
       public class ListNode {
          int val;
          ListNode next;
          ListNode() {}
          ListNode(int val) {
              this.val = val;
          }
          ListNode(int val, ListNode next) {
              this.val = val;
              this.next = next;
          }
      }
    public ListNode partition(ListNode head, int x) {
        //创建两个链表,一个存放小于x,一个存放大于等于x的
        ListNode lowerNode=new ListNode(-1);
        ListNode highNode=new ListNode(-1);
        //创建两个指针分别指向这两个链表
        ListNode lowerHead=lowerNode;
        ListNode highHead=highNode;
        //看头节点的下一个节点是否为空
        while (head!=null){
            if (head.val<x){//如果当前这个值是小于x,则将其存放在lowNode这个链表中
                lowerNode.next=head;
                lowerNode=lowerNode.next;
            }else {//如果当前这个值是大于或等于x,则将其存放在highNode这个链表中
                highNode.next=head;
                highNode=highNode.next;
            }
            //原来的链表的指针向后移动
            head=head.next;
        }
        //将小于x的链表拼接到大于或等于x的链表前面
        lowerNode.next=highHead.next;
        //大于或者等于x链表的最后一个节点的next指向Null
        highNode.next=null;
        return lowerHead.next;
    }
}

92.反转链表||

92. 反转链表 II - 力扣(LeetCode)

 

 

package leetcode.链表;

import 链表.List;

/**
 * @author lin
 * @creat 2023--01--13:18
 */
public class $_92_反转链表II {

       public class ListNode {
          int val;
          ListNode next;
          ListNode(int val) {
              this.val = val;
          }
          ListNode(int val, ListNode next) {
              this.val = val;
              this.next = next;
          }
      }
    public ListNode reverseBetween(ListNode head, int m, int n) {
        //判断输入的m和n是否相等
        if (m==n) return head;
        //创建一个虚拟头节点
        ListNode dummy=new ListNode(-1);
        dummy.next=head;

        //创建一个指针指向虚拟节点
        ListNode pre=dummy;
        //分别创建两个指针指向m和n所在的位置
        ListNode m_node=head;
        ListNode n_node=head;

        //分别将两个指针指向m和n所在的位置
        for (int i=0;i<m;i++){
            //pre记得也要往下遍历
            pre=pre.next;
            m_node=m_node.next;
        }
        for (int i=0;i<n;i++){
            n_node=n_node.next;
        }
        //只要n和m没有指向同一个数值的时候,就要一直转换
        while (m_node!=n_node){
            /*1(pre)->2(m_node)->3->4(n_node)->5->null*/
            //1.先让1的next指向3
            pre.next=m_node.next;
            //2.让2的next指向5
            m_node.next=n_node.next;
            //3.让4的next指向2
            n_node.next=m_node;
            //4.改变m_node的指向,要求指向下一个数值(即下一个值---3)
            m_node=pre.next;
        }
        return dummy.next;
    }
}

328.奇偶链表

328. 奇偶链表 - 力扣(LeetCode)

 

 

package leetcode.链表;

/**
 * @author lin
 * @creat 2023--01--21:17
 */
public class $_328_奇偶链表 {
       public class ListNode {
          int val;
          ListNode next;
          ListNode() {}
          ListNode(int val) {
              this.val = val;
          }
          ListNode(int val, ListNode next) {
              this.val = val;
              this.next = next;
          }
      }
    public ListNode oddEvenList(ListNode head) {
        if (head==null || head.next==null){
            return head;
        }
        //创建一个节点指向第一个节点(奇数节点)
        ListNode odd=head;
        //创建一个节点指向第二个节点(偶数节点)
        ListNode even=head.next;
        //创建一个指针指向第一个节点,因为最后奇数节点链表的最后一个节点指向偶数链表的第一个节点
        ListNode evenHead=head.next;

        //要判断偶数的下一个节点还存不存在
        while (even!=null && even.next!=null){
            //1.将奇数的next指向偶数的下一个节点
            odd.next=even.next;
            //2.将奇数的指针移动到下一个奇数上
            odd=odd.next;
            //3.偶数的next将指向新的奇数指针的下一个位置上
            even.next=odd.next;
            //4.将偶数的指针移动到下一个偶数上
            even=even.next;
        }
        //将奇数链表和偶数链表连接起来
        odd.next=evenHead;
        //因为第一个元素就是奇数链表的第一个节点
        return head;
    }
}

445.两数相加 ||

445. 两数相加 II - 力扣(LeetCode)

 

 

package leetcode.链表;

import java.util.Stack;

/**
 * @author lin
 * @creat 2023--01--22:53
 */
public class $_445_两数相加_II {
       public class ListNode {
          int val;
          ListNode next;
          ListNode() {}
          ListNode(int val) {
              this.val = val;
          }
          ListNode(int val, ListNode next) {
              this.val = val;
              this.next = next;
      }
       public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
            //分别创建两个栈来存放链表的数值
           Stack<Integer> stack1=new Stack<Integer>();
           Stack<Integer> stack2=new Stack<Integer>();
           //分别将两个链表的值放入栈中
           while (l1!=null){
               stack1.push(l1.val);
               //不断的遍历下一个值
               l1=l1.next;
           }
           while (l2!=null){
               stack2.push(l2.val);
               //不断的遍历下一个值
               l2=l2.next;
           }
           //创建一个值查看是否要进位
           int next1=0;
           //创建一个指针指向新节点
           ListNode cur=null;
           //判断两个栈是否为空,如果不为空就出栈
           //为什么是“||”--因为可能一个先结束
           while (!stack1.isEmpty() || !stack2.isEmpty()){
               //创建一个值来记录新链表的节点值
               int sum=0;
               if (!stack1.isEmpty()){
                   sum+=stack1.pop();
               }
               if (!stack2.isEmpty()){
                   sum+=stack2.pop();
               }
               //当前的节点值是l1.val+l2.val+next1(是否进位)
               sum+=next1;
               //创建一个新的节点存放新数值
               ListNode newNode=new ListNode(sum%10);
               //next1表示是否要进位--就是十位数
               next1=sum/10;
               //将新节点往旧节点的前面插入---即:将新节点的next指向cur
               newNode.next=cur;
               //将cur指向新节点
               cur=newNode;
           }
           //最后要判断是否要进位,如果要进位就要新创建一个节点存放
           if (next1!=0){
               ListNode newNode1=new ListNode(next1);
               newNode1.next=cur;
               cur=newNode1;
           }
           return cur;
       }
}

1171.从链表中删去总和为零的连续节点

1171. 从链表中删去总和值为零的连续节点 - 力扣(LeetCode)

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值