左神基础算法笔记-三

1. 用数组结构实现大小固定的队列和栈

// 使用一个index, index比数组下标大1
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");
        }
        arr = new Integer[initSize];
        index = 0;
    }
    
    public Integer peek() {
        if (index == 0) {
            return null;
        }
        return arr[index - 1];
    }
    
    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];
    }
}
// 使用end和start,end代表队列尾,start代表队列头
// 加数:加数后end向后移一位,若此时到底后回到开头
// 拿数:拿数后start向后移一位,若到底回到开头
// 用一个size变量约束end和start,size代表当前队列中有多少数
// 只要size不超过数组大小,end就按上面的逻辑
// 只要size不是0,start就按上面的逻辑
// 这样end和start没有直接联系,循环利用数组
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];
        size = 0;
        start = 0;
        end = 0;
    }
    
    public Integer peek() {
        if (size == 0) {
            return null;
        }
        return arr[start];
    }
    
    public void push(int obj) {
        if (size == arr.length) {
            throw new ArrayIndexOutOfBoundsException("The queue is full");
        }
        size++;
        arr[end] = obj;
        end = nextIndex(arr.length, end);
    }
    
    public Integer poll() {
        if (size == 0) {
            throw new ArrayIndexOutOfBoundsException("The queue is empty");
        }
        size--;
        int tmp = start;
        start = nextIndex(arr.length, start);
        return arr[tmp];
    }
    
    public int nextIndex(int size, int index) {
        return index == size - 1 ? 0 : index +1;
    }
}

2. 设计有 getMin 功能的栈

【要求】

  1. pop、push、getMin操作的时间复杂度都是O(1)。
  2. 设计的栈类型可以使用现成的栈结构。
    public static class GetMinClass1 {
            private Stack<Integer> stackData;
            private Stack<Integer> stackMin;
    
            public GetMinClass1(){
                stackData = new Stack<Integer>();
                stackMin = new Stack<Integer>();
            }
    
            public void push(Integer num){
                stackData.push(num);
                // 如果 Min 栈为空,同步压入 Min 栈
                if (stackMin.isEmpty()) {
                    stackMin.push(num);
                } else {
                    // 小于 Min 栈栈顶,也压入 Min 栈
                    if (num <= getMin()) stackMin.push(num);
                }
            }
    
            public Integer pop(){
                if (stackData.isEmpty()) {
                    throw new RuntimeException("Stack is empty.");
                } else if (stackData.peek() == getMin()) {
                    // 如果 Data 栈 和 Min 栈栈顶相同,都弹出
                    stackMin.pop();
                }
                return stackData.pop();
            }
    
        	// 返回 Min 栈的栈顶
            public Integer getMin() {
                if (stackMin.isEmpty()) {
                    throw new RuntimeException("Stack is empty.");
                }
                return stackMin.peek();
            }
        }
    
public static class GetMinClass2 {
        private Stack<Integer> stackData;
        private Stack<Integer> stackMin;

        public GetMinClass2() {
            stackData = new Stack<Integer>();
            stackMin = new Stack<Integer>();
        }

        public void push(Integer obj) {
            // Data 栈压入
            stackData.push(obj);
            if (stackMin.isEmpty()) {
                // 如果 Min 栈为空,压入 Min 栈
                stackMin.push(obj);
            } else if (obj <= getMin()) {
                // 小于 Min 栈栈顶,也压入 Min 栈
                stackMin.push(obj);
            } else {
                // 大于 Min 栈栈顶,重复压入 Min 栈顶
                stackMin.push(getMin());
            }
        }
		
    	
        public int pop() {
            if (stackData.isEmpty()) {
                throw new RuntimeException("Stack is empty.");
            }
            // Data 栈和 Min 栈 大小相同,同步弹出
            stackMin.pop();
            return stackData.pop();
        }

    	// 返回 Min 栈的栈顶
        public int getMin() {
            if (stackMin.isEmpty()) {
                throw new RuntimeException("Stack is empty.");
            }
            return stackMin.pop();
        }

    }
// 方法一压入时候占用空间少,但弹出时花费时间长
// 方法而压入时候占用空间多,但弹出时花费时间短

3. 如何仅用队列结构实现栈结构?如何仅用栈结构实现队列结构?

// 使用两个队列来回倒,每次倒的时候留队列尾部最后一个元素,然后pop()这个元素
public static class TwoQueuesStack {
    private Queue<Integer> queue;
    private Queue<Integer> help;
    
    public TwoQueueStack() {
        queue = new LinkedList<Integer>();
        help = new LinkedList<Integer>();
    }
    
    // 添加元素时添加到 queue 队列队尾
    public void push(int pushInt) {
        queue.add(pushInt);
    }
    
    public int peek() {
        if (queue.isEmpty()) {
            throw new RuntimeException("Stack is empty!");
        }
        // queue 队列的元素进入 help 队列,只留一个
    	   while (queue.size() != 1) {
            help.add(queue.poll());
        }
        int res = queue.poll();
        help.add(res);
        swap();
        return res;
    }
    
    public int pop() {
        if (queue.isEmpty()) {
            throw new RuntimeException("Stack is empty!");
        }
        // queue 队列的元素进入 help 队列,只留一个
        while (queue.size() > 1) {
            help.add(queue.poll());
        }
        int res = queue.poll();
        swap();
        return res;
    }
    
    private void swap() {
        Queue<Integer> tmp = help;
        help = queue;
        queue = tmp;
    }
    
}
// 一个push栈,一个pop栈
// 1. pop中如果不为空,push栈不能往pop栈倒
// 2. push栈往pop栈倒的时候,要一次将元素都倒过去
public static class TwoStacksQueue {
    private Stack<Integer> stackPop;
    private Stack<Integer> stackPush;
    
    public TwoStacksQueue() {
        stackPush = new Stack<Integer>();
        stackPop = new Stack<Integer>();
    }
    
    public void push(int pushInt) {
        stackPush.push(pushInt);
    }
    
    public int poll() {
        if(stackPop.isEmpty() && stackPush.isEmpty()) {
            throw new RuntimeException("Queue is empty.")
        }
        dao();
        return stackPop.pop();
    }
    
    public int peek() {
        if(stackPop.isEmpty() && stackPush.isEmpty()) {
            throw new RuntimeException("Queue is empty.")
        }
        dao();
        return stackPop.peek();
    }
    
    public void dao() {
        if (stackPop.isEmpty()) {
            while (!stackPush.isEmpty()) {
                stackPop.push(stackPush.pop());
            }
        }
    }
}

4. 猫狗队列

实现一种狗猫队列的结构,要求如下: 用户可以调用add方法将cat类或dog类的实例放入队列中; 用户可以调用pollAll方法,将队列中所有的实例按照进队列的先后顺序依次弹出; 用户可以调用pollDog方法,将队列中dog类的实例按照进队列的先后顺序依次弹出; 用户可以调用pollCat方法,将队列中cat类的实例按照进队列的先后顺序依次弹出; 用户可以调用isEmpty方法,检查队列中是否还有dog或cat的实例; 用户可以调用isDogEmpty方法,检查队列中是否有dog类的实例; 用户可以调用isCatEmpty方法,检查队列中是否有cat类的实例。

宠物、狗和猫的类如下:

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"); 
    } 
}
public static class PetEnterQueue {
    private Pet pet;
    private long index;
    
    public PetEnterQueue(Pet pet, long count) {
        this.pet = pet;
        this.index = count;
    }
    
    public Pet getPet() {
        return this.pet;
    }
    
    public long getCount() {
        return this.index;
    }
    
    public String getEnterPetType() {
        return this.pet.getPetType();
    }
}

public static class DogCatQueue {
    private Queue<PetEnterQueue> dogQ;
    private Queue<PetEnterQueue> catQ;
    private long index;
    
    public DogCatQueue() {
        this.dogQ = new LinkedList<PetEnterQueue>();
        this.catQ = new LinkedList<PetEnterQueue>();
        this.index = 0;
    }
    
    public void add(Pet pet) {
        if (pet.getPetType().equals("dog")) {
            this.dogQ.add(new PetEnterQueue(pet, this.index++));
        } else if (pet.getPetType().equals("cat")) {
            this.catQ.add(new PetEnterQueue(pet, this.index++));
        } else {
            throw new RuntimeException("err, not dog or cat");
        }
    }
    
    public Pet pollAll() {
        if (!this.dogQ.isEmpty() && !this.catQ.isEmpty()) {
            if (this.dogQ.peek().getCount() < this.catQ.peek().getCount()) {
                return this.dogQ.poll().getPet();
            } else {
                return this.catQ.poll().getPet();
            }
        } else if (!this.dogQ.isEmpty()) {
            return this.dog.poll().getPet();
        } else if (!this.catQ.isEmpty()) {
            return this.catQ.poll().getPet();
        } else {
            throw new RuntimeException("err, queue is empty!");
        }
    }
    
    public Dog pollDog() {
        if (!this.isDogQueueEmpty()) {
            return (Dog) this.dogQ.poll().getPet();
        } else {
            throw new RuntimeException("Dog queue is empty!");
        }
    }
    
    public Cat pollCat() {
			if (!this.isCatQueueEmpty()) {
				return (Cat) this.catQ.poll().getPet();
			} else
				throw new RuntimeException("Cat queue is empty!");
		}

		public boolean isEmpty() {
			return this.dogQ.isEmpty() && this.catQ.isEmpty();
		}

		public boolean isDogQueueEmpty() {
			return this.dogQ.isEmpty();
		}

		public boolean isCatQueueEmpty() {
			return this.catQ.isEmpty();
		}
}

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)。

// 不要试图去模拟打印的轨迹
// 使用(row1, col1) (row2, col2) 左上角与右下角的坐标,确定一个矩阵然后打印这个矩阵的一圈
public static void spiralOrderPrint(int[][] matrix) {
    int row1 = 0;
    int col1 = 0;
    int row2 = matrix.length - 1;
    int col2 = matrix[0].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.print(m[row1][i] + " ");
        }
    } else if (col1 == col2) {
        for (int i = row1; i <= row2; i++) {
            System.out.print(m[i][col1] + " ");
        }
    } else {
        int curC = col1;
        int curR = row1;
        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--;
        }
    }
}

四个while如下图

6. “之”字形打印矩阵

【题目】 给定一个矩阵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)。

// 不要去研究下标如何变动
// 两个开始都指向(0,0) (r1,c1)向右移动,到头后向下移动 (r2,c2)向下移动,到头后向右移动
public static void printMatrixZigZag(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;
    // row1越界就终止
    while (row1 != endR + 1) {
        printLevel(matrix, row1, col1, row2, col2, fromUp);
        row1 = col1 == endC ? row1 + 1 : row1; // row1到行末后开始增加
        col1 = col1 == endC ? col1 : col1 + 1;
        col2 = row2 == endR ? col2 + 1 : col2;
        row2 = row2 == endR ? row2 : row2 + 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[row1++][col1--] + " ")
        }
    } else {
        while (row2 != row1 - 1) {
            System.out.print(m[row2--][col2++] + " ");
        }
    }
}

练习问题:

在调整row1,row2,col1,col2时,row1和col1是以endC和col1的比较结果作为依据,所以row1的调整应该在col1之前;row2和col2是以endR和row2的比较结果作为依据,所以col2的调整应该在row2前;否则如果依据先调整变化了,row1和col2的变化就不会准确。

7. 搜索二维矩阵

【题目】 给定一个有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)。

  • 从右上角或左下角开始找……
class Solution {
    public boolean searchMatrix(int[][] matrix, int target) {
        if(matrix.length == 0 || matrix[0].length == 0 || matrix == null) {
            return false;
        }
        int row = 0, col = matrix[0].length - 1;
        while (row <= matrix.length - 1 && col >= 0) {
           if (target == matrix[row][col]){
               System.out.println(row+"--"+col);
               return true;
           } else if (target < matrix[row][col]){
               System.out.println(row+"--"+col);
               --col;
           } else {
               System.out.println(row+"--"+col);
               ++row;
           } 
        }  
        return false;
    }
}

练习问题:

在更改代码的时候条件连接符用的 || ,导致数组下标越界了。

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

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

  • 谁小往后移动一位指针,相同的话共同向后移动一位指针……
class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
 		ListNode prehead = new ListNode(-1);
        ListNode prev = prehead;
        while (l1 != null && l2 != null) {
            if (l1.val <= l2.val) {
                prev.next = l1;
                l1 = l1.next;
            } else {
                prev.next = l2;
                l2 = l2.next;
            }
            prev = prev.next;
        }
        prev.next = l1 == null ? l2 : l1;
        return prehead.next;
    }
}

9. 判断一个链表是否为回文结构

【题目】 给定一个链表的头节点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. 快指针一次两步,慢指针一次一步,快指针走到终点时,慢指针走到中点。将后一半入栈,与前一半比较。

  3. 快指针一次两步,慢指针一次一步,快指针走到终点时,慢指针走到中点。将后一半逆序,从前往后和从后往前同时遍历进行比较,有一次不等则不是回文,判断完后还要把链表后半的顺序调整回来。

class Solution {
    public boolean isPalindrome(ListNode head) {
        if (head == null) return true;
        if (head.next == null) return true;
        if (head.next.next == null) return head.val==head.next.val;
        // 快慢指针向后移动,快指针到终点时,慢指针到中点          
        ListNode quick = head;
        ListNode slow = head;
        while (quick.next != null && quick.next.next != null) {
            quick = quick.next.next;
            slow = slow.next;
        }
        if (quick.next != null) {
            quick = quick.next;
        }
        
        // 反转后半链表
        slow = slow.next;
        slow = reverseList(slow);
        // 比较后半链表与前半是否相同
        while (slow != null) {
            if (slow.val != head.val) {
                return false;
            }
            head = head.next;
            slow = slow.next;
        }
        return true;    
    }
    
    public ListNode reverseList(ListNode head) {
        ListNode newHead = null;
        while (head != null) {
            ListNode nextTemp = head.next;
            head.next = newHead;
            newHead = head;
            head = nextTemp;
        }
        return newHead;
    }
}

10. 分隔链表

【题目】 给定一个单向链表的头节点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的节点即可。对某部分内部的节点顺序不做 要求。

  • 生成数组存放Node结点,partition之后再连起来。

进阶: 在原问题的要求之上再增加如下两个要求。在左、中、右三个部分的内部也做顺序要求,要求每部分里的节点从左 到右的顺序与原链表中节点的先后次序一致。 例如:链表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)。

  • 如下图,遍历一遍链表分别找到第一个小于、等于、大于pivot的节点;然后在遍历一边,内存地址相同的直接跳过,不同的分类往small,equal,big后面挂;遍历完了再将三个部分重新连接起来。
  • 如何处理三部分中某一部分没有的问题?链表的题少有算法特别难的,且往往是面试的头一道或头两道题,这就考察实打实的coding能力,需要多练。
class Solution {
    
    public static ListNode listPartition2(ListNode head, int pivot) {
        if (head == null) {
            return head;
        }
        ListNode sH = null;
        ListNode sT = null;
        ListNode eH = null;
        ListNode eT = null;
        ListNode bH = null;
        ListNode bT = null;
        ListNode next = null;
        while (head != null) {
            next = head.next;
            head.next = null;
            if (head.val < pivot) {
                if (sH == null) {
                    sH = head;
                    sT = sH;
                } else {
                    sT.next = head;
                    sT = sT.next;
                }
            } else if (head.val > pivot) {
                if (bH == null) {
                    bH = head;
                    bT = bH;
                } else {
                    bT.next = head;
                    bT = bT.next;
                }
            } else {
                if (eH == null) {
                    eH = head;
                    eT = eH;
                } else {
                    eT.next = head;
                    eT = eT.next;
                }
            }
            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;
    }
    
    public static ListNode listPartition1(ListNode head, int pivot) {
        if (head == null) {
            return null;
        }
        ListNode node = head;
        int i = 0;
        while (node != null) {
            i ++;
            node = node.next;
        }
        ListNode[] arr = new ListNode[i];
        node = head;
        for (i=0; i<arr.length; i++) {
            arr[i] = node;
            node = node.next;
        }
        arrPartition(arr, pivot);
        for (i = 1; i < arr.length; i++) {
            arr[i-1].next = arr[i];
        }
        arr[i-1].next = null;
        return arr[0];
    }

    public static void arrPartition(ListNode[] arr, int pivot) {
        int small = -1;
        int big = arr.length;
        int index = 0;
        while (index != big) {
            if (arr[index].val < pivot) {
                swap(arr, ++small, index++);
            } else if (arr[index].val > pivot) {
                swap(arr, --big, index);
            } else {
                index++;
            }
        }
    }
    
}

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

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

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,请实现一个函数完成这个链表中所有结构的复制,并返回复制的新链表的头节点。

  • 如下图,第一遍遍历时,在map中存储原结点与复制结点的对应关系;第二遍遍历时,根据结点的next指针和rand指针和map拷贝;完成深度拷贝。
public static Node copyListWithRand1(Node head) {
    HashMap<Node, Node> map = new HashMap<Node, Node>();
    Node cur = head;
    // 第一次遍历,创建存储对应关系的map
    while (cur != null) {
        map.put(cur, new Node(cur.value));
        cur = cur.next;
    }
    Node X = head;
    // 第二次遍历,完成深度拷贝
    while (X != null) {
        map.get(X).next = map.get(X.next);
        map.get(X).rand = map.get(X.rand);
        X = X.next;
    }
    return map.get(head);
}

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

  • 如下图,原链表中每个结点后面跟一个自己的拷贝结点,遍历时就可以利用这种关系完成深度拷贝,深度拷贝后再将老链表与原链表分离。
// 好歹是做出来了,以后在优化
class Solution {
    public Node copyRandomList(Node head) {
        if (head == null) return null;
        if (head.next == null) {
            if (head.random == null) return new Node(head.val, null, null);
            if (head.random == head) {
                Node res = new Node(head.val, null, null);
                res.random = res;
                return res;
            }
            Node res = new Node(head.val, null, new Node(head.random.val, null, null));
            return res;
        }
        // 1. 复制的结点挂在对应的原结点后面
        Node cur1 = head;
        while (cur1 != null) {
            Node next = cur1.next;
            cur1.next = new Node(cur1.val,next,null);
            cur1 = cur1.next.next;
        }
        // 2. 利用 原结点->复制结点 的关系进行深拷贝
        Node cur2 = head;
        while (cur2 != null) {
            // random 指针为 null 时候特殊处理
            if (cur2.random == null) {
                cur2.next.random = null;
            } else {
                cur2.next.random = cur2.random.next;
            }
            cur2 = cur2.next.next;
        }
        // 3. 分离老链表与新链表
        Node cur3 = head;
        Node prehead = head.next;
        Node prev = prehead;
        while (prev.next != null) {
            Node next = prev.next;
            cur3.next = next;
            prev.next = next.next;
            cur3 = cur3.next;
            prev = prev.next;
        }
        cur3.next = null;
        
        return prehead;
    }
}

12. 判断链表有环

// 使用快慢指针,快指针一次走两步,慢指针一次走一步,快慢指针相遇时说明链表有环(会在两圈以内相遇)
// 相遇后快指针回到链表开头,变成一次走一步,再次相遇时快慢指针位于入环结点上(数学结论)
// 证明:环外长度L,环内长度R,第一次相遇时……

13. 两个单链表相交的一系列问题

【题目】 在本题中,单链表可能有环,也可能无环。给定两个单链表的头节点 head1和head2,这两个链表可能相交,也可能不相交。请实现一个函数, 如果两个链表相交,请返回相交的第一个节点;如果不相交,返回null 即可。 要求:如果链表1的长度为N,链表2的长度为M,时间复杂度请达到 O(N+M),额外空间复杂度请达O(1)。

// 先得到两个链表的入环结点(快慢指针法)
// 1. 入环结点都为空,两链表都无环
//  0) 判断两链表相交
//   a.看两链表尾结点是否相同,不相同则不相交
//   b.遍历两链表,得到两链表长度 m,n
//   c.计算差值|m-n|
//   d.长链表由|m-n|处开始遍历,短链表从头遍历,相遇结点即为相交的第一个结点
//  1) 两链表相交: 由于单链表只有一个 next 指针,相交只会出现“Y”形状,不会出现“X”形状
//  2) 两链表不相交:返回 null
// 2. 一个链表有环,一个链表无环
//  1) 一定不相交:可以画图验证,单链表只有一个 next 指针
// 3. 两个链表都有环
//  0) 三种情况如上图所示
//   a.入环结点 loop1 == loop2 时,为第二种情况,以入环结点为终止可以复用单链表相交的代码
//   b.入环结点 loop1 != loop2 时,loop1 继续向下走
//    - 若 loop1 回到自身都没有和 loop2 相遇,说明为第一种情况,返回null
//    - 若 loop1 和 loop2 相遇,说明为三种情况,loop1 与 loop2 都可以是相交的第一个结点
//  1) 两个链表各自成环
//  2) 两个链表入环前相交,共用一个环
//  3) 两个链表入环结点不同,共用一个环

14. 二分的小拓展

无序也可以二分

15. 反转单向和双向链表

【题目】 分别实现反转单向链表和反转双向链表的函数。
【要求】 如果链表长度为N,时间复杂度要求为O(N),额外空间复杂度要求为O(1)

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode reverseList(ListNode head) {
        ListNode prev = null;
        ListNode curr = head;
        while (curr != null) {
            ListNode nextTemp = curr.next;
            curr.next = prev;
            prev = curr;
            curr = nextTemp;
        }
        return prev;
    }
}

头插法:维护一个以 prev 为头节点的新链表,不断的在这个新链表的头部插入节点来达到反转链表的目的。刚开始的时候,prev 为 null,代表反转后的新链表的最后一个节点的 next 指针。遍历链表进行头插的过程中使用 nextTemp 保存当前位置的下一个节点。

评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值