LeetCode力扣链表算法题——题解与思路

目录

链表双指针模版

1.设计链表

2. 环形链表

3.环形链表 II

4.相交链表

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

6.反转链表

7.移除链表元素

8.奇偶链表

9.回文链表

10.设计链表(双链表)

11.合并两个有序链表

12. 两数相加

13.旋转链表

14.复制带随机指针的链表(深拷贝)

15.扁平化多级双向链表

链表双指针模版

// Initialize slow & fast pointers
ListNode slow = head;
ListNode fast = head;
/**
 * Change this condition to fit specific problem.
 * Attention: remember to avoid null-pointer error
 **/
while (slow != null && fast != null && fast.next != null) {
    slow = slow.next;           // move slow pointer one step each time
    fast = fast.next.next;      // move fast pointer two steps each time
    if (slow == fast) {         // change this condition to fit specific problem
        return true;
    }
}
return false;   // change return value to fit specific problem

1.设计链表

设计链表的实现。您可以选择使用单链表或双链表。单链表中的节点应该具有两个属性:val 和 next。val 是当前节点的值,next 是指向下一个节点的指针/引用。如果要使用双向链表,则还需要一个属性 prev 以指示链表中的上一个节点。假设链表中的所有节点都是 0-index 的。

在链表类中实现这些功能:

  • get(index):获取链表中第 index 个节点的值。如果索引无效,则返回-1。
  • addAtHead(val):在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点。
  • addAtTail(val):将值为 val 的节点追加到链表的最后一个元素。
  • addAtIndex(index,val):在链表中的第 index 个节点之前添加值为 val  的节点。如果 index 等于链表的长度,则该节点将附加到链表的末尾。如果 index 大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点。
  • deleteAtIndex(index):如果索引 index 有效,则删除链表中的第 index 个节点。

示例:

MyLinkedList linkedList = new MyLinkedList(); linkedList.addAtHead(1); linkedList.addAtTail(3); linkedList.addAtIndex(1,2); //链表变为1-> 2-> 3 linkedList.get(1); //返回2 linkedList.deleteAtIndex(1); //现在链表是1-> 3 linkedList.get(1); //返回3

提示:

  • 所有val值都在 [1, 1000] 之内。
  • 操作次数将在  [1, 1000] 之内。
  • 请不要使用内置的 LinkedList 库。
class ListNode{
	int val;
	ListNode next;
	public ListNode(int val){
		this.val=val;
	}
}

class MyLinkedList {
	
	ListNode head=null;
	ListNode tail=null;
	int size=0;

    /** Initialize your data structure here. */
    public MyLinkedList() {

    }
    
    /** Get the value of the index-th node in the linked list. If the index is invalid, return -1. */
    public int get(int index) {
    	if(index<0 || index>=size){
    		return -1;
    	}

    	ListNode cur=head;
    	for(int i=0;i<index;i++){
    		cur=cur.next;
    	}
    	return cur.val;

    }
    
    /** Add a node of value val before the first element of the linked list. After the insertion, the new node will be the first node of the linked list. */
    public void addAtHead(int val) {
    	ListNode node=new ListNode(val);
    	if(head==null){
    		head=node;
    		tail=node;
    	}else{
    		node.next=head;
    		head=node;
    	}
    	size++;


    }
    
    /** Append a node of value val to the last element of the linked list. */
    public void addAtTail(int val) {
    	ListNode node=new ListNode(val);
    	if(tail==null){
    		head=node;
    		tail=node;
    	}else{
    		tail.next=node;
    		tail=node;
    	}
    	size++;

    }
    
    /** Add a node of value val before the index-th node in the linked list. If index equals to the length of linked list, the node will be appended to the end of linked list. If index is greater than the length, the node will not be inserted. */
    public void addAtIndex(int index, int val) {
    	
    	if(index<0 || index>size) return;
    	if(index==0) addAtHead(val);
    	if(index==size) addAtTail(val);
    	else{
	    	ListNode cur=head;
	    	for(int i=0;i<index-1;i++){
	    		cur=cur.next;
	    	}
	    	ListNode node=new ListNode(val);
	    	node.next=cur.next;
	    	cur.next=node;
	    	size++;
    	}


    }
    
    /** Delete the index-th node in the linked list, if the index is valid. */
    public void deleteAtIndex(int index) {

    	if(index<0 || index>=size) return;
    	if(index==0){
    		head=head.next;
    	}else{
    		ListNode cur=head;
	    	for(int i=0;i<index-1;i++){
	    		cur=cur.next;
	    	}
	    	ListNode deleted=cur.next;
	    	cur.next=deleted.next;
	    	if(index==size-1){
	    		tail=cur;
	    	}
    	}
    	size--;
    }
}

/**
 * Your MyLinkedList object will be instantiated and called as such:
 * MyLinkedList obj = new MyLinkedList();
 * int param_1 = obj.get(index);
 * obj.addAtHead(val);
 * obj.addAtTail(val);
 * obj.addAtIndex(index,val);
 * obj.deleteAtIndex(index);
 */

2. 环形链表

给定一个链表,判断链表中是否有环。

为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。

示例 1:

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

示例 2:

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

示例 3:

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

 

进阶:

你能用 O(1)(即,常量)内存解决此问题吗?

题解思路:

定义两个指针,分别为slow、fast遍历链表。slow每次向后移动一位,fast每次向后两位。如果链表有环那么最终两个指针会重叠相遇,如果无环则遍历一次就停止。

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

3.环形链表 II

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

为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。

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

示例 1:

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

示例 2:

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

示例 3:

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

 

进阶:
你是否可以不用额外空间解决此题?

题解思路:

最好理解的方式就是使用哈希表,使用单指针遍历的同时将节点存入哈希表中,如果链表有环判断哈希表中是否已有该节点,如果有该节点就为入环节点。

不使用额外存储空间的方式便是使用双指针,首先判断是否是环形链表,定义快(fast)慢(slow)两个指针,从头结点出发,fast每次向后移动两位,slow向后移动一位。

设链表有a+b个节点,a表示头节点到环形链表入口节点(不算入口节点),b则是环形节点的个数。

当两个指针相遇重合时,判断该链表有环,而此时假设slow的移动距离为s,fast的移动距离为f,那么f = 2s;

已知重合时fast指针比slow多走了n圈环形部分,可得f = s+nb即s = nb;

当想让一个指针遍历至环形入口节点需要走k次,k = a+nb(nb为每次循环一次都会回到入口节点),可得k = a+s;

那么如何让当前已经走了s步的slow指针再走a步呢(a并不确定)?就是让fast回到头结点,同时再让fast、slow向后移动,移动a步后fast处于入口节点,而slow节点因k = a+s也会处于入口节点,这时返回fast即可。

public class Solution {
    public ListNode detectCycle(ListNode head) {
        if (head == null)
            return null;
        ListNode slow = head;
        ListNode fast = head;
        while (true) {
            if (fast == null || fast.next == null)
                return null;
            fast = fast.next.next;
            slow = slow.next;
            if (fast == slow) {
                break;
            }
        }
        fast = head;
        while (fast != slow) {
            fast = fast.next;
            slow = slow.next;
        }
        return fast;
    }
}

4.相交链表

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

如下面的两个链表

在节点 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。

注意:

  • 如果两个链表没有交点,返回 null.
  • 在返回结果后,两个链表仍须保持原有的结构。
  • 可假定整个链表结构中没有循环。
  • 程序尽量满足 O(n) 时间复杂度,且仅用 O(1) 内存。

注意:

  • 如果两个链表没有交点,返回 null.
  • 在返回结果后,两个链表仍须保持原有的结构。
  • 可假定整个链表结构中没有循环。
  • 程序尽量满足 O(n) 时间复杂度,且仅用 O(1) 内存。

题解思路:

假设链表A未相交长度为a,链表B未相交长度为b,两个链表相交公共长度为c。定义两个指针pA、pB,分别从headA与headB为起点向后遍历。当其中有一个链表先到达尾部时,让其从另一个链表头部重新遍历,而另一个到达尾部时让它从刚才那个链表投开始,即可得a+c+b+c=b+c+a+c。因为两个指针遍历的路程相同,这时两个指针会同时到达末尾,也就说明了从相交节点开始两个指针就是同时向后遍历的,即两指针在相交节点相遇。

而两个链表不相交时,pA与pB同样是a+b=b+a,但遍历到最后pA==pB==null。

public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        if(headA==null||headB==null)
            return null;
        ListNode pA = headA;
        ListNode pB = headB;
        while(pA!=pB){
            pA = pA==null?headB:pA.next;
            pB = pB==null?headA:pB.next;
        }
        return pA;
    }
}

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

给定一个链表,删除链表的倒数第 个节点,并且返回链表的头结点。

示例:

给定一个链表: 1->2->3->4->5, 和 n = 2. 当删除了倒数第二个节点后,链表变为 1->2->3->5.

说明:

给定的 n 保证是有效的。

进阶:

你能尝试使用一趟扫描实现吗?

题解思路:

求倒数第n个节点,一次扫描实现,就不能用正序遍历。使用递归方式从尾到头遍历,遍历的同时判断是否是第n个,并对其进行删除操作。

class Solution {
    private int n;
    public ListNode removeNthFromEnd(ListNode head, int n) {
        this.n = n;
        return removeNode(head);
    }
    private ListNode removeNode(ListNode head){
        if (head == null)
            return null;
        head.next = removeNthFromEnd(head.next, n);
        n--;
        if (n == 0) {
            head = head.next;
        }
        return head;
    }
}

6.反转链表

反转一个单链表。

示例:

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

进阶:
你可以迭代或递归地反转链表。你能否用两种方法解决这道题?

题解思路:(图片来自LeetCode)

一、双指针法

二、递归法

/*
    双指针方式
*/
public ListNode reverseNode(ListNode head) {
    ListNode prev = null;
    ListNode cur = head;
    while (cur != null) {
        ListNode temp = cur.next;
        cur.next = prev;
       prev = cur;
       cur = temp;
    }
    return prev;
}

/*
递归方式
*/
public ListNode reverseNode(ListNode head){
    if(head == null||head.next==null){
        return head;
    }
    ListNode cur = reverseNode(head.next);
    head.next.next = head;
    head.next = null;
    return cur;
}

7.移除链表元素

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

示例:

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

题解思路:

采用双指针方式遍历数组,在判断目标值与节点值是否相等并删除时会有以下几种情况:

1.当头节点的元素等于目标值时head=head.next,同时移动cur到当前头结点

2.当cur指针节点值等于目标值时pre.next = cur.next,移动cur到下一节点,pre指针不动

3.当cur节点值不等于目标值时pre = cur;cur = cur.next,同时向后移动

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

8.奇偶链表

给定一个单链表,把所有的奇数节点和偶数节点分别排在一起。请注意,这里的奇数节点和偶数节点指的是节点编号的奇偶性,而不是节点的值的奇偶性。

请尝试使用原地算法完成。你的算法的空间复杂度应为 O(1),时间复杂度应为 O(nodes),nodes 为节点总数。

示例 1:

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

示例 2:

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

说明:

  • 应当保持奇数节点和偶数节点的相对顺序。
  • 链表的第一个节点视为奇数节点,第二个节点视为偶数节点,以此类推。

题解思路:

图片来自LeetCode官方题解

采用4个指针,odd为奇数指针,head为奇数头结点,even为偶数指针,evenHead为偶数头结点。

算法将奇数节点放在奇数链表中,偶数节点放在偶数链表中,最后再将两个链表拼接。

class Solution {
    public ListNode oddEvenList(ListNode head) {
        if(head==null)
            return null;
        ListNode odd = head;// 奇数指针
        ListNode even = head.next;// 偶数指针
        ListNode evenHead = even;// 偶数头
        while(even!=null &&even.next!=null){
            odd.next = even.next;
            odd = odd.next;
            even.next = odd.next;
            even = even.next;
        }
        odd.next = evenHead;
        return head;
    }
}

 9.回文链表

请判断一个链表是否为回文链表。

示例 1:

输入: 1->2
输出: false

示例 2:

输入: 1->2->2->1
输出: true

进阶:
你能否用 O(n) 时间复杂度和 O(1) 空间复杂度解决此题?

题解思路:

一、将节点值存在数组中,利用头尾双指针进行比较

这个方法比较好理解,遍历链表将值复制到ArrayList中,定义start、end指针分别从头和尾开始一一比较,相等则start++;end--;继续比较。该方式的时间复杂度O(n),空间复杂度为O(n)。

class Solution {
    public boolean isPalindrome(ListNode head) {
        List<Integer> vals = new ArrayList<>();
        ListNode currentNode = head;
        while (currentNode != null) {
            vals.add(currentNode.val);
            currentNode = currentNode.next;
        }

        int start= 0;
        int end= vals.size() - 1;
        while (start< end) {
            if (!vals.get(start).equals(vals.get(end))) {
                return false;
            }
            start++;
            end--;
        }
        return true;
    }
}

二、递归方式

通过递归遍历方式获得尾节点与头结点值进行比较,函数栈返回上一层时头结点先后移动一位继续比较。

该方式时间复杂度为O(n),空间复杂度为O(n)(递归方式存在函数栈)

ListNode startNode = null;
public boolean isPalindrome(ListNode head) {
  startNode = head; 
  return checkPalindrome(head);
}

public boolean checkPalindrome(ListNode head){
    if(head == null){
        return true;
    }
    if(!checkPalindrome(head.next)){
        return false;
    }
   if(startNode.val != head.val){
       return false;
   }
   startNode = startNode.next;
   return true;
}

 三、快慢双指针法

定义fast、slow快慢两个指针分别从head出发,slow指针每次向后一位,fast指针每次向后两位。当fast遍历到尾节点或fast==null时,slow刚好遍历到链表对称前半部分的边界。定义pre、prepre两个指针,slow遍历的同时将前半部分链表进行反转。得到前半部分反转链表后与后半部分节点值一一比较,如果都相等,证明该链表是回文链表。

public boolean isPalindrome(ListNode head) {
    if(head==null||head.next==null){
        return true;
    }
    ListNode fast = head,slow=head;
    ListNode pre = head,prepre=null;
    while(fast!=null&&fast!=null){
        pre = slow;
        slow = slow.next;
       fast = fast.next.next;
       // 反转链表
       pre.next = prepre;
       prepre= pre;
    }
    if(fast!=null){ 
    // 当链表节点是基数个时遍历结束,fast指针在尾节点,slow指向中间节点
    // 而回文链表中间节点是不与其他节点对称的,所以跳过当前节点从下一节点开始比较
        slow = slow.next;
    }
    while(pre!=null&&slow!=null){
        if(pre.val!=slow!=val){
            return false;
        }
        pre = pre.next;
        slow = slow.next;
    }
    return true;
}

10.设计链表(双链表)

 

public class DoublyListNode {
    int val;
    DoublyListNode next, prev;
    DoublyListNode(int x) {val = x;}
}

class MyLinkedList {
	
	 DoublyListNode head = null;
    DoublyListNode tail = null;
    int size = 0;

    /**
     * Initialize your data structure here.
     */
    public MyLinkedList() {

    }

    /**
     * Get the value of the index-th node in the linked list. If the index is invalid, return -1.
     */
    public int get(int index) {
        if (index < 0 || index > size - 1)
            return -1;
        DoublyListNode node = head;
        while (node != null) {
            if (index == 0) {
                return node.val;
            }
            index--;
            node = node.next;
        }
        return -1;
    }

    /**
     * Add a node of value val before the first element of the linked list. After the insertion, the new node will be the first node of the linked list.
     */
    public void addAtHead(int val) {
        DoublyListNode node = new DoublyListNode(val);
        if (head == null) {
            this.head = node;
            tail = this.head;
        } else {
            head.prev = node;
            node.next = head;
            head = node;
        }
        size++;
    }

    /**
     * Append a node of value val to the last element of the linked list.
     */
    public void addAtTail(int val) {
        DoublyListNode node = new DoublyListNode(val);
        if (head == null) {
            this.head = node;
            tail = this.head;
        } else {
            tail.next = node;
            node.prev = tail;
            tail = node;
        }
        size++;
    }

    /**
     * Add a node of value val before the index-th node in the linked list. If index equals to the length of linked list, the node will be appended to the end of linked list. If index is greater than the length, the node will not be inserted.
     */
    public void addAtIndex(int index, int val) {
        if (index < 0 || index > size)
            return;

        if (index == 0) {
            addAtHead(val);
        } else if (index == size) {
            addAtTail(val);
        } else {
            DoublyListNode node = new DoublyListNode(val);
            DoublyListNode cur = head;
            while (cur != null) {
                if (index == 0) {
                    node.next = cur;
                    node.prev = cur.prev;
                    cur.prev.next = node;
                    cur.prev = node;
                }
                cur = cur.next;
                index--;
            }
            size++;
        }
    }

    /**
     * Delete the index-th node in the linked list, if the index is valid.
     */
    public void deleteAtIndex(int index) {
        if (index < 0 || index > size - 1 || head == null)
            return;
        if (index == 0) {
            head = head.next;
            if(head!=null)
                head.prev = null;
        } else if (index == size - 1) {
            tail = tail.prev;
            if(tail!=null)
                tail.next = null;
        } else {
            DoublyListNode cur = head;
            while (cur != null) {
                if (index == 0) {
                    cur.prev.next = cur.next;
                    cur.next.prev = cur.prev;
                }
                cur = cur.next;
                index--;
            }
        }
        size--;
    }
}

/**
 * Your MyLinkedList object will be instantiated and called as such:
 * MyLinkedList obj = new MyLinkedList();
 * int param_1 = obj.get(index);
 * obj.addAtHead(val);
 * obj.addAtTail(val);
 * obj.addAtIndex(index,val);
 * obj.deleteAtIndex(index);
 */

11.合并两个有序链表

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

示例:

输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4

题解思路:

本题使用递归法比较好理解,比较l1与l2值节点大小,l1值小于l2时,l1添加到新链表并向后移动一位继续与l2比较,相反如果l1大于l2,则l2添加到新链表并向后移动一位继续l2比较,递归方式进行比较操作直至其中一个链表遍历结束。这时二者长度长一些的未遍历部分直接与新链表链接。

 public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        if(l1 == null)
            return l2;
        if(l2 == null)
            return l1;
        ListNode head = null;
        if(l1.val < l2.val){
            head = l1;
            head.next = mergeTwoLists(l1.next,l2);
        } else {
            head = l2;
            head.next = mergeTwoLists(l1,l2.next);
        }
        return head;
    }

 12. 两数相加

给出两个 非空 的链表用来表示两个非负的整数。其中,它们各自的位数是按照 逆序 的方式存储的,并且它们的每个节点只能存储 一位 数字。

如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们的和。

您可以假设除了数字 0 之外,这两个数都不会以 0 开头。

示例:

输入:(2 -> 4 -> 3) + (5 -> 6 -> 4)
输出:7 -> 0 -> 8
原因:342 + 465 = 807

题解思路:(来自于LeetCode官方解题思路)

就像你在纸上计算两个数字的和那样,我们首先从最低有效位也就是列表 l1l1 和 l2l2 的表头开始相加。由于每位数字都应当处于 0 \ldots 90…9 的范围内,我们计算两个数字的和时可能会出现 “溢出”。例如,5 + 7 = 125+7=12。在这种情况下,我们会将当前位的数值设置为 22,并将进位 carry = 1carry=1 带入下一次迭代。进位 carrycarry 必定是 00 或 11,这是因为两个数字相加(考虑到进位)可能出现的最大和为 9 + 9 + 1 = 199+9+1=19。

 public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        ListNode dummyHead = new ListNode(0);// 哑结点
        ListNode p = l1,q=l2;
        ListNode cur = dummyHead;
        int carry = 0;
        while(p!=null||q!=null){
            int a = (p==null)?0:p.val;
            int b = (q==null)?0:q.val;
            int sum = a+b+carry;
            carry = sum/10;
            cur.next = new ListNode(sum%10);
            cur = cur.next;
            if(q!=null)q=q.next;
            if(p!=null)p=p.next;
        }
        if(carry!=0){
            cur.next = new ListNode(carry);
        }
        return dummyHead.next;
    }

13.旋转链表

题解思路:

链表节点向右移动k个位置,本质上是将倒数k个元素移动到头部,这其中包含几个特殊情况:

 

  • 空链表和链表只有一个节点,节点不移动,没变化。
  • 当k=0时,结点不移动,没变化。
  • 当k是结点数或结点数的倍数时,节点不移动,没变化。

除去以上情况,节点均需要向右移动。思路主要分以下几步,

  1. 遍历获取链表总得节点个数
  2. 对k取余,得到真正要移动多少个位置
  3. 遍历得到倒数k+1的节点,并暂时把头尾相连,头结点替换为cur.next,并cur.next置为null。
public ListNode rotateRight(ListNode head, int k) {
        if (head == null || head.next == null)
            return head;
        ListNode cur = head;
        int count = 1;
        while (cur.next != null) {
            count++;
            cur = cur.next;
        }
        k = k % count;
        if (k == count)
            return head;
        cur.next = head;
        for (int i = 0; i < count - k; i++) {
            cur = cur.next;
        }
        ListNode newHead = cur.next;
        cur.next = null;
        return newHead;
    }

14.复制带随机指针的链表(深拷贝)

给定一个链表,每个节点包含一个额外增加的随机指针,该指针可以指向链表中的任何节点或空节点。

要求返回这个链表的 深拷贝。 

我们用一个由 n 个节点组成的链表来表示输入/输出中的链表。每个节点用一个 [val, random_index] 表示:

  • val:一个表示 Node.val 的整数。
  • random_index:随机指针指向的节点索引(范围从 0 到 n-1);如果不指向任何节点,则为  null 。

 

示例 1:

输入:head = [[7,null],[13,0],[11,4],[10,2],[1,0]]
输出:[[7,null],[13,0],[11,4],[10,2],[1,0]]

示例 2:

输入:head = [[1,1],[2,1]]
输出:[[1,1],[2,1]]

示例 3:

输入:head = [[3,null],[3,0],[3,null]]
输出:[[3,null],[3,0],[3,null]]

示例 4:

输入:head = []
输出:[]
解释:给定的链表为空(空指针),因此返回 null。

 

提示:

  • -10000 <= Node.val <= 10000
  • Node.random 为空(null)或指向链表中的节点。
  • 节点数目不超过 1000 。

题解思路:

回溯算法的第一想法是将链表想象成一张图。链表中每个节点都有 2 个指针(图中的边)。因为随机指针给图结构添加了随机性,所以我们可能会访问相同的节点多次,这样就形成了环。

image.png上图中,我们可以看到随机指针指向了前一个节点,因此成环。我们需要考虑这种环的实现。

image.png

此方法中,我们只需要遍历整个图并拷贝它。拷贝的意思是每当遇到一个新的未访问过的节点,你都需要创造一个新的节点。遍历按照深度优先进行。我们需要在回溯的过程中记录已经访问过的节点,否则因为随机指针的存在我们可能会产生死循环。

 

算法

1.从头指针开始遍历整个图。

我们将链表看做一张图。下图对应的是上面的有向链表的例子,Head 是图的出发节点。

 

2.当我们遍历到某个点时,如果我们已经有了当前节点的一个拷贝,我们不需要重复进行拷贝。

3.如果我们还没拷贝过当前节点,我们创造一个新的节点,并把该节点放到已访问字典中,即:

visited_dictionary[current_node] = cloned_node_for_current_node.

4.我们针对两种情况进行回溯调用:一个顺着 random 指针调用,另一个沿着 next 指针调用。步骤 1 中将 random 和 next 指针分别红红色和蓝色标注。然后我们分别对两个指针进行函数递归调用:

cloned_node_for_current_node.next = copyRandomList(current_node.next);

cloned_node_for_current_node.random = copyRandomList(current_node.random);

class Solution {
    HashMap<Node,Node> hashMap = new HashMap();
    public Node copyRandomList(Node head) {
        if(head == null)
            return null;
        if(hashMap.containsKey(head)){
            return hashMap.get(head);
        }
        Node node = new Node(head.val);
        hashMap.put(head,node);
        node.next = copyRandomList(head.next);
        node.random = copyRandomList(head.random);
        return node;
    }
}

15.扁平化多级双向链表

 

多级双向链表中,除了指向下一个节点和前一个节点指针之外,它还有一个子链表指针,可能指向单独的双向链表。这些子列表也可能会有一个或多个自己的子项,依此类推,生成多级数据结构,如下面的示例所示。

给你位于列表第一级的头节点,请你扁平化列表,使所有结点出现在单级双链表中。

 

示例 1:

输入:head = [1,2,3,4,5,6,null,null,null,7,8,9,10,null,null,11,12]
输出:[1,2,3,7,8,11,12,9,10,4,5,6]
解释:

输入的多级列表如下图所示:



扁平化后的链表如下图:

示例 2:

输入:head = [1,2,null,3]
输出:[1,3,2]
解释:

输入的多级列表如下图所示:

  1---2---NULL
  |
  3---NULL

示例 3:

输入:head = []
输出:[]

 

如何表示测试用例中的多级链表?

以 示例 1 为例:

 1---2---3---4---5---6--NULL
         |
         7---8---9---10--NULL
             |
             11--12--NULL

序列化其中的每一级之后:

[1,2,3,4,5,6,null]
[7,8,9,10,null]
[11,12,null]

为了将每一级都序列化到一起,我们需要每一级中添加值为 null 的元素,以表示没有节点连接到上一级的上级节点。

[1,2,3,4,5,6,null]
[null,null,7,8,9,10,null]
[null,11,12,null]

合并所有序列化结果,并去除末尾的 null 。

[1,2,3,4,5,6,null,null,null,7,8,9,10,null,null,11,12]

 

提示:

  • 节点数目不超过 1000
  • 1 <= Node.val <= 10^5

 题解思路:

将扁平化多级列表顺时针旋转90度,看做是一个二叉树,以先序遍历方式遍历该链表。遍历的同时建立prev和next前驱后继关系

class Solution {
    public Node flatten(Node head) {
        if(head == null)return head;
        Node dummyHead = new Node(0);
        node.next = head;
        dfs(dummyHead,head);
        // 接触与哑结点前驱后继关系
        dummyHead.prev.next = null;
        return dummyHead.next;
    }
    
    private Node dfs(Node prev,Node cur){
        if(cur==null)return prev;
        // 构建前驱后继关系
       prev.next = cur;
       cur.prev = prev; 
       
       Node nextTemp = cur.next;
       Node tail = dfs(cur,cur.child);
       cur.child = null;
       // tail为子链表的尾节点
       return dfs(tail,nextTemp);
    }
}

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值