【刷题】链表

系列汇总:《刷题系列汇总》



——————《剑指offeer》———————

1. 两个链表的第一个公共结点

  • 题目描述:输入两个无环的单链表,找出它们的第一个公共结点。(注意因为传入数据是链表,所以错误测试数据的提示是用其他方式显示的,保证传入数据是正确的)
  • 优秀思路:将两个链表交换补充到对方的前面,然后逐位对比该位是否相同即可
    在这里插入图片描述
public class Solution {
    public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
        ListNode p1 = pHead1;
        ListNode p2 = pHead2;
        while(p1 != p2){
            p1 = p1==null ? pHead2 : p1.next; // 相当于 pHead2 前补 pHead1
            p2 = p2==null ? pHead1 : p2.next; // 相当于 pHead1 前补 pHead2
        }
        return p1;
    }
}

2. 链表中环的入口结点

  • 题目描述:给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null
  • 我的思路:利用哈希表存储已走过的结点,若遇到重复节点则该节点为入环节点
  • 优秀思路
    • 第一步,找环中相汇点:定义一快p1、一慢p2两个节点,p1 一次走2步,p2 一次走1步,则p1、p2一定会在环上的某个点相汇聚。设入环前共A个节点,一个环B个节点,p2入环走了x个节点时相遇,p1 绕了n圈,则 2*(x+A)= (A+x) + nB,则x = nB-A
      注意:n≥1,如果环前面的链表很长,而环短,那么快指针进入环以后可能转了好几圈才和慢指针相遇。但无论如何,慢指针在进入环的第一圈的时候就会和快的相遇。)

    • 第二步,找环的入口。将此时的p1指向头结点,速度调为1,p1、p2同时走,相遇时则为入环节点,证明如下:

    相遇时 p2 在环上走了 nB-A 个节点,等效的环上位置为 B-A
    还有 B-(B-A) = A 个节点到达入环起点 = 入环前的总结点数

public class Solution {
    ListNode EntryNodeOfLoop(ListNode pHead){
        // 边界条件
        if(pHead == null || pHead.next == null) return null;
        ListNode fast = pHead;
        ListNode slow = pHead;
        while(slow != null){
            fast = fast.next.next;
            slow = slow.next;
            if(fast == slow){
                fast = pHead;
                while(fast != slow){
                    fast = fast.next;
                    slow = slow.next;
                }
                return fast;
            }
        }
        return null;
    }
}

3. 链表中倒数第k个结点

  • 题目描述:输入一个链表,输出该链表中倒数第k个结点。如果该链表长度小于k,请返回空。
  • 优秀思路:假设总共n个节点,倒数第k个节点,其实就是正数n-k+1个节点。利用双指针,其中一个指针p1先走k步,再让另一个指针p2开始同时运动,当p1指向末尾时,p2指向的即为第n-k+1个节点
public class Solution {
    public ListNode FindKthToTail (ListNode pHead, int k) {
        if(pHead == null) return null;
        int index = 0;
        ListNode first = pHead;
        // p1 先行 k 步
        while(first != null && index < k){
            first = first.next;
            index++;
        }
        if(index != k) return null;
        ListNode second = pHead;
        // p2 紧跟其后
        while(first != null){
            first = first.next;
            second = second.next;
        }
        return second;
    }
}

4. 反转链表

  • 题目描述:输入一个链表,反转链表后,输出新链表的表头。
  • 优秀思路:因为链表里每个节点都存储了下一个节点的地址,所以只需要让下一个节点存储上一个节点的地址即可,如下图
    -
 public class Solution {
    public ListNode ReverseList(ListNode head) {
        if(head ==  null || head.next == null) return head;
        ListNode pre = null; // 当前节点的上一节点
        ListNode next = null; // 当前节点的下一节点
        // head 当前节点
        while(head != null){
            next = head.next; // 右移 下一节点
            head.next = pre; // 当前节点指向上一节点
            pre = head; // 右移 上一节点
            head = next; // 右移 当前节点
        }
        return pre;
    }
}

5. 合并两个排序的链表

  • 题目描述:输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。
  • 优秀思路:利用归并排序的思想,主要是代码细节问题
public class Solution {
    public ListNode Merge(ListNode list1,ListNode list2) {
        if(list1 == null) return list2;
        if(list2 == null) return list1;
        ListNode newNode = new ListNode(0); // 建立首节点为0的节点
        ListNode index = newNode; // 必须建立index指向newNode头结点
        
        /* 因为不用反复调用list1 list2,所以容许list1 list2可变 
        ListNode p1 = list1; 
        ListNode p2 = list2;
        */
        
        while(list1 != null && list2 != null){
            if(list1.val < list2.val){
                index.next = list1;
                list1 = list1.next;
            }else{
                index.next = list2;
                list2 = list2.next;
            }
            index = index.next; // 这步别忘了,不然sum一直指向的都是头部
        } 
        // 一句就指向list1后续没读完的所有部分
        if(list1 != null) index.next = list1;
        if(list2 != null) index.next = list2;
        // 不能输出index,只能输出newNode
        return newNode.next; // 加next是为了不输出头结点0
    }
}

6. 从尾到头打印链表

  • 题目描述:输入一个链表,按链表从尾到头的顺序返回一个ArrayList。
  • 我的思路:建立ArrayList,逐个add链表元素值后,利用Collectionsreverse()函数将ArrayList倒序输出即可
import java.util.ArrayList;
import java.util.Collections;
public class Solution {
    public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
        ArrayList<Integer> list = new ArrayList<Integer>();
        if(listNode ==  null) return list;
        while(listNode != null){
            list.add(listNode.val);
            listNode = listNode.next; // 这句别忘了
        }
        Collections.reverse(list);
        return list;
    }
}
  • 优秀思路1:在我的基础上便面了在倒一边序和调用库函数,直接将每次的链表元素值addArrayList0索引处(最前面)即可,本来已存在的元素会自动后移(本题也可利用栈先进后出的特点实现)
import java.util.ArrayList;
public class Solution {
    public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
        ArrayList<Integer> list = new ArrayList<Integer>();
        if(listNode ==  null) return list;
        while(listNode != null){
            list.add(0,listNode.val); // 核心!!!
            listNode = listNode.next;
        }
        return list;
    }
}

7. 复杂链表的复制(困难)

  • 题目描述:输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针random指向一个随机节点),请对此链表进行深拷贝,并返回拷贝后的头结点。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)
  • 优秀思路1:肯定不可以直接将节点赋值给新的节点,这样就是引用了。
    • 新建节点,然后新节点的值跟原来的节点的一样。
    • 但是要怎么存储呢?因为我们深拷贝的新的链表,每一个节点都是跟原来链表一一对应的,但是是互相独立的,所以我就想到了HashMap,它不就是存储k-v键值对的吗,这样不就可以将两个节点一一对应起来。(这里的一一对应的意思是,知道哪个是哪个的深拷贝节点)
    • 接着,我们还需要将新链表中的每个节点赋上next,random属性的值,那么我们就可以通过hashmap,通过key(原链表的节点),取出拷贝的节点,然后将这个key(旧节点)的两个属性值拷贝给新节点。
import java.util.*;
public class Solution {
public RandomListNode Clone(RandomListNode pHead){
        HashMap<RandomListNode, RandomListNode> map = new HashMap<RandomListNode,RandomListNode>();
        RandomListNode p = pHead;
        // 第一次遍历,新建立节点
        // map里面存储的键:原节点
        //              值:一个和原节点值相同的新节点
        while(p != null){
            RandomListNode newNode = new RandomListNode(p.label);
            map.put(p,newNode);
            p = p.next;
        }
        
		// 回到头结点,进行第二次遍历,赋值对应的关系
        p = pHead; 
        while(p != null){
        	// 不能是获取当前节点、next节点、random节点,都通过map.get()的方式获得,这个就保证了新链表中的每个节点都是新节点
            RandomListNode node = map.get(p);
            // 
            node.next = (p.next==null)? null : map.get(p.next);
            node.random = (p.random==null)? null : map.get(p.random);
            p = p.next;
        }
        return map.get(pHead);
    }
}
  • 优秀思路2
    在这里插入图片描述

    1. 遍历链表,复制每个结点,如复制结点A得到A1,将结点A1插到结点A后面;
    2. 重新遍历链表,复制老结点的随机指针给新结点,如A1.random = A.random.next;
    3. 拆分链表,将链表拆分为原链表和复制后的链表
public class Solution {
    public RandomListNode Clone(RandomListNode pHead) {
        if(pHead == null) {
            return null;
        }
 
        RandomListNode currentNode = pHead;
        //1、复制每个结点,如复制结点A得到A1,将结点A1插到结点A后面;
        while(currentNode != null){
            RandomListNode cloneNode = new RandomListNode(currentNode.label);
            RandomListNode nextNode = currentNode.next;
            currentNode.next = cloneNode;
            cloneNode.next = nextNode;
            currentNode = nextNode;
        }
 
        currentNode = pHead;
        //2、重新遍历链表,复制老结点的随机指针给新结点,如A1.random = A.random.next;
        while(currentNode != null) {
            currentNode.next.random = currentNode.random==null? null : currentNode.random.next;
            currentNode = currentNode.next.next;
        }
 
        //3、拆分链表,将链表拆分为原链表和复制后的链表
        currentNode = pHead;
        RandomListNode pCloneHead = pHead.next;
        while(currentNode != null) {
            RandomListNode cloneNode = currentNode.next;
            currentNode.next = cloneNode.next;
            cloneNode.next = cloneNode.next==null?null:cloneNode.next.next;
            currentNode = currentNode.next;
        }
        
        return pCloneHead;
    }
}

8. 删除链表中重复的结点

  • 题目描述:在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为1->2->5
  • 优秀思路1:两次遍历,第一次遍历找出所有的重复值,再遍历一次删除为重复值的链表元素,利用 HashSet 进行存储速度较快。
  • 优秀思路2:相邻两位进行比较,为防止开始的2位相同,对链表前面补一个0。(还有一种思路是)
public class Solution {
    public ListNode deleteDuplication(ListNode pHead){
        if (pHead==null || pHead.next==null) return pHead;
        ListNode newNode= new ListNode(0);
        newNode.next = pHead;
        ListNode pre = newNode;
        ListNode last = newNode.next;
        while(last != null && last.next != null){
            if(last.next.val == last.val){
            	// 找出最后一个相同值
                while(last.next != null && last.next.val == last.val){
                    last = last.next;
                }
                pre.next = last.next;
                last = last.next;
            }else{
                pre = last;
                last = last.next;
            }
        }
        return newNode.next;
    }
}

——————《LeectCode》———————

1. 两数相加

  • 题目描述:给你两个非空的链表,表示两个非负的整数。它们每位数字都是按照逆序的方式存储的,并且每个节点只能存储一位数字。请你将两个数相加,并以相同形式返回一个表示和的链表。你可以假设除了数字 0 之外,这两个数都不会以 0 开头。
  • 优秀思路(我的):在长度不一致的时候,对短的链表补0,解决长度不一致的问题
class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        int dec = 0; // 进位
        int temp = 0;
        ListNode sum = new ListNode(0);
        ListNode index = sum;
        while(l1 != null && l2 != null){
            if(l1.next != null || l2.next != null){
            	// 在长度不一致的时候,对短的链表补0
                if(l1.next == null) l1.next = new ListNode(0);
                if(l2.next == null) l2.next = new ListNode(0);
            }
            temp = dec + l1.val + l2.val;
            dec = temp / 10;
            index.next = new ListNode(temp % 10);

            index = index.next;
            l1 = l1.next;
            l2 = l2.next;
        }
        if(dec != 0) index.next = new ListNode(1);
        return sum.next;
    }
}

2. 合并两个有序链表(同剑指Offer.5)

  • 题目描述:将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
    在这里插入图片描述
  • 优秀思路(我的):利用归并排序的思想
class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
    	// 可精简部分 1
        if(l1 ==  null) return l2;
        if(l2 ==  null) return l1;

        ListNode newNode = new ListNode(0);
        ListNode index = newNode;
        while(l1 != null && l2 != null){
            if(l1.val < l2.val){
                index.next = l1;
                l1 = l1.next;
            }else{
                index.next = l2;
                l2 = l2.next;
            }
            index = index.next;
        }
        // 可精简部分 2
        if(l1 != null) index.next = l1;
        if(l2 != null) index.next = l2;
        return newNode.next;
    }
}
  • 代码精简
class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        ListNode newNode = new ListNode(0);
        ListNode index = newNode;
        // 精简1
        while(l1 != null && l2 != null){
            if(l1.val < l2.val){
                index.next = l1;
                l1 = l1.next;
            }else{
                index.next = l2;
                l2 = l2.next;
            }
            index = index.next;
        }
        // 精简2
        index.next = l1 == null? l2 : l1;
        return newNode.next;
    }
}

🚩3. 反转链表(同剑指Offer.4)

  • 题目描述:给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
  • 优秀思路1:【非递归】每2个相邻元素颠倒地址指向即可
class Solution {
    public ListNode reverseList(ListNode head) {
        if(head == null || head.next == null) return head;
        ListNode pre = null;
        ListNode next = null;
        while(head != null){ // 结束循环后 head 为null,应该输出pre
            next = head.next;
            head.next = pre;
            pre = head;
            head = next;
        }
        return pre;
    }
}
  • 优秀思路2:【递归】递归将地址指向颠倒即可,需要注意的是当前结点的下一个节点必须指向∅(即消除原来的地址指向)。如果忽略了这一点,链表中可能会产生环。
    在这里插入图片描述
    在这里插入图片描述
class Solution {
    public ListNode reverseList(ListNode head) {
        // 递归停止条件
        if(head == null || head.next == null) return head;
        ListNode res = reverseList(head.next); // 将迭代结果存在结点res里
        head.next.next = head; // 将当前节点后续所有结点部分的地址指向当前节点
        head.next = null; // 不能忽略!!当前节点地址指向空
        return res;
    }
}

🚩4. 合并K个升序链表(合并2个的升级版)

  • 题目描述:给你一个链表数组,每个链表都已经按升序排列。请你将所有链表合并到一个升序链表中,返回合并后的链表。
  • 在这里插入图片描述
  • 我的思路(31%-90%):定义双指针,从两端开始合并,逐步向中间进行
class Solution {
    public ListNode mergeKLists(ListNode[] lists) {
        if(lists.length == 0) return null;
        if(lists.length == 1) return lists[0];

        int left = 0;
        int right = lists.length - 1;
        ListNode newNode = new ListNode(Integer.MIN_VALUE);

        while(left <= right){
            if(left == right) newNode = mergeTwoLists(newNode,lists[left]);
            else newNode = mergeTwoLists(newNode,mergeTwoLists(lists[left],lists[right]));
            left++;
            right--;
        }
        return newNode.next;
    }

    private ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        ListNode newNode = new ListNode(0);
        ListNode index = newNode;
        // 精简1
        while(l1 != null && l2 != null){
            if(l1.val < l2.val){
                index.next = l1;
                l1 = l1.next;
            }else{
                index.next = l2;
                l2 = l2.next;
            }
            index = index.next;
        }
        // 精简2
        index.next = l1 == null? l2 : l1;
        return newNode.next;
    }
}
  • 优秀思路1:迭代分治合并,即1分2,2分4…,分别两两合并后,再逐步两两合并
class Solution {
    public ListNode mergeKLists(ListNode[] lists) {
        if(lists.length == 0) return null;
        if(lists.length == 1) return lists[0];
        if(lists.length == 2) return mergeTwoLists(lists[0], lists[1]);
        ListNode newNode = mergeAll(lists,0,lists.length-1);
        return newNode;
    }
	// 分治
    private ListNode mergeAll(ListNode[] lists,int left,int right){
        if(left==right) return lists[left]; // 不能再分了
        int mid = left + (right - left)/2;
        ListNode part1 = mergeAll(lists,left,mid);
        ListNode part2 = mergeAll(lists,mid+1,right); // 注意要加一
        return mergeTwoLists(part1, part2);
    }

    private ListNode mergeTwoLists(ListNode l1, ListNode l2){
        ListNode newNode = new ListNode(0);
        ListNode index = newNode;
        while(l1 != null && l2 != null){
            if(l1.val < l2.val){
                index.next = l1;
                l1 = l1.next;
            }else{
                index.next = l2;
                l2 = l2.next;
            }
            index = index.next;
        }
        index.next = l1 == null? l2 : l1;
        return newNode.next;
    }
}
  • 优秀思路2:利用优先级队列PriorityQueue自动排序的特性,将所有链表加入PriorityQueue,依次找出最小头节点后加入该节点所在链表的其他元素。注意参数的写法很重要,表明是比较两个节点的val值
class Solution {
    public ListNode mergeKLists(ListNode[] lists) {
    	if(lists.length == 0) return null;
        if(lists.length == 1) return lists[0];
        // 新建一个优先级队列(小根堆)
        Queue<ListNode> pq = new PriorityQueue<>((v1, v2) -> v1.val - v2.val); // 参数的写法很重要,表明是比较两个节点的val值

        // 所有链表的头结点加入PriorityQueue,此时排序根据的是头节点的值
        for (ListNode node: lists) {
            if (node != null) pq.offer(node); // offer 添加一个元素并返回true,如果队列已满,则返回false
        }
        ListNode resNode = new ListNode(0);
        ListNode index = resNode;
        while (!pq.isEmpty()) {
            // 小根堆的根元素最小
            ListNode minNode = pq.poll(); // poll移除并返问队列头部的元素,如果队列为空,则返回`null`
            index.next = minNode;
            // 如果最小节点所在链表还有其他元素,将下一节点加进优先级队列
            if (minNode.next != null) {
                pq.offer(minNode.next); // offer 添加一个元素并返回true,如果队列已满,则返回false
            }
            index = minNode;
        }
        return resNode.next;
    }
}
---

5. 删除链表中的节点(脑筋急转弯题目:无返回函数且未给需要操作的对象参数)

  • 题目描述:请编写一个函数,使其可以删除某个链表中给定的(非末尾)节点。传入函数的唯一参数为 要被删除的节点
  • 优秀思路:因为我们访问不了需被删除节点node之前的节点p1,所以无法让p1指向node后面的节点p2,所以采用的方法是用p2来替换node(这里要注意,传入的需删除节点是链表中真实存在的节点,它里面是存有值和下一个节点的地址的,不能当作一个单纯的外界参数)
class Solution {
    public void deleteNode(ListNode node) {
        node.val = node.next.val;
        node.next = node.next.next;
    }
}

6. 排序链表(思路待看)

  • 题目描述:给你链表的头结点 head ,请将其按 升序 排列并返回排序后的链表 。
  • 我的思路(17%-10%):取出链表的所有值排序,再根据排序值生成新的链表
import java.util.*;
class Solution {
    public ListNode sortList(ListNode head) {
        if(head == null) return null;
        ArrayList<Integer> queue = new ArrayList<Integer>();
        // 取出链表值
        while(head != null){
            queue.add(head.val);
            head = head.next;
        }
        Collections.sort(queue);
        // 生成新链表
        ListNode res = new ListNode(-5);
        ListNode index = res;
        for(int i = 0;i<queue.size();i++){
            index.next = new ListNode(queue.get(i));
            index = index.next;
        }
        return res.next;
    }
}
  • 分析时间复杂度O(nlogn) 的排序算法包括归并排序、堆排序和快速排序(快速排序的最差时间复杂度是 O(n^2)),其中最适合链表的排序算法是归并排序。归并排序基于分治算法。最容易想到的实现方式是自顶向下的递归实现,考虑到递归调用的栈空间,自顶向下归并排序的空间复杂度是O(logn)。如果要达到 O(1)的空间复杂度,则需要使用自底向上的实现方式。

  • 优秀思路1:【自顶向下归并排序】,对链表自顶向下归并排序的过程如下。

    • 1、找到链表的中点,以中点为分界,将链表拆分成两个子链表。寻找链表的中点可以使用快慢指针的做法,快指针每次移动 2步,慢指针每次移动 1步,当快指针到达链表末尾时,慢指针指向的链表节点即为链表的中点。(聪明!!!)
    • 2、对两个子链表分别排序。
    • 3、将两个排序后的子链表合并,得到完整的排序后的链表。
    • 上述过程可以通过递归实现。递归的终止条件是链表的节点个数小于或等于 1,即当链表为空或者链表只包含 1个节点时,不需要对链表进行拆分和排序。
class Solution {
    public ListNode sortList(ListNode head) {
        return mergeSort(head);
    }

    // 归并排序
    private ListNode mergeSort(ListNode head){
        // 如果没有结点/只有一个结点,无需排序,直接返回
        if (head == null || head.next == null) return head;
        // 快慢指针找出中位点
        ListNode slow = head,fast = head.next.next,l,r; // 此处fast不能设置为head,否则无法处理只有2个元素的情况
        while (fast != null && fast.next != null){
            slow = slow.next;
            fast = fast.next.next;
        }
        r = mergeSort(slow.next);// 对右半部分进行归并排序
        // 链表判断结束的标志:末尾节点.next==null
        slow.next = null; // 相当于将head的右半部分截断掉
        l = mergeSort(head);// 对左半部分进行归并排序
        return mergeList(l,r);
    }
    
    // 合并链表
    private ListNode mergeList(ListNode l,ListNode r){
        ListNode tmpHead=new ListNode(-1);
        ListNode p = tmpHead;
        while (l!=null&&r!=null){
            if (l.val<r.val){
                p.next=l;
                l=l.next;
            }else {
                p.next=r;
                r=r.next;
            }
            p=p.next;
        }
        p.next=l==null?r:l;
        return tmpHead.next;
    }
}
  • 优秀思路2(待看):【自底向上归并排序】由于要求空间复杂度为O(1),所以只能采用自底向上的方式。先两个两个的 merge,完成一趟后,再 4 个4个的 merge,直到结束。举个简单的例子:`[4,3,1,7,8,9,2,11,5,6]
step=1: (3->4)->(1->7)->(8->9)->(2->11)->(5->6)
step=2: (1->3->4->7)->(2->8->9->11)->(5->6)
step=3: (1->2->3->4->7->8->9->11)->(5->6)
step=4: (1->2->3->4->5->6->7->8->9->11)
class Solution {
    public ListNode sortList(ListNode head) {
        if (head == null || head.next == null) return head;
        int length = 0;
        ListNode node = head;
        // 求链表长度
        while (node != null) {
            length++;
            node = node.next;
        }
        ListNode resNode = new ListNode(0, head);
        // subLength <<= 1 相当于 subLength = subLength << 1 = subLength^(2)
        for (int subLength = 1; subLength < length; subLength <<= 1) {
            ListNode prev = resNode, curr = resNode.next; 
            while (curr != null) {
                ListNode head1 = curr;
                for (int i = 1; i < subLength && curr.next != null; i++) curr = curr.next;
                ListNode head2 = curr.next; // 后部分
                curr.next = null;
                curr = head2;

                for (int i = 1; i < subLength && curr != null && curr.next != null; i++) curr = curr.next;

                ListNode next = null;
                if (curr != null) {
                    next = curr.next;
                    curr.next = null;
                }

                ListNode merged = merge(head1, head2);
                prev.next = merged;
                while (prev.next != null) prev = prev.next;
                curr = next;
            }
        }
        return resNode.next;
    }

    public ListNode merge(ListNode head1, ListNode head2) {
        ListNode resNode = new ListNode(0);
        ListNode temp = resNode, temp1 = head1, temp2 = head2;
        while (temp1 != null && temp2 != null) {
            if (temp1.val <= temp2.val) {
                temp.next = temp1;
                temp1 = temp1.next;
            } else {
                temp.next = temp2;
                temp2 = temp2.next;
            }
            temp = temp.next;
        }
        if (temp1 != null) {
            temp.next = temp1;
        } else if (temp2 != null) {
            temp.next = temp2;
        }
        return resNode.next;
    }
}

🚩7. 复制带随机指针的链表(同剑指Offer.7)

  • 题目描述:给你一个长度为 n 的链表,每个节点包含一个额外增加的随机指针 random ,该指针可以指向链表中的任何节点或空节点。
  • 优秀思路1:利用 HashMap 存储新链表和给出链表的关系(两次遍历),从而避免新链表指向原链表
class Solution {
    public Node copyRandomList(Node head) {
        HashMap<Node,Node> map = new HashMap<Node,Node>();
        Node p = head;
        while(p != null){
            Node newNode = new Node(p.val);
            map.put(p,newNode);
            p = p.next;
        }
        p = head;
        while(p != null){
            Node temp = map.get(p);
            temp.next = p.next==null? null : map.get(p.next); // 这样获取出来的next是不指向head的
            temp.random = p.random==null? null : map.get(p.random);
            p = p.next;
        }
        return map.get(head); //新的头结点
    }
}
  • 优秀思路2:递归实现以上过程,强无敌
class Solution {
    private HashMap<Node, Node> map = new HashMap<>();
    public Node copyRandomList(Node head) {
        if (head == null) return null;
        // 不管是next还是random指向的节点,只要没有该节点,就创造该节点对应的新节点
        if (map.containsKey(head)) return map.get(head);
        Node root = new Node(head.val);
        map.put(head, root);
        root.next = copyRandomList(head.next);
        root.random = copyRandomList(head.random);
        return root;
    }
}

8. 环形链表

  • 题目描述:给定一个链表,判断链表中是否有环。
  • 普通思路:哈希集合存储已有值进行对比
public class Solution {
    public boolean hasCycle(ListNode head) {
        if(head == null || head.next == null) return false;
        HashSet<ListNode> set = new HashSet<>();
        while(head != null){
        // 可精简
            if(set.contains(head)) return true;
            else set.add(head);
            head = head.next;
        }
        return false;
    }
}
public class Solution {
    public boolean hasCycle(ListNode head) {
        Set<ListNode> seen = new HashSet<ListNode>();
        while (head != null) {
        	// 精简后:HashSet的add方法:如果不存在该元素则添加进去并返回true,否则返回false
            if (!seen.add(head)) return true;
            head = head.next;
        }
        return false;
    }
}
  • 优秀思路:快慢指针

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

  • 题目描述:给定一个链表,两两交换(每两个交换依次)其中相邻的节点,并返回交换后的链表。
  • 优秀思路1:【递归】其中我们应该关心的主要有三点:① 返回值;② 调用单元做了什么;③ 终止条件,在本题中:
    • 返回值:交换完成的子链表
    • 调用单元:设需要交换的两个点为 head 和 next,head 连接后面交换完成的子链表,next 连接 head,完成交换
    • 终止条件:head 为空指针或者 next 为空指针,也就是当前无节点或者只有一个节点,无法进行交换
class Solution {
    public ListNode swapPairs(ListNode head) {
        if(head == null || head.next == null) return head;
        ListNode newNode = head.next;
        head.next = swapPairs(newNode.next); // 1指向后面部分
        newNode.next = head;// 2指向1
        return newNode;
    }
}
  • 优秀思路1:【迭代】注意要在head前面补一个0,理由是简单分析一下就可以知道调转中间的两个节点时,需要知道三个地址,所以为了方便操作前两位,给head前面补0
class Solution {
    public ListNode swapPairs(ListNode head) {
        // 简单分析一下就可以知道调转中间的两个节点时,需要操作三次,所以为了方便操作前两位,给head前面补0
        ListNode newNode = new ListNode(0,head);
        ListNode cur = newNode;
        while(cur.next != null && cur.next.next != null){
            ListNode start = cur.next;
            ListNode end = cur.next.next;
            start.next = end.next;
            end.next = start;
            cur.next = end;
            cur = start;
        }
        return newNode.next;
    }
}

10. 回文链表

  • 题目描述:请判断一个链表是否为回文链表。
  • 我的思路(5% - 5%):利用栈后进先出的特点,根据链表节点数的奇偶性判断。若遇到相同元素则pop,否则push新元素
class Solution {
    public boolean isPalindrome(ListNode head) {
        if(head == null || head.next == null) return true;
        // 求长度
        int len = 0;

        ListNode index= head;
        while(index != null){
            len++;
            index = index.next;
        }
        // 判断是否回文
        Stack<Integer> stack = new Stack<Integer>();
        stack.push(-1);
        index = head; // 回到开头,别忘了
        if(len % 2 == 0){ // 偶数节点链表
            while(index != null){
                if(index.val == stack.peek()) stack.pop();
                else stack.push(index.val);
                index = index.next; //总是忘
            }
        }else{  // 奇数节点链表
            int count = 0;
            while(index != null){
                count++;
                if(count != (len+1)/2){
                    if(index.val == stack.peek()) stack.pop();
                    else stack.push(index.val);
                }
                index = index.next; //总是忘
            }
        }
        if(stack.peek() == -1) return true;
        return false;
    }
}
  • 稍优秀思路(30%-30%):新建存储数组列表存储链表值,再定义双指针从两端开始往中间比较
class Solution {
    public boolean isPalindrome(ListNode head) {
        List<Integer> vals = new ArrayList<Integer>();

        // 将链表的值复制到数组中
        ListNode currentNode = head;
        while (currentNode != null) {
            vals.add(currentNode.val);
            currentNode = currentNode.next;
        }

        // 使用双指针判断是否回文
        int front = 0;
        int back = vals.size() - 1;
        while (front < back) {
            if (!vals.get(front).equals(vals.get(back))) {
                return false;
            }
            front++;
            back--;
        }
        return true;
    }
}
  • 优秀思路(43 % - 57 %):使用快慢指针法找到链表中点,将前半部分(或后半部分)翻转,于另一部分进行比较(注意要忽略奇数链表的中间节点),相同则回文
// 我的代码
class Solution {
    public boolean isPalindrome(ListNode head) {
        if(head == null || head.next == null) return true;
        ListNode fast = head, slow = head, pre = null , post = null;; // pre 保存 slow的上一节点
        while(fast != null && fast.next != null){
            pre = slow;
            fast = fast.next.next;
            slow = slow.next;
        }
        post = pre.next; // 后半部分
        pre.next = null; // 截断head的右半部分
        if(fast != null){ //奇数项链表,需忽略中点
            post = post.next; 
        }
        pre = reverseListNode(head); // 前半部分颠倒
        return isSame(pre,post);
    }
    // 反转链表
    private ListNode reverseListNode(ListNode head){
        if(head == null || head.next == null) return head;
        ListNode pre = null,next = null;
        while(head != null){
            next = head.next;
            head.next = pre;
            pre = head;
            head = next;
        }
        return pre;
    }
    
    // 比较链表是否相同
    private boolean isSame(ListNode node1,ListNode node2){
        while(node1 != null && node2 != null){
            if(node1.val != node2.val) return false;
            node1 = node1.next;
            node2 = node2.next;
        }
        if(node1 != null || node2 != null) return false; // 长度不一致
        return true;
    }
}
  • 优秀思路改进(85% - 54 %):没有必要全部找出前半部分之后再翻转,可以想办法在寻找前半部分的同时进行翻转
class Solution {
    public boolean isPalindrome(ListNode head) {
        if(head == null || head.next == null) return true;
        ListNode fast = head, slow = head, pre = null , preReverse = null;; // pre 保存 slow的上一节点
        // 边寻找边反转
        while(fast != null && fast.next != null){
            // 寻找中点部分
            pre = slow;
            fast = fast.next.next;
            slow = slow.next;
            // 反转部分
            pre.next = preReverse;
            preReverse = pre;
        }
        if(fast != null){ //奇数项链表,后半部分需忽略中点
            slow = slow.next; 
        }
        while(preReverse != null && slow != null){
            if(preReverse.val != slow.val) return false;
            preReverse = preReverse.next;
            slow = slow.next;
        }
        if(preReverse != null || slow != null) return false; // 两链表长度不一致
        return true;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值