牛客网算法笔记(基础班第三课)

1

用数组结构实现大小固定的栈和队列(一面常见题目)

【思路】

栈:

​ 用 index 控制 push() 和 pop() 。

​ 当 index =0 时,不可以 pop();

​ 当 index = 数组长度 时,不可以 push()。

队列:

​ 循环队列,设置 start(重点)、end 和 size; //用size 解耦 start 和 end

​ 用 size 控制 start 和 end 的 存取,size=0时,不可以操作 start ,size = 数组大小 时,不可以操作 end。

public class Array_To_Stack_Queue {
    public static class ArrayStack {
        private Integer[] arr;
        private Integer index;
        
        public ArrayStack(int initSize){
            if(initSize < 0){
                throw new IllegalArgumentException("The init size is less than 0");
            }
            this.arr = new Integer[initSize];
            this.index = 0;
        }
        
        public void push(int obj){
            if(index == arr.length){
                throw new ArrayIndexOutOfBoundsException("The Stack is full");
            }
            arr[index++] = obj;
        }
        
        public Integer pop() {
            if (index == 0){
                throw new ArrayIndexOutOfBoundsException("The Stack is empty");
            }
            return arr[--index];
        }
        
        public Integer peek(){
            if (index == 0){
                return null;
            }
            return arr[index - 1];
        }
    }
    
    public static class ArrayQueue {
        private Integer[] arr;
        private Integer size;
        private Integer start;
        private Integer end;
        
        public ArrayQueue(int initSize){
            if (initSize < 0) {
                throw new IllegalArgumentException("The init size is less than 0");
            }
            arr = new Integer[initSize];
            this.size = 0;
            this.start = 0;
            this.end = 0;
        }
        
        public Integer poll(){
            if (size == 0){
                throw new ArrayOutOfBoundException("The Queue is empty");
            }
            size--;
            int tmp = arr[start];
            start = nextIndex(arr.length, start);
            return tmp;
        }
        
        public void push(int obj){
            if (size == arr.length){
                throw new ArrayOutOfBoundException("The Queue is full");
            }
            size++;
            
            //my
            arr[(end % arr.length)++] = obj;
            //zcy
            arr[end] = obj;
            end = nextIndex(arr.length, end);
        }
        
        public Integer peek(){
            if (size == 0){
                return null;
            }
            return arr[start];
        }
        
        public int nextIndex(int size, int index){
            return index == size - 1 ? 0 : index + 1;
        }
    }
}
2

实现一个特殊的栈,在实现栈的基本功能的基础上,再实现返回栈中最小元素的操作

【要求】

1、pop、push、getMin 操作的时间复杂度都是 O(1);

2、设计的栈类型可以使用现成的栈结构。

【思路】

未要求空间复杂度,

使用一个相同长度的辅助栈,记录压栈或出栈后对应更新的最小值。

【思路进阶】

节省辅助栈空间,

压栈:当前数 <= 辅助栈顶,才压入辅助栈道;

出栈:当前数 == 辅助栈顶, 辅助栈才出栈;

public class Array_To_Stack_Queue{
    public static class GetMinStack1{
        private Stack<Integer> stackData;
        private Stack<Integer> stackMin;
        
        public GetMinStack1(){
            this.stackData = new Stack<Integer>();
            this.stackMin = new Stack<Integer>();
        }
        
        public void push(int newNum){
            this.stackData.push(newNum);
            
            if(this.stackMin.isEmpty()){
                this.stackMin.push(newNum);
            }
            else if (newNum < this.getMin()){
                this.stackMin.push(newNUm);
            }
            else{
                int newMIn = this.stackMin.peek();
                this.stackMin.push(newMin);
            }
        }
        
        public int pop(){
            if(this.stackdata.isEmpty()){
                throw new RuntimeException("The stack is empty.");
            }
            this.stackMin.pop();
            return this.stackData.pop();
        }
        
        public int getMin(){
            if(this.stackMin.isEmpty()){
                throw new RuntimeException("The stack is empty.");
            }
            return this.stackMin.peek()
        }
    }
    
    public static class GetMinStack2 {
        private Stack<Integer> stackData;
        private Stack<Integer> stackMin;
        
        public GetMinStack2(){
            this.stackData = new Stack<Integer>();
            this.stackMin = new Stack<Integer>();
        }
        
        public void push(int newNum){
            this.stackData.push(newNum);
            if (this.stackMIn.isEmpty()){
                this.stackMin.push(newNUM);
            }
            if (newNum <= this.getMin()){
                this.stackMin.push(newNum);
            }
        }
        
        public int pop(){
            if(this.stackData.isEmpty()){
                throw new RuntimeException("The stack is empty.");
            }
            if(this.stackData.peek() == this.getMin()){
                this.stackMin.pop();
            }
            return this.stackData.pop()
        }
        
        public int getMin(){
            if(this.stackMin.isEmpty()){
                return new RuntimeException("The stack is empty.");
            }
            return this.stackMin.peek();
        }
    }
}
3

3-1 如何仅用队列结构实现栈结构?(套路:用队列做深度遍历)

3-2 如何仅用栈结构实现队列结构?()

【思路】

3-1:

​ 使用一个相同的辅助队列;

​ 压栈:正常操作;

​ 出栈:每次将其中一个队列的前 length-1 存入另一个队列,取出最后一个数,然后两个队列交换引用;

​ peek:同出栈,但最后一个数也要存入另一个队列。

3-2:

​ 使用一个相同的辅助栈,分别接收 push 和 pop;

​ 压栈:正常压入 push 栈;

​ 出栈:pop 栈为空时,将 push 栈全部倒入,pop 栈直接出栈;

​ peek:与出栈类似;

​ 关键点:

​ 倒的时机: pop 栈为空才入栈,且一次性倒完

public class Array_To_Stack_Queue{
    public static class TwoQueuesStack{
        private Queue<Integer> queue;
        private Queue<Integer> help;
        
        pulbic TwoQueuesStack() {
            queue = new LinkedList<Integer>();
            help = new LinkedeList<Integer>();
        }
        public void push(int pushInt){
            this.queue.add(pushInt);
        }
        
        public int pop(){
            if(this.queue.isEmpty()){
                throw new RuntimeException("The stack is empty.");
            }
            while(this.queue.size() > 1){
                this.help.add(this.queue.poll());
            }
            int result = this.queue.poll();
            swap();
            return result;
        }
        public int peek(){
            if(this.queue.isEmpty()){
                throw new RuntimeException("The stack is empty.");
            }
            while(this.queue.size() > 1){
                this.help.add(this.queue.poll());
            }
            int result = this.queue.poll();
            this.help.add(result);
            swap();
            return result;
        }
        pulbic void swap(){
            Queue<Integer> tmp = help;
            help = queue;
            queue = help;
        }
    }
    
    public static class TwoStacksQueue{
        private Queue<Integer> stackPop;
        private Queue<Integer> stackPush;
        
        public TwoStacksQueue(){
            stackPop = new Stack<Integer>();
            stackPush = new Stack<Integer>();
        }
        
        public void goTo(){
            if(stackPop.isEmpty()){
                while(!stackPush.isEmpty()){
                    stackPop.push(stackPush.pop());
                }
            }
        }
        
        public void push(int pushInt){
            stackPush.push(pushInt);
            goTo();
        }
        
        public int poll(){
            goTo();
            if(stackPop.isEmpty() && stackPush.isEmpty()){
                throw new RuntimeException("The queue is empty.");
            }
            return stackPop.pop();
        }
        
        public int peek(){
            goTo(); //放在哪里都可以,因为有判断条件。
            if(stackPop.isEmpty() && stackPush.isEmpty()){
                throw new RuntimeException("The queue is empty.");
            }
            return stackPop.peek();
        }
    }
}
4

猫狗队列

【 题目】 宠物、 狗和猫的类如下:
public class Pet {

​ private String type;

​ public Pet(String type) {

​ this.type = type; }

​ public String getPetType() {

​ return this.type; } }

public class Dog extends Pet {

​ public Dog() {

​ super(“dog”); } }

public class Cat extends Pet {

​ public Cat() {

​ super(“cat”);}}

实现一种狗猫队列的结构,要求如下:

用户可以调用 add 方法将 cat 类或 dog 类的实例放入队列中;

用户可以调用 pollAll 方法,将队列中所有实例按照进队列的先后顺序依次弹出;

用户可以调用 pollDog 方法,将队列中的 dog 类的实例按照进队列先后顺序依次弹出;

用户可以调用 pollCat 方法,将队列中的 cat 类的实例按照进队列先后顺序依次弹出;

用户可以调用 isEmpty 方法,检查队列中是否有实例;

用户可以调用 isDogEmpty 方法,检查队列中是否有 dog 类实例;

用户可以调用 isCatEmpty 方法,检查队列中是否有 cat 类实例。

【思路】

​ 用两个队列分别存 cat 和 dog;

​ 封装一个数据项表示总体进队顺序。

public class CatDogQueue {
    public static class Pet{
        private String type;
        
        public Pet(String type){
            this.type = type;
        }
        
        pulbic String getType(){
            return this.type;
        }
    }
    
    public static class Dog extends Pet {
        public Dog(){
            super("dog");
        }
    }
    
    public static class Cat extends Pet {
        public Cat() {
            super("cat");
        }
    }
    
    //上面的类没有表示进队顺序的数据项,因此封装一个新类
    public static class EnterQueuePet {
        private Pet pet;
        private long index;
        
        public EnterQueuePet(Pet pet, long index) {
            this.pet = pet;
            this.index = index;
        }
        
        public Pet getPet(){
            return this.pet;
        }
        
        public long getIndex() {
            return this.index;
        }
        
        public String getEnterPetType() {
            return this.pet.getType();
        }
    }
    
    public static class CatDogQueue {
        private Queue<EnterQueuePet> catQ;
        private Queue<EnterQueuePet> dogQ;
        private long index;
        
        public CatDogQueue() {
            catQ = new LinkedList<EnterQueuePet>();
            dogQ = new LinkedList<EnterQueuePet>();
            this.index = 0;
        }
        
        public void add(Pet pet) {
            if(pet.getType().equals("dog")){
                this.dogQ.add(new EnterQueuePet(pet, index++));
            }
            else if(pet.getType().equals("cat")){
                this.catQ.add(new EnterQueuePet(pet, index++));
            }
            else {
                throw new RuntimeException("Err, not cat or dog");
            }
        }
        
        public Dog pollDog() {
            if(!this.dogQ.isEmpty()){
                return (Dog) this.dogQ.poll().getPet();
            }
            else{
                throw new RuntimeException("Dog queue is empty.");
            }
        }
        
        public Cat pollCat() {
            if(this.catQ.isEmpty()){
                throw new RuntimeException("Cat queue is empty.");
            }
            else{
                return (Cat) this.catQ.poll().getPet();
            }
        }
        
        public Pet pollAll() {
            if(!this.catQ.isEmpty() && !this.dogQ.isEmpty()){
                if(this.catQ.peek().getIndex() < this.dogQ.peek().getIndex()){
                    return this.catQ.poll().getPet();
                }
                else{
                    return this.dogQ.poll().getPet();
                }
            }
            else if(!this.catQ.isEmpty()){
                return this.catQ.poll().getPet();
            }
            else if(!this.dogQ.isEmpty()){
                retutn this.dogQ.poll().getPet();
            }
            else{
                throw new RuntimeException("Err, queue is empty.");
            }
        }
        
        public boolean isEmpty(){
            if(!this.dogQ.isEmpty() || !this.catQ.isEmpty()){
                return true;
            }
            else{
                return false;
            }
        }
        
        public boolean isDogEmpty(){
            return this.dogQ.isEmpty() ? true : false;
        }
        
        public boolean isCatEmpty(){
            return this.catQ.isEmpty() ? true : false;
        }
    }
}
5

转圈打印矩阵

【题目】给定一个整型矩阵 matrix,请按照转圈的方式打印它。

例如:

1 2 3 4

5 6 7 8

9 10 11 12

13 14 15 16

打印结果为: 1 2 3 4 8 12 16 15 14 13 9 5 6 7 11 10

【要求】 额外空间复杂度为 O(1) 。

【思路】

宏观调度(借助全局点遍历)

​ 用 2 个坐标标记矩阵的首尾2个角 ,用坐标x或y的变化来遍历;

​ 考虑特殊情况:矩阵只有一列或一行;

​ 每一圈遍历后更新首尾两点。

public class PrintMatrixSpiralOrder {
    public static void spiralOrderPrint(int[][] matrix){
        int row1 = 0;
        int col1 = 0;
        int row2 = matrix[0].length - 1;
        int col2 = matrix.length - 1;
        
        while(row1 <= row2 && col1 <= col2){
            printEdge(matrix, row1++, col1++, row2--, col2--);
        }
    }
    
    public static void printEdge(int[][] m, int row1, int col1, int row2, int col2) {
    if(row1 == row2){
        for(int i=col1; i <= col2; i++){
            System.out.println(m[row1][i]);
        }
    }
    else if (col1 == col2){
        for(int i=row1; i <= row2; i++){
            System.out.println(m[i][col1]);
        }
    }
    else{
        int curR = row1;
        int curC = col1;
        while(curC != col2){
            System.out.print(m[row1][curC] + " ");
            curC++;
        }
        while(curR != row2){
            System.out.print(m[curR][col2] + " ");
            curR++;
        }
        while(curC != col1){
            System.out.print(m[row2][curC]);
            curC--;
        }
        while(curR != row1){
            System.out.print(m[curR][col1]);
            curR--;
        }
    }
}


6

旋转正方形矩阵

【题目】给定一个整型正方形矩阵matrix ,请把该矩阵调整成顺时针旋转90度的样子

【要求】额外空间复杂度为O(1)。

import java.util.Arrays;

7

反转单向和双向链表

【题目】分别实现反转单向链表和反转双向链表的函数。

【要求】如果链表长度为N,时间复杂度要求为O(N),额外空间复杂度要求为O(1)。

public class ReverseList {
    public static class Node {
        private int value;
        private Node next;
        
        public Node(int value){
            this.value = value;
        }
    }
    
    public static class BiNode {
        private int value;
        private BiNode prior;
        private BINode next;
        
        public BiNode(int value){
            this.value = value;
        }
    }
    
    public static Node reverseList(Node head){
        Node next = null;
        Node pre = null;
        while(head != null){
            next = head.next;
            head.next = pre;
            pre = head;
            head = next;
        }
        return pre;
    }
    
    public static BiNode reverseBiList(Node head){
        BiNode = next;
        BiNode pre = null;
        while(head != null){
            next = head.next;
            head.next = pre;
            head.prior = next;
            pre = head;
            head = next;
        }
    }
}
8

“之”字型打印矩阵

【题目】给定一个矩阵matrix,按照“之”字型的方式打印这个矩阵,例如:

1 2 3 4

5 6 7 8

9 10 11 12 ,

打印结果为:1 2 5 9 6 3 4 7 10 11 8 12

【要求】额外空间复杂度为O(1)。

【思路】

宏观调度:

​ 将任务拆分为打印对角线;

​ 用两个坐标标记对角线两端,每次对角线打印结束同时更新坐标;

​ 注意:打印对角线有两种方向。

public class PrintMatrixSpiralOrder {
    public static void printZigZag(int[][] matrix){
        int row1 = 0;
        int col1 = 0;
        int row2 = 0;
        int col2 = 0;
        int endR = matrix.length - 1;
        int endC = matrix[0].length - 1;
        boolean fromUp = false;
   
        while(row1 < endR + 1){
            printLevel(matrix, row1, col1, row2, col2, fromUp);
            col1 = row1 == endR ? col + 1 : col1;
            row1 = row1 == endR ? row1 : row1 + 1;
            row2 = col2 == endC ? row2 + 1 : row2;
            col2 = col2 == endC ? col2 : col2 + 1;
            fromUp = !fromUp;
        }
        System.out.println();
    }
    
    public static void printLevel(int[][] m, int row1, int col1, int row2, int col2, boolean fromUp) {
        if(fromup){
            while(row1 <= row2){
                System.out.print(m[row2++][col2--] + " ");
            }
        } else {
            while(row1 <= row2){
                System.out.print(m[row1--][col1++] + " ");
            }
        }
    }
}
9

在行列都排序好的矩阵中找数

【题目】给定一个有 N*M 的整型矩阵matrix和一个整数K,matrix的每一行和每一列都是排好序的。实现一个函数,判断K是否在matrix中。

例如:

0 1 2 5

2 3 4 7

4 4 4 8

5 7 7 9

如果K为7,返回true;如果K为6,返回false。

【要求】时间复杂度O(N+M),额外空间复杂度O(1)。

【思路】

从右上角或者左下角开始遍历(只有右上或左下的数在垂直于平行方向分别表示小于或大于当前数,从而可以构造条件确定搜索路径)

public class FindNumInSortedMatrix {
    public static boolean isContains(int[][] matrix, int K){
        int row = matrix.length -1;
        int col = 0;
        while(row >= 0 && col <= matrix[0].length - 1){
            if (matrix[row][col] == K){
                return true;
            }
            else if (matrix[row][col] < K){
                col++;
            }
            else{
                row--;
            }
        }
        return false;
    }
}
10

打印两个有序链表的公共部分

【题目】给定两个有序链表的头指针head1和head2,打印两个链表的公共部分。

【思路】

​ 从头节点开始比较,小的像后移动,如果相等则同时向后移动。

public class PrintCommonPart {
    public static class Node {
        private int value;
        private Node next;
        public Node(int value){
            this.value = value;
        }
    }
    
    public static void printCommonPart(Node head1, Node head2){
        System.out.println("Common part:");
        while(head1 != null && head2 != null){
            if(head1.value == head2.value){
                System.out.print(head1.value + " ");
                head1 = head1.next;
                head2 = head2.next;
            }
            else if (head1.value < head2.value){
                head1 = head1.next;
            }
            else{
                head2 = head2.next;
            }
            System.out.println();
        }
    }
}

11

判断一个链表是否为回文结构
【题目】 给定一个链表的头节点head,请判断该链表是否为回文结构。

例如:

1->2->1, 返回true。

1->2->2->1, 返回true。

15->6->15, 返回true。

1->2->3, 返回false。
【进阶】

如果链表长度为N,时间复杂度达到O(N),额外空间复杂度达到O(1)。

【思路】

基本1

​ 遍历链表存储到一个辅助栈中(逆序),然后正逆序比较,找公共部分。

基本2

​ 准备两个指针,一个每次走一步,一个每次走两步,遍历后一个在中点,一个在尾部,将后半部分压入辅助栈,然后比较公共部分。

进阶

​ 1、准备两个指针,一个每次走一步,一个每次走两步,遍历后一个在中点,一个在尾部;

​ 2、将后半部分逆序;

​ 3、从首尾开始向中点比较(将后半部分变回正序)。

public class IsPalindromeList {
    public static class Node {
        private int value;
        private Node next;
        
        public Node(int value){
            this.value = value;
        }
    }
    
    // need N extra space 
    public static boolean isPalindrome1(Node head){
        if(head == null || head.next == null){
            return true;
        }
        Stack<Node> stack = new Stack<Node>();
        Node cur = head;
        while(cur != null){
            Stack.push(cur);
            cur = cur.next;
        }
        while(head != null){
            if(head.value != stack.pop().value){
                return false;
            }
            head = head.next;
        }
        return true;
    }
    
    // need N/2 extra space
    public static boolean isPalindrome2(Node head){
        if(head == null || head.next == null){
            return true;
        }
        Stack<Node> stack = new Stack<Node>();
        Node fast = head;
        Node slow = head;
        while(slow.next != null && fast.next.next != null){
            slow = slow.next;
            fast = fast.next.next;
        }
        while(slow != null){
            stack.push(slow);
            slow = slow.next;
        }
        while(!stack.isEmpty()){
            if(head.value != stack.pop().value){
                return false;
            }
            head = head.next;
        }
        return true;
    } 
    
    // need O(1) extra space
    public static boolean isPalindrome3(Node head){
        if(head == null && head.next == null){
            return true;
        }
        Node n1 = head;
        Node n2 = head;
        while(n1.next != null && n2.next.next != null){
            n1 = n1.next;
            n2 = n2.next.next;
        }
        
        n2 = n1.next; // right part first node
        n1.next = null;
        Node tmp = null;
        while(n2 != null){
            tmp = n2.next;
            n2.next = n1;
            n1 = n2;
            n2 = tmp;
        }
        tmp = n1; // save last node
        n2 = head; // left first node
        boolean res = ture;
        while (n1 != null && n2 != null){
            if (n1.value != n2.value){
                res = false;
                break;
            }
            n1 = n1.next;
            n2 = n2.next;
        }
       	n1 = tmp.next;
        tmp.next = null;
        while(n1 != null) {
            n2 = n1.next;
            n1.next = tmp;
            tmp = n1;
            n1 = n2;
        }
        return res;
    }
}
12

将单向链表按某值划分成左边小、中间相等、右边大的形式
【题目】 给定一个单向链表的头节点head,节点的值类型是整型,再给定一个整数pivot。实现一个调整链表的数,将链表调整为左部分都是值小于 pivot 的节点, 中间部分都是值等于pivot的节点, 右部分都是值大于 pivot的节点。
除这个要求外, 对调整后的节点顺序没有更多的要求。

例如: 链表9->0->4->5-1,pivot=3。调整后链表可以是1->0->4->9->5,也可以是0->1->9->5->4。总之,满足左部分都是小于3的节点,中间部分都是等于3的节点(本例中这个部分为空), 右部分都是大于3的节点即可。 对某部分内部的节点顺序不做要求。

【进阶】在原问题的要求之上再增加如下两个要求。
在左、中、右三个部分的内部也做顺序要求,要求每部分的节点从左到右的顺序与原链表中节点的先后次序一致。

例如: 链表9->0->4->5->1,pivot=3。调整后的链表是0->1->9->4->5。在满足原问题要求的同时,左部分节点从左到右为0、1。在原链表中也是先出现0,后出现1;中间部分在本例中为空,不再讨论;右部分节点 从左到右为9、4、 5。在原链表中也是先出现9, 然后出现4,最后出现5。如果链表长度为N, 时间复杂度请达到O(N), 额外空间复杂度请达到O(1)。

【思路】

基础

​ 遍历链表,用辅助数组存储,在数组中排好序放入链表。

进阶

​ 分别用 3 个引用 small 、equal 、big 来重新组织链表;

​ 先遍历一次找到 small 、equal、 big 的第一个指向;

​ 再遍历一次将满足条件的结点分别链接在small、equal、big后面;

​ 最后将small、equal、big 链接起来。

注意:要考虑某部分为空的情况

public class SmalledEqualBigger {
    public static class Node {
        private int value;
        private Node next;
        public Node(int value){
            this.value = value;
        }
    }
    
    public static Node listPartition(Node head, int pivot){
        Node sH = null; //small head
        Node sT = null; //small tail
        Node eH = null;
        Node eT = null;
        Node bH = null;
        Node bT = null;
        Node next = null; //save next node
        while(head != null){
            next = head.next;
            head.next = null;
            if(head.value < pivot){
                if(sH == null){
                    sH = head;
                    sT = head
                }else{
                    sT.next = head;
                    sT = head;
                }
            }else if (head.value == pivot){
                if(eH == null){
                    eH = head;
                    eT = head;
                }else{
                    eT.next = head;
                    eT = head;
                }
            }else{
                if(bH == null){
                    bH = head;
                    bT = head;
                }else{
                    bT.next = head;
                    bT = head;
                }
            }
            head = next;
        }
        
        if(sT != null){
            sT.next = eH;
            eT = eT == null? sT : eT;
        }
        if(eT != null){
            eT.next = bH;
        }
        return sH != null ? sH : eH != null ? eH : bH;
    }
}
13

复制含有随机指针节点的链表

【 题目】 一种特殊的链表节点类描述如下:
public class Node {

​ public int value;

​ public Node next;

​ public Node rand;

​ public Node(int data) {

​ this.value = data;

​ }

}

Node类中的value是节点值,next指针和正常单链表中next指针的意义一样,都指向下一个节点, rand指针是Node类中新增的指针,这个指针可能指向链表中的任意一个节点, 也可能指向null。给定一个由Node节点类型组成的无环单链表的头节点head, 请实现一个函数完成这个链表中所有结构的复制,并返回复制的新链表的头节点。

【进阶】不使用额外的数据结构,只用有限几个变量,且在时间复杂度为 O(N)内完成原问题要实现的函数。

【思路】

基本

​ 遍历链表Node 并拷贝为 Node’,用哈希表以(key, value) = (Node, Node’)存储,根据映射关系实现Node’ 节点之间的 rand 指向。

进阶

​ 遍历,将新的 Node’ 链接到对应 Node 的 next 上,变成一个大链表;

​ (用next这种空间结构替代哈希表)

​ 遍历,根据 Node 的 rand 链接 Node’ 的 rand;

​ 将两个链表分离。

public class CopyListWithRandom {
    public static class Node {
        private int value;
        private Node next;
        private Node rand;
        
        public Node(int value){
            this.value = value;
        }
    }
    
    public static Node copyListWithRand1(Node head) {
        if(head == null){
            return null;
        }
        HashMap<Node, Node> map= new HashMap<Node, Node>();
        Node cur = head;
        whilr(cur != null){
            map.put(cur, new Node(cur.value));
            cur = cur.next;
        }
        cur = head;
        whilr(cur != null){
            map.get(cur).next = map.get(cur.next);
            map.get(cur).rand = map.get(cur.rand);
            cur = cur.next;
        }
        return map.get(head);
    }
    
    public static Node copyListWithRand2(Node head){
        if(head == null){
            return null;
        }
        Node cur = head;
        Node next = null;
        while (cur != null){
            next = cur.next;
            cur.next = new Node(cur.value);
            cur.next.next = next;
            cur = next;
        }
        cur = head;
        // set copy node rand
        while(cur != null){
            next = cur.next.next;
            cur.next.rand = cur.rand != null ? cur.rand.next : null;
            cur = next;
        }
        cur = head;
        Node res = head.next;
        // split
        while(cur != null){
            next = cur.next.next;
            cur.next.next = next != null ? next.next : null;
            cur.next = next;
            cur = next;
        }
        return res;
    }
}
14

两个单链表相交的一系列问题 (链表最难级别)
【 题目】 在本题中,单链表可能有环,也可能无环。给定两个单链表的头节点 head1 和 head2,这两个链表可能相交,也可能不相交。请实现一个函数,如果两个链表相交,请返回相交的第一个节点;如果不相交,返回null 即可。

【要求】如果链表1的长度为N,链表2的长度为M,时间复杂度请达到 O(N+M),额外空间复杂度请达到O(1)。

【思路】

判断有环:

​ 将遍历过的结点加到HashSet 中,如果新的结点在 Set 中,证明有环;

​ 此题额外空间复杂度 O(1),不可行;

​ 进阶:

​ (快慢指针法)

​ 设置快慢两个指针,一个每次走 2 步,另一个只走 1 步;

​ 快指针走到 null,无环;两个指针相遇,有环;

​ 如果相遇,快指针回到头节点,改为每次走一步,两个指针会在第一个入 环结点相遇,输出此节点为 loop;(数学结论,证明略,可以设环外长L,环内长R)

判断相交:

​ 情况1:如果都无环

​ (用HashSet)

​ 遍历其中一个链表,放入 HashSet 中;

​ 遍历另一个链表,找出第一出现在 HashSet 中的节点;

​ (不用HashSet)

​ 遍历找到 head1 、head2 的长度 len1、len2 和最后一个节点 end1、 end2;

​ 如果 end1 和 end2 不是同一个,则不相交;

​ 如果是同一个,则重头再次遍历,令较长链表先走|len1 - len2|步,则一 定会在第一个相交的节点相遇;

​ 情况2:如果一个有环一个无环:

​ 必然不会相交;

​ 情况3:如果两个都有环;

​ 如果 loop1 = loop2 , 将其看作两个无环链表相交;

​ 如果 loop1 != loop2, 继续移动 loop1,如果在回到 loop1 前遇到 loop2,则相交,相交点为 loop1 或 loop2,如果没有遇到,则不相交;

public class FindFirstIntersectNode {
    public static class Node {
        private int value;
        private Node next;
        
        public Node(int value){
            this.value = value;
        }
    }
    
    public static Node getIntersectNode(Node head1, Node head2){
        if(head1 == null || head2 == null){
            return null;
        }
        Node loop1 = getLoopNode(head1);
        Node loop2 = getLoopNode(head2);
        if(loop1 == null && loop2 == null){
            return noLoop(head1, head2);
        }else if (loop1 != null && loop2 != null){
            return bothLoop(head1, loop1, head2, loop2);
        }else{
            return null;
        }
    }
    
    public static Node getLoopNode(Node head){
        if(head != null && head.next != null && head.next.next != null){
            Node fast = head.next.next;
        	Node slow = head.next;
        }
       	while(fast != slow){
            if(fast.next == null || fast.next.next == null){
                return null;
            }
            fast = fast.next.next;
            slow = slow.next;
        }
        fast = head;
        while(fast != slow){
            fast = fast.next;
            slow = slow.next;
        }
        return fast;
    }
    
    public static Node noLoop(Node head1, Node head2){
        if(head1 == null || head2 == null){
            return null;
        }
        int len1 = 0;
        int	len2 = 0;
        Node cur1 = head1;
        Node cur2 = head2;
        while (cur1 != null){
            len1++;
            cur1 = cur1.next;
        }
        while(cur2 != null){
            len2++;
            cur2 = cur2.next;
        }
        if(cur1 != cur2){
            return null;
        }
        cur1 = len1 >= len2 ? head1 : head2;
        cur2 = cur1 == head1? head2 : head2;
        int n = len1 >= len2 ? len1 - len2 : len2 - len1;
        while(n != 0){
            cur1 = cur1.next;
            n--;
        }
        while(cur1 != cur2){
            cur1 = cur1.next;
            cur2 = cur2.next;
        }
        return cur1;
    }
    
    public static Node bothLoop(Node head1, Node loop1, Node head2, Node loop2){
        Node cur1 = null;
        Node cur2 = null;
        if(loop1 == loop2){
            int len1 = 0;
            int len2 = 0;
            while(cur1 != loop1){
                len1++;
                cur1 = cur1.next;
            }
            while(cur2 != loop2){
                len2++;
                cur2 = cur2.loop;
            }
            cur1 = len1 >= len2 ? head1 : head2;
            cur2 = cur1 == head1 ? head2 : head1;
            int n = len1 >= len2 ? len1 - len2 : len2 - len1;
            while(n != 0){
                n--;
                cur1 = cur.next;
            }
            while(cur1 != cur2){
                cur1 = cur1.next;
                cur2 = cur2.next;
            }
            return cur1;
        }else{
            cur1 = loop1.next;
            while(cur1 != loop1){
                if(cur1 == loop2){
                    return loop1;
                }
           		cur1 = cur1.next;
            }
            return null;
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值