LeetCode算法小抄--归并排序详解及应用

news2024/11/15 17:49:13

LeetCode算法小抄--归并排序详解及应用

    • 归并排序详解及应用
        • [912. 排序数组](https://leetcode.cn/problems/sort-an-array/)
        • [315. 计算右侧小于当前元素的个数](https://leetcode.cn/problems/count-of-smaller-numbers-after-self/)[hard]--华为笔试
        • [493. 翻转对](https://leetcode.cn/problems/reverse-pairs/)[hard]
        • [327. 区间和的个数](https://leetcode.cn/problems/count-of-range-sum/)[hard]

⚠申明: 未经许可,禁止以任何形式转载,若要引用,请标注链接地址。 全文共计9208字,阅读大概需要5分钟
🌈更多学习内容, 欢迎👏关注👀【文末】我的个人微信公众号:不懂开发的程序猿
个人网站:https://jerry-jy.co/

归并排序详解及应用

归并排序的代码框架

// 定义:排序 nums[lo..hi]
void sort(int[] nums, int lo, int hi) {
    if (lo == hi) {
        return;
    }
    int mid = (lo + hi) / 2;
    // 利用定义,排序 nums[lo..mid]
    sort(nums, lo, mid);
    // 利用定义,排序 nums[mid+1..hi]
    sort(nums, mid + 1, hi);

    /****** 后序位置 ******/
    // 此时两部分子数组已经被排好序
    // 合并两个有序数组,使 nums[lo..hi] 有序
    merge(nums, lo, mid, hi);
    /*********************/
}

// 将有序数组 nums[lo..mid] 和有序数组 nums[mid+1..hi]
// 合并为有序数组 nums[lo..hi]
void merge(int[] nums, int lo, int mid, int hi);

总结归并排序:先把左半边数组排好序,再把右半边数组排好序,然后把两半数组合并。

类似于二叉树的后序遍历:先递归左子树,再递归右子树,然后写后序位置

归并排序的过程可以在逻辑上抽象成一棵二叉树,树上的每个节点的值可以认为是 nums[lo..hi],叶子节点的值就是数组中的单个元素

在这里插入图片描述

然后,在每个节点的后序位置(左右子节点已经被排好序)的时候执行 merge 函数,合并两个子节点上的子数组

在这里插入图片描述

class Merge {

    // 用于辅助合并有序数组
    private static int[] temp;

    public static void sort(int[] nums) {
        // 先给辅助数组开辟内存空间
        temp = new int[nums.length];
        // 排序整个数组(原地修改)
        sort(nums, 0, nums.length - 1);
    }

    // 定义:将子数组 nums[lo..hi] 进行排序
    private static void sort(int[] nums, int lo, int hi) {
        if (lo == hi) {
            // 单个元素不用排序
            return;
        }
        // 这样写是为了防止溢出,效果等同于 (hi + lo) / 2
        int mid = lo + (hi - lo) / 2;
        // 先对左半部分数组 nums[lo..mid] 排序
        sort(nums, lo, mid);
        // 再对右半部分数组 nums[mid+1..hi] 排序
        sort(nums, mid + 1, hi);
        // 将两部分有序数组合并成一个有序数组
        merge(nums, lo, mid, hi);
    }

    // 将 nums[lo..mid] 和 nums[mid+1..hi] 这两个有序数组合并成一个有序数组
    private static void merge(int[] nums, int lo, int mid, int hi) {
        // 先把 nums[lo..hi] 复制到辅助数组中
        // 以便合并后的结果能够直接存入 nums
        for (int i = lo; i <= hi; i++) {
            temp[i] = nums[i];
        }

        // 数组双指针技巧,合并两个有序数组
        int i = lo, j = mid + 1;
        for (int p = lo; p <= hi; p++) {
            if (i == mid + 1) {
                // 左半边数组已全部被合并
                nums[p] = temp[j++];
            } else if (j == hi + 1) {
                // 右半边数组已全部被合并
                nums[p] = temp[i++];
            } else if (temp[i] > temp[j]) {
                nums[p] = temp[j++];
            } else {
                nums[p] = temp[i++];
            }
        }
    }
}

在这里插入图片描述

注意我们不是在 merge 函数执行的时候 new 辅助数组,而是提前把 temp 辅助数组 new 出来了,这样就避免了在递归中频繁分配和释放内存可能产生的性能问题

归并排序的时间复杂度, O(NlogN)

递归算法的复杂度计算,就是子问题个数 x 解决一个子问题的复杂度

执行的次数是二叉树节点的个数,每次执行的复杂度就是每个节点代表的子数组的长度,所以总的时间复杂度就是整棵树中「数组元素」的个数

所以从整体上看,这个二叉树的高度是 logN,其中每一层的元素个数就是原数组的长度 N,所以总的时间复杂度就是 O(NlogN)

912. 排序数组

给你一个整数数组 nums,请你将该数组升序排列。

// class Solution {
//     public int[] sortArray(int[] nums) {
//         // API选手(手动狗头)
//         Arrays.sort(nums);
//         return nums;
//     }
// }

// 归并排序
class Solution {
    public int[] sortArray(int[] nums) {
        Merge.sort(nums);
        return nums;
    }
}
class Merge {

    // 用于辅助合并有序数组
    private static int[] temp;

    public static void sort(int[] nums) {
        // 先给辅助数组开辟内存空间
        temp = new int[nums.length];
        // 排序整个数组(原地修改)
        sort(nums, 0, nums.length - 1);
    }

    // 定义:将子数组 nums[lo..hi] 进行排序
    private static void sort(int[] nums, int lo, int hi) {
        if (lo == hi) {
            // 单个元素不用排序
            return;
        }
        // 这样写是为了防止溢出,效果等同于 (hi + lo) / 2
        int mid = lo + (hi - lo) / 2;
        // 先对左半部分数组 nums[lo..mid] 排序
        sort(nums, lo, mid);
        // 再对右半部分数组 nums[mid+1..hi] 排序
        sort(nums, mid + 1, hi);
        // 将两部分有序数组合并成一个有序数组
        merge(nums, lo, mid, hi);
    }

    // 将 nums[lo..mid] 和 nums[mid+1..hi] 这两个有序数组合并成一个有序数组
    private static void merge(int[] nums, int lo, int mid, int hi) {
        // 先把 nums[lo..hi] 复制到辅助数组中
        // 以便合并后的结果能够直接存入 nums
        for (int i = lo; i <= hi; i++) {
            temp[i] = nums[i];
        }

        // 数组双指针技巧,合并两个有序数组
        int i = lo, j = mid + 1;
        for (int p = lo; p <= hi; p++) {
            if (i == mid + 1) {
                // 左半边数组已全部被合并
                nums[p] = temp[j++];
            } else if (j == hi + 1) {
                // 右半边数组已全部被合并
                nums[p] = temp[i++];
            } else if (temp[i] > temp[j]) {
                nums[p] = temp[j++];
            } else {
                nums[p] = temp[i++];
            }
        }
    }
}

315. 计算右侧小于当前元素的个数[hard]–华为笔试

给你一个整数数组 nums ,按要求返回一个新数组 counts 。数组 counts 有该性质: counts[i] 的值是 nums[i] 右侧小于 nums[i] 的元素的数量。

class Solution {
    public List<Integer> countSmaller(int[] nums) {
        // 暴力破解
        // 超出时间限制
        List<Integer> counts = new LinkedList<>();
        for(int i = 0; i < nums.length; i++){
            int count = 0;
            for(int j = i + 1; j < nums.length; j++){
                if(nums[j] < nums[i]) count++;
            }
            counts.add(count);
        }
        return counts;
    }
}

考虑归并排序

和归并排序什么关系呢,主要在 merge 函数,我们在使用 merge 函数合并两个有序数组的时候,其实是可以知道一个元素 nums[i] 后边有多少个元素比 nums[i] 小的

在这里插入图片描述

这时候我们应该把 temp[i] 放到 nums[p] 上,因为 temp[i] < temp[j]

但就在这个场景下,我们还可以知道一个信息:5 后面比 5 小的元素个数就是 左闭右开区间 [mid + 1, j) 中的元素个数,即 2 和 4 这两个元素:

在这里插入图片描述

换句话说,在对 nums[lo..hi] 合并的过程中,每当执行 nums[p] = temp[i] 时,就可以确定 temp[i] 这个元素后面比它小的元素个数为 j - mid - 1

class Solution {
    private class Pair {
        int val, id;
        Pair(int val, int id) {
            // 记录数组的元素值
            this.val = val;
            // 记录元素在数组中的原始索引
            this.id = id;
        }
    }

    // 归并排序所用的辅助数组
    private Pair[] temp;
    // 记录每个元素后面比自己小的元素个数
    private int[] count;

    public List<Integer> countSmaller(int[] nums) {
        int n = nums.length;
        count = new int[n];
        temp = new Pair[n];   
        Pair[] arr = new Pair[n];
        // 记录元素原始的索引位置,以便在 count 数组中更新结果
        for (int i = 0; i < n; i++)
            arr[i] = new Pair(nums[i], i);
        
        // 执行归并排序,本题结果被记录在 count 数组中
        sort(arr, 0, n - 1);
        
        List<Integer> res = new LinkedList<>();
        for (int c : count) res.add(c);
        return res;             
    }

    // 归并排序
    private void sort(Pair[] arr, int lo, int hi) {
        if (lo == hi) return;
        int mid = lo + (hi - lo) / 2;
        sort(arr, lo, mid);
        sort(arr, mid + 1, hi);
        merge(arr, lo, mid, hi);
    }

    // 合并两个有序数组
    private void merge(Pair[] arr, int lo, int mid, int hi) {
        for (int i = lo; i <= hi; i++) {
            temp[i] = arr[i];
        }
        
        int i = lo, j = mid + 1;
        for (int p = lo; p <= hi; p++) {
            if (i == mid + 1) { // 左边数组已经排序好了
                arr[p] = temp[j++];
            } else if (j == hi + 1) { // 右边数组已经排序好了
                arr[p] = temp[i++];
                // 更新 count 数组
                count[arr[p].id] += j - mid - 1;
            } else if (temp[i].val > temp[j].val) {
                arr[p] = temp[j++];
            } else {
                arr[p] = temp[i++];
                // 更新 count 数组
                count[arr[p].id] += j - mid - 1;
            }
        }
    }    
}

因为在排序过程中,每个元素的索引位置会不断改变,所以我们用一个 Pair 类封装每个元素及其在原始数组 nums 中的索引,以便 count 数组记录每个元素之后小于它的元素个数。

493. 翻转对[hard]

给定一个数组 nums ,如果 i < jnums[i] > 2*nums[j] 我们就将 (i, j) 称作一个重要翻转对

你需要返回给定数组中的重要翻转对的数量。

这道题目和题目是一个意思,而且和上一道题非常类似,只不过上一题求的是 nums[i] > nums[j],这里求的是 nums[i] > 2*nums[j] 罢了

解题的思路当然还是要在 merge 函数中做点手脚,当 nums[lo..mid]nums[mid+1..hi] 两个子数组完成排序后,对于 nums[lo..mid] 中的每个元素 nums[i],去 nums[mid+1..hi] 中寻找符合条件的 nums[j] 就行了

// 记录「翻转对」的个数
int count = 0;

// 将 nums[lo..mid] 和 nums[mid+1..hi] 这两个有序数组合并成一个有序数组
private void merge(int[] nums, int lo, int mid, int hi) {
    for (int i = lo; i <= hi; i++) {
        temp[i] = nums[i];
    }
    
    // 在合并有序数组之前,加点私货
    for (int i = lo; i <= mid; i++) {
        // 对于左半边的每个 nums[i],都去右半边寻找符合条件的元素
        for (int j = mid + 1; j <= hi; j++) {
            // nums 中的元素可能较大,乘 2 可能溢出,所以转化成 long
            if ((long)nums[i] > (long)nums[j] * 2) {
                count++;
            }
        }
    }
    
    // 数组双指针技巧,合并两个有序数组
    int i = lo, j = mid + 1;
    for (int p = lo; p <= hi; p++) {
        if (i == mid + 1) {
            nums[p] = temp[j++];
        } else if (j == hi + 1) {
            nums[p] = temp[i++];
        } else if (temp[i] > temp[j]) {
            nums[p] = temp[j++];
        } else {
            nums[p] = temp[i++];
        }
    }
}

不过呢,这段代码提交会超时,毕竟额外添加了一个嵌套 for 循环。怎么进行优化呢,注意子数组 nums[lo..mid] 是排好序的,也就是 nums[i] <= nums[i+1]

所以,对于 nums[i], lo <= i <= mid,我们在找到的符合 nums[i] > 2*nums[j]nums[j], mid+1 <= j <= hi,也必然也符合 nums[i+1] > 2*nums[j]

换句话说,我们不用每次都傻乎乎地去遍历整个 nums[mid+1..hi],只要维护一个开区间边界 end,维护 nums[mid+1..end-1] 是符合条件的元素即可

class Solution {
    public int reversePairs(int[] nums) {
        // 执行归并排序
        sort(nums);
        return count;
    }

    private int[] temp;

    public void sort(int[] nums) {
        temp = new int[nums.length];
        sort(nums, 0, nums.length - 1);
    }  
    
    // 归并排序
    private void sort(int[] arr, int lo, int hi) {
        if (lo == hi) return;
        int mid = lo + (hi - lo) / 2;
        sort(arr, lo, mid);
        sort(arr, mid + 1, hi);
        merge(arr, lo, mid, hi);
    }

    // 记录「翻转对」的个数
    private int count = 0;

    private void merge(int[] nums, int lo, int mid, int hi) {
        for (int i = lo; i <= hi; i++) {
            temp[i] = nums[i];
        }
        
        // 进行效率优化,维护左闭右开区间 [mid+1, end) 中的元素乘 2 小于 nums[i]
        // 为什么 end 是开区间?因为这样的话可以保证初始区间 [mid+1, mid+1) 是一个空区间
        int end = mid + 1;
        for (int i = lo; i <= mid; i++) {
            // nums 中的元素可能较大,乘 2 可能溢出,所以转化成 long
            while (end <= hi && (long)nums[i] > (long)nums[end] * 2) {
                end++;
            }
            count += end - (mid + 1);
        }

        // 数组双指针技巧,合并两个有序数组
        int i = lo, j = mid + 1;
        for (int p = lo; p <= hi; p++) {
            if (i == mid + 1) {
                nums[p] = temp[j++];
            } else if (j == hi + 1) {
                nums[p] = temp[i++];
            } else if (temp[i] > temp[j]) {
                nums[p] = temp[j++];
            } else {
                nums[p] = temp[i++];
            }
        }
    }          
}

327. 区间和的个数[hard]

给你一个整数数组 nums 以及两个整数 lowerupper 。求数组中,值位于范围 [lower, upper] (包含 lowerupper)之内的 区间和的个数

区间和 S(i, j) 表示在 nums 中,位置从 ij 的元素之和,包含 ij (ij)。

简单说,题目让你计算元素和落在 [lower, upper] 中的所有子数组的个数。

首先,解决这道题需要快速计算子数组的和,创建一个前缀和数组 preSum 来辅助我们迅速计算区间和。

preSum 中的两个元素之差其实就是区间和。

class Solution {
    private int lower, upper;

    public int countRangeSum(int[] nums, int lower, int upper) {
    this.lower = lower;
        this.upper = upper;
        // 构建前缀和数组,注意 int 可能溢出,用 long 存储
        long[] preSum = new long[nums.length + 1];
        for (int i = 0; i < nums.length; i++) {
            preSum[i + 1] = (long)nums[i] + preSum[i];
        }
        // 对前缀和数组进行归并排序
        sort(preSum);
        return count;
    }

    private long[] temp;

    public void sort(long[] nums) {
        temp = new long[nums.length];
        sort(nums, 0, nums.length - 1);
    }

    private void sort(long[] nums, int lo, int hi) {
        if (lo == hi) {
            return;
        }
        int mid = lo + (hi - lo) / 2;
        sort(nums, lo, mid);
        sort(nums, mid + 1, hi);
        merge(nums, lo, mid, hi);
    }

    private int count = 0;

    private void merge(long[] nums, int lo, int mid, int hi) {
        for (int i = lo; i <= hi; i++) {
            temp[i] = nums[i];
        }
        
        // 在合并有序数组之前加点私货(这段代码会超时)
        // for (int i = lo; i <= mid; i++) {
        //     for (int j = mid + 1; j <= hi; k++) {
        //         // 寻找符合条件的 nums[j]
        //         long delta = nums[j] - nums[i];
        //         if (delta <= upper && delta >= lower) {
        //             count++;
        //         }
        //     }
        // }
        
        // 进行效率优化
        // 维护左闭右开区间 [start, end) 中的元素和 nums[i] 的差在 [lower, upper] 中
        int start = mid + 1, end = mid + 1;
        for (int i = lo; i <= mid; i++) {
            // 如果 nums[i] 对应的区间是 [start, end),
            // 那么 nums[i+1] 对应的区间一定会整体右移,类似滑动窗口
            while (start <= hi && nums[start] - nums[i] < lower) {
                start++;
            }
            while (end <= hi && nums[end] - nums[i] <= upper) {
                end++;
            }
            count += end - start;
        }

        // 数组双指针技巧,合并两个有序数组
        int i = lo, j = mid + 1;
        for (int p = lo; p <= hi; p++) {
            if (i == mid + 1) {
                nums[p] = temp[j++];
            } else if (j == hi + 1) {
                nums[p] = temp[i++];
            } else if (temp[i] > temp[j]) {
                nums[p] = temp[j++];
            } else {
                nums[p] = temp[i++];
            }
        }
    }
}

我们依然在 merge 函数合并有序数组之前加了一些逻辑,这个效率优化有点类似维护一个滑动窗口,让窗口中的元素和 nums[i] 的差落在 [lower, upper] 中。

所有递归的算法,本质上都是在遍历一棵(递归)树,然后在节点(前中后序位置)上执行代码。你要写递归算法,本质上就是要告诉每个节点需要做什么

–end–

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/439423.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java版本工程项目管理系统源码-全面的工程项目管理

​ ​工程项目管理系统是指从事工程项目管理的企业&#xff08;以下简称工程项目管理企业&#xff09;受业主委托&#xff0c;按照合同约定&#xff0c;代表业主对工程项目的组织实施进行全过程或若干阶段的管理和服务。 如今建筑行业竞争激烈&#xff0c;内卷严重&#xff0c…

供应链优化的好处是什么?

企业可以从优质的供应链优化软件中获得许多好处&#xff1a; 成本降低 通过供应链优化&#xff0c;可以消除许多不必要的成本&#xff0c;从而简化业务运营费用。所有重复或无效的流程也可以根据需要消除或自动化。重点可以转移到通过准确及时交付来满足客户的需求上。通过供…

OpUtils网络端口扫描

网络端口扫描程序使 IT 管理员能够密切监视端口&#xff0c;这些端口是网络通信的端点。网络端口支持不同实体之间的数据传输&#xff0c;并支持运行各种服务或应用程序。在日常工作中&#xff0c;网络管理员的任务是扫描和监控数百个交换机端口。网络端口扫描程序工具通过帮助…

Intellij IDEA-插件开发环境搭建

插件开发有两种方式 1.基于Gradle&#xff08;新版IDEA&#xff0c;按钮名称叫做IDE Plugin&#xff0c;具体多新我也不清楚&#xff0c;反正2023的必须是这种&#xff09; 2.基于Dev kit&#xff08;旧版IDEA&#xff0c;按钮名称叫做IntelliJ Platform Plugin&#xff0c;具体…

【GitLab私有仓库】在Linux上用Gitlab搭建自己的私有库并配置cpolar内网穿透

前言 GitLab 是一个用于仓库管理系统的开源项目&#xff0c;使用Git作为代码管理工具&#xff0c;并在此基础上搭建起来的Web服务。 Gitlab是被广泛使用的基于git的开源代码管理平台, 基于Ruby on Rails构建, 主要针对软件开发过程中产生的代码和文档进行管理, Gitlab主要针对…

基于Udacity模拟器的端到端自动驾驶决策

1 端到端自动驾驶决策 端到端自动驾驶决策的输入为车辆的感知信息&#xff0c;如摄像头信息&#xff0c;输出为车辆的前轮转角和摄像头等信息。 如上图所示&#xff0c;为英伟达公司的端到端自动驾驶决策框架&#xff0c;其CNN网络如下图所示&#xff0c;其中包括一个归一化…

狂神说Docker

思维导图&#xff1a; 文章目录 1.Docker概念与安装1.1 概述1.2 安装1.3 卸载1.4 Docker的执行镜像原理1.5 Docker为什么比Vm快1.6 Docker底层原理 2.Docker常用命令2.1 帮助命令2.2 镜像命令2.3 容器命令2.4 其他常用命令 3.小练习3.1 Docker下安装Nginx3.2 Docker安装Tomcat…

[网络安全提高篇] 一二〇.恶意软件动态分析经典沙箱Cape批量提取动态API特征

终于忙完初稿,开心地写一篇博客。 “网络安全提高班”新的100篇文章即将开启,包括Web渗透、内网渗透、靶场搭建、CVE复现、攻击溯源、实战及CTF总结,它将更加聚焦,更加深入,也是作者的慢慢成长史。换专业确实挺难的,Web渗透也是块硬骨头,但我也试试,看看自己未来四年究…

SpringBoot设计了哪些可拓展的机制?

SpringBoot核心源码 public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) { ...this.primarySources new LinkedHashSet(Arrays.asList(primarySources));// Servletthis.webApplicationType WebApplicationType.deduceFromClass…

sentinel配置文件

转载自 https://blog.csdn.net/u012441222/article/details/80751390 Redis的哨兵机制是官方推荐的一种高可用&#xff08;HA&#xff09;方案&#xff0c;我们在使用Redis的主从结构时&#xff0c;如果主节点挂掉&#xff0c;这时是不能自动进行主备切换和通知客户端主节点下…

涨点神器:基于Yolov8小目标遮挡物性能提升(SEAM、MultiSEAM)

1.遮挡物检测简介 不同的目标检测应用场景有不同的检测难点,小目标、多尺度以及背景复杂等问题,被遮挡的物体仍然是最先进的物体检测器面临的挑战。本文尝试解决待测目标相互遮挡带来的检测困难,对于人脸遮挡提出了一个名为 SEAM 的注意力模块并引入了排斥损失来解决它,引…

机器学习入门实例-加州房价预测-3(选择与训练模型+调参)

选择与训练模型 使用线性回归 from sklearn.linear_model import LinearRegressionfrom sklearn.metrics import mean_squared_errorlin_reg LinearRegression()lin_reg.fit(housing_prepared, housing_labels)housing_predictions lin_reg.predict(housing_prepared)lin_ms…

直播预告 | TDengine Apache SeaTunnel 联合应用最佳实践

TDengine 自诞生之日起&#xff0c;除产品层面的技术创新和实力提升外&#xff0c;也在大力完善自身产品生态&#xff0c;以此进一步满足用户的业务需求、提升使用体验。 近日&#xff0c;TDengine 与 Apache SeaTunnel 展开集成合作&#xff0c;双方将于 4 月 18 日 19:00 联…

二、Java 并发编程(5)

本章概要 线程上下文切换 线程上下文切换的流程导致线程上下文切换的原因 Java中的阻塞队列 阻塞队列的主要操作Java中阻塞队列的实现 2.7 线程上下文切换 CPU 利用时间片轮询来为每个任务都服务一定的时间&#xff0c;然后把当前任务的状态保存下来&#xff0c;继续服务下…

IO多路转接—select,poll,epoll

目录 select 函数介绍 select基本工作流程 select的优缺点及适用场景 poll poll的优缺点 epoll epoll的相关系统调用 epoll_create epoll_ctl epoll_wait epoll工作原理 epoll服务器编写 epoll的优点 epoll工作方式 select 函数介绍 系统提供select函数来实现多路复…

Spring核心设计思想

目录 前言&#xff1a; Spring是什么 什么是IoC 传统开发思想 IoC开发思想 Spring IoC 什么是DI 小结&#xff1a; 前言&#xff1a; 官网中提出&#xff1a;Spring makes programming Java quicker, easier, and safer for everybody. Spring’s focus on speed, simp…

YOLOv7+单目测距(python)

YOLOv7单目测距&#xff08;python&#xff09; 1. 相关配置2. 测距原理3. 相机标定3.1&#xff1a;标定方法13.2&#xff1a;标定方法2 4. 相机测距4.1 测距添加4.2 主代码 5. 实验效果 相关链接 1. YOLOV5 单目测距&#xff08;python&#xff09; 2. YOLOV5 双目测距&…

基于springboot的招聘信息管理系统源码数据库论文

目 录 1 绪 论 1.1 课题背景与意义 1.2 系统实现的功能 1.3 课题研究现状 2系统相关技术 2.1 Java语言介绍 2.2 B/S架构 2.3 MySQL 数据库介绍 2.4 MySQL环境配置 2.5 SpringBoot框架 3系统需求分析 3.1系统功能 3.2可行性研究 3.2.1 经济可行性 …

力扣sql中等篇练习(六)

力扣sql中等篇练习(六) 1 购买了产品A和产品B却没有购买产品C的顾客 1.1 题目内容 1.1.1 基本题目信息 1.1.2 示例输入输出 1.2 示例sql语句 # 先求出既有的,然后再去筛选掉没有的 # 去重用不了内连接 SELECT t1.customer_id,c.customer_name FROM ( SELECT distinct cust…

《Spring MVC》 第二章 第一个程序

前言 Spring MVC 是 Spring 框架提供的一款基于 MVC 模式的轻量级 Web 开发框架。 Spring MVC 本质是对 Servlet 的进一步封装&#xff0c;其最核心的组件是DispatcherServlet&#xff0c;它是 Spring MVC 的前端控制器&#xff0c;主要负责对请求和响应的统一地处理和分发。C…