原创

Java数据结构和算法(代码版)-3


Java数据结构和算法(代码版)-3

动态数组

package com.lzlg.study.algorithm.new2023.dynamicarray;

import org.springframework.util.StopWatch;

/
 * 二维数组
 * 通过二维数组的遍历可知道缓存的空间局部性
 * 加载缓存时通常把相邻的数据也一起加载到缓存中
 * 因为一般认为相邻元素的访问比其他元素访问更频繁,下次概率用的到
 *
 * @author lzlg
 * 2023/7/17 11:57
 */
public class DoubleArray {
    // 行有1千万
    static final int rows = 1000_0000;
    // 列只有10个
    static final int columns = 10;

    public static void main(String[] args) {
        // 行有1千万,列只有10个的二维数组
        int[][] array = new int[rows][columns];
        // 这样一个二维数组的两种访问方式
        StopWatch watch = new StopWatch();

        watch.start("rowByColumn");
        rowByColumn(array);
        watch.stop();
        // ==============================
        watch.start("columnByRow");
        columnByRow(array);
        watch.stop();
        // 打印访问时间结果
        System.out.println(watch.prettyPrint());

        // 经过比较通过rowByColumn方式访问比columnByRow快很多,因为高速缓存的原因
        // 二维数组中一行是一个一维数组,而这一维数组在内存空间是连续的,因此在一行的循环中,依次访问列比较快
        // 如果先访问列,每次都会将下一行加载到高速缓存中,高速缓存几乎没有发挥作用,因此访问慢
    }

    private static void rowByColumn(int[][] array) {
        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < columns; j++) {
                int m = array[i][j];
            }
        }
    }

    private static void columnByRow(int[][] array) {
        for (int j = 0; j < columns; j++) {
            for (int i = 0; i < rows; i++) {
                int m = array[i][j];
            }
        }
    }

}
package com.lzlg.study.algorithm.new2023.dynamicarray;

import java.util.Arrays;
import java.util.Iterator;
import java.util.function.IntConsumer;
import java.util.stream.IntStream;

/
 * 动态数组
 *
 * @author lzlg
 * 2023/7/17 11:25
 */
public class DynamicArray implements Iterable<Integer> {

    private int size;

    private int capacity;

    private int[] array = {};

    public DynamicArray() {
        this.size = 0;
        this.capacity = 8;
    }

    /
     * 返回动态数组中的数组
     *
     * @return 数组
     */
    public int[] array() {
        return Arrays.copyOfRange(array, 0, size);
    }

    /
     * 添加到数组末尾
     *
     * @param element 要添加的元素
     */
    public void addLast(int element) {
//        array[size++] = element;
        this.add(size, element);
    }

    /
     * 添加到数组指定下标位置
     *
     * @param element 要添加的元素
     */
    public void add(int index, int element) {
        // 容量增加
        capacityGrowth();
        // 将下标为index的元素向后移动
        if (index >= 0 && index < size) {
            System.arraycopy(array, index, array, index + 1, size - index);
        }
        array[index] = element;
        size++;
    }

    /
     * 容量增加方法
     */
    private void capacityGrowth() {
        // 懒加载
        if (size == 0) {
            this.array = new int[capacity];
        } else if (size == capacity) { // 数组扩容
            // 扩容为原来的1.5倍
            capacity = capacity + capacity >> 1;
            // 创建新的数组
            int[] newArray = new int[capacity];
            // 将原数组中的数据copy到新数组中
            System.arraycopy(array, 0, newArray, 0, size);
            // 替换为新数组
            array = newArray;
        }
    }

    /
     * 删除指定下标的数组元素
     *
     * @param index 下标
     * @return 原数组元素
     */
    public int remove(int index) {
        if (index >= size) {
            throw new ArrayIndexOutOfBoundsException();
        }
        // 要删除的元素
        int removed = array[index];
        // 如果是删除最后一个元素则不进行移动
        if (index < size - 1) {
            // 把在index之后的元素向前移动
            System.arraycopy(array, index + 1, array, index, size - index - 1);
        }
        size--;
        // 将最后一个元素进行还原
        array[size] = 0;
        return removed;
    }

    /
     * 进行数组的遍历
     *
     * @param consumer 遍历方式函数式封装
     */
    public void iterate(IntConsumer consumer) {
        for (int i = 0; i < size; i++) {
            consumer.accept(array[i]);
        }
    }

    /
     * 使用迭代器进行遍历
     */
    @Override
    public Iterator<Integer> iterator() {
        return new Iterator<Integer>() {
            // 遍历元素的指针
            private int point = 0;

            @Override
            public boolean hasNext() {
                return point < size;
            }

            @Override
            public Integer next() {
                return array[point++];
            }
        };
    }

    /
     * 使用流式处理
     *
     * @return int流式处理
     */
    public IntStream stream() {
        // 使用Arrays复制一份数据给IntStream使用
        return IntStream.of(Arrays.copyOfRange(array, 0, size));
    }
}
package com.lzlg.study.algorithm.new2023.dynamicarray;

/
 * @author lzlg
 * 2023/7/17 11:47
 */
public class DynamicArrayTest {
    public static void main(String[] args) {
        DynamicArray array = new DynamicArray();

        array.addLast(1);
        array.addLast(2);
        array.addLast(3);
        array.addLast(4);
        array.addLast(5);

//        array.add(3, 6);

        array.remove(3);
        // 三种不同的遍历方式
        System.out.println("===========");
        array.iterate(System.out::println);
        System.out.println("===========");
        for (Integer ele : array) {
            System.out.println(ele);
        }
        System.out.println("===========");
        array.stream().forEach(System.out::println);
    }
}

跳表

package com.lzlg.study.algorithm.new2023.skiplist;

import java.util.HashMap;
import java.util.Map;
import java.util.Random;

/
 * @author lzlg
 * 2023/9/7 13:23
 */
public class HalfRandomTest {
    public static void main(String[] args) {
        final int LOOP = 1_000;
        Map<Integer, Integer> countMap = new HashMap<>();

        for (int i = 0; i < LOOP; i++) {
            int num = randomLevel(5);
            int count = countMap.computeIfAbsent(num, k -> 0);
            count++;
            countMap.put(num, count);
        }
        Map<Integer, String> resultMap = new HashMap<>();
        for (Map.Entry<Integer, Integer> entry : countMap.entrySet()) {
            Integer key = entry.getKey();
            Integer value = entry.getValue();
            resultMap.put(key, (value / 10.0) + "%");
        }
        System.out.println(resultMap);
    }

    static Random r = new Random();

    /
     * 设计一个方法,随机返回1到max之间的随机整数
     * 返回1的几率是50%,返回2的几率是25%,返回3的几率是12.5%...依次减半
     *
     * @param max 最大数字
     * @return 1到max的随机整数
     */
    public static int randomLevel(int max) {
        int x = 1;
        while (x < max) {
            if (r.nextBoolean()) {
                return x;
            }
            x++;
        }
        return x;
    }
}
package com.lzlg.study.algorithm.new2023.skiplist;

import java.util.Arrays;
import java.util.Random;

/
 * 跳表的实现
 *
 * @author lzlg
 * 2023/9/7 15:40
 */
public class SkipList {

    public static void main(String[] args) {
        SkipList sl = new SkipList();
        Node node3 = new Node(3);
        Node node7 = new Node(7);
        Node node11 = new Node(11);
        Node node12 = new Node(12);
        Node node16 = new Node(16);
        Node node19 = new Node(19);
        Node node22 = new Node(22);
        Node node23 = new Node(23);
        Node node26 = new Node(26);
        Node node30 = new Node(30);
        sl.head.next[0] = node3;
        sl.head.next[1] = node3;
        sl.head.next[2] = node3;
        sl.head.next[3] = node19;
        sl.head.next[4] = node19;
        sl.head.next[5] = node19;
        sl.head.next[6] = node19;
        sl.head.next[7] = node19;

        node3.next[0] = node7;
        node3.next[1] = node7;
        node3.next[2] = node7;

        node7.next[0] = node11;
        node7.next[1] = node12;
        node7.next[2] = node16;

        node11.next[0] = node12;

        node12.next[0] = node16;
        node12.next[1] = node16;

        node16.next[0] = node19;
        node16.next[1] = node19;
        node16.next[2] = node19;

        node19.next[0] = node22;
        node19.next[1] = node22;
        node19.next[2] = node22;
        node19.next[3] = node26;

        node22.next[0] = node23;
        node22.next[1] = node26;
        node22.next[2] = node26;

        node23.next[0] = node26;

        node26.next[0] = node30;

        Node[] path = sl.findPath(11);
        System.out.println(Arrays.toString(path));

        sl.add(15);
        path = sl.findPath(15);
        System.out.println(Arrays.toString(path));

    }

    // 头节点
    Node head;

    public SkipList() {
        // 创建头节点
        this.head = new Node(-1);
    }

    /
     * 根据value获取查找路径时的节点
     *
     * @param value 值value
     * @return 路径节点数组
     */
    private Node[] findPath(int value) {
        // 指向当前节点的指针,从头节点开始
        Node current = this.head;
        // 创建路径节点数组
        Node[] path = new Node[MAX_HEIGHT];
        // 使用level模拟层数,从高到低
        for (int level = MAX_HEIGHT - 1; level >= 0; level--) {
            // 当前节点的下一个节点不为null,且下一个节点的value小于要查找的value时,则向右查找
            while (current.next[level] != null && current.next[level].value < value) {
                current = current.next[level];
            }
            // 记录节点路径
            path[level] = current;
        }

        return path;
    }

    /
     * 进行查找value
     *
     * @param value 待查找的value
     * @return 查找到返回true
     */
    public boolean search(int value) {
        Node[] path = findPath(value);
        // 因为path记录的是找到value的前面的路径节点,因此下标为0的元素的next的第一个指向value的节点
        Node node = path[0].next[0];
        // 该节点可能为null,该节点的值可能不是value,因此需要判断
        return node != null && node.value == value;
    }

    /
     * 进行添加
     *
     * @param value 待添加的value
     */
    public void add(int value) {
        // 找到了添加前的路径节点列表
        Node[] path = findPath(value);
        // 创建新节点
        Node node = new Node(value);
        // 获取新节点的随机高度
        int level = randomLevel(MAX_HEIGHT);

        // 将高度为level的新节点的指针进行修改
        for (int i = 0; i < level; i++) {
            // 将新节点的next指针指向其后节点
            node.next[i] = path[i].next[i];
            // 将新节点的前节点的next指针指向新节点
            path[i].next[i] = node;
        }

    }

    /
     * 进行消除
     *
     * @param value 待消除的value
     * @return 成功消除返回true, 找不到返回false
     */
    public boolean erase(int value) {
        // 找到了添加前的路径节点列表
        Node[] path = findPath(value);
        // 因为path记录的是找到value的前面的路径节点,因此下标为0的元素的next的第一个指向value的节点
        Node node = path[0].next[0];
        // 没找到的情况
        if (node == null || node.value != value) {
            return false;
        }
        // 修改节点的指针
        for (int i = 0; i < MAX_HEIGHT; i++) {
            // 当[前节点]的next指向的是当前节点,才进行删除节点
            if (path[i].next[i] == node) {
                path[i].next[i] = node.next[i];
            } else {
                break;
            }
        }

        return true;
    }


    // 规定跳表的每个元素的高度为10
    static final int MAX_HEIGHT = 10;

    /
     * 跳表节点类
     */
    static class Node {

        int value;

        Node[] next;

        public Node(int value) {
            this.value = value;
            this.next = new Node[MAX_HEIGHT];
        }

        @Override
        public String toString() {
            return "Node{" + value + '}';
        }
    }

    static Random r = new Random();

    /
     * 设计一个方法,随机返回1到max之间的随机整数
     * 返回1的几率是50%,返回2的几率是25%,返回3的几率是12.5%...依次减半
     *
     * @param max 最大数字
     * @return 1到max的随机整数
     */
    public static int randomLevel(int max) {
        int x = 1;
        while (x < max) {
            if (r.nextBoolean()) {
                return x;
            }
            x++;
        }
        return x;
    }
}

package com.lzlg.study.algorithm.new2023.graph;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

/
 * 贝尔曼福特算法,求一个顶点到其他顶点最短距离的算法
 * 可以处理边的权重是负数的情况
 * 但是不能处理有负环(有向图有环,且环中所有边的权重之和是负数)的情况
 *
 * @author lzlg
 * 2023/8/8 11:32
 */
public class BellmanFoldAlgorithm {
    /
     * 贝尔曼福特算法
     * 循环次数是顶点个数-1次
     * 每次循环都遍历所有的边
     * 计算(边的起点的距离+边的权重)
     * 如果大于边的终点的距离,则更新边的终点的距离
     *
     * @param graph  图
     * @param origin 起点顶点
     */
    public static void bellmanFod(List<Vertex> graph, Vertex origin) {
        // 设置起点顶点的距离为0
        origin.dist = 0;
        // 循环次数是顶点的个数-1次
        int size = graph.size();
        for (int i = 0; i < size - 1; i++) {
            // 遍历图中所有顶点
            for (Vertex start : graph) {
                // 遍历顶点的所有边
                for (Edge edge : start.edges) {
                    // 边的终点
                    Vertex end = edge.linked;
                    // 计算新的距离
                    int dist = start.dist + edge.weight;
                    // 和原距离进行比较,如果小于,则进行更新
                    if (start.dist != Integer.MAX_VALUE && dist < end.dist) {
                        end.dist = dist;
                    }
                }
            }
        }

        for (Vertex vertex : graph) {
            System.out.println("顶点: " + origin.name
                    + " 到顶点: " + vertex.name
                    + "的距离是: " + vertex.dist);
        }
    }

    public static void main(String[] args) {
        // 获取图
        List<Vertex> graph = graph();
        bellmanFod(graph, graph.get(0));
    }

    /
     * 有向图,边有权重
     */
    private static List<Vertex> graph() {
        Vertex v1 = new Vertex("v1");
        Vertex v2 = new Vertex("v2");
        Vertex v3 = new Vertex("v3");
        Vertex v4 = new Vertex("v4");

        v1.edges = Arrays.asList(new Edge(v2, 2), new Edge(v3, 1));
        v2.edges = Collections.singletonList(new Edge(v3, -2));
        v3.edges = Collections.singletonList(new Edge(v4, 1));
        v4.edges = Collections.emptyList();

        return Arrays.asList(v1, v2, v3, v4);
    }
}
package com.lzlg.study.algorithm.new2023.graph;

import java.util.*;

/
 * 迪克斯特拉算法,求一个顶点到其他顶点最短距离的算法
 * 但遇到权重是负数的边的时候,算法失效
 * 因为算法默认边的权重是>=0的
 *
 * @author lzlg
 * 2023/8/8 10:32
 */
public class DijkstraAlgorithm {

    public static void main(String[] args) {
        // 获取图
        List<Vertex> graph = graph();
        dijkstra(graph, graph.get(0));
        System.out.println("===================");
        dijkstraWithPriority(graph, graph.get(0));
    }

    /
     * 迪克斯特拉算法,步骤
     * 1.使用一个集合将所有顶点放入
     * 2.标记起点顶点的距离为0
     * 3.开始遍历集合,每次取出距离最小的顶点A
     * 遍历顶点A的所有邻居顶点,如果顶点A到邻居的距离比邻居的距离小,则更新邻居顶点的距离
     * 4.从集合中删除当前最小顶点A
     * <p>
     * 如果想记录顶点到顶点的走过的顶点集合
     *
     * @param graph  图
     * @param origin 起点顶点
     */
    public static void dijkstra(List<Vertex> graph, Vertex origin) {
        // 创建集合
        List<Vertex> list = new ArrayList<>(graph);
        // 标记起点顶点的距离为0
        origin.dist = 0;
        // 遍历集合
        while (!list.isEmpty()) {
            // 找到顶点距离最小的
            Vertex current = list.stream().min(Comparator.comparingInt(v -> v.dist)).get();
            // 遍历当前顶点的邻居
            for (Edge edge : current.edges) {
                // 计算当前节点到邻居的距离
                int dist = current.dist + edge.weight;
                // 如果距离小于当前节点距离,则更新
                if (dist < edge.linked.dist) {
                    edge.linked.dist = dist;
                    // 记录邻居节点走过的顶点集合
                    edge.linked.prevList.add(current);
                }
            }
            // 删除当前节点
            list.remove(current);
        }

        for (Vertex vertex : graph) {
            System.out.println("顶点: " + origin.name
                    + " 到顶点: " + vertex.name
                    + "的距离是: " + vertex.dist
                    + ", 路径是: " + vertex.prevList);
        }
    }

    /
     * 使用优先级队列(小顶堆)改进
     *
     * @param graph  图
     * @param origin 起点顶点
     */
    public static void dijkstraWithPriority(List<Vertex> graph, Vertex origin) {
        // 使用优先级队列(小顶堆)-使用比较器直到比较的字段-距离
        PriorityQueue<Vertex> heap = new PriorityQueue<>(Comparator.comparingInt(v -> v.dist));
        // 标记起点顶点的距离为0
        origin.dist = 0;

        // 将顶点加入到优先级队列中
        for (Vertex vertex : graph) {
            heap.offer(vertex);
        }

        // 遍历优先级队列
        while (!heap.isEmpty()) {
            // 优先级队列的第一个就是距离最小的顶点
            Vertex current = heap.peek();

            // 当前节点没有访问过,才进行处理
            if (!current.visited) {
                // 遍历当前顶点的邻居
                for (Edge edge : current.edges) {
                    // 邻居节点
                    Vertex linked = edge.linked;
                    // 没有访问过才进行访问
                    if (!linked.visited) {
                        // 计算当前节点到邻居的距离
                        int dist = current.dist + edge.weight;
                        // 如果距离小于当前节点距离,则更新
                        if (dist < linked.dist) {
                            linked.dist = dist;
                            // 更新邻居顶点距离后,需再次调整优先级队列
                            // 会造成优先级队列中有重复的数据,导致再次遍历
                            // 因此需要标识当前节点已经访问过了
                            heap.offer(linked);
                        }
                    }
                }

                // 标识当前节点访问过了
                current.visited = true;
            }

            // 删除当前节点
            heap.poll();
        }

        for (Vertex vertex : graph) {
            System.out.println("顶点: " + origin.name
                    + " 到顶点: " + vertex.name
                    + "的距离是: " + vertex.dist);
        }
    }

    /
     * 有向图,边有权重
     */
    private static List<Vertex> graph() {
        Vertex v1 = new Vertex("1");
        Vertex v2 = new Vertex("2");
        Vertex v3 = new Vertex("3");
        Vertex v4 = new Vertex("4");
        Vertex v5 = new Vertex("5");
        Vertex v6 = new Vertex("6");

        v1.edges = Arrays.asList(new Edge(v6, 14), new Edge(v2, 7), new Edge(v3, 9));
        v2.edges = Arrays.asList(new Edge(v4, 15));
        v3.edges = Arrays.asList(new Edge(v6, 2), new Edge(v4, 11));
        v4.edges = Arrays.asList(new Edge(v5, 6));
        v5.edges = Collections.emptyList();
        v6.edges = Arrays.asList(new Edge(v5, 9));

        return Arrays.asList(v1, v2, v3, v4, v5, v6);
    }
}
package com.lzlg.study.algorithm.new2023.graph;

import java.util.Arrays;

/
 * 不相交集(并查集)
 * 用来判断图中顶点是否连通的数据结构
 * <p>
 * 其中有两个问题:
 * 1.因为不相交集是递归调用,比如有四个顶点下标分别为1,2,3,4,领头顶点为0
 * 如果只set[y] = x;使用此代码会造成顶点4->3->2->1->0
 * 如果查找顶点下标4的领头顶点下标,则会有四次递归
 * 为何不直接把顶点下标4的领头顶点下标改为0,这样只需一次就能找到
 * 这种方式称为路径压缩
 * <p>
 * 2.当要连接图中两个顶点时,如果顶点(领头)旗下的顶点数量比较多
 * 那应该把数量少的领头顶点下标的数组值更新为数量较多的领头顶点下标
 * 这样数量较多的一部分顶点下标的路径会保持原样,减少查找次数
 * 这种方式称为union by size
 *
 * @author lzlg
 * 2023/8/9 10:34
 */
public class DisjointSet {
    // 不相交集中的数组
    int[] set;
    // [统计顶点的连通顶点的数量]数组,初始时为1
    int[] size;

    public DisjointSet(int capacity) {
        // 数组中的下标对应图中顶点列表中的下标
        // 数组中的元素值,则是连通的顶点的下标
        set = new int[capacity];
        // 刚开始,只自己和自己连通
        for (int i = 0; i < capacity; i++) {
            set[i] = i;
        }
        // 初始化[统计顶点的连通顶点的数量]数组
        this.size = new int[capacity];
        for (int i = 0; i < capacity; i++) {
            // 开始时都是1(自己连通自己)
            size[i] = 1;
        }
    }

    /
     * 将两个顶点连通,顶点下标y连通到顶点下标x
     *
     * @param x 顶点下标x
     * @param y 顶点下标y
     */
    public void union(int x, int y) {
        // 进行连通顶点数量的对比
        // 如果顶点下标x的连通顶点数量小于顶点下标y的连通顶点数量
        if (size[x] < size[y]) {
            // 则以y作为领头节点
            set[x] = y;
            // 并更新领头节点的连通顶点数
            size[y] = size[y] + size[x];
        } else {
            // 反之,则以x作为领头节点
            // 把数组中顶点下标y对应的元素值改为要连通的顶点下标x
            set[y] = x;
            // 并更新领头节点的连通顶点数
            size[x] = size[x] + size[y];
        }
    }

    /
     * 查找顶点下标x的领头顶点下标
     * 领头节点下标的特点是数组下标和元素值一致
     *
     * @param x 顶点下标x
     * @return 领头顶点下标
     */
    public int find(int x) {
        // 领头节点下标的特点是数组下标和元素值一致
        if (x == set[x]) {
            return x;
        }
        // 如果不一致,则数组的元素值指向的是领头顶点的下标
        // 因此使用set[x]进行递归调用

        // 进行路径压缩
        // 把最终递归调用的结果(领头顶点的下标)赋值给当前顶点下标对应的数组元素值
        return set[x] = find(set[x]);
    }

    @Override
    public String toString() {
        return Arrays.toString(set);
    }

    public static void main(String[] args) {
        DisjointSet set = new DisjointSet(7);
        set.union(0, 1);
        System.out.println(set);
        set.union(4, 5);
        System.out.println(set);
    }
}
package com.lzlg.study.algorithm.new2023.graph;

/
 * 图的边
 *
 * @author lzlg
 * 2023/8/7 12:28
 */
public class Edge {
    // 有向边指向的顶点(箭头一端)
    Vertex linked;
    // 权重
    int weight;

    public Edge(Vertex linked) {
        this.linked = linked;
    }

    public Edge(Vertex linked, int weight) {
        this.linked = linked;
        this.weight = weight;
    }
}
package com.lzlg.study.algorithm.new2023.graph;

import java.util.*;
import java.util.stream.Collectors;

/
 * 弗洛伊德算法,求全部顶点到各个顶点的最短路径算法
 *
 * @author lzlg
 * 2023/8/8 12:55
 */
public class FloydAlgorithm {
    /
     * 弗洛伊德算法
     * 1.初始化一个二维的int数组,行和列都是顶点数
     * 2.如果一个顶点到另一个顶点有边,则更新相应二维数组的值(距离),如果没有边,则是无穷(最大)
     * 3.开始遍历顶点,比较[从顶点1借由顶点2到达顶点3的距离]和[顶点1直接到达顶点3的距离]
     * 如果小于,则将相应的二维数组的值更新为[从顶点1借由顶点2到达顶点3的距离]
     * 4.结束循环后,此时二维数组存储的就是各个顶点到其他顶点的最短路径值
     * <p>
     * 如何记录遍历路径?
     * 同样使用一个二维的顶点类数组,记录上一个顶点
     * <p>
     * 如何检测是否有负环呢?
     * 经过分析发现,如果有负环,则二维数组中对角线的值不是0了,而是小于0
     *
     * @param graph 图
     */
    public static void floyd(List<Vertex> graph) {
        // 顶点数
        int size = graph.size();
        // 初始化记录距离的二维数组
        int[][] dist = new int[size][size];
        // 记录遍历路径的二维顶点数组
        Vertex[][] prev = new Vertex[size][size];

        // 遍历图
        for (int i = 0; i < size; i++) {
            // 起始顶点
            Vertex start = graph.get(i);
            // 将起始顶点的边转换为以相邻顶点为key,权重为value的map
            Map<Vertex, Integer> map = start.edges.stream()
                    .collect(Collectors.toMap(e -> e.linked, e -> e.weight));

            for (int j = 0; j < size; j++) {
                // 结束顶点
                Vertex end = graph.get(j);
                // 如果起始顶点和结束顶点一致,则距离是0
                if (start == end) {
                    dist[i][j] = 0;
                } else {
                    // 如果能从map中找到,则返回map中的权重
                    // 如果找不到则不是通路,返回无穷(int的最大)
                    dist[i][j] = map.getOrDefault(end, Integer.MAX_VALUE);
                    prev[i][j] = map.get(end) == null ? null : start;
                }
            }
        }

        // 开始遍历二维数组
        // k层的循环是[从顶点1借由顶点2到达顶点3的距离]中的顶点2
        for (int k = 0; k < size; k++) {
            // i层的循环是[从顶点1借由顶点2到达顶点3的距离]中的顶点1
            for (int i = 0; i < size; i++) {
                // j层的循环是[从顶点1借由顶点2到达顶点3的距离]中的顶点3
                for (int j = 0; j < size; j++) {
                    // 此路不通(如果有一个无穷大,则表明此路不通)
                    if (dist[i][k] == Integer.MAX_VALUE || dist[k][j] == Integer.MAX_VALUE) {
                        continue;
                    }
                    // path是[从顶点1借由顶点2到达顶点3的距离]
                    int path = dist[i][k] + dist[k][j];
                    // 如果path小于[顶点1直接到顶点3的距离],则更新相应的二维数组的值
                    if (path < dist[i][j]) {
                        dist[i][j] = path;

                        // 如果是[从顶点1借由顶点2到达顶点3的距离]这种情况,则前一个顶点是顶点2
                        prev[i][j] = prev[k][j];
                    }

                    // 对角线(即i==j)的距离如果小于0,则证明图中有负环
                    if (i == j && dist[i][j] < 0) {
                        throw new RuntimeException("图中有负环");
                    }
                }
            }
        }
        System.out.println("======================");
        showPath(prev, graph);
    }

    /
     * 还原顶点到各个顶点的路径
     *
     * @param prev  顶点数组
     * @param graph 图
     */
    private static void showPath(Vertex[][] prev, List<Vertex> graph) {
        for (int i = 0; i < prev.length; i++) {
            for (int j = 0; j < prev[i].length; j++) {
                showPath(prev, graph, i, j);
            }
        }
    }

    /
     * 还原顶点到各个顶点的路径
     *
     * @param prev  顶点数组
     * @param graph 图
     * @param i     起点
     * @param j     终点
     */
    private static void showPath(Vertex[][] prev, List<Vertex> graph, int i, int j) {
        // 相同顶点的不打印
        if (i == j) {
            return;
        }
        System.out.print("起点: " + graph.get(i).name + "到终点: " + graph.get(j).name + "的路径是: ");
        // 使用栈,从后往前找
        Deque<String> stack = new LinkedList<>();
        // 先把终点加入到栈中
        stack.push(graph.get(j).name);
        // 从后往前找,最后i一定和j相等
        while (i != j) {
            // 取出前一个顶点
            Vertex p = prev[i][j];
            // 放入栈中
            stack.push(p.name);
            // 找到前一个顶点在图中的下标,并赋值给j,再次循环找前一个节点的前一个节点
            j = graph.indexOf(p);
        }
        // 打印栈中元素,即是遍历路径
        System.out.println(stack);
    }

    /
     * 打印二维顶点数组
     *
     * @param prev 顶点数组
     */
    private static void print(Vertex[][] prev) {
        for (int i = 0; i < prev.length; i++) {
            StringBuilder sb = new StringBuilder("[");
            for (int j = 0; j < prev[i].length; j++) {
                if (j != 0) {
                    sb.append("\t");
                }
                sb.append(prev[i][j] == null ? "null" : prev[i][j].name);
                if (j != prev[i].length - 1) {
                    sb.append(",");
                }
            }
            sb.append("]");
            System.out.println(sb);
        }
        System.out.println("=====================");
    }

    /
     * 打印二维距离数组
     *
     * @param dist 距离数组
     */
    private static void print(int[][] dist) {
        for (int i = 0; i < dist.length; i++) {
            StringBuilder sb = new StringBuilder("[");
            for (int j = 0; j < dist[i].length; j++) {
                if (j != 0) {
                    sb.append("\t");
                }
                sb.append(dist[i][j] == Integer.MAX_VALUE ? "∞" : dist[i][j]);
                if (j != dist[i].length - 1) {
                    sb.append(",");
                }
            }
            sb.append("]");
            System.out.println(sb);
        }
        System.out.println("=====================");
    }

    public static void main(String[] args) {
        // 获得图
        List<Vertex> graph = normalGraph();
        floyd(graph);
        System.out.println("=====================");
        graph = negativeGraph();
        floyd(graph);
    }

    /
     * 有向图,边有权重,无负环
     */
    private static List<Vertex> normalGraph() {
        Vertex v1 = new Vertex("v1");
        Vertex v2 = new Vertex("v2");
        Vertex v3 = new Vertex("v3");
        Vertex v4 = new Vertex("v4");

        v1.edges = Collections.singletonList(new Edge(v3, -2));
        v2.edges = Arrays.asList(new Edge(v1, 4), new Edge(v3, 3));
        v3.edges = Collections.singletonList(new Edge(v4, 2));
        v4.edges = Collections.singletonList(new Edge(v2, -1));

        return Arrays.asList(v1, v2, v3, v4);
    }

    /
     * 有向图,边有权重,有负环
     */
    private static List<Vertex> negativeGraph() {
        Vertex v1 = new Vertex("v1");
        Vertex v2 = new Vertex("v2");
        Vertex v3 = new Vertex("v3");
        Vertex v4 = new Vertex("v4");

        v1.edges = Collections.singletonList(new Edge(v2, 2));
        v2.edges = Collections.singletonList(new Edge(v3, -4));
        v3.edges = Arrays.asList(new Edge(v4, 1), new Edge(v1, 1));
        v4.edges = Collections.emptyList();

        return Arrays.asList(v1, v2, v3, v4);
    }
}
package com.lzlg.study.algorithm.new2023.graph;

/
 * 图的概念
 *
 * @author lzlg
 * 2023/8/7 12:08
 */
public class GraphDefinition {

    public static void main(String[] args) {
        System.out.println("Hello, Graph!");
        // 图是由顶点(vertex)和边(edge)组成的数据结构
        // 有向图的边有方向的,无向图的边是无向的
        // 度是指与该顶点相邻的边的数量
        // 有向图中,度细分为入度(指向该顶点的)和出度(从该顶点出去的)
        // 权:图中的边可以有权重的
        // 路径:从一个顶点到另一个顶点的一系列连续边
        // 路径长度:不考虑权重,长度是边的数量;考虑权重,长度是权重的累加
        // 在有向图中,从一个顶点开始,可以通过若干条有向的边返回到该顶点,那就形成了一个环
        // 图的连通性:如果两个顶点之间存在路径,这两个顶点是连通的;
        // 所有顶点都连通的图称为连通图.
        // 如果图中的子图连通,则该子图称为该图的连通分量
        // 图的两种表示方法:邻接矩阵和邻接表
    }
}
package com.lzlg.study.algorithm.new2023.graph;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.PriorityQueue;

/
 * 最小生成树算法(求图中所有顶点相连后的权重最小)-Kruskal算法
 * 该算法是以边作为突破点
 *
 * @author lzlg
 * 2023/8/9 9:39
 */
public class MinTreeKruskal {

    /
     * 最小生成树算法(求图中所有顶点相连后的权重最小)-Kruskal算法
     *
     * @param size 顶点的数量
     * @param heap 边的优先级队列
     */
    public static void kruskal(int size, PriorityQueue<Edge> heap) {
        // 输出边的集合,只要找出等于顶点个数-1条边即可
        List<Edge> list = new ArrayList<>();
        // 使用一个不相交集来判断顶点是否已经连通
        DisjointSet set = new DisjointSet(size);
        // 进行循环
        while (list.size() < (size - 1)) {
            // 从边的优先级队列中取出权重最小的边
            Edge poll = heap.poll();
            // 使用一个并查集(不相交集),来判断起点和终点是否连通
            int x = set.find(poll.start);
            // 取出顶点下标对应的不相交集中领头顶点的下标
            int y = set.find(poll.end);
            // 如果不相等,则没有连通,可加入到边的集合中
            if (x != y) {
                list.add(poll);
                // 在不相交集中,将两个领头顶点连通
                set.union(x, y);
            }
        }
        for (Edge edge : list) {
            System.out.println(edge);
        }
    }

    public static void main(String[] args) {
        Vertex v1 = new Vertex("v1");
        Vertex v2 = new Vertex("v2");
        Vertex v3 = new Vertex("v3");
        Vertex v4 = new Vertex("v4");
        Vertex v5 = new Vertex("v5");
        Vertex v6 = new Vertex("v6");
        Vertex v7 = new Vertex("v7");
        // 顶点合集
        List<Vertex> vertices = Arrays.asList(v1, v2, v3, v4, v5, v6, v7);
        // 根据权重的小顶堆
        PriorityQueue<Edge> heap = new PriorityQueue<>(Arrays.asList(
                new Edge(vertices, 0, 1, 2),
                new Edge(vertices, 0, 2, 4),
                new Edge(vertices, 0, 3, 1),
                new Edge(vertices, 1, 3, 3),
                new Edge(vertices, 1, 4, 10),
                new Edge(vertices, 2, 3, 2),
                new Edge(vertices, 2, 5, 5),
                new Edge(vertices, 3, 4, 7),
                new Edge(vertices, 3, 5, 8),
                new Edge(vertices, 3, 6, 4),
                new Edge(vertices, 4, 6, 6),
                new Edge(vertices, 5, 6, 1)
        ));

        kruskal(vertices.size(), heap);
    }

    /
     * 使用新的顶点
     */
    static class Vertex {
        String name;

        public Vertex(String name) {
            this.name = name;
        }
    }

    /
     * 使用新的边
     */
    static class Edge implements Comparable<Edge> {
        // 顶点合集
        List<Vertex> vertices;
        // 起始顶点下标
        int start;
        // 结束顶点下标
        int end;
        // 边的权重
        int weight;

        public Edge(List<Vertex> vertices, int start, int end, int weight) {
            this.vertices = vertices;
            this.start = start;
            this.end = end;
            this.weight = weight;
        }

        @Override
        public int compareTo(Edge edge) {
            return Integer.compare(this.weight, edge.weight);
        }

        @Override
        public String toString() {
            return "边:" + vertices.get(start).name + "<-->" + vertices.get(end).name + ", 权重: " + weight;
        }
    }
}
package com.lzlg.study.algorithm.new2023.graph;

import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.PriorityQueue;

/
 * 最小生成树算法(求图中所有顶点相连后的权重最小)-Prim算法
 * 算法思想和Dijkstra算法一致,顶点的距离最初是无穷
 * 把图中所有顶点加入优先级队列(小顶堆)
 * 选中一个顶点,先将顶点的距离置为0
 * 然后遍历顶点相关联的边,如果小于相邻顶点的距离,更新相邻顶点的距离为边的权重
 * (注意:这里是更新为边的权重,而不是Dijkstra算法更新为顶点距离+边的权重)
 * 遍历完成后删除该顶点
 *
 * @author lzlg
 * 2023/8/9 9:38
 */
public class MinTreePrim {
    /
     * 最小生成树算法(求图中所有顶点相连后的权重最小)-Prim算法
     *
     * @param graph  图
     * @param origin 起始顶点
     */
    public static void prim(List<Vertex> graph, Vertex origin) {
        // 创建优先级队列
        PriorityQueue<Vertex> heap = new PriorityQueue<>(Comparator.comparingInt(v -> v.dist));
        // 初始化起点顶点的距离为0
        origin.dist = 0;
        // 将所有顶点加入到优先级队列中
        for (Vertex vertex : graph) {
            heap.offer(vertex);
        }

        // 开始循环
        while (!heap.isEmpty()) {
            // 弹出距离最小的顶点
            Vertex peek = heap.peek();
            // 如果该顶点没有访问过
            if (!peek.visited) {
                // 遍历该顶点的边
                for (Edge edge : peek.edges) {
                    // 邻居节点
                    Vertex linked = edge.linked;
                    // 没有访问过才进行更新
                    if (!linked.visited) {
                        // 如果边的权重小于相邻顶点的距离,则更新相邻顶点的距离
                        if (edge.weight < linked.dist) {
                            linked.dist = edge.weight;
                            // 添加前一个顶点,记录路径
                            linked.prevList.add(peek);
                            // 需要再次调整小顶堆
                            heap.offer(linked);
                        }
                    }
                }
                peek.visited = true;
            }
            // 移出该顶点
            heap.poll();
        }

        // 打印结果
        for (Vertex vertex : graph) {
            System.out.println("顶点:" + vertex.name + ",距离:" + vertex.dist + ",前节点:" + vertex.prevList);
        }
    }

    public static void main(String[] args) {
        List<Vertex> graph = graph();
        prim(graph, graph.get(0));
    }

    /
     * 无向图,边有权重
     */
    public static List<Vertex> graph() {
        Vertex v1 = new Vertex("v1");
        Vertex v2 = new Vertex("v2");
        Vertex v3 = new Vertex("v3");
        Vertex v4 = new Vertex("v4");
        Vertex v5 = new Vertex("v5");
        Vertex v6 = new Vertex("v6");
        Vertex v7 = new Vertex("v7");

        v1.edges = Arrays.asList(new Edge(v2, 2), new Edge(v3, 4), new Edge(v4, 1));
        v2.edges = Arrays.asList(new Edge(v1, 2), new Edge(v4, 3), new Edge(v5, 10));
        v3.edges = Arrays.asList(new Edge(v1, 4), new Edge(v4, 2), new Edge(v6, 5));
        v4.edges = Arrays.asList(new Edge(v1, 1), new Edge(v2, 3), new Edge(v3, 2),
                new Edge(v5, 7), new Edge(v6, 8), new Edge(v7, 4));
        v5.edges = Arrays.asList(new Edge(v2, 10), new Edge(v4, 7), new Edge(v7, 6));
        v6.edges = Arrays.asList(new Edge(v3, 5), new Edge(v4, 8), new Edge(v7, 1));
        v7.edges = Arrays.asList(new Edge(v4, 4), new Edge(v5, 6), new Edge(v6, 1));

        return Arrays.asList(v1, v2, v3, v4, v5, v6, v7);
    }
}
package com.lzlg.study.algorithm.new2023.graph;

import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;

/
 * 测试初始化图
 *
 * @author lzlg
 * 2023/8/7 12:31
 */
public class TestInitGraph {

    public static void main(String[] args) {
        Vertex v = simpleGraphWithWeight();
        dfs(v);
        System.out.println("\n========================");
        v = simpleGraphWithWeight();
        dfsStack(v);
        System.out.println("\n========================");
        v = simpleGraphWithWeight();
        bfsQueue(v);
        System.out.println("\n========================");
    }

    /
     * 使用递归进行深度优先遍历
     *
     * @param v 顶点
     */
    private static void dfs(Vertex v) {
        // 顶点被访问了
        v.visited = true;
        System.out.print(v.name + "\t");
        // 从顶点的边关联的顶点
        for (Edge edge : v.edges) {
            // 如果顶点没访问过,则进行递归
            if (!edge.linked.visited) {
                dfs(edge.linked);
            }
        }
    }

    /
     * 使用栈进行深度优先遍历
     *
     * @param v 顶点
     */
    private static void dfsStack(Vertex v) {
        // 栈
        LinkedList<Vertex> stack = new LinkedList<>();
        // 标识顶点访问过
        v.visited = true;
        // 先将顶点放入栈中
        stack.push(v);
        // 循环到栈为空
        while (!stack.isEmpty()) {
            // 弹出栈元素
            Vertex pop = stack.pop();
            System.out.print(pop.name + "\t");

            // 遍历边集合,把没有访问过的放入栈中
            for (Edge edge : pop.edges) {
                if (!edge.linked.visited) {
                    edge.linked.visited = true;
                    stack.push(edge.linked);
                }
            }
        }
    }

    /
     * 使用队列进行广度优先遍历
     *
     * @param v 顶点
     */
    private static void bfsQueue(Vertex v) {
        // 队列
        LinkedList<Vertex> queue = new LinkedList<>();
        // 标识顶点访问过
        v.visited = true;
        // 先加入顶点
        queue.offer(v);
        // 循环到队列不为空
        while (!queue.isEmpty()) {
            // 出队
            Vertex poll = queue.poll();
            System.out.print(poll.name + "\t");

            // 遍历边集合,把没有访问过的放入队列中
            for (Edge edge : poll.edges) {
                if (!edge.linked.visited) {
                    edge.linked.visited = true;
                    queue.offer(edge.linked);
                }
            }
        }
    }

    /
     * 有向图: a -> b -> c  b -> d c -> d
     * 简单有向图,边无权重
     */
    private static Vertex simpleGraph() {
        // 有向图: a -> b -> c  b -> d c -> d
        Vertex a = new Vertex("A");
        Vertex b = new Vertex("B");
        Vertex c = new Vertex("C");
        Vertex d = new Vertex("D");

        a.edges = Arrays.asList(new Edge(b), new Edge(c));
        b.edges = Arrays.asList(new Edge(d));
        c.edges = Arrays.asList(new Edge(d));
        d.edges = Collections.emptyList();

        return a;
    }

    /
     * 有向图,边有权重
     */
    private static Vertex simpleGraphWithWeight() {
        Vertex v1 = new Vertex("1");
        Vertex v2 = new Vertex("2");
        Vertex v3 = new Vertex("3");
        Vertex v4 = new Vertex("4");
        Vertex v5 = new Vertex("5");
        Vertex v6 = new Vertex("6");

        v1.edges = Arrays.asList(new Edge(v6, 14), new Edge(v2, 7), new Edge(v3, 9));
        v2.edges = Arrays.asList(new Edge(v4, 15));
        v3.edges = Arrays.asList(new Edge(v6, 2), new Edge(v4, 11));
        v4.edges = Arrays.asList(new Edge(v5, 6));
        v5.edges = Collections.emptyList();
        v6.edges = Arrays.asList(new Edge(v5, 9));

        return v1;
    }
}```
```java
package com.lzlg.study.algorithm.new2023.graph;

import java.util.*;

/
 * 图的拓扑排序,分有环和无环的两种情况
 *
 * @author lzlg
 * 2023/8/8 9:28
 */
public class TopologicalSort {
    /
     * 拓扑排序,使用入度为0的特点来排序
     *
     * @param graph 图
     */
    public static void topologicalSort(List<Vertex> graph) {
        // 遍历顶点,初始化入度数据
        for (Vertex vertex : graph) {
            // 如果顶点的边关联了其他顶点,则其他顶点的入度+1
            for (Edge edge : vertex.edges) {
                edge.linked.inDegree++;
            }
        }
        // 打印顶点的入度
//        for (Vertex vertex : graph) {
//            System.out.println(vertex.name + ", " + vertex.inDegree);
//        }
        // 使用队列
        Deque<Vertex> queue = new LinkedList<>();

        // 遍历顶点,把入度为0的加入队列
        for (Vertex vertex : graph) {
            if (vertex.inDegree == 0) {
                queue.offer(vertex);
            }
        }
        // 使用此列表可检测出是否有环
        // 如果有环,则有些节点是遍历不到的,因此对比此列表的size和图的顶点数可判断是否有环
        List<String> result = new ArrayList<>();
        // 进行循环
        while (!queue.isEmpty()) {
            // 将入度为0的顶点出队
            Vertex poll = queue.poll();
            // 进行打印
            System.out.println(poll.name);
            // 加入结果,判断是否有环
            result.add(poll.name);
            // 把出队节点相关联的顶点的入度-1
            for (Edge edge : poll.edges) {
                edge.linked.inDegree--;
                // 如果发现相关联的顶点的入度为0了,则入度
                if (edge.linked.inDegree == 0) {
                    queue.offer(edge.linked);
                }
            }
        }

        System.out.println("是否有环? " + (result.size() != graph.size()));
    }

    /
     * 拓扑排序,使用DFS(深度优先遍历)和栈
     * 顶点使用一个状态变量来标记,0-未访问,1-访问中,2-已访问
     *
     * @param graph 图
     */
    public static void topologicalSortDFS(List<Vertex> graph) {
        // 创建栈
        Deque<Vertex> stack = new LinkedList<>();
        // 遍历图中的顶点,依次进行dfs遍历
        for (Vertex vertex : graph) {
            topologicalSortDFS(vertex, stack);
        }
        // 从栈中依次弹出顶点
        while (!stack.isEmpty()) {
            System.out.println(stack.pop().name);
        }
    }

    /
     * 拓扑排序,dfs递归方法
     *
     * @param vertex 顶点
     * @param stack  栈
     */
    private static void topologicalSortDFS(Vertex vertex, Deque<Vertex> stack) {
        if (vertex.status == 2) {
            return;
        }
        // 检测是否有环,如果有环,则顶点会再次进入此递归方法,且状态为1
        if (vertex.status == 1) {
            throw new RuntimeException("图中有环");
        }
        // 标识正在访问中
        vertex.status = 1;
        // 因为是深度优先,所以先递归到最终顶点
        for (Edge edge : vertex.edges) {
            topologicalSortDFS(edge.linked, stack);
        }
        // 然后入栈
        stack.push(vertex);
        // 标识该顶点已经访问过了
        vertex.status = 2;
    }

    public static void main(String[] args) {
        List<Vertex> graph = graphNoCycle();
        System.out.println("=======无环========");
        topologicalSort(graph);
        System.out.println("=======dfs========");
        topologicalSortDFS(graph);

        System.out.println("=========有环======");
        graph = graphWithCycle();
        topologicalSort(graph);
        System.out.println("=======dfs========");
        topologicalSortDFS(graph);
    }

    /
     * 无环的有向图
     *
     * @return 顶点列表
     */
    private static List<Vertex> graphNoCycle() {
        // 网页基础 -> Java Web -> Spring框架 -> 微服务框架 -> 实战项目
        // Java基础 -> Java Web -> Spring框架 -> 微服务框架 -> 实战项目
        // 数据库 -> Spring框架 -> 微服务框架 -> 实战项目
        Vertex v1 = new Vertex("网页基础");
        Vertex v2 = new Vertex("Java基础");
        Vertex v3 = new Vertex("Java Web");
        Vertex v4 = new Vertex("数据库");
        Vertex v5 = new Vertex("Spring框架");
        Vertex v6 = new Vertex("微服务框架");
        Vertex v7 = new Vertex("实战项目");

        v1.edges = Collections.singletonList(new Edge(v3));
        v2.edges = Collections.singletonList(new Edge(v3));
        v3.edges = Collections.singletonList(new Edge(v5));
        v4.edges = Collections.singletonList(new Edge(v5));
        v5.edges = Collections.singletonList(new Edge(v6));
        v6.edges = Collections.singletonList(new Edge(v7));
        v7.edges = Collections.emptyList();

        return Arrays.asList(v1, v2, v3, v4, v5, v6, v7);
    }

    /
     * 有环的有向图
     *
     * @return 顶点列表
     */
    private static List<Vertex> graphWithCycle() {
        // 网页基础 -> Java Web -> Spring框架 -> 微服务框架 <-> 实战项目
        // Java基础 -> Java Web -> Spring框架 -> 微服务框架 <-> 实战项目
        // 数据库 -> Spring框架 -> 微服务框架 <-> 实战项目
        Vertex v1 = new Vertex("网页基础");
        Vertex v2 = new Vertex("Java基础");
        Vertex v3 = new Vertex("Java Web");
        Vertex v4 = new Vertex("数据库");
        Vertex v5 = new Vertex("Spring框架");
        Vertex v6 = new Vertex("微服务框架");
        Vertex v7 = new Vertex("实战项目");

        v1.edges = Collections.singletonList(new Edge(v3));
        v2.edges = Collections.singletonList(new Edge(v3));
        v3.edges = Collections.singletonList(new Edge(v5));
        v4.edges = Collections.singletonList(new Edge(v5));
        v5.edges = Collections.singletonList(new Edge(v6));
        v6.edges = Collections.singletonList(new Edge(v7));
        v7.edges = Collections.singletonList(new Edge(v6));

        return Arrays.asList(v1, v2, v3, v4, v5, v6, v7);
    }
}
package com.lzlg.study.algorithm.new2023.graph;

import java.util.ArrayList;
import java.util.List;

/
 * 图的顶点
 *
 * @author lzlg
 * 2023/8/7 12:28
 */
public class Vertex {
    // 顶点的名称
    final String name;
    // 顶点关联的边
    List<Edge> edges;
    // 标识顶点是否访问过
    boolean visited = false;

    // 顶点的入度,默认0
    int inDegree = 0;
    // 状态变量,0-未访问,1-访问中,2-已访问
    int status = 0;

    // 距离,默认整数最大(无穷)
    int dist = Integer.MAX_VALUE;

    // 走过的顶点集合
    List<Vertex> prevList = new ArrayList<>();

    public Vertex(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    @Override
    public String toString() {
        return "V:" + name;
    }
}

package com.lzlg.study.algorithm.new2023.heap;

/
 * 大顶堆
 *
 * @author lzlg
 * 2023/7/25 11:48
 */
public class MaxTopHeap {
    // 数组
    private int[] array;
    // 元素数量
    private int size;

    public MaxTopHeap(int capacity) {
        if (capacity < 1) {
            throw new IllegalArgumentException("不能小于1");
        }
        this.array = new int[capacity];
        this.size = 0;
    }

    public MaxTopHeap(int[] array) {
        if (array == null || array.length == 0) {
            throw new NullPointerException();
        }
        this.array = array;
        this.size = array.length;
        // 进行建堆操作
        this.buildHeap();
    }

    /
     * 查看大顶堆堆顶元素
     */
    public int peek() {
        // 如果为空,抛出异常
        if (isEmpty()) {
            throw new IllegalArgumentException("空");
        }
        return array[0];
    }

    /
     * 删除大顶堆堆顶元素
     * 1.先交换堆顶元素和最后一个元素,这样不用移动数组元素性能更高
     * 2.然后进行下沉操作
     *
     * @return 堆顶元素
     */
    public int poll() {
        // 如果为空,抛出异常
        if (isEmpty()) {
            throw new IllegalArgumentException("空");
        }
        // 返回的元素值
        int value = array[0];
        // 交换第一个和最后一个元素
        swap(0, --size);
        // 进行下沉
        down(0);
        return value;
    }

    /
     * 删除指定下标的大顶堆堆顶元素
     * 1.先交换要擅长的堆顶元素和最后一个元素,这样不用移动数组元素性能更高
     * 2.然后进行下沉操作
     *
     * @return 堆顶元素
     */
    public int poll(int index) {
        // 如果为空,抛出异常
        if (isEmpty()) {
            throw new IllegalArgumentException("空");
        }
        if (index < 0 || index >= size) {
            throw new IllegalArgumentException(index + "不合法");
        }
        // 返回的元素值
        int value = array[index];
        // 交换第一个和最后一个元素
        swap(index, --size);
        // 进行下沉
        down(index);
        return value;
    }

    /
     * 添加元素到大顶堆,进行上浮操作
     *
     * @param value 元素
     * @return 结果
     */
    public boolean offer(int value) {
        if (isFull()) {
            return false;
        }
        // 先将元素添加到数组末尾
        array[size] = value;
        // 进行上浮
        up(size);
        // 元素数量+1
        size++;
        return true;
    }

    /
     * 支持扩容的添加方法
     *
     * @param value 新元素
     */
    public void unlimitedOffer(int value) {
        // 如果数组已满,则进行扩容
        if (isFull()) {
            capacityGrow();
        }
        // 先将元素添加到数组末尾
        array[size] = value;
        // 进行上浮
        up(size);
        // 元素数量+1
        size++;
    }

    /
     * 扩容
     */
    private void capacityGrow() {
        // 扩容为原来的1.5倍
        int capacity = size + size >>> 1;
        // 创建新数组
        int[] newArray = new int[capacity];
        // 进行数据的复制
        System.arraycopy(array, 0, newArray, 0, size);
        // 复制给array
        array = newArray;
    }

    /
     * 替换大顶堆堆顶元素
     *
     * @param newValue 新元素
     * @return 返回旧元素
     */
    public int replace(int newValue) {
        if (isEmpty()) {
            throw new IllegalArgumentException("空");
        }
        // 旧元素
        int oldValue = array[0];
        // 第一个就是堆顶元素,赋值新元素
        array[0] = newValue;
        // 进行下沉
        down(0);
        return oldValue;
    }

    /
     * 替换指定下标的元素
     *
     * @param index    下标
     * @param newValue 新元素
     * @return 返回旧元素
     */
    public int replace(int index, int newValue) {
        if (isEmpty()) {
            throw new IllegalArgumentException("空");
        }
        if (index < 0 || index >= size) {
            throw new IllegalArgumentException(index + "不合法");
        }
        // 旧元素
        int oldValue = array[index];
        // 新元素赋值
        array[index] = newValue;
        // 如果新值大于旧值,则进行上浮
        if (newValue > oldValue) {
            up(index);
        } else if (newValue < oldValue) {// 如果小于,则进行下沉
            down(index);
        }
        return oldValue;
    }

    /
     * 判断是否为空
     *
     * @return 结果
     */
    public boolean isEmpty() {
        return size == 0;
    }

    /
     * 判断是否已满
     *
     * @return 结果
     */
    public boolean isFull() {
        return size == array.length;
    }

    /
     * 堆中元素数量
     *
     * @return 元素数量
     */
    public int size() {
        return size;
    }

    /
     * 建堆操作
     * 1.找到最后一个非叶子的节点
     * 2.然后依次下沉
     */
    private void buildHeap() {
        // 最后一个非叶子节点下标为 size / 2 - 1
        for (int i = (size >>> 1) - 1; i >= 0; i--) {
            down(i);
        }
    }

    /
     * 下沉,就是把父节点和左右子节点比较
     * 如果都大,则不进行操作
     * 如果小于某一子节点,则进行交换,直到符合大顶堆的性质
     *
     * @param parent 下沉时下标
     */
    private void down(int parent) {
        // 递归做法
//        recursionDown(parent);
        // 循环做法
        loopDown(parent);
    }

    /
     * 循环下沉
     *
     * @param parent 下沉时下标
     */
    private void loopDown(int parent) {
        // 最大
        int max = parent;
        // 孩子节点
        int child = parent * 2 + 1;
        // 进行循环
        while (child < size) {
            // 比较左子节点
            if (array[child] > array[max]) {
                max = child;
            }
            if (child + 1 < size && array[child + 1] > array[max]) {
                max = child + 1;
            }
            // 如果不是之前的父节点
            if (max != parent) {
                // 则进行交换
                swap(max, parent);
                // 把最大的赋值给parent,继续循环
                parent = max;
                // 更新child
                child = parent * 2 + 1;
            } else {
                // 如果是则无须下沉,退出循环
                break;
            }
        }
    }

    /
     * 递归下沉
     *
     * @param parent 下沉时下标
     */
    private void recursionDown(int parent) {
        // 左子节点
        int left = parent * 2 + 1;
        // 右子节点
        int right = left + 1;
        // 最大
        int max = parent;
        // 找到左右子节点和父节点中最大的一个
        if (left < size && array[left] > array[max]) {
            max = left;
        }
        if (right < size && array[right] > array[max]) {
            max = right;
        }
        // 如果和原父节点不一样,则进行交换,并继续下沉
        if (max != parent) {
            swap(max, parent);
            recursionDown(max);
        }
    }

    /
     * 上浮,就是子节点向上浮动
     * 如果子节点比父节点大,则进行上浮,直到符合大顶堆的性质
     *
     * @param index 新元素的下标
     */
    private void up(int index) {
        // 新元素
        int value = array[index];
        // 取出child下标的值
        int child = index;
        // 进行循环
        while (child > 0) {
            // 找到父节点
            int parent = (child - 1) >>> 1;
            // 如果子节点的值(注意使用value而不是array[child])大于父节点的
            // 则把父节点的值赋给子节点
            if (value > array[parent]) {
                array[child] = array[parent];
            } else {
                break;
            }
            // 改变child下标,再次上浮
            child = parent;
        }
        // 最后上浮不动了,则child就是元素要插入的位置
        array[child] = value;
    }

    /
     * 元素交换
     *
     * @param i 下标i
     * @param j 下标j
     */
    private void swap(int i, int j) {
        int temp = array[i];
        array[i] = array[j];
        array[j] = temp;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder("[");
        for (int i = 0; i < size; i++) {
            sb.append(array[i]);
            if (i != size - 1) {
                sb.append(", ");
            }
        }
        sb.append("]");
        return sb.toString();
    }
}
package com.lzlg.study.algorithm.new2023.heap;

/
 * 小顶堆
 *
 * @author lzlg
 * 2023/7/25 11:48
 */
public class MinTopHeap {
    // 数组
    private int[] array;
    // 元素数量
    private int size;

    public MinTopHeap(int capacity) {
        if (capacity < 1) {
            throw new IllegalArgumentException("不能小于1");
        }
        this.array = new int[capacity];
        this.size = 0;
    }

    public MinTopHeap(int[] array) {
        if (array == null || array.length == 0) {
            throw new NullPointerException();
        }
        this.array = array;
        this.size = array.length;
        // 进行建堆操作
        this.buildHeap();
    }

    /
     * 查看小顶堆堆顶元素
     */
    public int peek() {
        // 如果为空,抛出异常
        if (isEmpty()) {
            throw new IllegalArgumentException("空");
        }
        return array[0];
    }

    /
     * 删除小顶堆堆顶元素
     * 1.先交换堆顶元素和最后一个元素,这样不用移动数组元素性能更高
     * 2.然后进行下沉操作
     *
     * @return 堆顶元素
     */
    public int poll() {
        // 如果为空,抛出异常
        if (isEmpty()) {
            throw new IllegalArgumentException("空");
        }
        // 返回的元素值
        int value = array[0];
        // 交换第一个和最后一个元素
        swap(0, --size);
        // 进行下沉
        down(0);
        return value;
    }

    /
     * 删除指定下标的小顶堆堆顶元素
     * 1.先交换要擅长的堆顶元素和最后一个元素,这样不用移动数组元素性能更高
     * 2.然后进行下沉操作
     *
     * @return 堆顶元素
     */
    public int poll(int index) {
        // 如果为空,抛出异常
        if (isEmpty()) {
            throw new IllegalArgumentException("空");
        }
        if (index < 0 || index >= size) {
            throw new IllegalArgumentException(index + "不合法");
        }
        // 返回的元素值
        int value = array[index];
        // 交换第一个和最后一个元素
        swap(index, --size);
        // 进行下沉
        down(index);
        return value;
    }

    /
     * 添加元素到小顶堆,进行上浮操作
     *
     * @param value 元素
     * @return 结果
     */
    public boolean offer(int value) {
        if (isFull()) {
            return false;
        }
        // 先将元素添加到数组末尾
        array[size] = value;
        // 进行上浮
        up(size);
        // 元素数量+1
        size++;
        return true;
    }

    /
     * 支持扩容的添加方法
     *
     * @param value 新元素
     */
    public void unlimitedOffer(int value) {
        // 如果数组已满,则进行扩容
        if (isFull()) {
            capacityGrow();
        }
        // 先将元素添加到数组末尾
        array[size] = value;
        // 进行上浮
        up(size);
        // 元素数量+1
        size++;
    }

    /
     * 扩容
     */
    private void capacityGrow() {
        // 扩容为原来的1.5倍
        int capacity = size + size >>> 1;
        // 创建新数组
        int[] newArray = new int[capacity];
        // 进行数据的复制
        System.arraycopy(array, 0, newArray, 0, size);
        // 复制给array
        array = newArray;
    }

    /
     * 替换小顶堆堆顶元素
     *
     * @param newValue 新元素
     * @return 返回旧元素
     */
    public int replace(int newValue) {
        if (isEmpty()) {
            throw new IllegalArgumentException("空");
        }
        // 旧元素
        int oldValue = array[0];
        // 第一个就是堆顶元素,赋值新元素
        array[0] = newValue;
        // 进行下沉
        down(0);
        return oldValue;
    }

    /
     * 替换指定下标的元素
     *
     * @param index    下标
     * @param newValue 新元素
     * @return 返回旧元素
     */
    public int replace(int index, int newValue) {
        if (isEmpty()) {
            throw new IllegalArgumentException("空");
        }
        if (index < 0 || index >= size) {
            throw new IllegalArgumentException(index + "不合法");
        }
        // 旧元素
        int oldValue = array[index];
        // 新元素赋值
        array[index] = newValue;
        // 如果新值小于旧值,则进行上浮
        if (newValue < oldValue) {
            up(index);
        } else if (newValue > oldValue) {// 如果大于,则进行下沉
            down(index);
        }
        return oldValue;
    }

    /
     * 判断是否为空
     *
     * @return 结果
     */
    public boolean isEmpty() {
        return size == 0;
    }

    /
     * 判断是否已满
     *
     * @return 结果
     */
    public boolean isFull() {
        return size == array.length;
    }

    /
     * 堆中元素数量
     *
     * @return 元素数量
     */
    public int size() {
        return size;
    }

    /
     * 建堆操作
     * 1.找到最后一个非叶子的节点
     * 2.然后依次下沉
     */
    private void buildHeap() {
        // 最后一个非叶子节点下标为 size / 2 - 1
        for (int i = (size >>> 1) - 1; i >= 0; i--) {
            down(i);
        }
    }

    /
     * 下沉,就是把父节点和左右子节点比较
     * 如果都大,则不进行操作
     * 如果小于某一子节点,则进行交换,直到符合小顶堆的性质
     *
     * @param parent 下沉时下标
     */
    private void down(int parent) {
        // 递归做法
        recursionDown(parent);
        // 循环做法
//        loopDown(parent);
    }

    /
     * 循环下沉
     *
     * @param parent 下沉时下标
     */
    private void loopDown(int parent) {
        // 最小
        int min = parent;
        // 孩子节点
        int child = parent * 2 + 1;
        // 进行循环
        while (child < size) {
            // 比较左子节点
            if (array[child] < array[min]) {
                min = child;
            }
            if (child + 1 < size && array[child + 1] < array[min]) {
                min = child + 1;
            }
            // 如果不是之前的父节点
            if (min != parent) {
                // 则进行交换
                swap(min, parent);
                // 把最大的赋值给parent,继续循环
                parent = min;
                // 更新child
                child = parent * 2 + 1;
            } else {
                // 如果是则无须下沉,退出循环
                break;
            }
        }
    }

    /
     * 递归下沉
     *
     * @param parent 下沉时下标
     */
    private void recursionDown(int parent) {
        // 左子节点
        int left = parent * 2 + 1;
        // 右子节点
        int right = left + 1;
        // 最小
        int min = parent;
        // 找到左右子节点和父节点中最大的一个
        if (left < size && array[left] < array[min]) {
            min = left;
        }
        if (right < size && array[right] < array[min]) {
            min = right;
        }
        // 如果和原父节点不一样,则进行交换,并继续下沉
        if (min != parent) {
            swap(min, parent);
            recursionDown(min);
        }
    }

    /
     * 上浮,就是子节点向上浮动
     * 如果子节点比父节点大,则进行上浮,直到符合小顶堆的性质
     *
     * @param index 新元素的下标
     */
    private void up(int index) {
        // 新元素
        int value = array[index];
        // 取出child下标的值
        int child = index;
        // 进行循环
        while (child > 0) {
            // 找到父节点
            int parent = (child - 1) >>> 1;
            // 如果子节点的值(注意使用value而不是array[child])小于父节点的
            // 则把父节点的值赋给子节点
            if (value < array[parent]) {
                array[child] = array[parent];
            } else {
                break;
            }
            // 改变child下标,再次上浮
            child = parent;
        }
        // 最后上浮不动了,则child就是元素要插入的位置
        array[child] = value;
    }

    /
     * 元素交换
     *
     * @param i 下标i
     * @param j 下标j
     */
    private void swap(int i, int j) {
        int temp = array[i];
        array[i] = array[j];
        array[j] = temp;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder("[");
        for (int i = 0; i < size; i++) {
            sb.append(array[i]);
            if (i != size - 1) {
                sb.append(", ");
            }
        }
        sb.append("]");
        return sb.toString();
    }
}
package com.lzlg.study.algorithm.new2023.heap.quiz;

import com.lzlg.study.algorithm.new2023.heap.MinTopHeap;

/
 * 求数组中第k个最大的元素
 * 使用小顶堆
 * 1.开始时,将k个元素加入到小顶堆中
 * 2.开始从k到数组末尾循环
 * 1)如果数组中的元素小于等于堆顶元素,则不进行操作
 * 2)如果大于,则替换堆顶元素
 * 3.循环结束后,堆顶元素就是数组中第k个最大的元素
 *
 * @author lzlg
 * 2023/7/26 9:54
 */
public class KBigNumber {

    /
     * 求数组中第k个最大的元素
     *
     * @param numbers 数组
     * @param k       k值
     * @return 第k个最大的元素
     */
    public static int findKthLargest(int[] numbers, int k) {
        // 创建小顶堆
        MinTopHeap heap = new MinTopHeap(k);
        // 把前k个元素添加到小顶堆中
        for (int i = 0; i < k; i++) {
            heap.offer(numbers[i]);
        }
        for (int i = k; i < numbers.length; i++) {
            // 如果数组中元素大于堆顶元素
            if (numbers[i] > heap.peek()) {
                // 则替换掉堆顶元素
                heap.replace(numbers[i]);
            }
        }
        return heap.peek();
    }

    public static void main(String[] args) {
        int[] array = {3, 2, 1, 5, 6, 4};
        System.out.println(findKthLargest(array, 2));
        array = new int[]{3, 2, 3, 1, 2, 4, 5, 5, 6};
        System.out.println(findKthLargest(array, 4));
    }
}
package com.lzlg.study.algorithm.new2023.heap.quiz;

import com.lzlg.study.algorithm.new2023.heap.MinTopHeap;

/
 * 求数据流中的第k个最大元素
 * 一个添加数据的方法,数据是一直变动的
 *
 * @author lzlg
 * 2023/7/26 10:14
 */
public class StreamKBigNumber {

    private final MinTopHeap heap;

    public StreamKBigNumber(int k, int[] init) {
        // 初始化小顶堆
        heap = new MinTopHeap(k);
        for (int num : init) {
            add(num);
        }
    }

    /
     * 添加一个元素时,返回第k个最大元素(构造方法传入的)
     *
     * @param newValue 新元素
     * @return 第k个最大元素
     */
    public int add(int newValue) {
        // 如果堆还未满,则添加新元素到堆中
        if (!heap.isFull()) {
            heap.offer(newValue);
            // 如果新元素大于堆顶元素时,则替换堆顶元素为新元素
        } else if (newValue > heap.peek()) {
            heap.replace(newValue);
        }
        // 堆顶元素是第k个最大元素
        return heap.peek();
    }

    public static void main(String[] args) {
        StreamKBigNumber test = new StreamKBigNumber(3, new int[]{4, 5, 8, 2});
        // 初始化完成后小顶堆为: [4, 5, 8]
        System.out.println(test.add(3));
        System.out.println(test.add(5));
        System.out.println(test.add(10));
        System.out.println(test.add(9));
        System.out.println(test.add(4));
    }
}
package com.lzlg.study.algorithm.new2023.heap.quiz;

import com.lzlg.study.algorithm.new2023.heap.MaxTopHeap;
import com.lzlg.study.algorithm.new2023.heap.MinTopHeap;

import java.util.PriorityQueue;

/
 * 数据流中位数
 * [1, 2, 3] 中位数是 2.0
 * [1, 2, 3, 4] 中位数是 (2 + 3) / 2.0 = 2.5
 * 数据是不断添加的
 * 使用两个堆,左边用大顶堆,右边用小顶堆,保证这两个堆的元素数量最多差一个
 * 添加时如果左边的数量等于右边的数量,则添加到左边
 * 如果左边的数量不等于右边的数量,则添加到右边
 * [在添加之前]
 * 如果是添加到左边,则先把元素添加到右边,然后右边弹出堆顶元素,将此堆顶元素添加到左边
 * 同样的
 * 如果是添加到右边,则先把元素添加到左边,然后左边弹出堆顶元素,将此堆顶元素添加到右边
 * [计算中位数]
 * 如果左边的数量等于右边的,则弹出两个堆的堆顶元素,相加除以2.0
 * 如果不等于,则用左边的堆顶元素
 *
 * @author lzlg
 * 2023/7/26 10:41
 */
public class StreamMedianNumber {
    // 左边是大顶堆
    private final MaxTopHeap left = new MaxTopHeap(10);
    // 右边是小顶堆
    private final MinTopHeap right = new MinTopHeap(10);

    /
     * 添加元素
     *
     * @param num 元素
     */
    public void addNum(int num) {
        // 如果左边的元素数量等于右边的,则添加到左边
        if (left.size() == right.size()) {
            // 添加时,先添加到右边
            right.offer(num);
            // 从右边弹出最小的(因为右边是小顶堆)添加到左边
            left.offer(right.poll());
        } else {
            // 如果不等,则添加到右边
            // 添加时,先添加到左边
            left.offer(num);
            // 从左边弹出最大的(因为左边是大顶堆)添加到右边
            right.offer(left.poll());
        }
    }

    /
     * 获得中位数
     *
     * @return 中位数
     */
    public double median() {
        // 如果左边和右边的数量相等,则使用两个堆的堆顶元素相加,并除以2.0
        if (left.size() == right.size()) {
            return (left.peek() + right.peek()) / 2.0;
        }
        return left.peek();
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder("[");
        sb.append(left);
        sb.append(" <---> ");
        sb.append(right);
        return sb.toString();
    }

    // 可切换为java里的PriorityQueue
    // 默认是小顶堆,修改为大顶堆,使用比较器,默认是Integer.compare(a, b)
    // 改为Integer.compare(b, a)就变成大顶堆
    PriorityQueue<Integer> leftQueue = new PriorityQueue<>((a, b) -> Integer.compare(b, a));
    // 默认是小顶堆
    PriorityQueue<Integer> rightQueue = new PriorityQueue<>();

    public static void main(String[] args) {
        StreamMedianNumber test = new StreamMedianNumber();
        test.addNum(1);
        test.addNum(2);
        test.addNum(3);
        test.addNum(7);
        test.addNum(8);
        test.addNum(9);
        System.out.println(test);
        System.out.println(test.median());
        test.addNum(10);
        System.out.println(test);
        System.out.println(test.median());
        test.addNum(4);
        System.out.println(test);
        System.out.println(test.median());
    }
}
package com.lzlg.study.algorithm.new2023.heap;

/
 * 测试堆
 *
 * @author lzlg
 * 2023/7/25 12:45
 */
public class TestHeap {

    public static void main(String[] args) {
        // 堆排序步骤
        // 1.调整堆为大顶堆
        // 2.交换堆顶和数组最后一个元素
        // 3.根据堆顶再次调整为大顶堆
        // 4.直到堆中只剩一个元素
        heapSort(new int[]{2, 4, 1, 8, 6, 3, 5, 7, 9});

        testHeap();
    }

    private static void heapSort(int[] array) {
        // 构建大顶堆
        MaxTopHeap heap = new MaxTopHeap(array);
        while (!heap.isEmpty()) {
            System.out.println(heap.poll());
        }
    }


    /
     * 测试堆
     */
    private static void testHeap() {
        MaxTopHeap heap = new MaxTopHeap(7);
        heap.offer(1);
        heap.offer(2);
        heap.offer(3);
        heap.offer(4);
        heap.offer(5);
        heap.offer(6);
        heap.offer(7);
        System.out.println(heap);
    }
}

Hash算法

package com.lzlg.study.algorithm.new2023.hash;

import cn.hutool.core.lang.hash.MurmurHash;

import java.util.Arrays;
import java.util.Map;
import java.util.stream.Collectors;

/
 * 自定义Hash表
 *
 * @author lzlg
 * 2023/8/2 11:46
 */
public class MyHashTable {
    // hash表数组
    Entry[] table;
    // 元素数量
    int size;
    // 扩容的阈值
    int threshold;

    public MyHashTable() {
        // 初始化数组长度为16,为2的n次幂
        this.table = new Entry[16];
        this.size = 0;
        // 阈值是0.75(3/4)
        this.threshold = table.length * 3 / 4;
    }

    /
     * 根据key查找值value
     *
     * @param key 关键字key
     * @return 值value
     */
    public Object get(Object key) {
        int hash = hash(key);
        return get(hash, key);
    }

    /
     * 根据key放置value
     *
     * @param key   关键字key
     * @param value 值value
     */
    public void put(Object key, Object value) {
        int hash = hash(key);
        put(hash, key, value);
    }

    /
     * 根据key删除value
     *
     * @param key 关键字key
     * @return 被删除的值value
     */
    public Object remove(Object key) {
        int hash = hash(key);
        return remove(hash, key);
    }

    /
     * hash函数
     *
     * @param key 关键字
     * @return hash值
     */
    private int hash(Object key) {
        if (key instanceof String) {
            String k = (String) key;
//            int hash = 0;
//            for (int i = 0; i < k.length(); i++) {
//                hash = hash * 31 + k.charAt(i);
//            }
//            return hash;
            return MurmurHash.hash32(k);
        }
        // JDK自带实现的hash函数
        return key.hashCode();
    }

    /
     * 根据hash和key查找value
     *
     * @param hash hash值
     * @param key  关键字key
     * @return 值value
     */
    Object get(int hash, Object key) {
        // 相当于hash%table.length,前提是table.length必须是2的n次幂
        int idx = hash & (table.length - 1);
        // 如果指定下标没有元素,直接返回null
        if (table[idx] == null) {
            return null;
        }
        // 如果有,则遍历链表进行查找
        Entry p = table[idx];
        while (p != null) {
            // 找到和key一致的节点,然后返回节点的值
            if (p.key.equals(key)) {
                return p.value;
            }
            p = p.next;
        }
        return null;
    }

    /
     * 根据hash和key放置value
     *
     * @param hash  hash值
     * @param key   关键字key
     * @param value 值value
     */
    void put(int hash, Object key, Object value) {
        // 相当于hash%table.length,前提是table.length必须是2的n次幂
        int idx = hash & (table.length - 1);
        // 如果下标对应的元素为null,则直接赋值
        if (table[idx] == null) {
            table[idx] = new Entry(hash, key, value);
            size++;
            return;
        }
        // 如果该下标已有元素
        // 1.没有查找到,则添加到链表中
        // 2.查找到,则进行更新
        Entry p = table[idx];
        // 开始循环
        while (true) {
            // 如果找到和key相同的,则进行更新
            if (p.key.equals(key)) {
                p.value = value;
                return;
            }
            // 当在链表最后一个节点时退出循环
            if (p.next == null) {
                break;
            }
            p = p.next;
        }

        // 如果没找到,则添加到链表中
        p.next = new Entry(hash, key, value);
        size++;

        // 如果元素数量大于阈值,则进行扩容
        if (size > threshold) {
            resize();
        }
    }

    /
     * 扩容
     * 规律:按 hash & table.length 是否等于0划分链表
     * 1.如果等于0,则还是留在和原下标一样
     * 2.如果不等于0,则留在原下标+原数组长度的下标
     * <p>
     * 原因:因为数组长度是2的n次幂
     * 因此 hash & table.length 是0的一定是小于数组长度的hash
     * 且hash的二进制的后n+1位在扩容(扩容后数组长度变为2的n+1次幂)后
     * 仍然保持和扩容前一致,因此留在原下标
     * <p>
     * 若数组长度为16,扩容到32,如果hash值有11和21两个
     * 原数组(长度为16即2的4次幂)时:
     * 16--0001 0000
     * 11--0000 1011   下标为11(看后4位)
     * 21--0001 0101   下标为5(看后4位)
     * 扩容后数组(长度为32即2的5次幂)时:
     * 32--001 00000
     * 11--000 01011   下标为11(看后5位)--保持不变
     * 21--000 10101   下标为21(看后5位)--原下标+原数组长度
     * <p>
     * 而不是0的,在数组扩容后的hash的二进制后n+1和扩容前不一致,且相差一个数组长度
     * 因此留在原下标+原数组长度的下标
     */
    private void resize() {
        // 容量为原数组的长度的2倍
        Entry[] newTable = new Entry[table.length << 1];
        // 转移旧数组元素到新数组中
        for (int i = 0; i < table.length; i++) {
            // 元素节点
            Entry p = table[i];
            // 不为空的才进行处理
            if (p != null) {
                // 规律:按 hash & table.length 是否等于0划分链表
                // 保留在原下标的链表指针
                Entry stay = null;
                // 保留在原下标的链表头指针
                Entry stayHead = null;

                // 转移到新下标的链表指针
                Entry trans = null;
                // 转移到新下标的链表头指针
                Entry transHead = null;

                // 开始循环链表
                while (p != null) {
                    // 如果等于0,则还是留在和原下标一样
                    if ((p.hash & table.length) == 0) {
                        // 如果不为空,则stay的next是p
                        if (stay != null) {
                            stay.next = p;
                        } else {// 头节点赋值

                            stayHead = p;
                        }
                        // 将节点转移到新链表上,且移动stay指针到末尾
                        stay = p;

                    } else { // 如果不等于0,则留在原下标+table.length的下标
                        // 如果不为空,则trans的next是p
                        if (trans != null) {
                            trans.next = p;
                        } else {// 头节点赋值
                            transHead = p;
                        }
                        // 将节点转移到新链表上,且移动trans指针到末尾
                        trans = p;
                    }
                    p = p.next;
                }

                // 循环退出后,开始赋值
                if (stay != null) {
                    // 断开和原链表的关联
                    stay.next = null;
                    // 保留在原下标的
                    newTable[i] = stayHead;
                }
                if (trans != null) {
                    // 断开和原链表的关联
                    trans.next = null;
                    // 保留在原下标+原数组长度的
                    newTable[i + table.length] = transHead;
                }
            }
        }

        // 新数组赋值给table
        table = newTable;
        // 更新阈值
        threshold = table.length * 3 / 4;
    }

    /
     * 根据hash和key删除value
     *
     * @param hash hash值
     * @param key  关键字key
     * @return 被删除的值value
     */
    Object remove(int hash, Object key) {
        // 相当于hash%table.length,前提是table.length必须是2的n次幂
        int idx = hash & (table.length - 1);
        // 如果指定下标的数组元素为null,则直接返回null
        if (table[idx] == null) {
            return null;
        }
        // 遍历链表
        Entry p = table[idx];
        // 记录前一个节点,方便删除
        Entry prev = null;
        while (p != null) {
            // 如果找到了,则直接删除
            if (p.key.equals(key)) {
                // 如果前一个节点为null,则删除的是链表头部
                if (prev == null) {
                    // 链表头部改为p的next
                    table[idx] = p.next;
                } else {
                    // 上一个节点的next是待删除节点的next
                    prev.next = p.next;
                }
                // 元素数量-1
                size--;
                return p.value;
            }
            prev = p;
            p = p.next;
        }
        // 如果p为null,则没找到,返回null
        return null;
    }

    /
     * 节点类
     */
    static class Entry {
        // 节点的hash值
        int hash;
        // 节点的key
        Object key;
        // 节点的value
        Object value;
        // 节点的下个节点
        Entry next;

        public Entry(int hash, Object key, Object value) {
            this.hash = hash;
            this.key = key;
            this.value = value;
        }
    }

    /
     * 打印当前hash表的冲突情况,统计每个下标里的元素数量
     * 然后根据元素数量进行分组汇总
     */
    public void testConflict() {
        int[] sum = new int[table.length];
        for (int i = 0; i < table.length; i++) {
            int iSum = 0;
            // 统计链表长度
            Entry p = table[i];
            while (p != null) {
                iSum++;
                p = p.next;
            }
            sum[i] = iSum;
        }
        Map<Integer, Long> collect = Arrays.stream(sum)
                .boxed()
                .collect(Collectors.groupingBy(s -> s, Collectors.counting()));
        System.out.println(collect);
    }

    public static void main(String[] args) {
        MyHashTable table = new MyHashTable();
        String s1 = "abc";
        String s2 = new String("abc");
        // String类型的hashCode方法被重写了
        // 是每个字符的int值*31,如abc的hash计算为
        // ((a + 0*31)*31 + b)*31) + c
        System.out.println(s1.hashCode());
        System.out.println(s2.hashCode());
        System.out.println(table.hash(s1));

        System.out.println("=============================");

//        for (int i = 0; i < 20_0000; i++) {
//            Object obj = new Object();
//            table.put(obj, obj);
//        }
//
//        table.testConflict();

        // MurmurHash
        System.out.println(MurmurHash.hash32(s1));

        // 1.上面的HashTable实现用的是尾插入法,可使用头插入法
        // 但是头插入法在HashMap1.7版本使用,但是在多线程环境下会造成死循环的bug

        // 2.JDK的HashMap采用了将对象的hashCode高低位相互异或的方式减少冲突
        // hash ^ (hash >>> 16)

        // 3.也可以不用2的n次幂作为hash表中数组的容量,如JDK的Hashtable(使用的是质数)

        // 4.JDK8的HashMap在链表过长时转化为红黑树,防止多个冲突的hash造成链表过长导致的性能急剧下降
    }
}
package com.lzlg.study.algorithm.new2023.hash.quiz;

import java.util.HashMap;
import java.util.Map;

/
 * 从字符串中找到第一个不重复的字符,返回下标
 * 字符串只有小写字母
 *
 * @author lzlg
 * 2023/8/4 11:08
 */
public class FindFirstUniqueChar {
    /
     * 从字符串中找到第一个不重复的字符,返回下标
     * 方法1:使用HashMap
     *
     * @param s 字符串
     * @return 字符的下标, 找不到返回-1
     */
    public static int findFistUniqueChar(String s) {
        // 使用HashMap,key是字符,value是字符出现的次数
        Map<Character, Integer> map = new HashMap<>();
        // 遍历字符串的字符数组,并统计字符出现的次数
        char[] chars = s.toCharArray();
        for (char ch : chars) {
            if (map.containsKey(ch)) {
                int count = map.get(ch);
                count++;
                map.put(ch, count);
            } else {
                map.put(ch, 1);
            }
        }
        // 重新遍历一次字符数组,找到第一个不重复的字符下标
        for (int i = 0; i < chars.length; i++) {
            // 如果该字符的出现次数为1,则是第一个不重复的字符
            if (map.get(chars[i]) == 1) {
                // 返回下标
                return i;
            }
        }
        // 找不到返回-1
        return -1;
    }

    /
     * 从字符串中找到第一个不重复的字符,返回下标
     * 方法1:使用26长度的int数组
     *
     * @param s 字符串
     * @return 字符的下标, 找不到返回-1
     */
    public static int findFistUniqueCharArray(String s) {
        // 创建统计字符出现次数的长度为26的int数组
        int[] array = new int[26];
        // 遍历字符串的字符数组,并统计字符出现的次数
        char[] chars = s.toCharArray();
        for (char ch : chars) {
            array[ch - 97]++;
        }
        // 重新遍历一次字符数组,找到第一个不重复的字符下标
        for (int i = 0; i < chars.length; i++) {
            // 如果该字符的出现次数为1,则是第一个不重复的字符
            if (array[chars[i] - 97] == 1) {
                // 返回下标
                return i;
            }
        }
        // 找不到返回-1
        return -1;
    }

    public static void main(String[] args) {
        System.out.println(findFistUniqueChar("leetcodel"));
        System.out.println(findFistUniqueCharArray("leetcodel"));
    }
}
package com.lzlg.study.algorithm.new2023.hash.quiz;

import java.util.HashSet;

/
 * 找到不重复的数字
 * 给定一个数组,重复的数字只有两个,不重复的只有一个
 * 找到不重复的数字
 *
 * @author lzlg
 * 2023/8/3 16:42
 */
public class FindNotRepeatNumber {
    /
     * 方法1:使用HashSet
     * 使用HashSet的add方法添加元素,如果添加成功,则返回true
     * 如果是替换,则返回false,此时需要把元素从HashSet中移出
     *
     * @param nums 数组
     * @return 不重复的元素
     */
    public static int findNotRepeatNumber(int[] nums) {
        HashSet<Integer> set = new HashSet<>();
        // 遍历数组
        for (int num : nums) {
            // 如果添加不成功,则删除该元素
            if (!set.add(num)) {
                set.remove(num);
            }
        }
        // 遗留下来的元素(只有一个)返回
        return set.toArray(new Integer[0])[0];
    }

    /
     * 方法2:使用异或
     * 两个相同的数字的异或结果为0
     * 而0与任何一个数字的异或结果是该数字
     *
     * @param nums 数组
     * @return 不重复的元素
     */
    public static int findNotRepeatNumber2(int[] nums) {
        int num = nums[0];
        for (int i = 1; i < nums.length; i++) {
            num = num ^ nums[i];
        }
        return num;
    }


    public static void main(String[] args) {
        System.out.println(findNotRepeatNumber2(new int[]{1, 2, 3, 2, 3}));
        System.out.println(findNotRepeatNumber2(new int[]{4, 1, 2, 1, 2}));
    }
}
package com.lzlg.study.algorithm.new2023.hash.quiz;

import java.util.HashMap;
import java.util.Map;

/
 * 判断是否有重复的元素
 *
 * @author lzlg
 * 2023/8/3 15:58
 */
public class FindRepeatNumber {
    /
     * 判断是否有重复的元素
     *
     * @param nums 数组
     * @return 结果, 有返回true
     */
    public static boolean findRepeat(int[] nums) {
        Map<Integer, Integer> map = new HashMap<>();
        for (int num : nums) {
            if (map.containsKey(num)) {
                return true;
            }
            map.put(num, num);
        }
        return false;
    }

    /
     * 判断是否有重复的元素,更快速
     *
     * @param nums 数组
     * @return 结果, 有返回true
     */
    public static boolean fastFindRepeat(int[] nums) {
        Map<Integer, Integer> map = new HashMap<>();
        final Integer value = 1;
        for (int num : nums) {
            // 因为map的put方法如果是新增的话是返回null,如果是替换的话,返回原来的值
            // 这样写比上面的少了一个containsKey的判断,更加快速
            if (map.put(num, value) != null) {
                return true;
            }
        }
        return false;
    }

    public static void main(String[] args) {
        System.out.println(fastFindRepeat(new int[]{1, 2, 3, 1}));
        System.out.println(fastFindRepeat(new int[]{1, 2, 3, 4}));
    }
}
package com.lzlg.study.algorithm.new2023.hash.quiz;

import java.util.*;

/
 * 分组字母异位词,如abc和cba是一组字母异位词
 * 给定的字符串都是小写字母
 *
 * @author lzlg
 * 2023/8/3 15:28
 */
public class GroupAnagrams {

    /
     * 通过给定的字符串数字,把此数组按照字母异位词进行分组
     * 方式1:通过排序和HashMap来解决
     * 方式2:因为只有小写字母,可使用长度为26的int数组作为HashMap的key,需重写equals和hashCode方法
     * 方式3:因为只有小写字母,将这些小写字母的int值之和,作为HashMap的key
     *
     * @param strings 字符串数组
     * @return 分组结果
     */
    public static List<List<String>> groupAnagrams1(String[] strings) {
        // HashMap表
        Map<String, List<String>> map = new HashMap<>();
        // 遍历字符串数组
        for (String str : strings) {
            // 获取字符串的字符数组
            char[] chars = str.toCharArray();
            // 进行排序
            Arrays.sort(chars);
            // 排序后的新字符串作为key
            String key = new String(chars);
            // 放入map中
            List<String> list = map.computeIfAbsent(key, s -> new ArrayList<>());
            // 添加原字符串到list中
            list.add(str);
        }
        return new ArrayList<>(map.values());
    }

    /
     * 方式2,使用长度为26的int数组存储
     *
     * @param strings 字符串数组
     * @return 分组结果
     */
    public static List<List<String>> groupAnagrams2(String[] strings) {
        // HashMap表
        Map<ArrayKey, List<String>> map = new HashMap<>();
        // 遍历字符串数组
        for (String str : strings) {
            // 创建ArrayKey
            ArrayKey key = new ArrayKey(str);
            // 放入map中
            List<String> list = map.computeIfAbsent(key, s -> new ArrayList<>());
            // 添加原字符串到list中
            list.add(str);
        }
        return new ArrayList<>(map.values());
    }

    /
     * 方式2的key,因此需要重写equals和hashCode方法
     */
    static class ArrayKey {

        int[] chars;

        public ArrayKey(String str) {
            this.chars = new int[26];
            for (int i = 0; i < str.length(); i++) {
                // 因为都是小写字母,而字符a的int值为97,因此下标为ch-97
                char ch = str.charAt(i);
                chars[ch - 97]++;
            }
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            ArrayKey arrayKey = (ArrayKey) o;
            return Arrays.equals(chars, arrayKey.chars);
        }

        @Override
        public int hashCode() {
            return Arrays.hashCode(chars);
        }
    }

    public static void main(String[] args) {
        String[] strings = new String[]{"eat", "tea", "tan", "ate", "nat", "bat"};
        System.out.println(groupAnagrams2(strings));
    }
}
package com.lzlg.study.algorithm.new2023.hash.quiz;

import java.util.Arrays;

/
 * 判断是否是字母异位词
 * 字符串都是由小写字母组成
 *
 * @author lzlg
 * 2023/8/4 10:50
 */
public class JudgeIsAnagrams {
    /
     * 判断两个字符串是否是字母异位词
     * 方法1:把字符串的字符数组排序后比较
     *
     * @param s 字符串s
     * @param t 字符串t
     * @return 结果, 是返回true
     */
    public static boolean isAnagrams(String s, String t) {
        if (s == null || t == null || s.length() != t.length()) {
            return false;
        }
        // 转化为字符数组
        char[] sChars = s.toCharArray();
        // 排序
        Arrays.sort(sChars);

        // 转化为字符数组
        char[] tChars = t.toCharArray();
        // 排序
        Arrays.sort(tChars);

        // 开始比较,可使用Arrays.equals的API
        for (int i = 0; i < sChars.length; i++) {
            // 如果有一个字符不相等,则返回false
            if (sChars[i] != tChars[i]) {
                return false;
            }
        }
        // 循环过后,则是字母异位词
        return true;
    }

    /
     * 判断两个字符串是否是字母异位词
     * 方法2:因为只有小写字母,可通过创建长度为26的int数组来比较
     *
     * @param s 字符串s
     * @param t 字符串t
     * @return 结果, 是返回true
     */
    public static boolean isAnagramsArray(String s, String t) {
        if (s == null || t == null || s.length() != t.length()) {
            return false;
        }
        // 数组长度为26
        final int LEN = 26;

        // 创建字符串s对应的int数组
        int[] sa = new int[LEN];
        // 字符串s转化为字符数组
        char[] sChars = s.toCharArray();
        // 遍历字符数组
        for (char ch : sChars) {
            // 将相应的下标增加
            sa[ch - 97]++;
        }

        // 创建字符串t对应的int数组
        int[] ta = new int[LEN];
        // 字符串t转化为字符数组
        char[] tChars = t.toCharArray();
        // 遍历字符数组
        for (char ch : tChars) {
            // 将相应的下标增加
            ta[ch - 97]++;
        }

        // 最后比较两个int数组
        for (int i = 0; i < LEN; i++) {
            if (sa[i] != ta[i]) {
                return false;
            }
        }
        // 可使用Arrays.equals的API
        return true;
    }

    public static void main(String[] args) {
        String s = "anagram";
        String t = "nagrama";
        System.out.println(isAnagrams(s, t));
        System.out.println(isAnagramsArray(s, t));
    }
}
package com.lzlg.study.algorithm.new2023.hash.quiz;

import java.util.HashMap;
import java.util.Map;

/
 * 最长无重复字符子串的长度
 * 如字符串是: abcabcca
 * 则最长的没有重复字符的子串是: abc, bca, cab
 *
 * @author lzlg
 * 2023/8/3 12:09
 */
public class LongestSubString {

    /
     * 获取字符串的最长无重复字符子串的长度
     * 使用HashMap和两个指针
     * 一个begin指针指向不重复字符的最大下标
     * 一个end指针遍历字符串的字符
     *
     * @param str 字符串
     * @return 长度
     */
    public static int lengthLongestSubString(String str) {
        // 使用HashMap,key是字符,value是字符所在字符串的最大位置下标
        Map<Character, Integer> map = new HashMap<>();
        // 返回的最大子串长度
        int maxLength = 0;
        // 开始指针,当遇到重复的时候移动
        int begin = 0;
        // 结束指针,遍历字符串字符使用
        for (int end = 0; end < str.length(); end++) {
            // 字符串中单个字符
            char ch = str.charAt(end);
            // 如果map中有此字符,则更新begin,且更新此字符的最新下标
            if (map.containsKey(ch)) {
                // 记录begin的最大下标,防止往回走
                begin = Integer.max(begin, map.get(ch) + 1);
                // 更新此字符的最新下标
                map.put(ch, end);
            } else {
                // 如果map中没有,则放入
                map.put(ch, end);
            }
            // 打印无重复字符的子串
            System.out.println(str.substring(begin, end + 1));
            // 记录最大的子串长度
            maxLength = Integer.max(maxLength, end - begin + 1);
        }

        return maxLength;
    }

    public static void main(String[] args) {
        String str = "abcabcca";
        System.out.println(lengthLongestSubString(str));
    }
}
package com.lzlg.study.algorithm.new2023.hash.quiz;

import java.util.*;

/
 * 找到一段文字中出现次数最多的单词
 * 题目只有一个唯一答案
 *
 * @author lzlg
 * 2023/8/4 11:29
 */
public class MostCommonWord {
    /
     * 找到一段文字中出现次数最多的单词
     *
     * @param paragraph 段落
     * @param banned    禁用词
     * @return 单词, 没有返回null
     */
    public static String findMostCommonWord(String paragraph, String[] banned) {
        // 根据不是字母的进行切分为单词字符串数组
        String[] words = paragraph.toLowerCase().split("[^a-z]+");
        // 使用Set封装禁用词词组
        Set<String> banSet = new HashSet<>(Arrays.asList(banned));
        // 使用HashMap统计单词的出现次数,key是单词,value是单词的出现次数
        Map<String, Integer> map = new HashMap<>();
        // 使用HashMap统计单词的出现次数
        for (String word : words) {
            // 禁用词里不包括该单词,才加入到map中
            if (!banSet.contains(word)) {
                // 使用word作为key,如果v值为null,则v值为1,否则为更新v值为原来的v值+1
                map.compute(word, (k, v) -> v == null ? 1 : v + 1);
            }

        }
        // 找出出现次数最多的单词
        // 根据value值比较找到最大的Map.Entry
        Optional<Map.Entry<String, Integer>> optional = map.entrySet().stream().max(Map.Entry.comparingByValue());
        // 从optional中获取entry的key,如果没有则返回null
        return optional.map(Map.Entry::getKey).orElse(null);
    }

    /
     * 找到一段文字中出现次数最多的单词,效率高的版本
     *
     * @param paragraph 段落
     * @param banned    禁用词
     * @return 单词, 没有返回null
     */
    public static String fastFindMostCommonWord(String paragraph, String[] banned) {
        // 自己遍历段落的字符,拼接为单词并加入到map集合中
        char[] chars = paragraph.trim().toLowerCase().toCharArray();
        // 使用Set封装禁用词词组
        Set<String> banSet = new HashSet<>(Arrays.asList(banned));
        // 使用HashMap统计单词的出现次数,key是单词,value是单词的出现次数
        Map<String, Integer> map = new HashMap<>();

        // 拼接单词使用的
        StringBuilder sb = new StringBuilder();
        // 遍历字符数组
        for (char ch : chars) {
            // 如果是小写字母范围的,则进行拼接
            if (ch >= 'a' && ch <= 'z') {
                sb.append(ch);
            } else {
                // 如果遇到不是小写字母的,则将以前拼接的转化为单词
                String key = sb.toString();
                if (!banSet.contains(key)) {
                    map.compute(key, (k, v) -> v == null ? 1 : v + 1);
                }
                // 重置拼接字符使用的sb的长度
                sb.setLength(0);
            }
        }
        // 防止段落只有一个单词造成的问题
        if (sb.length() > 0) {
            // 把最后一个单词放入map中
            String key = sb.toString();
            if (!banSet.contains(key)) {
                map.compute(key, (k, v) -> v == null ? 1 : v + 1);
            }
        }

        // 统计完成后,自己实现找到出现次数最多的单词
        // 找到次数最多的
        int max = 0;
        // 找到次数最多的key
        String maxKey = null;
        // 遍历map集合
        for (Map.Entry<String, Integer> entry : map.entrySet()) {
            int value = entry.getValue();
            // 如果有比max大的,则进行记录
            if (value > max) {
                max = value;
                maxKey = entry.getKey();
            }
        }
        return maxKey;
    }

    public static void main(String[] args) {
        // 段落
        String paragraph = "Bob hit a ball, the hit BALL flew far after it was hit.";
        // 禁用词
        String[] banned = {"hit"};
        System.out.println(findMostCommonWord(paragraph, banned));

        paragraph = "Bob";
        System.out.println(findMostCommonWord(paragraph, banned));
    }
}
package com.lzlg.study.algorithm.new2023.hash.quiz;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

/
 * 两数之和
 * <p>
 * 给定一个数组和一个目标值
 * 找到数组中两个数字之和等于目标值的下标数组,如
 * 数组: [3, 2, 6, 7, 1]
 * 目标值: 8
 * 返回结果是: [1, 2]
 * 给定的数据必定有一个结果
 *
 * @author lzlg
 * 2023/8/3 11:41
 */
public class TwoNumberSum {

    /
     * 两数之和
     *
     * @param nums   数组
     * @param target 目标值
     * @return 两个数下标的数组
     */
    public static int[] twoNumberSum(int[] nums, int target) {
        // 使用HashMap解决,key是数组中数字,value是下标
        Map<Integer, Integer> map = new HashMap<>();
        // 遍历数组
        for (int i = 0; i < nums.length; i++) {
            // 数组中的数字
            int x = nums[i];
            // 目标值和数字的相差值
            int y = target - x;
            // 如果map中有相应的相差值,则找到了,直接返回
            if (map.containsKey(y)) {
                // 返回的是下标
                return new int[]{map.get(y), i};
            } else {
                // 如果没有找到,则继续添加到map中
                map.put(x, i);
            }
        }
        return null;
    }


    public static void main(String[] args) {
        int[] nums = {3, 2, 6, 7, 1};
        int target = 9;
        System.out.println(Arrays.toString(twoNumberSum(nums, target)));
    }
}
数据结构
算法
程序员内功
  • 作者:lzlg520
  • 发表时间:2023-09-13 21:46
  • 版权声明:自由转载-非商用-非衍生-保持署名
  • 公众号转载:请在文末添加作者公众号二维码