左神算法基础班Class-03

本文深入讲解数据结构中的栈、队列、链表等核心概念,包括其实现方式、应用场景及算法优化技巧,帮助读者掌握高效的数据组织与处理方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

//数组表示栈 先进后出

    public static class ArrayStack {
        private Integer[] arr;
        private Integer index;

        public ArrayStack(int initSize) throws IllegalAccessException {
            if (initSize < 0) throw new IllegalAccessException("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];
        }
    }

//数组表示栈队列 先进先出

public static class ArrayQueue {
        private Integer[] arr;
        private Integer size;
        private Integer start;
        private Integer end;

        public ArrayQueue(int initSize) throws IllegalAccessException {
            if (initSize < 0) throw new IllegalAccessException("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 = end == arr.length - 1 ? 0 : end + 1; //end到底后再返回0位置
        }
        //出队
        public Integer poll() {
            if (size == 0) {
                throw new ArrayIndexOutOfBoundsException("The queue is empty");
            }
            size--;
            int tmp = start;
            start = start == arr.length - 1 ? 0 : start + 1;
            return arr[tmp];
        }
    }

实现一个特殊的栈

在实现栈的基本功能的基础上,再实现返回栈中最小元素的操作。
【要求】
1.pop、push、getMin操作的时间复杂度都是O(1)。
2.设计的栈类型可以使用现成的栈结构。

package basic_algorithm.class_03;

import java.util.Stack;

public class GetMinStack {
    //用两个栈 一个正常压数据 另一个压入较小值
    private Stack<Integer> stackData;
    private Stack<Integer> stackMin;

    public GetMinStack() {
        this.stackData = new Stack<Integer>();
        this.stackMin = new Stack<Integer>();
    }

    public void push(int newNum) {
        if (stackMin.isEmpty()) {
            stackMin.push(newNum);
        } else if (newNum < stackMin.peek()) {
            stackMin.push(newNum);
        } else {
            int newMin = stackMin.peek();
            stackMin.push(newMin);
        }
        stackData.push(newNum);
    }

    public int pop() {
        if (stackData.isEmpty()) {
            System.out.println("This stack is empty!");
        } else {
            stackData.pop();
        }
        return stackMin.pop();
    }

    public int getMin() {
        if (stackData.isEmpty()) {
            System.out.println("This stack is empty!");
        }
        return stackMin.peek();
    }

    public static void main(String[] args) {
        GetMinStack stack = new GetMinStack();
        stack.push(7);
        stack.push(3);
        stack.push(2);
        stack.push(5);
        stack.push(6);
        System.out.println(stack.getMin());
        stack.pop();
        stack.pop();
        stack.pop();
        System.out.println(stack.getMin());
    }
}

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

数据按{1,2,3,4,5}入栈的话 出栈顺序应该是5,4,3,2,1
用两个队列 一个是数据队列 一个是辅助队列
比如队列data现在入队后是[5,4,3,2,1](右边是队 尾) 那么将[4,3,2,1]入队列help 再返回队列data的队头即可得到数据5 再把[3,2,1]入队列data 返回help的队头即可得到数据4 以此类推 就会依次得到 5,4,3,2,1

package basic_algorithm.class_03;

import java.util.LinkedList;
import java.util.Queue;

public class Two_Queues_To_Stack {
    private Queue<Integer> data;
    private Queue<Integer> help;
    public Two_Queues_To_Stack(){
        data=new LinkedList<Integer>();
        help=new LinkedList<Integer>();
    }
    public void push(int i){
        data.add(i);
    }
    public int pop(){
        if (data.isEmpty()) {
            System.out.println("This queue is empty!");
        }
        while(data.size()>1){//每次都留下最后一个元素
            help.add(data.poll());
        }
        int res=data.poll();
        swap();
        return res;
    }
    public int peek(){
        if (data.isEmpty()) {
            System.out.println("This queue is empty!");
        }
        while(data.size()>1){
            help.add(data.poll());
        }
        int res=data.poll();
        help.add(res);//返回的值不抹掉
        swap();
        return res;
    }
    private void swap() {//交换引用
        Queue<Integer> temp=help;
        help=data;
        data=temp;
    }

    public static void main(String[] args) {
        Two_Queues_To_Stack queue=new Two_Queues_To_Stack();
        queue.push(1);
        queue.push(2);
        queue.push(3);
        queue.push(4);
        queue.push(5);
        System.out.println(queue.pop()); //5
        System.out.println(queue.peek());//4
        System.out.println(queue.pop());//4
        System.out.println(queue.pop());//3

    }
}

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

数据按{1,2,3,4,5}入队的话 出队顺序应该是1,2,3,4,5
用两个栈 一个是push栈 一个是pop栈 先将数据按{1,2,3,4,5}压入到push栈 则push栈中数据的顺序是5,4,3,2,1 再将push栈中的数据依次出栈 入pop栈 则pop栈中的数据顺序是1,2,3,4,5 再将pop栈中数据出栈 即可得到 顺序是1,2,3,4,5 实现了队列的先进先出
注意:

  1. 将push栈中的数据压入pop栈中时必须保证一次性全部压入
  2. 当pop栈中有数据时 不能将push栈中的数据压入pop栈
  3. dao()函数保证了以上两点
  • private void dao() { if (stackPop.isEmpty()){ while (!stackPush.isEmpty()){ stackPop.push(stackPush.pop()); } } }
package basic_algorithm.class_03;

import java.util.Stack;

public class Two_Stacks_To_Queue {
    private Stack<Integer> stackPush;
    private Stack<Integer> stackPop;
    public Two_Stacks_To_Queue(){
        stackPush=new Stack<Integer>();
        stackPop=new Stack<Integer>();
    }
    public void push(int num){
        stackPush.push(num);
    }
    public int poll(){
        if (stackPush.isEmpty()&&stackPop.isEmpty()){
            System.out.println("This queue is empty!");
        }
        dao();
        return stackPop.pop();
    }
    public int peek(){
        if (stackPush.isEmpty()&&stackPop.isEmpty()){
            System.out.println("This queue is empty!");
        }
        dao();
        return stackPop.peek();
    }
    private void dao() {
        if (stackPop.isEmpty()){
            while (!stackPush.isEmpty()){
                stackPop.push(stackPush.pop());
            }
        }
    }

    public static void main(String[] args) {
        Two_Stacks_To_Queue queue=new Two_Stacks_To_Queue();
        queue.push(1);
        queue.push(2);
        queue.push(3);
        queue.push(4);
        queue.push(5);
        System.out.println(queue.poll());//1
        System.out.println(queue.peek());//2
        System.out.println(queue.poll());//2

    }
}

猫狗队列

宠物、狗和猫的类如下:
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方法,检查队列中是否还有dog或cat的实例; 用户可以调用isDogEmpty方法,检查队列中是否有dog类的实例; 用户可以调用isCatEmpty方法,检查队列中是否有cat类的实例。

package basic_algorithm.class_03;

import java.util.LinkedList;
import java.util.Queue;

public class DogCatQueues {

    public static class Pet {
        private String type;

        public Pet(String type) {
            this.type = type;
        }

        public String getPetType() {
            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 PetEnter {
        private Pet pet;
        private long count;//表示是第几个进的

        public PetEnter(Pet pet, long count) {
            this.pet = pet;
            this.count = count;
        }


        public Pet getPet() {
            return pet;
        }

        public void setPet(Pet pet) {
            this.pet = pet;
        }

        public long getCount() {
            return this.count;
        }

        public void setCount(int count) {
            this.count = count;
        }

        public String getEnterPetType() {
            return this.pet.getPetType();
        }
    }

    public static class DogCatQueue {
        private Queue<PetEnter> dogQ;
        private Queue<PetEnter> catQ;
        private long count;

        public DogCatQueue() {
            this.dogQ = new LinkedList<PetEnter>();
            this.catQ = new LinkedList<PetEnter>();
            this.count = 0;

        }

        //用户可以调用add方法将cat类或dog类的 实例放入队列中
        public void add(Pet pet) {
            if (pet.getPetType().equals("dog")) {
                this.dogQ.add(new PetEnter(pet, this.count++));
            } else if (pet.getPetType().equals("cat")) {
                this.catQ.add(new PetEnter(pet, this.count++));
            } else {
                System.out.println("sorry not dog or cat!");
            }

        }

        //用户可以调用pollAll方法,将队列中所有的实例按照进队列 的先后顺序依次弹出
        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 (!dogQ.isEmpty()) {
                return this.dogQ.poll().getPet();
            } else if (!catQ.isEmpty()) {
                return this.catQ.poll().getPet();
            } else {
                throw new RuntimeException("sorry this queue is empty!");
            }
        }

        //用户可以调用pollDog方法,将队列中dog类的实例按照 进队列的先后顺序依次弹出
        public Dog pollDog() {
            if (!this.isDogQueueEmpty()) {
                return (Dog) this.dogQ.poll().getPet();
            } else {
                throw new RuntimeException("Dog queue is empty!");
            }
        }

        //用户可以调用pollCat方法,将队列中cat类的实 例按照进队列的先后顺序依次弹出;
        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();
        }
    }

    public static void main(String[] args) {
        DogCatQueue queue = new DogCatQueue();

        Pet dog1 = new Dog();
        Pet cat1 = new Cat();
        Pet dog2 = new Dog();
        Pet cat2 = new Cat();
        Pet dog3 = new Dog();
        Pet cat3 = new Cat();

        queue.add(dog1);
        queue.add(cat1);
        queue.add(dog2);
        queue.add(cat2);
        queue.add(dog3);
        queue.add(cat3);

        queue.add(dog1);
        queue.add(cat1);
        queue.add(dog2);
        queue.add(cat2);
        queue.add(dog3);
        queue.add(cat3);

        queue.add(dog1);
        queue.add(cat1);
        queue.add(dog2);
        queue.add(cat2);
        queue.add(dog3);
        queue.add(cat3);
        while (!queue.isDogQueueEmpty()) {
            System.out.println(queue.pollDog().getPetType());
        }
        while (!queue.isEmpty()) {
            System.out.println(queue.pollAll().getPetType());
        }
    }
}

转圈打印矩阵

【题目】 给定一个整型矩阵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)。
思路分析:确定好矩阵的左上和右下两个点,然后打印外面的框框 (左->下->右->上)最外圈打印完以后将左上的点向左下方移动一位 右下的点同理向右上方移动一位 继续打印框框 如此循环 直到左上的点的行和列超过了右下的点的行和列
代码实现:

package basic_algorithm.class_03;

public class PrintMatrixSpiralOrder {
    public static void spiralOrderPrint(int[][] matrix) {
        //行 列坐标
        int num1_row = 0, num1_col = 0;
        int num2_row = matrix.length - 1, num2_col = matrix[0].length - 1;
        while (num1_row <= num2_row && num1_col <= num2_col) {
            printEdge(matrix, num1_row++, num1_col++, num2_row--, num2_col--);
        }
    }

    private static void printEdge(int[][] matrix, int num1_row, int num1_col, int num2_row, int num2_col) {
        if (num1_row == num2_row) {//同一行
            for (int i = num1_col; i <= num2_col; i++) {
                System.out.print(matrix[num1_row][i] + " ");
            }
        } else if (num1_col == num2_col) {//同一列
            for (int i = num1_row; i <= num2_row; i++) {
                System.out.print(matrix[i][num1_col] + " ");
            }
        } else {//螺旋打印
            int curCol = num1_col;
            int curRow = num1_row;
            while (curCol != num2_col) {//向左
                System.out.print(matrix[num1_row][curCol] + " ");
                curCol++;
            }
            while (curRow != num2_row) {//向下
                System.out.print(matrix[curRow][num2_col] + " ");
                curRow++;
            }
            while (curCol != num1_col) {//向右
                System.out.print(matrix[num2_row][curCol] + " ");
                curCol--;
            }
            while (curRow != num1_row) {//向上
                System.out.print(matrix[curRow][num1_col] + " ");
                curRow--;
            }

        }
    }

    public static void main(String[] args) {
        int[][] matrix = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12},
                {13, 14, 15, 16}};
        spiralOrderPrint(matrix);//1 2 3 4 8 12 16 15 14 13 9 5 6 7 11 10 
    }
}

旋转正方形矩阵

【题目】 给定一个整型正方形矩阵matrix,请把该矩阵调整成 顺时针旋转90度的样子。
【要求】 额外空间复杂度为O(1)
思路分析:
画图更好理解
1 2 3 4
5 6 7 8
9 10 11 12
13 14 15 16
如上面加粗的数字:要实现正方形顺时针旋转九十度 只需要1换到4的位置 4换到16的位置 16换到13的位置 13换到1的位置 这时最外面的一层完成了旋转 然后再从里面一层(2->8 8->15 15->9 9->2)循环一层层的旋转过来
代码实现:

package basic_algorithm.class_03;

public class RotateMatrix {
    public static void rotate(int[][] matrix) {
        int a = 0;
        int b = 0;
        int c = matrix.length - 1;
        int d = matrix[0].length - 1;
        while (a < c) {
            rotateEdge(matrix, a++, b++, c--, d--);
        }
    }

    public static void rotateEdge(int[][] m, int a, int b, int c, int d) {
        int times = d - b;
        int tmp = 0;
        for (int i = 0; i < times; i++) {
            tmp = m[a][b + i];
            m[a][b + i] = m[c - i][b];
            m[c - i][b] = m[c][d - i];
            m[c][d - i] = m[a + i][d];
            m[a + i][d] = tmp;
        }
    }

    public static void printMatrix(int[][] matrix) {
        for (int i = 0; i < matrix.length; i++) {
            for (int j = 0; j < matrix[0].length; j++) {
                System.out.print(matrix[i][j] + " ");
            }
            System.out.println();
        }
    }

    public static void main(String[] args) {
        int[][] matrix = { { 1, 2, 3, 4 }, { 5, 6, 7, 8 }, { 9, 10, 11, 12 },
                { 13, 14, 15, 16 } };
        printMatrix(matrix);
        rotate(matrix);
        System.out.println("=========");
        printMatrix(matrix);
    }
}

反转单向和双向链表

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

package class_03;

public class Code_07_ReverseList {

	public static class Node {
		public int value;
		public Node next;

		public Node(int data) {
			this.value = data;
		}
	}

	public static Node reverseList(Node head) {
		Node pre = null;
		Node next = null;
		while (head != null) {
			next = head.next;
			head.next = pre;
			pre = head;
			head = next;
		}
		return pre;
	}

	public static class DoubleNode {
		public int value;
		public DoubleNode last;
		public DoubleNode next;

		public DoubleNode(int data) {
			this.value = data;
		}
	}

	public static DoubleNode reverseList(DoubleNode head) {
		DoubleNode pre = null;
		DoubleNode next = null;
		while (head != null) {
			next = head.next;
			head.next = pre;
			head.last = next;
			pre = head;
			head = next;
		}
		return pre;
	}

	public static void printLinkedList(Node head) {
		System.out.print("Linked List: ");
		while (head != null) {
			System.out.print(head.value + " ");
			head = head.next;
		}
		System.out.println();
	}

	public static void printDoubleLinkedList(DoubleNode head) {
		System.out.print("Double Linked List: ");
		DoubleNode end = null;
		while (head != null) {
			System.out.print(head.value + " ");
			end = head;
			head = head.next;
		}
		System.out.print("| ");
		while (end != null) {
			System.out.print(end.value + " ");
			end = end.last;
		}
		System.out.println();
	}

	public static void main(String[] args) {
		Node head1 = new Node(1);
		head1.next = new Node(2);
		head1.next.next = new Node(3);
		printLinkedList(head1);
		head1 = reverseList(head1);
		printLinkedList(head1);

		DoubleNode head2 = new DoubleNode(1);
		head2.next = new DoubleNode(2);
		head2.next.last = head2;
		head2.next.next = new DoubleNode(3);
		head2.next.next.last = head2.next;
		head2.next.next.next = new DoubleNode(4);
		head2.next.next.next.last = head2.next.next;
		printDoubleLinkedList(head2);
		printDoubleLinkedList(reverseList(head2));

	}

}

“之” 字形打印矩阵

【 题目】 给定一个矩阵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)
思路分析:要学会宏观的思考
设两个点 a和b 都从(0,0)开始 a点往右走直到最右然后再往下走 b点往下走直到最下然后再往右走 两点每次都只移动一步 则每一步a点b点的连线就能构成“之”字型路线 记得判断是从左下开始连线ba 还是 右上开始连线ab
代码实现:

package basic_algorithm.class_03;

public class ZigZagPrintMatrix {
    public static void printMatrixZigZag(int[][] matrix) {
        int aR = 0, aC = 0;
        int bR = 0, bC = 0;
        int endR = matrix.length - 1;
        int endC = matrix[0].length - 1;
        boolean fromUp = false;//标记是从右上还是左下开始打印  false时为左下开始
        while (aR != endR + 1) {
            printLevel(matrix, aR, aC, bR, bC, fromUp);
            aR = aC == endC ? aR + 1 : aR;
            aC = aC == endC ? aC : aC + 1;
            bC = bR == endR ? bC + 1 : bC;
            bR = bR == endR ? bR : bR + 1;
            fromUp = !fromUp;
        }

    }

    private static void printLevel(int[][] matrix, int aR, int aC, int bR, int bC, boolean fromUp) {
        if (fromUp){//右上开始
            while(aR!=bR+1){
                System.out.println(matrix[aR++][aC--]+" ");
            }
        }else{//左下开始
            while (bR!=aR-1){
                System.out.println(matrix[bR--][bC++]+" ");
            }
        }
    }

    public static void main(String[] args) {
        int[][] matrix = { { 1, 2, 3, 4 }, { 5, 6, 7, 8 }, { 9, 10, 11, 12 } };
        printMatrixZigZag(matrix);
    }
}

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

【 题目】 给定一个有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)。
思路分析:两种方法
1、从右上角开始遍历,当前数比K大则向左移,反之下移
2、从左下角开始遍历,当前数比K大则向上移,反之右移
代码实现:

package basic_algorithm.class_03;

public class FindNumInSortedMatrix {
    public static boolean isContains(int [][] matrix,int K){
        int row=0;//右上角开始
        int col=matrix[0].length-1;
        while (row<matrix.length&&col>-1){
            if (matrix[row][col]==K){
                return true;
            }else if (matrix[row][col]>K){
                col--;
            }else{
                row++;
            }
        }
        return false;
    }
    public static void main(String[] args) {
        int[][] matrix = new int[][] { { 0, 1, 2, 3, 4, 5, 6 },// 0
                { 10, 12, 13, 15, 16, 17, 18 },// 1
                { 23, 24, 25, 26, 27, 28, 29 },// 2
                { 44, 45, 46, 47, 48, 49, 50 },// 3
                { 65, 66, 67, 68, 69, 70, 71 },// 4
                { 96, 97, 98, 99, 100, 111, 122 },// 5
                { 166, 176, 186, 187, 190, 195, 200 },// 6
                { 233, 243, 321, 341, 356, 370, 380 } // 7
        };
        int K = 233;
        System.out.println(isContains(matrix, K));
    }
}

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

【 题目】 给定两个有序链表的头指针head1和head2, 打印两个链表的公共部分。
思路分析:类似外排 遍历的时候比较若相等则打印出来
代码实现:

package basic_algorithm.class_03;

public class PrintCommonPart {
    public static class Node{
        public int val;
        public Node next;

        public Node(int val) {
            this.val = val;
        }
    }
    public static void printCommonPart(Node head1, Node head2) {
        while (head1!=null&&head2!=null){
            if (head1.val<head2.val){
                head1=head1.next;
            }else if (head1.val>head2.val){
                head2=head2.next;
            }else{
                System.out.println(head1.val+" ");
                head1=head1.next;
                head2=head2.next;
            }
        }
        System.out.println();
    }
    public static void printLinkedList(Node node){
        while (node!=null){
            System.out.println(node.val+" ");
            node=node.next;
        }
        System.out.println();
    }

    public static void main(String[] args) {
        Node node1 = new Node(2);
        node1.next = new Node(3);
        node1.next.next = new Node(5);
        node1.next.next.next = new Node(6);

        Node node2 = new Node(1);
        node2.next = new Node(2);
        node2.next.next = new Node(5);
        node2.next.next.next = new Node(7);
        node2.next.next.next.next = new Node(8);

        printLinkedList(node1);
        printLinkedList(node2);
        printCommonPart(node1, node2);
    }
}

链表问题

在笔试时只要能快速解出来题目即可 不必在意额外空间复杂度
而在面试时要尽可能的找最优解法 降低额外空间复杂度

  • 判断一个链表是否为回文结构
    【 题目】 给定一个链表的头节点head, 请判断该链表是否为回文结构。 例如: 1->2->1, 返回true。 1->2->2->1, 返回true。15->6->15, 返回true。 1->2->3, 返回false。
    解法一:利用栈先进后出的特性 遍历链表将链表的所有节点压入栈后再从栈中弹出节点 每一次都与原链表比对成功 即为回文结构
//解法一:额外空间复杂度为O(N)
    public static boolean isPalindrome1(Node head){
        Stack<Node> stack=new Stack<>();
        Node cur=head;
        while (cur!=null){
            stack.push(cur);
            cur=cur.next;
        }
        while (head!=null){
            if (head.val!=stack.pop().val){
                return false;
            }
            head=head.next;
        }
        return true;
    }

解法二:依然是利用栈 利用快慢指针找到中间位置 将后半段入栈(相当于只将后半段逆序)然后和前半段依次比对

//解法二:额外空间为N/2  额外空间复杂度也还是O(N)
    public static boolean isPalindrome2(Node head) {
        if (head == null || head.next == null) {
            return true;
        }
        Node slow = head.next;
        Node fast = head;
        while (fast.next != null && fast.next.next != null) {
            slow = slow.next;
            fast = fast.next.next;
        }
        Stack<Node> stack = new Stack<Node>();
        while (slow != null) {
            stack.push(slow);
            slow = slow.next;
        }
        while (!stack.isEmpty()) {
            if (head.val != stack.pop().val) {
                return false;
            }
            head = head.next;
        }
        return true;
    }

进阶: 如果链表长度为N, 时间复杂度达到O(N), 额外空间复杂度达到O(1)
解法三:还是快慢指针 但此方法不需要额外空间

//解法三:额外空间复杂度为O(1)  (这个方法要记得再复习哦!画图比较好理解!!)
    public static boolean isPalindrome3(Node head) {
        if (head == null || head.next == null) {
            return true;
        }
        Node slow = head;
        Node fast = head;
        while (fast.next != null && fast.next.next != null) { // find mid node
            slow = slow.next; // slow -> mid
            fast = fast.next.next; // fast -> end
        }
        fast = slow.next; // fast -> right part first node
        slow.next = null; // mid.next -> null
        Node next = null;
        while (fast != null) { // right part convert
            next = fast.next; // next -> save next node
            fast.next = slow; // next of right node convert
            slow = fast; // slow move
            fast = next; // fast move
        }
        next = slow; // next -> save last node
        fast = head;// fast -> left first node
        boolean res = true;
        while (slow != null && fast != null) { // check palindrome
            if (slow.val != fast.val) {
                res = false;
                break;
            }
            slow = slow.next; // left to mid
            fast = fast.next; // right to mid
        }
        slow = next.next;
        next.next = null;
        while (slow != null) { // 恢复链表
            fast = slow.next;
            slow.next = next;
            next = slow;
            slow = fast;
        }
        return res;
    }
  • 将单向链表按某值划分成左边小、中间相等、右边大的形式
    【题目】 给定一个单向链表的头节点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的节点即可。对某部分内部的节点顺序不做 要求。
    思路分析:荷兰国旗问题 建立一个节点类型的数组 把链表里的节点都存入这个数组中 在数组中调好 再重新重连链表(笔试中用这个方法最快)
    代码实现:
 //类似荷兰国旗问题的方法
    public static Node listPartition1(Node head, int pivot) {
        if (head == null) return head;
        Node cur = head;
        int size = 0;
        while (cur != null) {
            size++;
            cur = cur.next;
        }
        Node[] nodeArr = new Node[size];//建立一个节点类型的数组
        cur = head;
        int i = 0;
        for (i = 0; i < nodeArr.length; i++) {
            nodeArr[i] = cur;
            cur = cur.next;
        }
        arrPartition(nodeArr, pivot);
        for (i = 1; i < nodeArr.length; i++) {
            nodeArr[i - 1].next = nodeArr[i];
        }
        nodeArr[i - 1].next = null;
        return nodeArr[0];
    }

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

    private static void swap(Node[] nodeArr, int a, int b) {
        Node tmp = nodeArr[a];
        nodeArr[a] = nodeArr[b];
        nodeArr[b] = tmp;
    }

进阶: 在原问题的要求之上再增加如下两个要求。 在左、中、右三个部分的内部也做顺序要求,要求每部分里的节点从左 到右的 顺序与原链表中节点的先后次序一致。 例如:链表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)
思路分析:准备三个链表 分别将原链表中的节点放到相应的链表中 最后再把三个链表首尾连起来
代码实现:

//进阶:稳定
    public static Node listPartition2(Node head, int pivot) {
        Node sh = null, st = null;//小于pivot的链表的头尾
        Node eh = null, et = null;//等于pivot的链表的头尾
        Node bh = null, bt = null;//大于piovt的链表的头尾
        Node next = null;
        while (head != null) {
            next = head.next;
            head.next = null;
            if (head.val < pivot) {
                if (sh == null) {
                    sh = head;
                    st = head;
                } else {
                    st.next = head;
                    st = head;
                }
            } else if (head.val == 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;

    }

  • 复制含有随机指针节点的链表
    【题目】 一种特殊的链表节点类描述如下:
    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,请实现一个 函数完成 这个链表中所有结构的复制,并返回复制的新链表的头节点。
    思路分析:利用HashMap 将原链表的节点和复制出的链表的节点 存入哈希表 然后再通过哈希表找到对应节点的next和rand 连接到复制出的链表节点上(笔试时用这个方法即可 但额外空间复杂度是O(N))
    代码实现:
//哈希表法
    public static Node copyListWithRandom1(Node head) {
        HashMap<Node, Node> map = new HashMap<Node, Node>();
        Node cur = head;
        while (cur != null) {
            map.put(cur, new Node(cur.value));//copy节点放入哈希表
            cur = cur.next;
        }
        cur = head;
        while (cur != null) {
            map.get(cur).next = map.get(cur.next);
            map.get(cur).rand = map.get(cur.rand);//给复制的节点连接上next和rand
            cur = cur.next;
        }
        return map.get(head);
    }

进阶: 不使用额外的数据结构,只用有限几个变量,且在时间复杂度为 O(N) 内完成原问题要实现的函数
思路分析:将原链表的每个节点的next指向复制节点形成一个大的链表 然后遍历这个大链表 找每个节点的rand连接到复制链表上 再把大链表分离
代码实现:

//不用哈希表  额外空间复杂度是O(1)
    public static Node copyListWithRandom2(Node head) {
        if (head == null) {
            return head;
        }
        Node cur = head;
        Node next = null;
        //把copy节点连接到原节点的next
        while (cur != null) {
            next = cur.next;
            cur.next = new Node(cur.value);
            cur.next.next = next;
            cur = next;
        }
        cur = head;
        Node curCopy = null;
        //为cpoy节点设置rand
        while (cur != null) {
            next = cur.next.next;
            curCopy = cur.next;
            curCopy.rand = cur.rand != null ? cur.rand.next : null;
            cur = next;
        }
        Node copyHead = head.next;
        cur = head;
        //分离
        while (cur != null) {
            next = cur.next.next;
            curCopy = cur.next;
            cur.next = next;
            curCopy.next = next != null ? next.next : null;
            cur = next;
        }
        return copyHead;
    }
  • 两个单链表相交的一系列问题
    【题目】 在本题中,单链表可能有环,也可能无环。给定两个 单链表的头节点 head1和head2,这两个链表可能相交,也可能 不相交。请实现一个函数, 如果两个链表相交,请返回相交的 第一个节点;如果不相交,返回null 即可。 要求:如果链表1 的长度为N,链表2的长度为M,时间复杂度请达到 O(N+M),额外 空间复杂度请达到O(1)
    思路分析:
    第一步:我们需要知道这两个链表有没有环

    • 法一:利用哈希表 遍历链表将节点存入哈希表(只需要Key 所以可以用HashSet)遍历时判断表中是否已经有了该节点 若有 则链表有环 第一个发现表中已经存在的节点 即为第一个入环节点 此方法简单但是额外空间复杂度是O(N)
    • 法二:快慢指针的方法 快指针一次走两步 慢指针一次走一步 若有环则两指针一定会在环上相遇(数学证明可得)相遇后将快指针指向头节点 然后两指针同步(一次走一步)再相遇时即为第一个入环节点处 此方法仅用了有限个变量 所以额外空间复杂度为O(1)

    第二步:分三种

    • 两个链表都无环
      因为是单链表 所以遍历得到链表尾 若两个链表的表尾内存地址一样则必相交 反之不相交 但是相交部分的起点不一定是表尾 所以我们先统计两个链表的长度(比如 一个长100 一个长80) 让长的链表先走(100的链表先走20步)然后再同短的链表同步前进 相遇时即为第一个相交的节点-
    • 两个链表都有环 存在三种情况
      1)各自成环 但不相交:若loop1往下走的时候没遇到loop2则不相交 否则即为情况3
      2)先相交然后共享一个环:则环以上等同于无环链表相交
      3)共享一个环
    • 一个有环一个无环 不可能相交!

    代码实现:

package basic_algorithm.class_03;

public class FindFirstIntersectNode {
    public static class Node {
        public int value;
        public Node next;

        public Node(int data) {
            this.value = data;
        }
    }

    public static Node getIntersectNode(Node head1, Node head2) {
        if (head1 == null || head2 == null) return null;
        Node loop1 = getLoopNode(head1);//获得链表1 的第一个入环节点
        Node loop2 = getLoopNode(head1);//获得链表2 的第一个入环节点
        if (loop1 == null && loop2 == null) {
            return noLoop(head1, head2);//两个无环单链表相交问题
        } else if (loop1 != null && loop2 != null) {
            return bothLoop(head1, loop1, head2, loop2);//两个有环单链表相交问题
        } else return null;//两个单链表 一个有环一个无环不可能相交
    }
    
    //快慢指针的方法获取第一个入环节点 若没有环则返回null
    private static Node getLoopNode(Node head) {
        if (head == null || head.next == null || head.next.next == null) return null;
        Node slow = head.next;
        Node fast = head.next.next;
        while (slow != fast) {
            if (fast.next == null || fast.next.next == null) return null;
            fast = fast.next.next;
            slow = slow.next;
        }//相遇时退出了while循环
        fast = head;
        while (slow != fast) {
            slow = slow.next;
            fast = fast.next;
        }
        return slow;
    }
    //两个无环链表的相交问题
    private static Node noLoop(Node head1, Node head2) {
        if (head1 == null || head2 == null) return null;
        Node cur1 = head1;
        Node cur2 = head2;
        int n = 0;//两个链表长度的差值
        while (cur1.next != null) {
            n++;
            cur1 = cur1.next;
        }
        while (cur2.next != null) {
            n--;
            cur2 = cur2.next;
        }
        if (cur1 != cur2) return null;
        cur1 = n > 0 ? head1 : head2;
        cur2 = cur1 == head1 ? head2 : head1;
        n=Math.abs(n);
        while (n!=0){
            cur1=cur1.next;//长的先走
            n--;
        }
        while (cur1!=cur2){
            cur1=cur1.next;
            cur2=cur2.next;
        }
        return cur1;

    }

    //两个有环链表的相交问题
    private static Node bothLoop(Node head1, Node loop1, Node head2, Node loop2) {
        Node cur1=null;
        Node cur2=null;
        if (loop1==loop2){//情况2:先相交然后共享一个环
            cur1 = head1;
            cur2 = head2;
            int n = 0;
            while (cur1 != loop1) {
                n++;
                cur1 = cur1.next;
            }
            while (cur2 != loop2) {
                n--;
                cur2 = cur2.next;
            }
            cur1 = n > 0 ? head1 : head2;
            cur2 = cur1 == head1 ? head2 : head1;
            n = Math.abs(n);
            while (n != 0) {
                n--;
                cur1 = cur1.next;
            }
            while (cur1 != cur2) {
                cur1 = cur1.next;
                cur2 = cur2.next;
            }
            return cur1;
        }else{
            cur1=head1.next;
            while (cur1!=loop1){
                if (cur1==loop2){//情况3:共享一个环
                    return loop1;//返回loop2也对
                }
                cur1=cur1.next;
            }
            return null;//情况1:不相交
        }
    }
    public static void main(String[] args) {
        // 1->2->3->4->5->6->7->null
        Node head1 = new Node(1);
        head1.next = new Node(2);
        head1.next.next = new Node(3);
        head1.next.next.next = new Node(4);
        head1.next.next.next.next = new Node(5);
        head1.next.next.next.next.next = new Node(6);
        head1.next.next.next.next.next.next = new Node(7);

        // 0->9->8->6->7->null
        Node head2 = new Node(0);
        head2.next = new Node(9);
        head2.next.next = new Node(8);
        head2.next.next.next = head1.next.next.next.next.next; // 8->6
        System.out.println(getIntersectNode(head1, head2).value);

        // 1->2->3->4->5->6->7->4...
        head1 = new Node(1);
        head1.next = new Node(2);
        head1.next.next = new Node(3);
        head1.next.next.next = new Node(4);
        head1.next.next.next.next = new Node(5);
        head1.next.next.next.next.next = new Node(6);
        head1.next.next.next.next.next.next = new Node(7);
        head1.next.next.next.next.next.next = head1.next.next.next; // 7->4

        // 0->9->8->2...
        head2 = new Node(0);
        head2.next = new Node(9);
        head2.next.next = new Node(8);
        head2.next.next.next = head1.next; // 8->2
        System.out.println(getIntersectNode(head1, head2).value);

        // 0->9->8->6->4->5->6..
        head2 = new Node(0);
        head2.next = new Node(9);
        head2.next.next = new Node(8);
        head2.next.next.next = head1.next.next.next.next.next; // 8->6
        System.out.println(getIntersectNode(head1, head2).value);

    }

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值