【算法面试题汇总】LeetBook列表的算法面试题汇总---链表题目及答案

如果有错的还请各位大佬指出呀
有些是copy的还望不要介意
本人只做学习记录

链表

      • 复制带随机指针的链表
      • 环形链表
      • 排序链表
      • 相交链表
      • 反转链表
      • 回文链表
      • 删除链表中的节点
      • 奇偶链表

复制带随机指针的链表

题目描述:
给你一个长度为 n 的链表,每个节点包含一个额外增加的随机指针 random ,该指针可以指向链表中的任何节点或空节点。
构造这个链表的深拷贝。深拷贝应该正好由 n 个全新节点组成,其中每个新节点的值都设为其对应的原节点的值。新节点的 next 指针和 random 指针也都应指向复制链表中的新节点,并使原链表和复制链表中的这些指针能够表示相同的链表状态。复制链表中的指针都不应指向原链表中的节点
例如,如果原链表中有 X 和 Y 两个节点,其中 X.random --> Y 。那么在复制链表中对应的两个节点 x 和 y ,同样有 x.random --> y 。
返回复制链表的头节点。
用一个由 n 个节点组成的链表来表示输入/输出中的链表。每个节点用一个 [val, random_index] 表示:

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

你的代码 只 接受原链表的头节点 head 作为传入参数。

示例1:
【算法面试题汇总】LeetBook列表的算法面试题汇总---链表题目及答案_第1张图片

输入:head = [[7,null],[13,0],[11,4],[10,2],[1,0]]
输出:[[7,null],[13,0],[11,4],[10,2],[1,0]]
  • 回溯+哈希表
    时间O(n)空间O(n)
/*
// Definition for a Node.
class Node {
    int val;
    Node next;
    Node random;

    public Node(int val) {
        this.val = val;
        this.next = null;
        this.random = null;
    }
}
*/

class Solution {
    //记录每个节点对应新创建的节点
    Map<Node,Node> map = new HashMap<>();

    public Node copyRandomList(Node head) {
        if(head==null){
            return null;
        }
        if(!map.containsKey(head)){
            //map中没有便新创建
            Node newNode = new Node(head.val);
            map.put(head,newNode);
            newNode.next = copyRandomList(head.next);
            newNode.random = copyRandomList(head.random);
        }
        //因为一个节点可能被多个节点指向,所以为了防止重复拷贝,可以直接从哈希表中取出拷贝后的节点的指针
        return map.get(head);
    }
}
  • 拆分
    时间O(n)空间O(1)
/*
// Definition for a Node.
class Node {
    int val;
    Node next;
    Node random;

    public Node(int val) {
        this.val = val;
        this.next = null;
        this.random = null;
    }
}
*/

class Solution {
    public Node copyRandomList(Node head) {
        if(head == null){
            return null;
        }
        //复制节点:A->A1->B->B1
        Node cur = head;
        while(cur != null){
            Node next = cur.next; //B
            cur.next = new Node(cur.val); //A1
            cur.next.next = next;
            cur = next;
        }

        //复制随机节点
        cur = head;
        while(cur != null){
            Node curNew = cur.next;//A1
            A->A1->B->B1->C->C1 比如A.random->c 让A1.random->C.next 也就是C1
            curNew.random = cur.random == null ? null : cur.random.next;
            cur = cur.next.next;//B
        }

        //拆分,比如把 A->A1->B->B1拆分成 A->B和A1->B1
        Node headNew = head.next;
        cur = head;
        Node curNew = head.next;
        while(cur != null){
            cur.next = cur.next.next;
            cur = cur.next;
            curNew.next = cur == null ? null : cur.next;
            curNew = curNew.next;
        }
        return headNew;
    }
}

环形链表

题目描述:
给你一个链表的头节点 head ,判断链表中是否有环。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。
如果链表中存在环 ,则返回 true 。 否则,返回 false 。

示例1:
【算法面试题汇总】LeetBook列表的算法面试题汇总---链表题目及答案_第2张图片

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

    时间O(n)空间O(n)

/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public boolean hasCycle(ListNode head) {
        Set<ListNode> set = new HashSet<>();
        while(head != null){
            if(!set.add(head)){
                return true;
            }
            head = head.next;
        }
        return false;
    }
}

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

  • 快慢双指针

    时间O(n)空间O(1)

/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public boolean hasCycle(ListNode head) {
        if(head == null || head.next == null){
            return false;
        }
        ListNode slow = head;
        ListNode fast = head.next;
        while(slow != fast){
            if(fast == null || fast.next == null){
                return false;
            }
            slow = slow.next;
            fast = fast.next.next;
        }
        return true;
    }
}

排序链表

题目描述:
给你链表的头结点 head ,请将其按 升序 排列并返回 排序后的链表

示例:
【算法面试题汇总】LeetBook列表的算法面试题汇总---链表题目及答案_第3张图片

输入:head = [4,2,1,3]
输出:[1,2,3,4]
  • 自顶向下归并排序

    时间O(nlogn)空间O(logn)

/**
 * Definition for singly-linked list.
 * 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; }
 * }
 */
class Solution {
    public ListNode sortList(ListNode head) {
        return sort(head,null);
    }
    private ListNode sort(ListNode head,ListNode tail){
        if(head == null){
            return head;
        }
        //此时链表只有一个节点了
        if(head.next == tail){
            //
            head.next = null;
            return head;
        }
        ListNode slow = head,fast = head;
        while(fast != tail){
            //当fast到达尾部时,slow为中间节点
            slow = slow.next;
            fast = fast.next;
            if(fast != tail){
                fast = fast.next;//fast移动两个位置
            }
        }
        ListNode mid = slow;
        ListNode list1 = sort(head,mid);
        ListNode list2 = sort(mid,tail);
        ListNode sorted = merge(list1,list2);
        return sorted;
    }

    private ListNode merge(ListNode list1,ListNode list2){
        ListNode sortHead = new ListNode(0);
        ListNode temp = sortHead,temp1 = list1,temp2 = list2;
        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 sortHead.next;
    }
}
  • 自底向上归并*

    时间O(nlogn)空间O(1)
    (这种本人还未搞明白所以是直接复制一位大佬的)

class Solution {
     // 自底向上归并排序
    public ListNode sortList(ListNode head) {
        if(head == null){
            return head;
        }

        // 1. 首先从头向后遍历,统计链表长度
        int length = 0; // 用于统计链表长度
        ListNode node = head;
        while(node != null){
            length++;
            node = node.next;
        }

        // 2. 初始化 引入dummynode
        ListNode dummyHead = new ListNode(0);
        dummyHead.next = head;

        // 3. 每次将链表拆分成若干个长度为subLen的子链表 , 并按照每两个子链表一组进行合并
        for(int subLen = 1;subLen < length;subLen <<= 1){ // subLen每次左移一位(即sublen = sublen*2) PS:位运算对CPU来说效率更高
            ListNode prev = dummyHead;
            ListNode curr = dummyHead.next;     // curr用于记录拆分链表的位置

            while(curr != null){               // 如果链表没有被拆完
                // 3.1 拆分subLen长度的链表1
                ListNode head_1 = curr;        // 第一个链表的头 即 curr初始的位置
                for(int i = 1; i < subLen && curr != null && curr.next != null; i++){     // 拆分出长度为subLen的链表1
                    curr = curr.next;
                }

                // 3.2 拆分subLen长度的链表2
                ListNode head_2 = curr.next;  // 第二个链表的头  即 链表1尾部的下一个位置
                curr.next = null;             // 断开第一个链表和第二个链表的链接
                curr = head_2;                // 第二个链表头 重新赋值给curr
                for(int i = 1;i < subLen && curr != null && curr.next != null;i++){      // 再拆分出长度为subLen的链表2
                    curr = curr.next;
                }

                // 3.3 再次断开 第二个链表最后的next的链接
                ListNode next = null;        
                if(curr != null){
                    next = curr.next;   // next用于记录 拆分完两个链表的结束位置
                    curr.next = null;   // 断开连接
                }

                // 3.4 合并两个subLen长度的有序链表
                ListNode merged = mergeTwoLists(head_1,head_2);
                prev.next = merged;        // prev.next 指向排好序链表的头
                while(prev.next != null){  // while循环 将prev移动到 subLen*2 的位置后去
                    prev = prev.next;
                }
                curr = next;              // next用于记录 拆分完两个链表的结束位置
            }
        }
        // 返回新排好序的链表
        return dummyHead.next;
    }


    // 此处是Leetcode21 --> 合并两个有序链表
    public ListNode mergeTwoLists(ListNode l1,ListNode l2){
        ListNode dummy = new ListNode(0);
        ListNode curr  = dummy;

        while(l1 != null && l2!= null){ // 退出循环的条件是走完了其中一个链表
            // 判断l1 和 l2大小
            if (l1.val < l2.val){
                // l1 小 , curr指向l1
                curr.next = l1;
                l1 = l1.next;       // l1 向后走一位
            }else{
                // l2 小 , curr指向l2
                curr.next = l2;
                l2 = l2.next;       // l2向后走一位
            }
            curr = curr.next;       // curr后移一位
        }

        // 退出while循环之后,比较哪个链表剩下长度更长,直接拼接在排序链表末尾
        if(l1 == null) curr.next = l2;
        if(l2 == null) curr.next = l1;

        // 最后返回合并后有序的链表
        return dummy.next; 
    }
}

相交链表

给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。

图示两个链表在节点 c1 开始相交:

【算法面试题汇总】LeetBook列表的算法面试题汇总---链表题目及答案_第4张图片
题目数据 保证 整个链式结构中不存在环。
注意,函数返回结果后,链表必须 保持其原始结构 。
自定义评测:
评测系统 的输入如下(你设计的程序 不适用 此输入):

intersectVal - 相交的起始节点的值。如果不存在相交节点,这一值为 0
listA - 第一个链表
listB - 第二个链表
skipA - 在 listA 中(从头节点开始)跳到交叉节点的节点数
skipB - 在 listB 中(从头节点开始)跳到交叉节点的节点数

评测系统将根据这些输入创建链式数据结构,并将两个头节点 headA 和 headB 传递给你的程序。如果程序能够正确返回相交节点,那么你的解决方案将被 视作正确答案 。

示例:
【算法面试题汇总】LeetBook列表的算法面试题汇总---链表题目及答案_第5张图片

输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,6,1,8,4,5], skipA = 2, skipB = 3
输出:Intersected at '8'
解释:相交节点的值为 8 (注意,如果两个链表相交则不能为 0)。
从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,6,1,8,4,5]。
在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。
— 请注意相交节点的值不为 1,因为在链表 A 和链表 B 之中值为 1 的节点 (A 中第二个节点和 B 中第三个节点) 是不同的节点。换句话说,它们在内存中指向两个不同的位置,而链表 A 和链表 B 中值为 8 的节点 (A 中第三个节点,B 中第四个节点) 在内存中指向相同的位置。
  • 代码实现

    时间O(n)空间O(1)

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        if(headA == null || headA == null){
            return null;
        }
        ListNode listA = headA;
        ListNode listB = headB;

        while(listA != listB){
            //若该链表的节点移动到null时还没找到则指向另一个链表找
            listA = listA == null? headB : listA.next;
            listB = listB == null? headA : listB.next;
        }
        return listA;
    }
}

反转链表

题目描述:

给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。

示例:
【算法面试题汇总】LeetBook列表的算法面试题汇总---链表题目及答案_第6张图片

输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]

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

  • 迭代

    时间O(n)空间O(1)

/**
 * Definition for singly-linked list.
 * 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; }
 * }
 */
class Solution {
    public ListNode reverseList(ListNode head) {
        ListNode prev = null;
        ListNode cur = head;

        //将指向下一个节点的指针指向上一个节点
        while(cur != null){
            ListNode next = cur.next;
            cur.next = prev;
            prev = cur;
            cur = next;
        }
        return prev;
    }
}
  • 递归

    时间O(n)空间O(n)

/**
 * Definition for singly-linked list.
 * 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; }
 * }
 */
class Solution {
    public ListNode reverseList(ListNode head) {
        //终止条件
        if(head == null || head.next == null){
            return head;
        }
        ListNode cur = reverseList(head.next);
        //当前节点的下一个节点指向当前节点
        head.next.next = head;
        //原本当前节点指向下一节点的指针指向null
        //使x->y改为x<-y,将->指向null 
        head.next = null;
        return cur;
    }
}

回文链表

题目描述:

给你一个单链表的头节点 head ,请你判断该链表是否为回文链表。如果是,返回 true ;否则,返回 false

示例:

【算法面试题汇总】LeetBook列表的算法面试题汇总---链表题目及答案_第7张图片

输入:head = [1,2,2,1]
输出:true
  • 数组

    时间O(n)空间O(n)

/**
 * Definition for singly-linked list.
 * 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; }
 * }
 */
class Solution {
    public boolean isPalindrome(ListNode head) {
        //将链表的值保存在数组列表中
        List<Integer> list = new ArrayList<>();
        ListNode cur = head;
        while(cur != null){
            list.add(cur.val);
            cur = cur.next;
        }
        
        int left = 0;
        int right = list.size()-1;
        while(left<right){
            if(list.get(left) != list.get(right)){
                return false;
            }
            left++;
            right--;
        }
        return true;
    }
}
  • 快慢指针反转后半部分链表
/**
 * Definition for singly-linked list.
 * 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; }
 * }
 */
class Solution {
    public boolean isPalindrome(ListNode head) {
        ListNode fast = head,slow = head;
        //通过快慢指针找到中点
        while(fast != null && fast.next != null){
            fast = fast.next.next;
            slow = slow.next;
        }
        //若fast不为null,则为奇数
        if(fast != null){
            slow = slow.next;
        }
        //反转后半部分链表
        slow = reverse(slow);

        //快指针返回到头节点,和慢指针同步移动进行比较
        fast = head;
        while(slow!=null){
            if(fast.val != slow.val){
                return false;
            }
            fast = fast.next;
            slow = slow.next;
        }
        return true;
    }

    private ListNode reverse(ListNode head){
        ListNode prev = null;
        while(head!=null){
            ListNode next = head.next;
            head.next = prev;
            prev = head;
            head = next;
        }
        return prev;
    }
}
  • 递归
/**
 * Definition for singly-linked list.
 * 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; }
 * }
 */
class Solution {
    ListNode temp;
    public boolean isPalindrome(ListNode head) {
        temp = head;
        return check(head);
    }
    public boolean check(ListNode head){
        if(head == null){
            return true;
        }
        /**链表的逆序打印
        *private void printListNode(ListNode head) {
        *   if (head == null)
        *       return;
        *   printListNode(head.next);
        *   System.out.println(head.val);
        *}
        */
        //通过逆序遍历和正序遍历进行比较
        boolean res = check(head.next) && (temp.val == head.val);
        temp = temp.next;
        return res;
    }
}
/**
 * Definition for singly-linked list.
 * 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; }
 * }
 */
class Solution {
    public boolean isPalindrome(ListNode head) {
        if(head==null){
            return true;
        }
        ListNode temp = head;
        Stack<Integer> stack = new Stack();
        int len = 0;
        //把链表的值存放在栈中
        while(temp != null){
            stack.push(temp.val);
            temp = temp.next;
            len++;
        }
        //链表长度除以2
        len>>=1;
        //出一半的栈
        while(len-->=0){
            if(head.val != stack.pop()){
                return false;
            }
            head = head.next;
        }
        return true;


    }
}

删除链表中的节点

题目描述:
有一个单链表的 head,我们想删除它其中的一个节点 node。
给你一个需要删除的节点 node 。你将 无法访问 第一个节点 head。
链表的所有值都是 唯一的,并且保证给定的节点 node 不是链表中的最后一个节点
删除给定的节点。注意,删除节点并不是指从内存中删除它。这里的意思是:

给定节点的不应该存在于链表中。
链表中的节点数应该减少 1。
node 前面的所有值顺序相同。
node 后面的所有值顺序相同。

自定义测试:
对于输入,你应该提供整个链表 head 和要给出的节点 node。node 不应该是链表的最后一个节点,而应该是链表中的一个实际节点。
我们将构建链表,并将节点传递给你的函数。
输出将是调用你函数后的整个链表。

示例:

【算法面试题汇总】LeetBook列表的算法面试题汇总---链表题目及答案_第8张图片

输入:head = [4,5,1,9], node = 5
输出:[4,1,9]
解释:指定链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9
  • 与下一个节点交换

    时间O(1)空间O(1)

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public void deleteNode(ListNode node) {
        //将该节点的值改为下一个节点的值
        //4->5->1->9 => 4->1->1->9
        node.val = node.next.val;
        //将该节点的下一个节点删除
        //4->1->9
        node.next = node.next.next;
    }
}

奇偶链表

题目描述:

给定单链表的头节点 head ,将所有索引为奇数的节点和索引为偶数的节点分别组合在一起,然后返回重新排序的列表。
第一个节点的索引被认为是 奇数 , 第二个节点的索引为 偶数 ,以此类推。
请注意,偶数组和奇数组内部的相对顺序应该与输入时保持一致。
你必须在 O(1) 的额外空间复杂度和 O(n) 的时间复杂度下解决这个问题。

示例:

【算法面试题汇总】LeetBook列表的算法面试题汇总---链表题目及答案_第9张图片

输入: head = [1,2,3,4,5]
输出: [1,3,5,2,4]
  • 分离奇偶节点后合并

    时间O(n)空间O(1)

/**
 * Definition for singly-linked list.
 * 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; }
 * }
 */
class Solution {
    public ListNode oddEvenList(ListNode head) {
        if(head == null){
            return head;
        }
        ListNode evenHead = head.next;
        //头节点为奇数链的头节点
        ListNode odd = head,even = evenHead;
        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;

    }
}

你可能感兴趣的:(数据结构与算法,链表,算法,数据结构,java)