算法:二分查找题目练习

目录

题目一:二分查找

朴素的二分模版

题目二:在排序数组中查找元素的第⼀个和最后⼀个位置

查找区间左端点

查找区间右端点

查找区间左端点二分模版

查找区间右端点二分模版

题目三:搜索插⼊位置

题目四:x的平方根

题目五:山峰数组的峰顶

题目六:寻找峰值

题目七:搜索旋转排序数组中的最小值

题目八:0〜n-1中缺失的数字


二分查找算法原理

二分查找算法适用于数组有序的情况,当然数组如果无序但是能够找到一个规律,也是可以使用二分查找算法的

二分查找算法模版

这里的模块需要理解之后再记忆

有三个模版,分别是:

①朴素的二分模版
②查找左边界的二分模版
③查找右边界的二分模版


题目一:二分查找

给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target  ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1
示例 1:

输入: nums = [-1,0,3,5,9,12], target = 9输出: 4
解释: 9 出现在 nums中并且下标为 4

示例 2:

输入: nums= [-1,0,3,5,9,12], target = 2输出: -1
解释: 2 不存在 nums中因此返回 -1

解法一:暴力解法

初看这个题,可以很容易想到暴力解法,也就是遍历一遍这个数组,直到找到这个目标值target,返回该目标值的下标即可,若没有找到该目标值,就返回-1即可

暴力解法时间复杂度是O(N)


解法二:二分查找算法

二分查找算法是当数组有二段性的时候,就能够使用二分查找算法了,因为在暴力解法中,依次遍历一次只能排除一个数,而如果数组有二段性,二分查找算法就可以依次排除一块区域的数,因此效率比较高

二段性就是我们所发现一个规律,根据这个规律选取一个点之后(一般选取中间的点),能够将数字分成两部分,根据规律能舍去其中一部分,进而在另一个部分里面继续查找的时候,此时就可以使用二分查找算法了

下面具体讲解二分查找算法:

有一个数组,左端点下标为left,右端点下标为right,中间位置的下标为mid,此时要查找的目标值是t,数组的中间值为x

此时选取中间的点x,与目标值t比较会有三种情况:

①x < t :left = mid +1,接着在 [ left, right ]的区间查找
②x > t :right = mid - 1,接着在 [ left, right ]的区间查找
③x == t:返回结果mid

循环的条件:left > right时就停止,因为每次判断完,如果没有找到结果,要不就是right左移或left右移,直到移动结束后left > right,就说明遍历结束了

二分查找算法的时间复杂度是O(logN)

代码如下:

class Solution {
public:
    int search(vector<int>& nums, int target) 
    {
        int left = 0, right = nums.size() - 1;
        while(left <= right)
        {
            int mid = left + (right - left) / 2; //防止溢出的风险
            if(nums[mid] < target) left = mid + 1;
            else if(nums[mid] > target) right = mid - 1;
            else return mid;
        }
        return -1;
    }
};

有个细节,在算中间值的下标mid时,没有采用(left + right) / 2来计算,因为这种计算方式有可能会导致溢出的风险,因为如果left和right都是极大的数,这两个数相加就可能会导致溢出

因此在这里采用(right - left) / 2,计算出left和right之间的距离的一半,再与left相加,就不会有上述风险了


朴素的二分模版

        while(left <= right)
        {
            int mid = left + (right - left) / 2;
            if(......) 
                left = mid + 1;
            else if(......) 
                right = mid - 1;
            else 
                return ......;
        }


题目二:在排序数组中查找元素的第⼀个和最后⼀个位置

给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。

如果数组中不存在目标值 target,返回 [-1, -1]

你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。

示例 1:

输入:nums = [5,7,7,8,8,10], target = 8
输出:[3,4]

示例 2:

输入:nums = [5,7,7,8,8,10], target = 6
输出:[-1,-1]

示例 3:

输入:nums = [], target = 0
输出:[-1,-1]

解法一:暴力解法

同样在遇到这个题时,最容易想到的就是暴力解法了,依旧是遍历数组,在找到的第一个位置处做标记,在找到的最后一个位置处做标记,最后返回,如果没有找到要求的数,就返回[-1, -1]

暴力查找的时间复杂度依旧是O(N)


解法二:朴素二分

这个朴素二分的方法,在这道题中,就明显不太适用了,因为如果一个数组全部是同一个数,那么我使用朴素二分,从中间去一个点,并不能确定剩下符合条件的数是在左边还是右边,所以还是需要向左和右继续找,这样的时间复杂度和暴力解法并没有区别


所以下面详细说明一下查找区间的左端点和右端点的情况:

查找区间左端点

有一个数组如下所示,目标值t等于3,此时需要查找区间的左端点,也就是第一个3出现的位置

所以需要利用二段性, 将数组划分为上图所示的两部分,小于t和大于等于t这两部分

有一个数组,左端点下标为left,右端点下标为right,中间位置的下标为mid,此时要查找的目标值是t,数组的中间值为x

与朴素的二分模版不同,此题的target可能不止一个数,所以中间值与目标值t的比较就有如下两种情况:

x < t:left = mid + 1接着在 [ left, right ]的区间查找
x >= t:right = mid接着在 [ left, right ]的区间查找

需要注意,当x < t时,因为此时mid所指向的x都是不满足题意的,所以left需要移动到mid的右边,所以是 left = mid + 1

而当 x >= t时,此时的x有可能是目标值,所以right不能像二分的朴素模版那样,移动到mid的左边,而是移动到mid的位置

下面详细说明二分的两个细节:循环条件和求中点的操作

循环条件的相关细节:

关于循环条件:这里选的是 left < right 就进入循环,而不是left <= right

分下面三种情况讨论,来说明为什么循环条件选择 left < right

①数组中有结果:
数组如下,最终结果在ret这个位置开始:

最开始的时候,left是处于不合法的区间,而right是处于合法的区间,left区间和right区间如下所示:

right一直在合法区间上移动的时候,是绝对不会超过ret这个点的,因为right永远执行的是right = mid这个操作
而left是永远想跳出这个不合法区域的,因为left永远执行的是 left = mid + 1这个操作

而当left跳出区域与right相遇后,所指的这个位置正好是最终结果

所以当left = right的时候,就是最终结果了,无需继续判断

②数组全大于t:

如果全是大于t的,那么right是只会向左移动,直到移动到left的位置为止,因为这种情况下,left永远都不会移动,只有right不停地执行right = mid这个操作

此时left和right相遇了,但是这种情况下是没有最终结果的,所以只需判断一下相遇位置的值是否等于t即可,如果相等就返回这个位置的值,如果不相等就返回[-1, -1]即可

所以当left = right的时候,只需再与t比较一次判断是否是左端点的值即可,无需继续进入循环判断

③数组全小于t:

如果全是小于t的,那么left是只会向右移动,直到移动到right的位置为止

当他们相遇时,同样只需判断一下相遇位置的值是否等于t即可

所以当left = right的时候,只需再与t比较一次判断是否是左端点的值即可,无需继续进入循环判断

通过上述的三种情况,证明了循环条件是 left < right,不需要=,因为相等的时候就已经得出最终结果了,没有必要再进入循环中判断了

如果我们的循环条件不是 left < right,而是写成了 left <= right,就会出现死循环的情况

因为如果是第一种情况,left和right都指向了ret的位置,此时继续判断,right依旧是指向该位置,并不发生改变,继续进入循环......从而导致死循环


求中点的操作的相关细节:

在第一题中使用的朴素的二分模版,求中点时,采用的公式是:mid = left + (right - left) / 2

其实还有一个公式也可以求中点:mid = left + (right - left + 1) / 2

区别就是在括号中 +1,那么这两个的区别是什么呢?

很简单,当数组个数是偶数时,例如共有6个数,第一种公式求出来是下图这个位置:
mid = 0 + (5 - 0) / 2 = 2

如果是第二种公式,求出来则是这个位置:
mid = 0 + (5 - 0 + 1) / 2 = 3

可以观察到,在数组个数是偶数时,中间位置是两块,第一个公式指向的是偏左的那一块位置,而第二个公式指向的则是偏右的那一块位置

这两种情况再朴素二分中都可以使用,而在这种情况下则会有问题

当最后一个left和right指向下图所示的情况时:

如果采用第二个公式,计算出来mid指向中间偏右的位置,即:

如果是x < t,那么left = mid + 1,此时再判断不满足循环条件left < right,循环就会终止
而如果是x >= t,那么right = mid,此时mid和right指向同一块位置,这时就会出现死循环的情况

因此得出结论,在求区间左端点时,使用第二个公式求中点,就会陷入死循环

而如果采用第一个公式计算中点,mid就会指向:

如果是x < t,那么left = mid + 1,此时left和right相遇,循环终止
而如果是x >= t,那么right = mid,此时left和right同样相遇,循环终止

不会出现上述死循环的情况


查找区间右端点

同样是该数组如下所示,目标值t等于3,此时需要查找区间的右端点,也就是最后一个3出现的位置:

所以需要利用二段性, 将数组划分为上图所示的两部分,小于等于t和大于t这两部分

有一个数组,左端点下标为left,右端点下标为right,中间位置的下标为mid,此时要查找的目标值是t,数组的中间值为x

x <= t:left = mid接着在 [ left, right ]的区间查找
x > t:right = mid - 1接着在 [ left, right ]的区间查找

当x <= t时,表示结果就在mid的左边这个区域,此时的x有可能是目标值,所以left不能像二分的朴素模版那样,移动到mid的右边,而是移动到mid的位置
当x > t时,因为此时mid所指向的x都是不满足题意的,所以right需要移动到mid的左边,所以是 right= mid + -1

下面详细说明二分的两个细节:循环条件和求中点的操作

循环条件的相关细节:

循环条件同样是left < right

具体证明和上面求左端点的步骤一样,以此类推

求中点的操作的相关细节

求中点的方式依旧是这两个公式

①mid = left + (right - left) / 2
②mid = left + (right - left + 1) / 2

第一个公式求的是靠左的,第二个公式求的是靠右的

当最后一个left和right指向下图所示的情况时:

如果采用第一个公式,计算出来mid指向中间偏左的位置,即:

如果是x <= t,那么left = mid,此时mid和right指向同一块位置,这时就会出现死循环的情况

因此得出结论,在求区间右端点时,使用第一个公式求中点,就会陷入死循环

而如果采用第二个公式计算中点,mid就会指向中间偏右的位置:

如果是x <= t,那么left = mid,此时left和right相遇,循环终止
而如果是x > t,那么right = mid - 1,此时left和right同样相遇,循环终止

不会出现上述死循环的情况


利用上述讲解的计算左端点和右端点的方法,解决此题:

代码如下:

class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        if(nums.size() == 0) return {-1,-1}; //判断边界条件
        int left = 0, right = nums.size()-1,begin = 0;
        while(left < right) //计算左端点
        {
            int mid = left + (right -left) / 2;
            if(nums[mid] < target) left = mid + 1;
            else right = mid;
        }
        //判断是否有结果
        if(nums[left] != target) return {-1,-1}; 
        else begin = left; //begin存储左端点的下标

        left = 0, right = nums.size()-1;
        while(left < right) //计算右端点
        {
            int mid = left + (right -left + 1) / 2;
            if(nums[mid] <= target) left = mid;
            else right = mid - 1;
        }
        return {begin,right};        
    }
};

查找区间左端点二分模版

        while(left < right)
        {
            int mid = left + (right -left) / 2;
            if(......) left = mid + 1;
            else right = mid;
        }

查找区间右端点二分模版

        while(left < right)
        {
            int mid = left + (right -left + 1) / 2;
            if(......) left = mid;
            else right = mid - 1;
        }

题目三:搜索插入位置

给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。

请必须使用时间复杂度为 O(log n) 的算法。

示例 1:

输入: nums = [1,3,5,6], target = 5
输出: 2

示例 2:

输入: nums = [1,3,5,6], target = 2
输出: 1

示例 3:

输入: nums = [1,3,5,6], target = 7
输出: 4

这道题也可以很容易的看出这个数组的二段性

也就是所插入的值要不和数组中的数相等, 要不就是第一次出现比它大的这个数的位置,所以可以得到结论:最终找到的位置应该是大于等于目标值target的

所以利用二段性分为小于target,和大于等于目标值target

代码如下:

class Solution {
public:
    int searchInsert(vector<int>& nums, int target) {
        int left = 0, right = nums.size()-1;
        if(nums[right] < target) return nums.size(); // 判断边界条件
        while(left < right)
        {
            int mid = left + (right - left) / 2;
            if(nums[mid] < target) left = mid + 1;
            else right = mid;
        }
        return left;
    }
};

题目四:x的平方根

给你一个非负整数 x ,计算并返回 x 的 算术平方根 。

由于返回类型是整数,结果只保留 整数部分 ,小数部分将被 舍去 。

注意:不允许使用任何内置指数函数和算符,例如 pow(x, 0.5) 或者 x ** 0.5 。

示例 1:

输入:x = 4
输出:2

示例 2:

输入:x = 8
输出:2
解释:8 的算术平方根是 2.82842..., 由于返回类型是整数,小数部分将被舍去。

解法一:暴力解法

依次列举1、2、3、4、5....的平方,与x做比较,如果比x小就继续往后试,直到找到这个数的平方要么是大于这个数,要么是等于这个数位置,就说明找到了最终解

当等于时就返回这个数,当是大于时就返回它前一个数


解法二:二分查找

二分查找最重要的就是找到二段性

我们可以发现,整个数组可以分为小于等于x和大于x这两个区域

所以就有两种情况:

①mid * mid <= x:left = mid
②mid * mid > x:right = mid - 1

分情况讨论即可


代码如下:

class Solution {
public:
    int mySqrt(int x) {
        if(x < 1) return 0; // 处理边界条件
        long long left = 1, right = x;
        while(left < right)
        {
            long long mid = left + (right - left + 1) / 2;//防溢出
            if(mid * mid <= x) left = mid;
            else right = mid - 1;
        }
        return left;
    }
};

题目五:山峰数组的峰顶

符合下列属性的数组 arr 称为 山脉数组 :

  • arr.length >= 3
  • 存在 i0 < i < arr.length - 1)使得:
    • arr[0] < arr[1] < ... arr[i-1] < arr[i]
    • arr[i] > arr[i+1] > ... > arr[arr.length - 1]

给你由整数组成的山脉数组 arr ,返回满足 arr[0] < arr[1] < ... arr[i - 1] < arr[i] > arr[i + 1] > ... > arr[arr.length - 1] 的下标 i 。

你必须设计并实现时间复杂度为 O(log(n)) 的解决方案。

示例 1:

输入:arr = [0,1,0]
输出:1

示例 2:

输入:arr = [0,2,1,0]
输出:1

示例 3:

输入:arr = [0,10,5,2]
输出:1

这个题目也是比较好理解的,所给的数组元素的大小,都是先上升再下降的,都存在一个最大值,求这个最大值的下标

解法一:暴力枚举

从前往后依次枚举,当枚举到一个数的值是大于前一个数的,这个数就是峰值,返回下标即可

暴力解法的时间复杂度是O(N)


解法二:二分查找算法

此时根据这个峰值数组的特性,可以找到这个数组的二段性, 如下图所示:

左边的区间包含峰值,每一个数都大于前一个数

右边区域不包含峰值,每一个数都小于小于前一个数

代码如下:

class Solution {
public:
    int peakIndexInMountainArray(vector<int>& arr) {
        int left = 0, right = arr.size()-1;
        while(left < right)
        {
            int mid = left + (right - left + 1) / 2;
            if(arr[mid] > arr[mid-1]) left = mid;
            else right = mid - 1;
        }
        return left;
    }
};

题目六:寻找峰值

峰值元素是指其值严格大于左右相邻值的元素。

给你一个整数数组 nums,找到峰值元素并返回其索引。数组可能包含多个峰值,在这种情况下,返回 任何一个峰值 所在位置即可。

你可以假设 nums[-1] = nums[n] = -∞ 。

你必须实现时间复杂度为 O(log n) 的算法来解决此问题。

示例 1:

输入:nums = [1,2,3,1]
输出:2
解释:3 是峰值元素,你的函数应该返回其索引 2。

示例 2:

输入:nums = [1,2,1,3,5,6,4]
输出:1 或 5 
解释:你的函数可以返回索引 1,其峰值元素为 2;
     或者返回索引 5, 其峰值元素为 6。

这道题需要注意的是:nums[-1] = nums[n] = -∞ 这个条件

表示默认第一个元素前和最后一个元素之后都是最小的,所以如果是如下这种情况,最后一个位置也是峰值,如下:

或是这种情况,第一个也是峰值:

这就是刚买这个条件所表达的意思


解法一:暴力枚举

从第一个位置开始暴力枚举,分下面的情况讨论即可:

①第二个数比第一个小,此时第一个数就是峰值
②在走的过程中一直上升,直到遇到一个比该值小的,此时该值就是峰值
③从第一个数开始一直上升,直到最后一个数为止,此时最后一个数是峰值

暴力枚举的时间复杂度是O(N)


解法二:二分查找算法

在arr数组中,有两种情况,一个坐标是i另一个坐标是i+1

如果arr[i] > arr[i+1],说明此时是下降趋势,也就是说答案就在 <= i的区域,此时需要改变right的值

如果arr[i] <  arr[i+1],说明此时是上升趋势,也就是说答案就在 > i的区域,此时需要改变left的值

将上述所说的i替换为mid,就可以使用二分查找算法进行计算了

代码如下:

class Solution {
public:
    int findPeakElement(vector<int>& nums) {
        int left = 0, right = nums.size()-1;
        while(left < right)
        {
            int mid = left + (right - left) / 2;
            if(nums[mid] > nums[mid + 1]) right = mid;
            else left = mid + 1;
        }
        return left;
    }
};

题目七:搜索旋转排序数组中的最小值

已知一个长度为 n 的数组,预先按照升序排列,经由 1 到 n 次 旋转 后,得到输入数组。例如,原数组 nums = [0,1,2,4,5,6,7] 在变化后可能得到:

  • 若旋转 4 次,则可以得到 [4,5,6,7,0,1,2]
  • 若旋转 7 次,则可以得到 [0,1,2,4,5,6,7]

注意,数组 [a[0], a[1], a[2], ..., a[n-1]] 旋转一次 的结果为数组 [a[n-1], a[0], a[1], a[2], ..., a[n-2]] 。

给你一个元素值 互不相同 的数组 nums ,它原来是一个升序排列的数组,并按上述情形进行了多次旋转。请你找出并返回数组中的 最小元素 。

你必须设计一个时间复杂度为 O(log n) 的算法解决此问题。

示例 1:

输入:nums = [3,4,5,1,2]
输出:1
解释:原数组为 [1,2,3,4,5] ,旋转 3 次得到输入数组。

示例 2:

输入:nums = [4,5,6,7,0,1,2]
输出:0
解释:原数组为 [0,1,2,4,5,6,7] ,旋转 3 次得到输入数组。

示例 3:

输入:nums = [11,13,15,17]
输出:11
解释:原数组为 [11,13,15,17] ,旋转 4 次得到输入数组。

需要注意数组中的数是互不相同的,需要找出最小元素

解法一:暴力枚举

暴力枚举,遍历一遍数组,找出最小的元素

时间复杂度O(N)


解法二:二分查找算法

此题也可以找到数组的二段性,每一个数组都大致可以分为下面这种情况,即原本的递增数字旋转后,会出现先递增,再递增的趋势:

AB这一段都大于D点,CD这一段都小于等于D点,就有了二分查找算法所需的二段性

设D点为x,分为下面两种情况:

①arr[mid] > x,表示在AB线段上,此时改变需要改变左区间
②arr[mid] <= x,表示在CD线段上,此时改变需要改变右区间

代码如下:

class Solution {
public:
    int findMin(vector<int>& nums) {
        int left = 0, right = nums.size()-1;
        int x = nums[right];
        while(left < right)
        {
            int mid = left + (right - left) / 2;
            if(nums[mid] > x) left = mid + 1; 
            else right = mid;
        }
        return nums[left];
    }
};

题目八:0〜n-1中缺失的数字

某班级 n 位同学的学号为 0 ~ n-1。点名结果记录于升序数组 records。假定仅有一位同学缺席,请返回他的学号。

示例 1:

输入: records = [0,1,2,3,5]
输出: 4

示例 2:

输入: records = [0, 1, 2, 3, 4, 5, 6, 8]
输出: 7

这里的最后一道题,其实是很简单的,其中是有非常多的解法的,具体如下:

前四种的时间复杂度都是O(N),因为都需要遍历一遍数组

解法一:遍历数组

因为是学号从0开始递增的,所以直接遍历数组,看哪个数断开了

代码如下:

class Solution {
public:
    int takeAttendance(vector<int>& records) {
        int i = 0;
        for (i = 0; i < records.size(); i++) 
        {
            if (records[i] != i)
                break;
        }
        return i;
    }
};

解法二:哈希表

建立大小为n的哈希表,遍历原始数组,填进哈希表中,接着遍历哈希表,发现哪个数字没有填,答案就是那个数字

class Solution {
public:
    int takeAttendance(vector<int>& records) {
        int i = 0;
        unordered_map<int, int> ump; 
        for (auto& it : records) ump[it]++;
        for (i = 0; i < records.size(); i++) 
        {
            if(!ump.count(i)) break;
        }
        return i;
    }
};

用数组替代哈希表:

class Solution {
public:
    int takeAttendance(vector<int>& records) {
        int i = 0;
        int hash[10000] = {0};
        for (auto& it : records) hash[it]++;
        for (i = 0; i < records.size(); i++) 
        {
            if(hash[i] == 0)
                break;
        }
        return i;
    }
};

解法三:位运算(异或运算)

异或运算时有个特点:相同为0,相异为1,所以我们可以先把数组中的元素异或一遍,再异或n个递增的数组,异或完后剩下来的就是缺失的数

因为缺失了一个数,所以for循环中个数需要加1

代码如下:

class Solution {
public:
    int takeAttendance(vector<int>& records) {
        int i = 0, res = 0;
        for (auto& it : records) res ^= it;
        for (i = 0; i < records.size()+1; i++) res ^= i;
        return res;
    }
};

解法四:数学(高斯求和公式)

也就是将n个数加起来,依次减去原数组中的数,剩下的就是缺失的数

因为n个数从0开始递增,所以公差是1,可以使用(首项 + 末项) * 项数 / 2计算

因为从0开始,共有n个数,所以首项是0,末项是records.size()也就是n,因为假设有3个数,末项是2,而records中缺少了一个数,records.size()就为2,所以可以将records.size()当做末项

项数是n+1,因为n是records.size(),而records缺少了一个数,所以项数需要+1

代码如下:

class Solution {
public:
    int takeAttendance(vector<int>& records) {
        int i = 0, res = 0, n = records.size();
        res = (0 + n)*(n + 1)/2;
        for (i = 0; i < records.size(); i++) res -= records[i];
        return res;
    }
};

解法五:二分查找算法

这道题光看数字可能看不出来怎么使用二分,而如果将下标都标注出来, 此时就可以很清晰的看出二段性了,假设数组是0,1,2,3,5,如下所示,黑色的表示数字元素,红色的表示下标:

可以很清晰的看到,绿框中元素与所对应的下标是相等的,而紫框中的元素与下标是不同的,这里就体现了二段性,我们要找的就是下标为4的这个位置

①arr[mid] == mid,表示此时在绿框中,需要改变left,即left = mid + 1
②arr[mid] != mid,表示此时在紫框中,需要改变right,并且mid可能是答案,所以right = mid

需要注意边界情况,如果数组是0,1,2,3,4这样的类型,缺了一个5,此时我们按照上述的情况使用二分查找算法,会使得最终的left指向4这个值的下标,所以我们需要最后比较一下,left所对应的值和left是否相等, 如果相等就表示是这种特殊情况,需要进行特殊处理

代码如下:

class Solution {
public:
    int takeAttendance(vector<int>& records) {
        int left = 0,right = records.size()-1;
        while(left < right)
        {
            int mid = left + (right - left) / 2;
            if(records[mid] == mid) left = mid + 1;
            else right = mid;
        }
        //处理细节
        return records[left] == left ? left + 1 : left;
    }
};

二分查找题目到此结束

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

unity开发Hololens,使用unity自带的UGUI

hololens 使用UGUI 新建画布&#xff0c;添加组件&#xff0c; 画布模式改成WorldSpace&#xff0c;这样在能在3D场景里 随意的移动位置&#xff0c; 添加NearIteractionTouchaBleUnityUI、CanvasUtility组件 EaventsToReceive改成Pointer&#xff0c; 这样&#xff0c;UGUI的…

Sass是什么?有哪些优缺点?

目录 一、Sass是什么&#xff1f; 二、Sass的优缺点 三、Sass与SaaS 一、Sass是什么&#xff1f; Sass是世界上最成熟、最稳定、最强大的专业级CSS扩展语言。 Sass makes CSS fun again. Sass is an extension of CSS, adding nested rules, variables, mixins, selector in…

基于hive的酒店价格数据可视化分析系统设计和实现

摘要 本文基于Django框架和Hive技术&#xff0c;设计和实现了一种酒店价格数据可视化分析系 统&#xff0c;旨在为酒店管理者提供直观、清晰的数据洞察和决策支持。在研究中&#xff0c;首先深入分 析了酒店价格数据可视化分析系统的背景和意义&#xff0c;认识到对于酒店行…

电商项目之有趣的支付签名算法

文章目录 1 问题背景2 思路3 代码实现 1 问题背景 在发起支付的时候&#xff0c;一般都需要对发送的请求参数进行加密或者签名&#xff0c;下文简称这个过程为“签名”。行业内比较普遍的签发算法有&#xff1a; &#xff08;1&#xff09;按支付渠道给定的字段排序进行拼接&am…

一篇文章讲透排序算法之希尔排序

希尔排序是对插入排序的优化&#xff0c;如果你不了解插入排序的话&#xff0c;可以先阅读这篇文章&#xff1a;插入排序 目录 1.插入排序的问题 2.希尔排序的思路 3.希尔排序的实现 4.希尔排序的优化 5.希尔排序的时间复杂度 1.插入排序的问题 如果用插入排序对一个逆序…

改进rust代码的35种具体方法-类型(十八)-不要惊慌

上一篇文章 它看起来非常复杂&#xff0c;这就是为什么它贴合的塑料盖上用大号友好字母印上“不要恐慌”的原因之一。——道格拉斯亚当斯 此项目的标题将更准确地描述为更喜欢返回Result而不是使用panic!&#xff08;但不要惊慌更吸引人&#xff09;。 Rust的panic机制主要是为…

Mycat+Mysql搭建数据集群实现数据分片存储

前言 MyCAT介绍 * 一个彻底开源的,面向企业应用开发的“大数据库集群”; * 支持事务、ACID、可以替代MySQL的加强版数据库; * 一个可以视为“MySQL”集群的企业级数据库,用来替代昂贵的Oracle集群; * 一个融合内存缓存技术、Nosql技术、HDFS大数据的新型SQL; * 一个新颖…

Linux_应用篇(08) 信号-基础

本章将讨论信号&#xff0c;虽然信号的基本概念比较简单&#xff0c;但是其所涉及到的细节内容比较多&#xff0c;所以本章篇幅也会相对比较长。 事实上&#xff0c;在很多应用程序当中&#xff0c;都会存在处理异步事件这种需求&#xff0c;而信号提供了一种处理异步事件的方法…

DNS的服务与部署(2)

1、dns的安装及开启 dnf install bind.x86_64 -y #安装 #Berkeley Internet Name Domain (BIND) systemctl enable --now named #启用dns服务&#xff0c;服务名称叫named firewall-cmd --permanent --add-servicedns #火墙设置 firewall-cmd --reload …

重学java 39.多线程 — 线程安全

逐渐成为一个情绪稳定且安静成长的人 ——24.5.24 线程安全 什么时候发生&#xff1f; 当多个线程访问同一个资源时&#xff0c;导致了数据有问题&#xff0c;出现并发问题&#xff0c;数据不能及时更新&#xff0c;导致数据发生错误&#xff0c;出现线程安全问题 多线程安全问…

【C++项目】实时聊天的在线匹配五子棋对战游戏

目录 项目介绍 开发环境 核心技术 项目前置知识点介绍 Websocketpp 1. WebSocket基本认识 2. WebSocket协议切换原理解析 3. WebSocket报文格式 4. Websocketpp介绍 5. 搭建一个简单WebSocket服务器 JsonCpp 1. Json格式的基本认识 2. JsonCpp介绍 3. 序列化与反序…

DatePicker日期选择框(antd-design组件库)简单使用

1.DatePicker日期选择框 输入或选择日期的控件。 2.何时使用 当用户需要输入一个日期&#xff0c;可以点击标准输入框&#xff0c;弹出日期面板进行选择。 组件代码来自&#xff1a; 日期选择框 DatePicker - Ant Design 3.本地验证前的准备 参考文章【react项目antd组件-demo:…

C#应用的用户配置窗体方案 - 开源研究系列文章

这次继续整理以前的代码。本着软件模块化的原理&#xff0c;这次笔者对软件中的用户配置窗体进行剥离出来&#xff0c;单独的放在一个Dll类库里进行操作&#xff0c;这样在其它应用程序里也能够快速的复用该类库&#xff0c;达到了快速开发软件的效果。 笔者其它模块化应用的例…

基于Python对评论进行情感分析可视化

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 一、项目背景与意义 在当今数字化时代&#xff0c;用户生成内容&#xff08;UGC&#xff09;如在线评论、社交媒体…

力扣HOT100 - 75. 颜色分类

解题思路&#xff1a; 单指针&#xff0c;对数组进行两次遍历。 class Solution {public void sortColors(int[] nums) {int p 0;int n nums.length;for (int i 0; i < n; i) {if (nums[i] 0) {int tmp nums[i];nums[i] nums[p];nums[p] tmp;p;}}for (int i p; i …

面试八股之JVM篇3.5——垃圾回收——G1垃圾回收器

&#x1f308;hello&#xff0c;你好鸭&#xff0c;我是Ethan&#xff0c;一名不断学习的码农&#xff0c;很高兴你能来阅读。 ✔️目前博客主要更新Java系列、项目案例、计算机必学四件套等。 &#x1f3c3;人生之义&#xff0c;在于追求&#xff0c;不在成败&#xff0c;勤通…

D - AtCoder Wallpaper(abc)

思路&#xff1a;f(c, d) f(a, b) - f(a, d) - f(c, b) 代码&#xff1a; int f(int x, int y){if(y % 2 0){y y / 2;int ans y * (x / 4) * 8;x % 4;if(x 1){ans y * 3;}else if(x 2){ans y * 6;}else if(x 3){ans y * 7;}return ans;}else{y / 2;int ans y * (x…

Python--面向对象

面向对象⭐⭐ 1. 面向对象和面向过程思想 面向对象和面向过程都是一种编程思想,就是解决问题的思路 面向过程&#xff1a;POP(Procedure Oriented Programming)面向过程语言代表是c语言面向对象&#xff1a;OOP(Object Oriented Programming)常见的面向对象语言包括:java c g…

简易进程池的实现

什么是进程池&#xff1f; 进程池&#xff08;Process Pool&#xff09;是一种用于管理和复用多个进程的技术或设计模式。在进程池中&#xff0c;一定数量的进程会被预先创建并保持在内存中&#xff0c;以便在需要时立即使用&#xff0c;而不是每次需要进程时都重新创建新的进程…