[LeetCode]-链表-3

前言

记录 LeetCode 刷题中遇到的链表相关题目,第三篇

206. 反转链表

class Solution {
    
    
   public ListNode reverseList(ListNode head) {
    
    
       if(head == null){
    
    
           return null;
       }
        ListNode temp = head.next;
        head.next = null;
        //整个递归过程结束后会返回反转后的新的头节点
        return recrution(head,temp);
    }
    //递归方法,每次反转两个连续的结点node1和node2 (node1往前的部分已经反转好了),
    //具体操作就是先记录 node2后面的下一个结点,即node2.next
    //然后将node2.next指向node1,然后继续往后对node2以及一开始保存的node2一开始的
    //下一结点调用递归方法
    public ListNode recrution(ListNode node1,ListNode node2) {
    
    
        if(node2 == null){
    
    
            return node1;
        }
        ListNode temp = node2.next;
        node2.next = node1;
        return recrution(node2,temp);
    }
}

25. K 个一组翻转链表

首先,反转部分使用的就是 206 题的代码
遍历整个链表,每遍历 K 个,就把这 K 个单独截出来,然后这一段链表,这时我们可以得到反转后的新的头节点以及尾结点。我们记录反转上一段后得到的尾结点 lastTail,每反转完一段,就可以使 lastTail 的 next 指向反转后这一段的新的头结点,然后继续遍历这一段后续的其它部分

class Solution {
    
    
    public ListNode reverseList(ListNode head) {
    
    
       if(head == null){
    
    
           return null;
       }
        ListNode temp = head.next;
        head.next = null;
        return recrution(head,temp);
    }
    public ListNode recrution(ListNode node1,ListNode node2) {
    
    
        if(node2 == null){
    
    
            return node1;
        }
        ListNode temp = node2.next;
        node2.next = node1;
        return recrution(node2,temp);
    }
    public ListNode reverseKGroup(ListNode head, int k) {
    
    
    	//链表经典技巧,设置一个哑节点
        ListNode dummy = new ListNode();
        ListNode lastTail = dummy; //反转上一段后得到的尾结点
        ListNode nextHead = head;  //要反转的下一段的头结点
        ListNode cur,tmp;
        int i;
        while(true){
    
    
            cur = nextHead;   //cur用来遍历链表
            for(i = 1;i < k && cur != null;i++){
    
    
                cur = cur.next;
            }
            if(cur == null || i < k){
    
    
                break;
            }
            tmp = cur.next; //tmp用来记录cur的下一个节点,也就是下一段要反转的链表的头节点
            cur.next = null; //截出这一段
            reverseList(nextHead); //反转后cur就是新的头节点,nextHead 就是新的尾结点
            lastTail.next = cur;
            lastTail = nextHead; //更新lastTail
            nextHead = tmp; //更新要反转的下一段的头结点
        }
        //最后一段的节点个数可能不满足 K 个,也就在上面的循环中接到lastTail后面
        //所以在跳出循环再接上
        lastTail.next = nextHead;
        return dummy.next;
    }
}

92. 反转链表 II

大致思路:首先创建一个哑节点 dummy 作为临时头节点。变量 tmp 遍历链表,当遍历到原链表第 left 个节点的前一个节点时,用一个 reversedTail 变量记录第 left 个节点,这个节点将变为反转后链表的最后一个节点

然后就开始对第 left 个节点开始的链表进行反转。当反转到第 right 个节点时,用 reversedTailNext 变量记录第 right 个节点的下一个节点,这个节点就是反转后的链表段的下一个节点。然后返回第 right 个节点,那么最终整个递归方法的返回值就是反转后链表段的头节点,记为 reversedHead

最后就是收尾工作,将反转后链表段与原来的链表连起来
由于 tmp 就是原链表第 left 个节点的前一个节点,所以 tmp 的后继节点应变为 reversedHead;然后 reversedTailNext 保存的是反转后的链表段的下一个节点,reversedTail 保存的是反转后链表的最后一个节点,所以让 reversedTail 的后继节点变为 reversedTailNext。连接工作完成,返回 dummy.next 即可

class Solution {
    
    
    int count; //记录反转时反转到了第几个节点
    ListNode reversedTailNext;  //记录反转后链表的下一个节点
    //递归反转链表的方法
    ListNode rec(ListNode p1, ListNode p2,int right){
    
    
        if(count == right){
    
     
            reversedTailNext = p2;
            return p1;
        }
        ListNode tmp = p2.next;
        p2.next = p1;
        count++;
        return rec(p2,tmp,right);
    }
    public ListNode reverseBetween(ListNode head, int left, int right) {
    
    
        int cur = 1; //记录当前遍历到哪个节点
        ListNode dummy = new ListNode(-1,head);
        ListNode tmp = dummy;
        while(cur++ < left){
    
    
            tmp = tmp.next;
        }
        count = left;
        ListNode reversedTail = tmp.next;
        ListNode reversedHead =  rec(tmp.next,tmp.next.next,right);
        tmp.next = reversedHead;
        reversedTail.next = reversedTailNext;
        return dummy.next;
    }
}

剑指 Offer 22. 链表中倒数第k个节点

首先想到的做法应该是先遍历一遍链表求出链表的长度 len,然后再从头开始找到第 len - k + 1 个节点就是倒数第 k 个节点

这种做法需要两次遍历,复杂度 O(n + n)。做单链表的题总要想想能不能只用一次遍历就解决问题,本题的做法就是:既然要找倒数第 k 个节点,如果有两个指针 p1 和 p2,p1 在前 p2 在后,它们距离一直为 k,那么当 p2 指向链表最后面的 null 的时候,p1 指向的不就是倒数第 k 个节点吗。所以先让 p2 走到第 k + 1 个节点的位置,然后再让 p1 从头开始,两个指针每次都一起向后走一步,当 p2 走到 null 的时候,p1 的位置就是倒数第 k 个节点

public ListNode getKthFromEnd(ListNode head, int k) {
    
    
    if(head == null) return null;
    ListNode p2 = head;
    int endP2 = k + 1; //p2 一开始先走到第 k + 1个节点的位置
    int count = 1; //记录 p2 的位置,一开始在第 1 个节点
    while(p2 != null && count < endP2){
    
    
        p2 = p2.next;
        count++;
    }
    if(p2 == null){
    
      //如果循环结束时 p2 就指向 null ,根据count 判断是刚好走到尾还是不够 k 个节点
        if(count == endP2) return head;
        return null;
    }
    //然后让 p1 开始走
    ListNode p1 = head;
    while(p2 != null){
    
    
        p1 = p1.next;
        p2 = p2.next;
    }
    return p1;
}

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

题目的数据保证了倒数第 N 个结点一定存在,那么我们只需要找到链表倒数第 N + 1 个结点,让其 next 域更改为倒数第 N 个结点的 next 即可

那么第一步就是找到链表中倒数第 N + 1 个结点,参考前文 剑指 Offer 22. 链表中倒数第k个节点即可

public ListNode removeNthFromEnd(ListNode head, int n) {
    
    
    ListNode dummmy = new ListNode(-1,head);
    ListNode p1 = dummmy,p2 = dummmy;
    int count = n + 1;
    while(count-- > 0){
    
    
        p2 = p2.next;
    }
    while(p2 != null){
    
    
        p1 = p1.next;
        p2 = p2.next;
    }
    p1.next = p1.next.next;
    return dummmy.next;
}

148. 排序链表

采用归并排序的思想,每次将链表段分为前后两段,对这两段分别排序,再对排序后的两个链表段进行合并

public ListNode sortList(ListNode head) {
    
    
		//递归边界
        if(head == null || head.next == null) return head;
        ListNode slow = head,fast = head,tmp = slow;
        //快慢双指针找到后一段链表的第一个节点让slow指向它。用tmp维护slow的前一节点
        while(fast != null && fast.next != null){
    
    
            fast = fast.next.next;
            tmp = slow;
            slow = slow.next;
        }
        //前后两段链表断开
        tmp.next = null;
        //两段链表分别排序
        ListNode l1 =  sortList(head);
        ListNode l2 =  sortList(slow);
        ListNode dummy = new ListNode();
        tmp = dummy;
        //合并链表
        while(l1 != null && l2 != null){
    
    
            if(l1.val < l2.val){
    
    
                tmp.next = l1;
                l1 = l1.next;
                tmp = tmp.next;
            }else{
    
    
                tmp.next = l2;
                l2 = l2.next;
                tmp = tmp.next;
            }
        }
        tmp.next = l1 == null ? l2 : l1;
        /*while(l1 != null){
            tmp.next = l1;
            l1 = l1.next;
            tmp = tmp.next;
        }
        while(l2 != null){
            tmp.next = l2;
            l2 = l2.next;
            tmp = tmp.next;
        }*/
        return dummy.next;
    }

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

swapPairs 方法将以 head 为头节点的链表的第一跟第二个结点交换位置,然后返回新的头节点,即原来的 head 的下一个结点。递归对每两个结点为一组进行交换

public ListNode swapPairs(ListNode head) {
    
    
	//head为null或者只有一个结点就无需交换
    if(head == null || head.next == null) return head;
    //记录第三个结点
    ListNode tmp = head.next.next;
    //交换头节点和次结点
    ListNode p1 = head,p2 = head.next;
    p2.next = p1;
    //对后续链表段进行递归操作
    p1.next = swapPairs(tmp);
    return p2;
}

138. 复制带随机指针的链表

在官方题解的基础上改为了更容易理解的版本:
原链表跟新链表的结点间存在着一一对应的关系,我们可以用一个哈希表 map 存储这种对应关系,键为原链表中的某个结点,值为新链表中对应的结点

具体来说,对于原链表 o1->o2->o3->o4,这里我们先只考虑 next 属性。那么我们可以按照 next 属性来遍历原链表创建新链表,得到 n1->n2->n3->n4,此时的链表结点的 random 域还未设值。同时在创建新链表的过程中更新 map,最后得到的 map 为 {(o1,n1),(o2,n2),(o3,n3),(o4,n4)}

然后我们再从头遍历一遍原链表和新链表,将新链表中的每个结点的 random 域指向原链表中相应结点的 random 域结点作为键在 map 中对应的值。举个例子具体来说,在遍历到 o1 以及 n1 时,发现 o1 的 random 域为 o3,那么 o3 在 map 中对应的值就为 n3,于是 n1.random = n3,以此类推

class Solution {
    
    
    HashMap<Node,Node> map = new HashMap<Node, Node>();
    public Node copyRandomList(Node head) {
    
    
        if (head == null) return null;
        //newHead为新链表的头节点,newCur用来遍历新链表,oldCur用来遍历原链表
        Node newHead = new Node(head.val),newCur = newHead,oldCur = head;
        map.put(oldCur,newCur);
        oldCur = oldCur.next;
        //遍历原链表依次创建新链表的每个结点
        while(oldCur != null){
    
    
            newCur.next = new Node(oldCur.val);
            newCur = newCur.next;
            map.put(oldCur,newCur);
            oldCur = oldCur.next;
        }
        newCur.next = null; //最后一个结点的next记得置为null
        newCur = newHead;
        oldCur = head;
        //再遍历一遍两个链表对random域进行设值
        while(newCur != null){
    
    
            newCur.random = map.get(oldCur.random);
            newCur = newCur.next;
            oldCur = oldCur.next;
        }
        return newHead;
    }
}

猜你喜欢

转载自blog.csdn.net/Pacifica_/article/details/125574828