• [问题求助] C#如何实现:Modbus通讯监控暖房温度PID控制
    ①暖房温度对象安装300W小电炉加热,DC24V风扇降温,铂热电阻温度变送器检测暖房温度,组成加热冷却过程对象。设计主电路和控制电路,给装置供电,连接线路。②铂热电阻由DC24V驱动输出4-20mA电流信号(二线制4-20mA电路),开发板检测暖房温度信号,通过双极性PID算法控制电炉加热和风扇冷却,实现暖房温度控制。③Modbus通讯开发板中设计Modbus-RTU通讯程序。④计算机通过usb→485转换器和Modbus通讯开发板联通。上位机发送多字节写指令(功能码0x10)向开发板传递温度设定值、PID参数等,开发板使用串口中断接收指令并存入缓冲区(数组),通过分析指令帧的信息控制暖房加热冷却过程。上位机发送读取寄存器指令(功能码0x04)要求开发板回传温度当前值PV、PID输出变量等。在上位机上描绘温度曲线、显示控制参数,并设置启动、停止按钮控制系统运行。
  • [问题求助] 三步请流demo怎么使用
    这些参数都应该填什么呢注册端口填写6060主动注册后开始请流控件无法使用
  • [问题求助] 消息跟踪导出的压缩包解压失败
    消息跟踪导出的压缩包,解压的时候总是出现这个错误
  • [技术干货] C++ 调用C#的DLL
    理论知识在C++中直接调用C#并传递String^(这看起来像是C++/CLI中的托管字符串类型)和C#对象是比较复杂的,因为C++和C#是两种不同的编程语言,运行在不同的运行时环境中。但是,你可以通过几种方法来实现这种交互:使用C++/CLI作为桥梁: 你可以使用C++/CLI来编写一个包装器类,该类可以在C++和C#之间传递数据。C++/CLI允许你在C++代码中使用.NET类型,并可以很容易地与C#代码交互。**使用P/Invoke或C++/CX (COM)**: 如果你的C#代码暴露为COM组件或DLL(使用C++/CX或P/Invoke),则可以从C++代码中调用它。但是,这通常不直接支持传递C#对象;你可能需要将这些对象序列化为字节数组或其他可以在两种语言之间传递的类型。**使用IPC (进程间通信)**: 你可以使用命名管道、WCF、TCP/IP套接字或其他IPC机制在C++和C#进程之间传递数据。这允许你在两个独立的进程中进行通信,每个进程都可以使用它自己的语言运行时。使用C#的DLLImport: 如果你的C++代码是编译为DLL的,并且你希望从C#中调用它,你可以使用C#的DllImport属性来导入C++函数。但是,这通常只适用于简单的C函数,而不是C++类或对象。对于你的情况,一个使用C++/CLI作为桥梁的简单示例可能如下所示:代码实战C#代码 (YourCSharpLibrary.dll)public class YourCSharpClass { public string YourMethod(string input) { // ... 处理输入并返回结果 ... return "Processed: " + input; } }C++/CLI代码 (YourBridge.dll)// YourBridge.cpp #include "YourBridge.h" #using "YourCSharpLibrary.dll" as_friend YourCSharpClass^ CreateCSharpObject() { return gcnew YourCSharpClass(); } String^ CallCSharpMethod(YourCSharpClass^ obj, String^ input) { return obj->YourMethod(input); }C++代码 (调用C++/CLI)// YourNativeCppCode.cpp #include <iostream> #include <msclr/marshal_cppstd.h> // 用于字符串转换(如果需要) #using "YourBridge.dll" int main() { YourCSharpClass^ obj = CreateCSharpObject(); String^ csharpString = gcnew String("Hello from C++"); String^ result = CallCSharpMethod(obj, csharpString); // 如果需要,将String^转换为std::string msclr::auto_gcroot<System::String^> resultWrapped(result); std::string stdResult = msclr::interop::marshal_as<std::string>(resultWrapped); std::cout << "Result from C#: " << stdResult << std::endl; return 0; }请注意,这个示例假设你已经有了C#库(YourCSharpLibrary.dll)和C++/CLI桥接库(YourBridge.dll)。C++/CLI桥接库负责在C++和C#之间转换和管理对象。在C++代码中,你通过C++/CLI桥接库来创建C#对象、调用C#方法,并在需要时转换数据类型。注意C# 是托管对象,C++ 是非托管对象,在参数传递的时候有很多限制。例如,不能把C#的托管对象传给C++的线程,这点一定要注意甄别
  • [问题求助] 求助: 鲲鹏(ARM)环境安装.NET Core 8.0+支持
    最近在做信创测试, 在鲲鹏920上分别测试了.net 3.1, 6.0, 8.0, 9.0-preview, 测试结果如下测试方法:dotnet new webapicurl http://localhost:port/whetherforcast 测试的版本:.net core 3.1: 通过.net core 6.0: 通过.net core 8.0: 失败, 提示内存操作失败异常.net core 9.0-preview: 失败, 提示内存操作失败异常请问哪里可以找到技术支持么?
  • [问题求助] 使用华为IPC SDK 进行保存实况数据( IVS_PU_SaveRealData)生成视频文件时的问题
    1、生成文件结果图2、代码实现图(C#语言)在使用华为IPC SDK 进行保存实况数据( IVS_PU_SaveRealData)生成视频文件时的问题,文件夹中生成的是一堆无法播放的mp4 文件如:1、生成文件结果图图。实现代码如上:2、代码实现图。现在我想请教 应该如何设置将实况数据保存生成到一个视频文件(mp4)中并且能播放? 
  • [技术干货] C++轻量级代理模式
    前言    在通过QT工具C++语言开发SmartRoom过程中,发现设计代理模式时,存在代码分散的问题。    C++的代理模式有如下2种:        一、抽象类代理                代理接口(虚函数)全部放在抽象类中,后面通过派生抽象类的方式生成实现代理类,在需要代理时,将派生类对象传递给数据源,数据源后面通过抽象接口,传递数据给派生类对象,派生类对象再将数据转给使用方            二、函数代理模式函数代理,使用通过将函数地址传给数据源,需求方后面通过函数地址接着传回数据给代理函数,代理函数再将数据分发给使用方可以看出抽象类代理是函数代理的升级款,各有各的使用场景:对于只有1个场景返回时:使用函数代理对于多场景返回时:使用抽象类代理优点:1、代理方和使用方完全分离,代理方只处理数据不需要处理业务,业务在抛给对应的使用方缺点:1、使用方和代理方代码过于分散,代码分布广,查找阅读困难2、对于多使用方的场景使用成本高,不利于管理,需专门构建分发模块,通过分发模块将数据分配给对应的使用方3、而且存在野指针、空指针风险4、对于小场景使用成本高,比如:http请求,每个使用http请求的点都需要实现代理函数从下图可以看出缺陷:设计针对这些存在问题,需要一个更轻量的代码模型,根据Objective-C的block思想,串讲如下模式核心:std::function<lambda>作为代理针对常规代理模式存在的问题,这里都可以解决1、代码集中,使用方和代理方在一块,可以相关输出数据,是一对一的关系2、多使用方时,各使用方独立管理自身逻辑,代码完全分割3、代码集中,存在野指针和空指针的风险较小4、成本特别小举例说明:合入请求:cid:link_0原型:独立代理:代理嵌套实现原理lambda捕获外部变量时,会根据捕获方式生成对应的捕获类,并根据捕获类生成对应的对象比如:第一种:拷贝捕获捕获类:第二种 引用捕获        捕获类:使用场景:iOS/Mac中间层问题:iOS和mac的中间层就是通过第一种代理模式,通过数组来保存多使用方,导致请求和返回非一对一关系,经常存在错乱,使用此种模式可解决此问题
  • [生态对接] C#如何使用HttpClient请求华为FusionInsignt平台带认证的ElasticSearch?
    我下载了krb5.conf和user.keytab这两个Kerberos认证所需的文件,C#如何使用HttpClient请求华为FusionInsignt平台带认证的ElasticSearch?
  • [技术干货] 解决DEV C++源码编译后控制台输出中文乱码问题-转载
     在使用DEV C++编译源码通过win10控制台输出时发现中文显示乱码!网上查了很多资料,都不靠谱。后来结合网上的各种不同方式解决了该问题。Embarcadero DEV C++ 6.3亲测有效~  网上有文章说修改注册表HKEY_CURRENT_USER\Console\%SystemRoot%_system32_cmd.exe的CodePage值,这个方法可以解决部分人的问题。但还有一部分人的win10在该路径下是没有cmd相关信息的,只有powershell,这部分用户此方法就没有可行性。  针对上图所示情况,可按如下操作解决乱码问题  切换CMD版本(不论是从新至旧,或旧至新都行)  首先打开DEV C++编译运行源码,然后右键单击CMD顶部空白区域,选择“属性”,勾选“使用旧版控制台”  2. 按如下路径打开注册表  计算机\HKEY_CURRENT_USER\Console  刷新注册表(重要)!!!刷新前如上图,刷新后如下图 会生成DEV C++相关的注册信息  右侧修改HKEY_CURRENT_USER\Console\D:_Dev-Cpp_ConsolePauser.exe 的CodePage值为65001  修改完成后切换CMD为新版(旧版重新编译运行源码后可能仍为乱码)!!! 运行源码显示正常,搞定~~   -----------------------------E------N------D----------------------------- ———————————————— 版权声明:本文为CSDN博主「江枫听雨」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/baidu_41711780/article/details/122090345 
  • [技术干货] 【算法题】1630. 等差子数组-转载
     题目: 如果一个数列由至少两个元素组成,且每两个连续元素之间的差值都相同,那么这个序列就是 等差数列 。更正式地,数列 s 是等差数列,只需要满足:对于每个有效的 i , s[i+1] - s[i] == s[1] - s[0] 都成立。  例如,下面这些都是 等差数列 :  1, 3, 5, 7, 9 7, 7, 7, 7 3, -1, -5, -9 下面的数列 不是等差数列 :  1, 1, 2, 5, 7 给你一个由 n 个整数组成的数组 nums,和两个由 m 个整数组成的数组 l 和 r,后两个数组表示 m 组范围查询,其中第 i 个查询对应范围 [l[i], r[i]] 。所有数组的下标都是 从 0 开始 的。  返回 boolean 元素构成的答案列表 answer 。如果子数组 nums[l[i]], nums[l[i]+1], … , nums[r[i]] 可以 重新排列 形成 等差数列 ,answer[i] 的值就是 true;否则answer[i] 的值就是 false 。  示例 1:  输入:nums = [4,6,5,9,3,7], l = [0,0,2], r = [2,3,5] 输出:[true,false,true] 解释: 第 0 个查询,对应子数组 [4,6,5] 。可以重新排列为等差数列 [6,5,4] 。 第 1 个查询,对应子数组 [4,6,5,9] 。无法重新排列形成等差数列。 第 2 个查询,对应子数组 [5,9,3,7] 。可以重新排列为等差数列 [3,5,7,9] 。 示例 2:  输入:nums = [-12,-9,-3,-12,-6,15,20,-25,-20,-15,-10], l = [0,1,6,4,8,7], r = [4,4,9,7,9,10] 输出:[false,true,false,false,true,true]  提示:  n == nums.length m == l.length m == r.length 2 <= n <= 500 1 <= m <= 500 0 <= l[i] < r[i] < n -105 <= nums[i] <= 105  java代码: class Solution {      public List<Boolean> checkArithmeticSubarrays(int[] nums, int[] l, int[] r) {         List<Boolean> ans = new ArrayList<>();         for (int i = 0; i < r.length; i++) {             int[] arr = Arrays.copyOfRange(nums, l[i], r[i] + 1);             Arrays.sort(arr);             int dif = arr[1] - arr[0];             boolean cur = true;             for (int j = 2; j < arr.length; j++) {                 if (arr[j] - arr[j - 1] != dif) {                     cur = false;                     break;                 }             }             ans.add(cur);         }         return ans;     }  }  ———————————————— 版权声明:本文为CSDN博主「程序猿不脱发2」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/kangbin825/article/details/129739893 
  • 第十四届蓝桥杯三月真题刷题训练——第 20 天-转载
    第 1 题:纸张尺寸 问题描述 在 ISO 国际标准中定义了 A0 纸张的大小为 1189mm × 841mm, 将 A0 纸 沿长边对折后为 A1 纸, 大小为 841mm × 594mm, 在对折的过程中长度直接取 下整 (实际裁剪时可能有损耗)。将 A1 纸沿长边对折后为 A2 纸, 依此类推。  输入纸张的名称, 请输出纸张的大小。  输入格式 输入一行包含一个字符串表示纸张的名称, 该名称一定是 A0、A1、A2、 A3、A4、A5、A6、A7、A8、A9 之一。  输出格式 输出两行,每行包含一个整数,依次表示长边和短边的长度。  样例输入1 A0 样例输出1 1189 841 样例输入 2 A1 样例输出 2 841 594 运行限制 最大运行时间:1s 最大运行内存: 512M 代码: package 第十四届蓝桥杯三月真题刷题训练.day20;   import java.io.*;   /**  * @author yx  * @date 2023-03-23 13:16  */ public class 纸张尺寸 {     static PrintWriter out =new PrintWriter(System.out);     static BufferedReader ins=new BufferedReader(new InputStreamReader(System.in));     static StreamTokenizer in=new StreamTokenizer(ins);     /**      * 输入      * in.nextToken()      * int a= (int)in.nval;      *      * 输出      * out.print();      * out.flush();      *      * 读文件:      * BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("C:\\Users\\yx\\Desktop\\primes.txt")));      * String s = br.readLine();s读取每一行数据      * if (s == null)break;读取文件终止的语句      **/     public static void main(String[] args) throws IOException {         String[] spl=ins.readLine().split("");         int n=Integer.parseInt(spl[1]);         int length_long=1189;         int length_short=841;         int max=0;         for (int i = 0; i < n; i++) {             max=Math.max(length_long,length_short);             if(length_long==max){                 length_long/=2;             }else {                 length_short/=2;             }         }         out.println(Math.max(length_long,length_short));         out.println(Math.min(length_long,length_short));         out.flush();     } }  解析: 这题签到题,直接循环就完事儿,每次更新一下最长最小边即可,最后输出Max,Min  第 2 题:最大数字  第 3 题:全排列的价值_递推公式 问题描述 对于一个排列 A=(a1,a2,⋯ ,an)A=(a1​,a2​,⋯,an​), 定义价值 cici​ 为 a1a1​ 至 ai−1ai−1​ 中小于 aiai​ 的数 的个数, 即 ci=∣{aj∣j<i,aj<ai}∣。 ci​=∣{aj​∣j<i,aj​<ai​}∣。 ​  定义 A 的价值为 ∑i=1nci。  给定 nn, 求 1 至 n 的全排列中所有排列的价值之和。  输入格式 输入一行包含一个整数 nn 。  输出格式 输出一行包含一个整数表示答案, 由于所有排列的价值之和可能很大, 请 输出这个数除以 998244353 的余数。  样例输入 1 3 样例输出 1 9 样例输入 2 2022 样例输出 2 593300958 样例说明 1 至 3 构成的所有排列的价值如下: (1,2,3):0+1+2=3(1,3,2):0+1+1=2(2,1,3):0+0+2=2(2,3,1):0+1+0=1(3,1,2):0+0+1=1(3,2,1):0+0+0=0​故总和为 3+2+2+1+1=9 。  x测用例规模与约定 对于 40% 的评测用例, n≤20;  对于 70% 的评测用例, n≤5000;  对于所有评测用例, 2≤n≤10^6 。  运行限制 最大运行时间:1s 最大运行内存: 512M 代码: package 第十四届蓝桥杯三月真题刷题训练.day20;   import java.io.*; import java.util.Scanner;   /**  * @author yx  * @date 2023-03-23 14:02  */ public class 全排列的价值 {     static PrintWriter out =new PrintWriter(System.out);     static BufferedReader ins=new BufferedReader(new InputStreamReader(System.in));     static StreamTokenizer in=new StreamTokenizer(ins);     /**      * 输入      * in.nextToken()      * int a= (int)in.nval;      *      * 输出      * out.print();      * out.flush();      *      * 读文件:      * BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("C:\\Users\\yx\\Desktop\\primes.txt")));      * String s = br.readLine();s读取每一行数据      * if (s == null)break;读取文件终止的语句      **/     public static void main(String[] args) throws IOException {         Scanner scanner = new Scanner(System.in);         long n=scanner.nextLong();         /**          * 递推方程:          * f(x)=f(n-1)*n+(n*(n-1)/2)*(n-1)!          *          * 初始:          * f1=0;          * f2=1;          * f3=9;          */         if(n==1){             System.out.println(0);         }else if(n==2){             System.out.println(1);         }else if(n==3){             System.out.println(9);         }else {             //f从f3开始算             long f=9;             //阶乘从(3-1)!开始算             long jieCheng=2;             for (long i = 4; i <= n; i++) {                 jieCheng=jieCheng*(i-1)™8244353;                 f=(f*i™8244353+((i*(i-1))/2™8244353)*jieCheng)™8244353;             }             System.out.println(f);         }     } }  解析: 第十三届蓝桥杯省赛Java A 组 F 题、Python A 组 G 题、Python B 组 G题——全排列的价值 (AC)_蓝桥杯全排列的价值_执 梗的博客-CSDN博客 第十三届蓝桥杯省赛Java A 组 F 题、Python A 组 G 题、Python B 组 G题——全排列的价值 (AC) https://blog.csdn.net/m0_57487901/article/details/127897935?spm=1001.2014.3001.5502 借鉴我的好兄弟--“执梗”的题解,讲的太™好了,醍醐灌顶,看完了不会可以来找我!! ———————————————— 版权声明:本文为CSDN博主「小羊不会飞」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/m0_55858611/article/details/129728734 
  • [技术干货] 经典七大比较排序算法 ·上-转载
     1 选择排序 1.1 算法思想 选择排序,每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完 。 大致操作步骤如下:  第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置; 然后再从剩余的未排序元素中寻找到最小(大)元素,然后放到已排序的序列的末尾; 重复第二步操作,直到全部待排序的数据元素的个数为零。  选择排序视频演示  1.2 代码实现 选择排序还是很好理解的。但这里的实现会对直接选择排序做一些小的改进。 既然直接选择排序遍历一遍可以找出最小的数据,那遍历一遍同样可以找出最大的元素。这样每遍历一次,待排序数据元素的起始位置就变成最小的了,结束位置就变成最大的了。 参考代码如下:  //n - 数据个数 void SelectSort(int* a, int n) {     int begin = 0; //待排序数据起始位置下标     int end = n - 1;//待排序数据结束位置下标     int minIndex = 0;//记录最小数据的下标     int maxIndex = 0;//记录最大数据的下标          //待排序数据大于1时进入循环     while (begin < end)     {         //假设一开始最小元素和最大元素下标都是第一个待排序数据下标         minIndex = begin;         maxIndex = begin;                  //遍历一遍         for (int i = begin + 1; i <= end; ++i)         {             //选出最小元素下标             if (a[i] < a[minIndex])             {                 minIndex = i;             }                          //选出最大元素下标             if (a[i] > a[maxIndex])             {                 maxIndex = i;             }         }                  //将最小元素和待排序序列的起始位置交换         Swap(&a[begin], &a[minIndex]);                  //将最大元素和待排序序列的结束位置交换         Swap(&a[end], &a[maxIndex]);                  //缩小待排序区间,重复上述过程         ++begin;         --end;     } } 很多老铁在敲完上面代码之后,觉得已经没有问题了。  但实际上,像遇到上面情况。如果[maxIndex]指向的是[begin]中存放的内容,[minIndex]的内容先和[begin]交换之后,[maxIndex]指向的就不在是待排序序列中的最大元素了。需要做相应的修改。          Swap(&a[begin], &a[minIndex]);                  //如果begin和maxIndex重叠,那么修正maxInedx         if (begin == maxIndex)         {             maxIndex = minIndex;         }         Swap(&a[end], &a[maxIndex]); 1.3 选择排序特性 选择排序作为一种简单直观的排序算法,虽然好理解,但效率并不高。 无论是在面对什么样的排序序列,时间复杂度都是稳定的O ( n 2 ) O(n^2)O(n  2  )量级,实际中很少使用。  2 冒泡排序 2.1 算法思想 冒泡排序作为一种交换排序,它会重复地走访要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到不再需要交换,也就是说该数列已经排序完成。 大致操作步骤如下:  比较相邻的两个元素。如果第一个和第二个大元素的顺序不符合要求,就交换他们两个。 针对所有的元素重复以上的步骤,除了最后一个。 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。  冒泡排序视频演示  2.2 代码实现 //n - 数据个数 void BubbleSort(int* a, int n) {     for (int i = 0; i < n - 1; ++i)     {         for (int j = 0; j < n - 1 - i; ++j)         {             //排升序             if (a[j] > a[j + 1])             {                 Swap(&a[j], &a[j + 1]);             }         }     } } 冒泡排序还有一种优化的方法,就是立一个 flag,当在一趟序列遍历中元素没有发生交换,则证明该序列已经有序,不需要再继续进行冒泡排序了。  void BubbleSort(int* a, int n) {     for (int i = 0; i < n - 1; ++i)     {         int flag = 1;         for (int j = 0; j < n - 1 - i; ++j)         {             if (a[j] > a[j + 1])             {                 flag = 0;                 Swap(&a[j], &a[j + 1]);             }         }         if (1 == flag)         {             break;         }     } } 但这种改进优化在实际上对于提升性能来说并没有什么作用并不是很大。  2.3 冒泡排序特性 冒泡排序也是一种非常简单直观的排序。 在最好的情况下(序列接近有序),冒泡排序的时间复杂度是O ( n ) O(n)O(n)量级的; 在最坏的情况下(序列逆序),冒泡排序的时间复杂度是O ( n 2 ) O(n^2)O(n  2  )量级的。  3 堆排序 关于堆排序的算法思想和代码实现相关内容,可以参考阿顺的堆结构的两个应用这篇博客,里面有比较详细的介绍。这里就不在赘述了。  3.1 堆排序特性: 堆排序使用堆的结构来选数,效率就高了很多。 堆排序的平均时间复杂度是O ( n ∗ l o g 2 n ) O(n*log_2n)O(n∗log  2 ​  n)量级的。  4 快速排序 4.1 算法思想 快速排序是Hoare提出的一种二叉树结构的交换排序方法。  其基本思想为: 任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两个子序列,其中左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后对左右子序列重复该过程,直到所有元素都排列在相应位置上为止。 算法步骤:  从序列中挑出一个元素作为基准 排序数列,将所有比基准值小的元素摆放在基准前面,所有比基准值大的元素摆放在基准后面(相同的数可以在任一边)。在这个分区排完之后,该基准就处于数列的中间位置 递归地把小于基准值元素的子数列和大于基准值元素的子数列排序  快速排序视频演示  4.2 代码实现 快速排序在发展的过程中涌现出了很多种不同的版本,但核心思想是不会变的。这里会介绍3种常见版本,以供大家参考。  //a - 待排序序列 //begin - 待排序序列起始位置 //end - 待排序序列结束位置 void QuickSort(int* a, int begin, int end) {     //序列只有一个值或没有值就不再排序     if (begin >= end)     {         return;     }          //对区间进行快速排序,并返回排序后的基准值位置     int keyIndex = QuickPartSort(a, begin, end);          //[begin, keyIndex - 1] keyIndex [keyIndex + 1, end]     //左区间排序     QuickSort(a, begin, keyIndex - 1);     //右区间排序     QuickSort(a, keyIndex + 1, end);      } 上面代码就是快速排序实现的主框架,QuickPartSort函数有不同的实现方式。 其实从主框架上可以看出快速排序的递归实现和二叉树的前序遍历还是很像的。  hoare版本 //以排升序为例 int QuickPartSort1(int* a, int begin, int end) {     int left = begin;     int right = end;          //取序列第1个数据做基准值     int keyIndex = left;     while (left < right)     {         //右边先走,找小         while (left < right && a[right] >= a[keyIndex])         {             --right;         }          //左边再走,找大         while (left < right && a[left] <= a[keyIndex])         {             ++left;         }                  //左大右小-交换         Swap(&a[left], &a[right]);     }          //left和right相遇,此时将基准值和相遇值交换     Swap(&a[keyIndex], &a[left]);     //基准值下标跟着改变     keyIndex = left;      return keyIndex; } 观察hoare版本的代码发现,选择的是最左边的值做基准值。此时,却是让右边的索引先走,那能不能让左边的值先走呢?又为什么选择右边的值先走呢? 答案是左边的值先走也可以,但是处理起来细节上更复杂一些。 选择右边先走的原因是为了保证相遇位置的值,比基准值小(或者就是基准位置的值)。 右边先走的情况无非就是:  right先走,right遇到比基准值小的,right停下来,让left走,left去遇到了right。 这种情况相遇位置就是right停下来的位置,right停的位置也就是比基准值要小的位置。 right先走,right没有遇到比基准值要小的值,right去遇到了left。 这种情况下,相遇位置是left上一轮停下来的位置,该位置要么是基准值的位置,要么就是比基准值小的位置。 这时,反过来想一下,如果选最左边为基准值,left先走的话,相遇位置的值就是大于基准值的,这样相遇位置的值就不能和基准值交换,这里需要做一些处理,来让基准值交换到合适的位置去,最终徒劳增加了麻烦。 所以,一般建议是: 左边做基准值,让右边先走; 右边做基准值,让左边先走。 挖坑版本 在面对上面hoare版本比较不好理解的左边做基准值,右边先走的问题时,挖坑法的版本能让快速排序更好理解一点点。 int QuickPartSort2(int* a, int begin, int end) {     //最左边做基准值     int key = a[begin];     //坑位给到基准值位置     int pitIndex = begin;      int left = begin;     int right = end;          while (left < right)     {         //右边找小,填到左边的坑里面去。这个位置形成新的坑。         while (left < right && a[right] >= key)         {             --right;         }         a[pitIndex] = a[right];         pitIndex = right;          //左边找大,填到右边的坑里面去。这个位置形成新的坑。         while (left < right && a[right] <= key)         {             ++left;         }         a[pitIndex] = a[left];         pitIndex = left;     }     //坑位再填上基准值     a[pitIndex] = key;      return pitIndex; } 因为坑位的存在,左边有坑,右边就走,然后填坑;右边有坑,左边就走,然后填坑。这个过程就更好理解了。但是挖坑法在算法性能上相比hoare版本并没有什么大的改进。 3. 前后指针版本 前后指针的使用和前两个版本的排序方式就有较大的区别了。  int QuickPartSort3(int* a, int begin, int end) {     //基准值选择     int keyIndex = begin;     int prev = begin;//前指针     int cur = begin + 1;//后指针      while (cur <= end)     {         //cur位置的值小于keyIndex位置的值         if (a[cur] < a[keyIndex])         {             ++prev;             Swap(&a[prev], &a[cur]);         }          ++cur;     }          Swap(&a[prev], &a[keyIndex]);     keyIndex = prev;     return keyIndex; } 前后指针方法中两个指针都是从左向右的方向在走,直到cur走完整个序列。 但是在走的过程中,prev的位置+1后,如果和cur位置相等,因为指向同一个位置,就不需要交换了,代码进一步优化如下:  if (a[cur] < a[keyIndex] && ++prev != cur) {     Swap(&a[prev], &a[cur]); } 前后指针版本不仅好理解,而且代码也很简洁,是几个版本中最推荐的了。 但是,以上方法选基准值还是存在一定问题的。 鉴于以上几个版本都选择的是最左边作为基准值,如果是排已经有序的序列的话,如下图所示:  序列有多少的数据就递归多少次,很容易栈溢出。 所以,为了让每次选择基准值避免选择到序列中的最值,有的地方给出用随机数的方法选择基准值,但这里主要介绍三数取中选择基准值的方法。 所谓三数取中,不过是在序列的起止位置,中间位置,末位位置选择这三个数出来,通过比较,找到不是最大也不是最小的一个数,让这个数做基准值。  int GetMidIndex(int* a, int begin, int end) {     int mid = begin + (end - begin) / 2;      if (a[begin] < a[mid])     {         if (a[mid] < a[end])         {             return mid;         }         else if (a[begin] < a[end])         {             return end;         }         else         {             return begin;         }     }     else//begin>=mid     {         if (a[mid] > a[end])         {             return mid;         }         else if (a[begin] < a[end])         {             return begin;         }         else         {             return end;         }     } } 找到基准值之后,可以将基准值和序列的起始位置进行一个交换,这样就和之前排序的代码一样仍是选择序列的起始位置做基准值了。代码也不需要更多的修改了。  //三数取中优化 int mid = GetMidIndex(a, begin, end); Swap(&a[keyIndex], &a[mid]); 1 2 3 因为三数取中的优化,使的每次递归左右子序列更加的二分,代码效率自然就得到了提高。 当然,我们知道递归需要是创建销毁和销毁栈帧的(具体参考阿顺的这篇博文你也能看懂的的函数栈帧创建与销毁) 因为快速排序的递归和二叉树前序递归很像,如果快速递归的划分接近二分的话,就会像满二叉树的遍历一样,最后一层的数量(2 h − 1 2^{h-1}2  h−1  )会是整棵树数量(2 h − 1 2^h-12  h  −1)的一半,也就是快速排序最后一层的递归次数是整个过程递归次数的一半。在面对小数据量时,还要递归这么多次,进行栈帧的开销,确实会影响到程序的效率。 所以,可以对小区间进行优化:当递归划分小区间,区间比较小的时候,就不再递归划分去排序这个小区间。而是可以考虑直接用其它排序对小区间进行处理。 这里就给出对小区间元素小于10时的插入排序处理。  if (end - begin > 10) {     //int keyIndex = PartSort1(a, begin, end);     //int keyIndex = PartSort2(a, begin, end);     int keyIndex = QuickPartSort3(a, begin, end);      QuickSort(a, begin, keyIndex - 1);     QuickSort(a, keyIndex + 1, end); } else {     InsertSort(a + begin, end - begin + 1); } 通过计算可以得出,以上代码在采用小区间优化后,函数调用次数可以减少80 % 80\€%左右,这种提升还是很明显的。 以上都是对快速排序的递归版本的介绍。但是递归有一个死穴就是递归太深,栈溢出,程序会崩溃。所以,也要能够对快速排序进行非递归的实现。 可以借助栈数据结构或者队列数据结构进行非递归实现。这里就选择栈数据结构来进行实现。借助栈来实现的运行逻辑是可以和递归实现的递归逻辑达成一致的。 因为是C语言实现,所以对于栈数据结构的需要可以参考阿顺的这篇博文顺序表实现栈(C语言)  void QuickSortNonR(int* a, int begin, int end) {     Sk stack;     StackInit(&stack);          StackPush(&stack, end);     StackPush(&stack, begin);      while (!StackEmpty(&stack))     {         int left = StackTop(&stack);         StackPop(&stack);         int right = StackTop(&stack);         StackPop(&stack);          int keyIndex = QuickPartSort3(a, left, right);         // [left, keyIndex-1] keyIndex [keyIndex+1, right]          if (keyIndex + 1 < right)         {             StackPush(&stack, right);             StackPush(&stack, keyIndex + 1);         }          if (left < keyIndex - 1)         {             StackPush(&stack, keyIndex - 1);             StackPush(&stack, left);         }     }      StackDestroy(&stack); } 代码结合画图就很容易理解快速排序的非递归了。  4.3 快速排序特性 快速排序,一听到这个名字你就知道它存在的意义l。而且快速排序整体的综合性能和使用场景都是比较好的,所以也才敢叫快速排序。  快速排序的一次划分算法从两头交替搜索,直到left和right重合,因此其时间复杂度是O ( n ) O(n)O(n);而整个快速排序算法的时间复杂度与划分的趟数有关。 理想的情况是,每次划分所选择的中间数恰好将当前序列几乎等分,经过l o g 2 n log_2nlog  2 ​  n趟划分,便可得到长度为1的子表。这样,整个算法的时间复杂度为O ( n ∗ l o g 2 n ) O(n*log_2n)O(n∗log  2 ​  n)。 最坏的情况是,每次所选的中间数是当前序列中的最大或最小元素,这使得每次划分所得的子表中一个为空表,另一子表的长度为原表的长度-1。这样,长度为n的数据表的快速排序需要经过n趟划分,使得整个排序算法的时间复杂度为O ( n 2 ) O(n2)O(n2)。 从空间性能上看,尽管快速排序只需要一个元素的辅助空间,但快速排序需要一个栈空间来实现递归。最好的情况下,即快速排序的每一趟排序都将元素序列均匀地分割成长度相近的两个子表,所需栈的最大深度为l o g 2 ( n + 1 ) log_2{(n+1)}log  2 ​  (n+1);但最坏的情况下,栈的最大深度为n。  5 归并排序 5.1 算法思想 归并排序是建立在归并操作上的一种有效的排序算法,该算法是采用分治法的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序。若将两个有序表合并成一个有序表,称为二路归并。 算法步骤:  申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列; 设定两个指针,最初位置分别指向两个已经排序序列的起始位置; 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置; 重复步骤 3 直到某一指针到达序列尾; 将另一序列剩下的所有元素直接复制到合并序列尾。  归并排序视频演示  5.2 代码实现 作为一种典型的分而治之思想的算法应用,归并排序的实现有两种方法:  自上而下的递归; //n - 数据个数 void MergeSort(int* a, int n) {     //开辟归并空间     int* tmp = (int*)malloc(n * sizeof(int));     assert(tmp != NULL);          //归并排序     _MergeSort(a, 0, n - 1, tmp);      free(tmp); } void _MergeSort(int* a, int begin, int end, int* tmp) {     //递归区间不足1个数据就不再递归     //因为1个数据即有序     if (begin >= end)     {         return;     }          int mid = begin + (end - begin) / 2;     // [begin, mid][mid+1, end] 分治递归,让子区间有序      //左区间递归     _MergeSort(a, begin, mid, tmp);     //又去见递归     _MergeSort(a, mid+1, end, tmp);      //归并过程     int begin1 = begin;     int end1 = mid;     int begin2 = mid + 1;     int end2 = end;     int i = begin1;     while (begin1 <= end1 && begin2 <= end2)     {         if (a[begin1] < a[begin2])         {             tmp[i++] = a[begin1++];         }         else         {             tmp[i++] = a[begin2++];         }     }      while (begin1 <= end1)     {         tmp[i++] = a[begin1++];     }      while (begin2 <= end2)     {         tmp[i++] = a[begin2++];     }      //把归并数据拷贝回原数组     memcpy(a + begin, tmp + begin, (end - begin + 1) * sizeof(int)); } 自下而上的迭代。  所谓自下而上的迭代,不过是最开始从1个和1个开始归并(因为1个数据即有序)。 之后每次归并数据都扩2倍,直到最后一组的数据已经大于等于整个序列的数据为止就完成归并了。可以用循环来解决。 void MergeSortNonR(int* a, int n) {     int* tmp = (int*)malloc(n * sizeof(int));     assert(tmp != NULL);          //最开始,一组只有一个数据     int gap = 1;     //一组的数据要小于序列长度     while (gap < n)     {         for (int i = 0; i < n; i += 2 * gap)         {             //[i,i+gap-1]和[i+gap, i+2*gap-1]区间的数据进行归并             int begin1 = i;             int end1 = i + gap - 1;             int begin2 = i + gap;             int end2 = i + 2 * gap - 1;              int j = begin1;             while (begin1 <= end1 && begin2 <= end2)             {                 if (a[begin1] < a[begin2])                 {                     tmp[j++] = a[begin1++];                 }                 else                 {                     tmp[j++] = a[begin2++];                 }             }              while (begin1 <= end1)             {                 tmp[j++] = a[begin1++];             }              while (begin2 <= end2)             {                 tmp[j++] = a[begin2++];             }         }         //一趟归并结束进行拷贝         memcpy(a, tmp, n * sizeof(int));                  //一组数据扩2倍         gap *= 2;     }          free(tmp); } 但是上面代码有一个致命的bug就是,只能适用于2 n 2^n2  n  个数据的归并。否则就存在越界的错误。 所以需要对归并区间进行界线的判断和修正。 因为begin1=i不会越界,而end1 begin2 end2都可能越界。 所以存在三种情况下的修正:  //修正边界 if (end1 >= n) {     end1 = n - 1;     begin2 = n;     end2 = n - 1; } else if (begin2 >= n) {     begin2 = n;     end2 = n - 1; } else if (end2 >= n) {     end2 = n - 1; } 上述是修正的一种方法。还可以采用更省事的策略:在遇到越界的区间时,就不再归并了。  if (end1 >= n || begin1 >= n) {     break; } else if (end2 >= n) {     end2 = n - 1; } 但这种方法,需要每归并一次,就拷贝一次,而不是等到一趟归并完在拷贝。memcpy函数应该放在for循环之内。  5.3 归并排序特性 归并排序的时间复杂度是稳定的O ( n ∗ l o g 2 n ) O(n*log_2n)O(n∗log  2 ​  n),而因为要开辟额外的归并数组,所以空间复杂度是O ( n ) O(n)O(n)。 归并排序速度仅次于快速排序,一般用于对总体无序,但是各子项相对有序的数列排序。 归并排序的比较次数小于快速排序的比较次数,移动次数一般多于快速排序的移动次数。 虽然归并排序比较占用内存,但却是一种效率高且稳定的算法。 ———————————————— 版权声明:本文为CSDN博主「阿阿阿顺Yaya」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/weixin_62172209/article/details/129620963 
  • [技术干货] 算法leetcode|30. 串联所有单词的子串(rust重拳出击)-转载
     30. 串联所有单词的子串: 给定一个字符串 s 和一个字符串数组 words。 words 中所有字符串 长度相同。  s 中的 串联子串 是指一个包含 words 中所有字符串以任意顺序排列连接起来的子串。  例如,如果 words = ["ab","cd","ef"], 那么 "abcdef", "abefcd","cdabef", "cdefab","efabcd", 和 "efcdab" 都是串联子串。 "acdbef" 不是串联子串,因为他不是任何 words 排列的连接。 返回所有串联字串在 s 中的开始索引。你可以以 任意顺序 返回答案。  样例 1: 输入:     s = "barfoothefoobarman", words = ["foo","bar"]      输出:     [0,9]      解释:     因为 words.length == 2 同时 words[i].length == 3,连接的子字符串的长度必须为 6。     子串 "barfoo" 开始位置是 0。它是 words 中以 ["bar","foo"] 顺序排列的连接。     子串 "foobar" 开始位置是 9。它是 words 中以 ["foo","bar"] 顺序排列的连接。     输出顺序无关紧要。返回 [9,0] 也是可以的。  样例 2: 输入:     s = "wordgoodgoodgoodbestword", words = ["word","good","best","word"]      输出:     []      解释:     因为 words.length == 4 并且 words[i].length == 4,所以串联子串的长度必须为 16。     s 中没有子串长度为 16 并且等于 words 的任何顺序排列的连接。     所以我们返回一个空数组。 样例 3: 输入:     s = "barfoofoobarthefoobarman", words = ["bar","foo","the"]  输出:     [6,9,12]      解释:     因为 words.length == 3 并且 words[i].length == 3,所以串联子串的长度必须为 9。     子串 "foobarthe" 开始位置是 6。它是 words 中以 ["foo","bar","the"] 顺序排列的连接。     子串 "barthefoo" 开始位置是 9。它是 words 中以 ["bar","the","foo"] 顺序排列的连接。     子串 "thefoobar" 开始位置是 12。它是 words 中以 ["the","foo","bar"] 顺序排列的连接。 提示: 1 <= s.length <= 104 1 <= words.length <= 5000 1 <= words[i].length <= 30 words[i] 和 s 由小写英文字母组成 分析: 面对这道算法题目,二当家的陷入了沉思。 由于题干里罗列了所有单词可能的串联出的子串,所以可能理所当然的考虑罗列所有子串,然后再去字符串中查看是否存在,但是这样太慢了,肯定是不对的。 由于题干中说所有字符串 长度相同,考虑用单词计数,滑动窗口解决。 需要注意的一些点是,首先窗口滑动的大小应该是单词的长度才对,另外,由于窗口的大小是单词的长度,那么起始下标就需要考虑偏移。 题解: rust impl Solution {     pub fn find_substring(s: String, words: Vec<String>) -> Vec<i32> {         let mut ans = Vec::new();         let (m, n, ls) = (words.len(), words[0].len(), s.len());         for i in 0..n {             if i + m * n > ls {                 break;             }             let mut differ = std::collections::HashMap::new();             for j in 0..m {                 let word = &s[i + j * n..i + (j + 1) * n];                 *differ.entry(word).or_insert(0) += 1;             }             for word in words.iter() {                 let count = differ.entry(word).or_insert(0);                 if *count == 1 {                     differ.remove(word as &str);                 } else {                     *count -= 1;                 }             }             for start in (i..ls - m * n + 1).step_by(n) {                 if start != i {                     let mut word = &s[start + (m - 1) * n..start + m * n];                     let mut count = differ.entry(word).or_insert(0);                     if *count == -1 {                         differ.remove(word);                     } else {                         *count += 1;                     }                     word = &s[start - n..start];                     count = differ.entry(word).or_insert(0);                     if *count == 1 {                         differ.remove(word);                     } else {                         *count -= 1;                     }                 }                 if differ.is_empty() {                     ans.push(start as i32);                 }             }         }         return ans;     } } go func findSubstring(s string, words []string) (ans []int) {     m, n, ls := len(words), len(words[0]), len(s)     for i := 0; i < n && i+m*n <= ls; i++ {         differ := map[string]int{}         for j := 0; j < m; j++ {             differ[s[i+j*n:i+(j+1)*n]]++         }         for _, word := range words {             if differ[word] == 1 {                 delete(differ, word)             } else {                 differ[word]--             }         }         for start := i; start < ls-m*n+1; start += n {             if start != i {                 word := s[start+(m-1)*n : start+m*n]                 if differ[word] == -1 {                     delete(differ, word)                 } else {                     differ[word]++                 }                 word = s[start-n : start]                 if differ[word] == 1 {                     delete(differ, word)                 } else {                     differ[word]--                 }             }             if len(differ) == 0 {                 ans = append(ans, start)             }         }     }     return } c++ class Solution { public:     vector<int> findSubstring(string s, vector<string>& words) {         vector<int> ans;         int m = words.size(), n = words[0].size(), ls = s.size();         for (int i = 0; i < n && i + m * n <= ls; ++i) {             unordered_map<string, int> differ;             for (int j = 0; j < m; ++j) {                 ++differ[s.substr(i + j * n, n)];             }             for (string &word: words) {                 if (--differ[word] == 0) {                     differ.erase(word);                 }             }             for (int start = i; start < ls - m * n + 1; start += n) {                 if (start != i) {                     string word = s.substr(start + (m - 1) * n, n);                     if (++differ[word] == 0) {                         differ.erase(word);                     }                     word = s.substr(start - n, n);                     if (--differ[word] == 0) {                         differ.erase(word);                     }                 }                 if (differ.empty()) {                     ans.emplace_back(start);                 }             }         }         return ans;     } }; python class Solution:     def findSubstring(self, s: str, words: List[str]) -> List[int]:         ans = []         m, n, ls = len(words), len(words[0]), len(s)         for i in range(n):             if i + m * n > ls:                 break             differ = Counter()             for j in range(m):                 word = s[i + j * n: i + (j + 1) * n]                 differ[word] += 1             for word in words:                 if differ[word] == 1:                     del differ[word]                 else:                     differ[word] -= 1             for start in range(i, ls - m * n + 1, n):                 if start != i:                     word = s[start + (m - 1) * n: start + m * n]                     if differ[word] == -1:                         del differ[word]                     else:                         differ[word] += 1                     word = s[start - n: start]                     if differ[word] == 1:                         del differ[word]                     else:                         differ[word] -= 1                 if len(differ) == 0:                     ans.append(start)         return ans java class Solution {     public List<Integer> findSubstring(String s, String[] words) {         List<Integer> ans = new ArrayList<Integer>();         int           m   = words.length, n = words[0].length(), ls = s.length();         for (int i = 0; i < n && i + m * n <= ls; ++i) {             Map<String, Integer> differ = new HashMap<String, Integer>();             for (int j = 0; j < m; ++j) {                 String word = s.substring(i + j * n, i + (j + 1) * n);                 differ.put(word, differ.getOrDefault(word, 0) + 1);             }             for (String word : words) {                 differ.put(word, differ.getOrDefault(word, 0) - 1);                 if (differ.get(word) == 0) {                     differ.remove(word);                 }             }             for (int start = i; start < ls - m * n + 1; start += n) {                 if (start != i) {                     String word = s.substring(start + (m - 1) * n, start + m * n);                     differ.put(word, differ.getOrDefault(word, 0) + 1);                     if (differ.get(word) == 0) {                         differ.remove(word);                     }                     word = s.substring(start - n, start);                     differ.put(word, differ.getOrDefault(word, 0) - 1);                     if (differ.get(word) == 0) {                         differ.remove(word);                     }                 }                 if (differ.isEmpty()) {                     ans.add(start);                 }             }         }         return ans;     } }  ———————————————— 版权声明:本文为CSDN博主「二当家的白帽子」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/leyi520/article/details/128422146 
  • [技术干货] 菜鸟刷题Day5-转载
     一.一维数组的动态和:1480. 一维数组的动态和 - 力扣(LeetCode) 描述  给你一个数组 nums 。数组「动态和」的计算公式为:runningSum[i] = sum(nums[0]…nums[i]) 。  请返回 nums 的动态和。  示例:  输入:nums = [1,2,3,4] 输出:[1,3,6,10] 解释:动态和计算过程为 [1, 1+2, 1+2+3, 1+2+3+4] 。  解题思路  1.通过观察示例可以发现,其实runningSum[0]和nums[0]相等,runningSum[1]=runningSum[0]+nums[1];所以我们可以得到这样一个结论:当 i > 0时:runningSum[i]=runningSum[i-1]+nums[i];  我可以新开辟一个数组(大小和nums一样大),将累加的结果存放到这个新开的数组中,再返回这个新开辟的数组。  int* runningSum(int* nums, int numsSize, int* returnSize) {         int*tmp=(int*)malloc(sizeof(int)*numsSize);     tmp[0]=nums[0];          for(int i=1;i<numsSize;i++)     {         tmp[i]=tmp[i-1]+nums[i];     }     *returnSize=numsSize;          return tmp; } 2.直接在原地改变,除了第一项不用改变以外,后面的每一项元素都是前面元素累加的结果。  int* runningSum(int* nums, int numsSize, int* returnSize) {     for(int i=1;i<numSize;i++)     {          nums[i]+=nums[i-1];     }          *returnSize=numsSize;          return nums; } 二.搜索插入位置:35. 搜索插入位置 - 力扣(LeetCode) 描述  给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。  请必须使用时间复杂度为 O(log n) 的算法。  解题思路  看到说时间复杂度位O(log n)就让人想起二分查找算法,二分查找虽然强大,但是有一个很大的缺陷就是要求被查找的数据必须要有序。正好题目说了是一个排序数组,那还不二分走起  int searchInsert(int* nums, int numsSize, int target) {     int begin=0;     int end=numsSize-1;     int mid=0;          while(begin<=end)     {         mid=(begin+end)/2;                  if(nums[mid]<target)         {             begin=mid+1;         }         else if(nums[mid]>target)         {             end=mid-1;         }         else//相等         {             return mid;         }               }    return begin; 关于最后的返回值:位于begin左边的数一定小于目标值,而在end右边的数一定是大于end的,也就是说目标值要么在begin和end的中间,要么就在begin和end之间的某个位置插入,而最后的结束条件是begin>end,也就是说这中间只有begin是一直在改变的,因此如果最后在数组中找不到这个元素,那么它的插位置一定是begin位置  三.搜索旋转排序数组:33. 搜索旋转排序数组 - 力扣(LeetCode) 描述  整数数组 nums 按升序排列,数组中的值 互不相同 。  在传递给函数之前,nums 在预先未知的某个下标 k(0 <= k < nums.length)上进行了 旋转,使数组变为 [nums[k], nums[k+1], …, nums[n-1], nums[0], nums[1], …, nums[k-1]](下标 从 0 开始 计数)。例如, [0,1,2,4,5,6,7] 在下标 3 处经旋转后可能变为 [4,5,6,7,0,1,2] 。  给你 旋转后 的数组 nums 和一个整数 target ,如果 nums 中存在这个目标值 target ,则返回它的下标,否则返回 -1 。  你必须设计一个时间复杂度为 O(log n) 的算法解决此问题。  解题思路  首先映入眼帘的还是O(log n),那么用二分查找吗?一看是升序排序数组,但是这里又说了,会在某个位置旋转一下。好像不能用二分  但是(我知道你在等但是),在k位置旋转以后,仍然是局部有序的。如果中间的数小于最右边的数,则右半段是有序的,若中间数大于最右边数,则左半段是有序的,我们只要在有序的半段里用首尾两个数组来判断目标值是否在这一区域内,这样就可以确定保留哪一半了  这里只要通过控制下标就可以实现在一个数组内分割出两个数组的目的  int search(int* nums, int numsSize, int target) {     //核心在于只是局部有序      int begin=0,end=numsSize-1;     while(begin<=end)     {         int mid=(begin+end)/2;          if(nums[mid]==target)             return mid;          //二分查找只能在有序数列中进行,所以要判断有序范围,一定是局部有序         if(nums[mid]<nums[end])//说明右边数组有序         {             //判断一下目标值是否在右边数组中,既然在右边数组,那么就要重新调整一下mid的值             if(nums[mid]<target&&nums[end]>=target)                  begin=mid+1;             else                 end=mid-1;               }         else         {             //左边数组有序,还要判断一下是否在左边数组中             if(nums[mid]>target&&nums[begin]<=target)                 end=mid-1;             else                 begin=mid+1;         }      }     //到这里就是找不到     return -1; } 四.二进制链表转整数:1290. 二进制链表转整数 - 力扣(LeetCode) 描述  给你一个单链表的引用结点 head。链表中每个结点的值不是 0 就是 1。已知此链表是一个整数数字的二进制表示形式。请你返回该链表所表示数字的 十进制值 链表不为空; 链表的结点总数不超过 30; 每个结点的值不是0就是 1  示例:  输入:head = [1,0,1] 输出:5 解释:二进制数 (101) 转化为十进制数 (5) 解题思路  1.建立一个数组,然后遍历链表,将链表中的值存取到数组中,将数组的内容转换成十进制,在size-1位置的反而是于二的零次方相乘,所以可以得到如下代码      int getDecimalValue(struct ListNode* head)  {      int arr[31];     int i=0;     int flag=0;//做标记,如果数组中没有1,无论链表多长结果都是0     while(head!=NULL)     {         arr[i]=head->val;         i++;         if(head->val==1)         {             flag=1;         }         head=head->next;     }     //将链表中的数值全部提取到数组中,必须要拿到数组的有效长度     int sum=0;     int sz=i;      if(flag==0)         return 0;     else     {         for(i=0;i<sz;i++)         {            if(arr[i]!=0)            {                 sum+=pow(2,sz-1-i);            }         }     }      return sum; } 2.此外还可以利用位运算,第一次就从链表中拿到的那个数其实是二的size-1次方,而左移一位就有乘二的效果  int getDecimalValue(struct ListNode* head) {     int sum=0;          while(head!=NULL)     {         sum=(sum<<1)+head->val;                  head=head->next;     }          return sum; } ———————————————— 版权声明:本文为CSDN博主「别动我的饭」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/m0_62633482/article/details/129737637 
  • [技术干货] 米哈游春招算法岗-2023.03.19-第一题-交换字符-简单题-转载
     交换字符 Problem Description 米小游拿到了一个仅由小写字母组成的字符串,她准备进行恰好一次操作:交换两个相邻字母,在操作结束后使得字符串的字典序尽可能大。 请你输出最终生成的字符串。  input 一个仅由小写字母组成的字符串,长度不小于 2 ,不超过200000 。  ouput 操作后的字符串。  Sample Input ba  Sample Output ab  题目类型、难度、来源 类型:暴力 难度:简单 来源:米哈游春招算法岗-2023.03.19-第一题-交换字符 总体思路: 此题很简单,输入一个字符串,必须要交换一次,使得字符串的字典序尽可能大。 ①首先从左到右遍历,如果遇到右边的字符比左边的字符大,马上交换,并break。 ②如果第一种情况不存在,说明不能通过交换使字符串字典序变大,此时就要注意避免交换导致字典序减小。就要看字符串中是否有相邻的两个字符相同大小。如果存在,那么可以使用一次交换使字典序不变。 ③如果上面两种情况都不存在,就要尽量让字典序减少得尽可能少。此时应该交换字符串最后的两个元素。 AC代码 #include <iostream> #include <string> using namespace std; void swap(char &a, char &b){     char t = a;     a = b;     b = t; } int main(){     string s;     cin >> s;     int i, flag = 0;     for (i = 0; i < s.size()-1; i++){         if (s[i+1] > s[i]){             swap(s[i], s[i+1]);             break;         }else if (s[i+1] == s[i]){             flag = 1;         }     }     if (i == s.size()-1){         if (flag == 0){             swap(s[s.size()-1], s[s.size()-2]);         }     }     cout << s;     return 0; } ———————————————— 版权声明:本文为CSDN博主「新时代原始人」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/weixin_40735291/article/details/129719584 
总条数:47 到第
上滑加载中