https://github.com/HealUP/MyBlogRSS feed of HealUP's MyBlog2024-04-14T05:46:53.175431+00:00HealUP1571826275@qq.compython-feedgenhttps://github.com/HealUP/MyBlog/issues/4510月份-11月份计划🍾2024-04-14T05:46:53.364485+00:00行动是解决焦虑的最好方式!💪
  • 坚持投简历 -> 找工作(实习转正
  • 毕业设计代码开发达到60%
  • 持续技术学习->输出技术文档(一周至少3篇)
  • 开源项目(整理core项目并开源)
  • 软考备考
  • 一周3跑 (至少5k)
  • 10-11月份总结复盘
]]>
2023-10-21T09:04:12+00:00
https://github.com/HealUP/MyBlog/issues/448月份计划🗓️2024-04-14T05:46:53.436880+00:00八月份已经过得差不多了,补充下八月份的计划

  • 准备面试(八股)模拟面试
  • 完成公司项目开发并上线
  • 毕设提前了解,确定选题
  • 算法训练
  • 熟悉项目
  • 看一本书
  • 完成实习的周记
  • 单词500
]]>
2023-08-21T13:47:43+00:00
https://github.com/HealUP/MyBlog/issues/42UML图2024-04-14T05:46:53.506568+00:00UML图

统一建模语言(Unified Modeling Language,缩写UML)是非专利的第三代建模和规约语言。 UML是一种开放的方法,用于说明、可视化、构建和编写一个正在开发的、面向对象的、软件密集系统的制品的开放方法。 分类: ● UML模型 ○ 功能模型:从用户的角度展示系统的功能,包括用例图。 ○ 对象模型:采用对象,属性,操作,关联等概念展示系统的结构和基础,包括类别图、对象图。 ○ 动态模型:展现系统的内部行为。包括序列图,活动图,状态图。 ● UML图 ○ 结构性图形(Structure diagrams)强调的是系统式的建模: ■ 静态图(static diagram):包括类图、对象图、包图 ■ 实现图(implementation diagram):包括组件图、部署图 ■ 剖面图 ■ 复合结构图 ○ 行为式图形(Behavior diagrams)强调系统模型中触发的事件 ■ 活动图 ■ 状态图 ■ 用例图 ○ 交互性图形(Interaction diagrams),属于行为图形的子集合,强调系统模型中的资料流程 ■ 通信图 ■ 交互概述图 ■ 时序图 ■ 时间图 下面详细讲解类图

类图

作用:解析项目的系统结构和架构层次,可以简洁明了的帮助我们理解项目中类之间的关系。 类图的格式: ● 类名:粗体(类是抽象类则类名显示为斜体) ● 属性: ○ 格式:可见性 名称:类型[=默认值] ○ 可见性一般为public、private和protected,在类图分别用+、-和#表示 ● 方法: ○ 格式:可见性 名称(参数列表 参数1,参数2) :返回类型 ■ 可见性如上名称表达式的介绍 ■ 名称就是方法名 ■ 参数列表是可选的项,多参数的话参数直接用英文逗号隔开 ■ 返回值也是个可选项,返回值类型可以说基本的数据类型、用户自定义类型和void。(如果是构造方法,则无返回类型) image.png

类与类之间的关系: 泛化(继承)、实现、依赖、关联、聚合、组合 image.png

聚合:部分可以脱离整理而存在 组合:部分若脱离了整体,则不复存在 关联:长期性的 依赖:临时性的

]]>
2023-07-29T15:11:47+00:00
https://github.com/HealUP/MyBlog/issues/4128. 找出字符串中第一个匹配项的下标——KMP算法2024-04-14T05:46:53.581383+00:00KMP算法 28. 找出字符串中第一个匹配项的下标

思路:使用KMP算法,一定要构造next数组(即前缀表)

  • next数组,就是以模式串中各个字符串结尾的最长相等前后缀的集合

KMP算法 1.构造next数组(和模式串大小一致) 2.模式串用next数组匹配文本串

那怎么构造next数组呢,思路:

1.初始化 2.处理前后缀不相同的情况 3.处理前后缀相同的情况

注:以下统称haystack为文本串, needle为模式串

时间复杂度:其中n为文本串长度,m为模式串长度,因为在匹配的过程中,根据前缀表不断调整匹配的位置,可以看出匹配的过程是O(n),之前还要单独生成next数组,时间复杂度是O(m)。时间复杂度是O(n+m)

法一:前缀表不减一

class Solution {
    public int strStr(String haystack, String needle) {
        //前后缀不减一的实现方式
        //边界判断
        if (needle.length() == 0) return 0;
        int[] next = new int[needle.length()];//创建一个和模式串一样大的next数组
        getNext(next, needle);//得到填充好的next数组

        //使用next数组来做匹配 在文本串里找是否出现过模式串
        //定义两个下标,j指向模式串起始位置,i指向文本串起始位置
        int j = 0;//和next数组中j的起始位置对应
        for (int i = 0; i < haystack.length(); i++) {
            //不匹配的情况
            while (j > 0 && needle.charAt(j) != haystack.charAt(i)) {//模式串的j>0,=0的话,下面的next就是next[-1]就出界了
                //找到回退的位置,即改变前缀末尾的位置j
                j = next[j - 1];
            }
            if (needle.charAt(j) == haystack.charAt(i)) {
                j++;//前缀末尾后移
            }
            //判断在文本串s里出现了模式串t呢,如果j指向了模式串t的末尾,那么就说明模式串t完全匹配文本串s里的某个子串了
            if (j == needle.length()) {
                return i - needle.length() + 1;//因为返回的是下标,所以要+1;比如:长度是3,其最大下标就是2,数组下标从0开始的
            }
        }
        //跳出循环了,说明找不到匹配的模式串的了
        return -1;
    }

    //构造next数组
    /*
    思路:1.初始化
         2.处理前后缀不相同的情况
         3.处理前后缀相同的情况
    */
    private void getNext(int[] next, String s) {
        //定义两个指针i和j,j指向前缀末尾位置,i指向后缀末尾位置
        //next[i] 表示 i(包括i)之前最长相等的前后缀长度(其实就是j)
        //next[j]就是记录着j(包括j)之前的子串的相同前后缀的长度
        int j = 0;
        next[0] = j;
        for (int i = 1; i < s.length(); i++) {//从1开始,因为0的位置肯定是0的,只有一个字符的时候,不存在相等的前后缀
            //不相等前后缀的情况,回退  这里用while 是因为只要不相等就一致回退到相等或者到索引为1的位置
            while (j > 0 && s.charAt(j) != s.charAt(i)) {
                //改变前缀的末尾
                j = next[j - 1];//不匹配字符的上一个位置
            }
            // 找到相同的前后缀
            if (s.charAt(j) == s.charAt(i)) {
                j++;//索引为i的位置的值加1
                next[i] = j;//更新当前下标为i的next数组
                }
            }
        }
}
]]>
2023-07-10T23:59:55+00:00
https://github.com/HealUP/MyBlog/issues/40单例模式2024-04-14T05:46:53.662180+00:00单例模式类图展示: image.png

单例模式就是一个类只能有一个对象 使用场景:

  • 你希望这个类只有一个且只能有一个实例;
  • 项目中的一些全局管理类(Manager)可以用单例来实现。

下面要介绍的几种单例模式的实现方式image.png

饿汉式: 步骤一:

public class SingleObject {
    private static SingleObject instance = new SingleObject();
    
    // 构造函数为private,避免被实例化
    private SingleObject(){};
    
    // 获取唯一可用的对象
    public static SingleObject getInstance() {
        return instance;
    }
    
    public showMessage(){
        System.out.println("hello guy!");
    }
}

步骤二:

public class SingleObjectDemo {
    public static void main(String[] args) {
        // 获取唯一可用的对象
        SingleObject object = SingleObject.getInstance();

        // 调用方法
        object.showMessage();
    }
}

步骤三: hello guy!

以上就是饿汉式的实现方式,优点是没有加锁,执行效率高;支持多线程(利用的是JVM的类加载机制,在类初始化时就已经被加载,保证同一时刻只有一个线程获取到实例) ps:这种方式是最常用的,但是容易产生垃圾对象

懒汉式-线程安全

public class SingleObject {
    private static SingleObject instance;

    private SingleObject(){};

    public static synchronized  SingleObject getInstance() {
        if (instance = null) {
            instance = new SingleObject();
        }
        return instance;
    }
}

ps:优点是支持多线程,lazy加载(一开始先不实例化可以避免内存的浪费);必须使用但是使用synchronized才能保证单例,但是加了加锁操作,效率比较低,99%情况下是不需要同步的,不推荐使用。

懒汉式—线程不安全

public class SingleObject {
    private static SingleObject instance;

    private SingleObject(){};

    public static SingleObject getInstance() {
        if (instance = null) {
            instance = new SingleObject();
        }
        return instance;
    }
}

ps:这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。因为没有加锁 synchronized,所以严格意义上它并不算单例模式。

双检锁(DCL,即 double-checked locking)

public class Singleton {  
    private volatile static Singleton singleton;  
    private Singleton (){}  
    public static Singleton getSingleton() {  
    if (singleton == null) {  
        synchronized (Singleton.class) {  
            if (singleton == null) {  
                singleton = new Singleton();  
            }  
        }  
    }  
    return singleton;  
    }  
}

ps:多了volatile,同时,又用了synchronized,这种方式采用双锁机制,安全且在多线程情况下能保持高性能。 使用场景:getInstance() 的性能对应用程序很关键。

登记式

public class Singleton {
    private static class SingletonHolder{
        private static final Singleton INSTANCE = new Singleton();
    }

    private Singleton(){};
    public static final Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

PS:使用了静态内部类,这种方式能达到双检锁方式一样的功效,但实现更简单。对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。

解释:这种方式同样利用了 classloader 机制来保证初始化 instance 时只有一个线程,它跟饿汉式方式不同的是:饿汉式只要 Singleton 类被装载了,那么 instance 就会被实例化(没有达到 lazy loading 效果);而这种方式是 Singleton 类被装载了,instance 不一定被初始化。因为 SingletonHolder 类没有被主动使用,只有通过显式调用 getInstance 方法时,才会显式装载 SingletonHolder 类,从而实例化 instance。

使用场景:如果实例化 instance 很消耗资源,所以想让它延迟加载,另外一方面,又不希望在 Singleton 类加载时就实例化,因为不能确保 Singleton 类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化 instance 显然是不合适的。这个时候,这种方式相比饿汉式就显得比较合理

枚举

public enum Singleton {  
    INSTANCE;  
    public void whateverMethod() {  
    }  
}

总结::一般情况下,不建议使用懒汉方式,建议使用饿汉方式(线程安全,但没有懒加载)。只有在要明确实现 lazy loading 效果时,才会使用登记方式。如果涉及到反序列化创建对象时,可以尝试使用枚举方式。如果有其他特殊的需求,可以考虑使用双检锁方式。

]]>
2023-07-10T23:22:59+00:00
https://github.com/HealUP/MyBlog/issues/39策略模式2024-04-14T05:46:53.733145+00:001.UML图 统一建模语言(Unified Modeling Language,缩写UML)是非专利的第三代建模和规约语言。 UML是一种开放的方法,用于说明、可视化、构建和编写一个正在开发的、面向对象的、软件密集系统的制品的开放方法。 分类: ● UML模型 ○ 功能模型:从用户的角度展示系统的功能,包括用例图。 ○ 对象模型:采用对象,属性,操作,关联等概念展示系统的结构和基础,包括类别图、对象图。 ○ 动态模型:展现系统的内部行为。包括序列图,活动图,状态图。 ● UML图 ○ 结构性图形(Structure diagrams)强调的是系统式的建模: ■ 静态图(static diagram):包括类图、对象图、包图 ■ 实现图(implementation diagram):包括组件图、部署图 ■ 剖面图 ■ 复合结构图 ○ 行为式图形(Behavior diagrams)强调系统模型中触发的事件 ■ 活动图 ■ 状态图 ■ 用例图 ○ 交互性图形(Interaction diagrams),属于行为图形的子集合,强调系统模型中的资料流程 ■ 通信图 ■ 交互概述图 ■ 时序图 ■ 时间图 下面详细讲解类图 1.1 类图 作用:解析项目的系统结构和架构层次,可以简洁明了的帮助我们理解项目中类之间的关系。 类图的格式: ● 类名:粗体(类是抽象类则类名显示为斜体) ● 属性: ○ 格式:可见性 名称:类型[=默认值] ○ 可见性一般为public、private和protected,在类图分别用+、-和#表示 ● 方法: ○ 格式:可见性 名称(参数列表 参数1,参数2) :返回类型 ■ 可见性如上名称表达式的介绍 ■ 名称就是方法名 ■ 参数列表是可选的项,多参数的话参数直接用英文逗号隔开 ■ 返回值也是个可选项,返回值类型可以说基本的数据类型、用户自定义类型和void。(如果是构造方法,则无返回类型) image.png 类与类之间的关系: 泛化(继承)、实现、依赖、关联、聚合、组合 image 聚合:部分可以脱离整理而存在 组合:部分若脱离了整体,则不复存在 关联:长期性的 依赖:临时性的 2.设计模式 2.1 策略模式 image.png 定义一系列算法,将每个算法都封装起来,并且使他们之间可以相互替换。策略模式使算法可以独立于使用它的用户而变化(聚合) 策略模式的结构: ● 环境(Context)角色: 持有一个Strategy的引用; ● 抽象策略(Strategy)角色: 这是一个抽象角色,通常由一个接口或抽象类实现。此角色给出所有的具体策略类所需的接口;(关系是:实现或继承) ● 具体策略(ConcreteStrategy)角色: 包装了相关的算法或行为 使用步骤:(举例) ● 定义Strategy接口,包含抽象方法

public interface Strategy {
   public int doOperation(int num1, int num2);
}

● 创建实现接口的实体类:分别是加、减、乘的类,里面封装了这三种算法

public class OperationAdd implements Strategy{
   @Override
   public int doOperation(int num1, int num2) {
      return num1 + num2;
   }
}
public class OperationSubtract implements Strategy{
   @Override
   public int doOperation(int num1, int num2) {
      return num1 - num2;
   }
}
public class OperationMultiply implements Strategy{
   @Override
   public int doOperation(int num1, int num2) {
      return num1 * num2;
   }
}

● 创建 Context 类 (Strategy类是Context的一部分——聚合关系)

public class Context {
   private Strategy strategy;// Strategy类是Context的一部分

    // 有参构造
   public Context(Strategy strategy){
      this.strategy = strategy;
   }
 
   public int executeStrategy(int num1, int num2){
      return strategy.doOperation(num1, num2);
   }
}

● 使用Context来改变策略,达到使用不同代码逻辑的作用

public class StrategyPatternDemo {
   public static void main(String[] args) {
      Context context = new Context(new OperationAdd());    
      System.out.println("10 + 5 = " + context.executeStrategy(10, 5));
 
      context = new Context(new OperationSubtract());      
      System.out.println("10 - 5 = " + context.executeStrategy(10, 5));
 
      context = new Context(new OperationMultiply());    
      System.out.println("10 * 5 = " + context.executeStrategy(10, 5));
   }
}

结果:

10 + 5 = 15
10 - 5 = 5
10 * 5 = 50

]]>
2023-07-06T00:04:03+00:00
https://github.com/HealUP/MyBlog/issues/38🗓️7月份计划2024-04-14T05:46:53.809625+00:00
  • 熟悉公司项目🧑‍💻⭐⭐⭐
  • 结合项目持续学习redis🧑‍💻⭐⭐⭐
  • 设计模式🧑‍💻⭐⭐⭐
  • Mysql总结输出🧑‍💻⭐⭐⭐
  • 微服务架构持续学习🧑‍💻⭐⭐⭐
  • 业务文档整理、技术方案总结,文字表达能力🧑‍💻⭐⭐
  • JVM学习+总结排错思路🧑‍💻 ⭐⭐
  • 出一版新的简历内容⭐⭐⭐⭐⭐
  • 英语听力+单词🚇⭐⭐⭐
  • 算法+SQL⭐⭐
  • 月度总结❗
  • ]]>
    2023-07-01T15:20:19+00:00
    https://github.com/HealUP/MyBlog/issues/37索引✅2024-04-14T05:46:53.877278+00:001.索引

    1.1 创建索引的方式

    索引就是用来帮助表快速检索目标数据的 那么,索引有哪几种创建方式: ● CREATE

    CREATE INDEX indexName ON tableName (columnName(length) [ASC|DESC]); indexName:当前创建的索引,创建成功后索引的名字; tableName:要在哪张表上创建一个索引,这里指定表名; columnName:要为表中的哪个字段创建索引,这里指定字段名; length:如果字段存储的值过长,选用值的前多少个字符创建索引; ASC|DESC:指定索引的排序方式,ASC是升序,DESC是降序,默认ASC

    ● ALTER ALTER TABLE tableName ADD INDEX indexName(columnName(length) [ASC|DESC]);

    ● DDL (建表时创建索引)适合已经确定了索引项的情况下建立

    CREATE TABLE tableName(  
      columnName1 INT(8) NOT NULL,   
      columnName2 ....,
      .....,
      INDEX [indexName] (columnName(length))  
    );
    

    不同的索引有不同的创建方式:(后面会具体介绍各类索引)

    ● 唯一索引

    -- 方式①
    CREATE UNIQUE INDEX indexName ON tableName (columnName(length));
    
    -- 方式②
    ALTER TABLE tableName ADD UNIQUE INDEX indexName(columnName);
    
    -- 方式③
    CREATE TABLE tableName(  
      columnName1 INT(8) NOT NULL,   
      columnName2 ....,
      .....,
      UNIQUE INDEX [indexName] (columnName(length))  
    );
    

    ● 主键索引

    -- 方式①
    ALTER TABLE tableName ADD PRIMARY KEY indexName(columnName);
    
    -- 方式②
    CREATE TABLE tableName(  
      columnName1 INT(8) NOT NULL,   
      columnName2 ....,
      .....,
      PRIMARY KEY [indexName] (columnName(length))  
    );
    

    ● 全文索引——不常用

    -- 方式①
    ALTER TABLE tableName ADD FULLTEXT INDEX indexName(columnName);
    
    -- 方式②
    CREATE FULLTEXT INDEX indexName ON tableName(columnName);
    

    注意点: ○ 5.6版本的MySQL中,存储引擎必须为MyISAM才能创建。 ○ 创建全文索引的字段,其类型必须要为CHAR、VARCHAR、TEXT等文本类型。 ○ 如果想要创建出的全文索引支持中文,需要在最后指定解析器:with parser ngram。 ○ 优化器无法自动选择全文索引,他有自己的语法

    ● 空间索引(仅有MyISAM支持空间索引)—— 不常用 ALTER TABLE tableName ADD SPATIAL KEY indexName(columnName);

    注意:空间索引必须要建立在类型为GEOMETRY、POINT、LINESTRING、POLYGON的字段上 ● 联合索引 ○ 联合索引的意思是可以使用多个字段建立索引。那该如何创建联合索引呢,不需要特殊的关键字,方法如下:

    CREATE INDEX indexName ON tableName (column1(length),column2...);
    ALTER TABLE tableName ADD INDEX indexName(column1(length),column2...);
    

    注意: ● 使用联合索引时,SELECT语句的查询条件中,必须包含组成联合索引的第一个字段,此时才会触发联合索引,否则是无法使用联合索引的。 ● 创建主键索引时,必须要将索引字段先设为主键,否则会抛1068错误码。 ● 这里也不能使用CREATE(指方式①)语句创建索引,否则会提示1064语法错误。不过:一般主键索引都会在建表的DDL语句中创建,不会在表已经建立后再创建 ● 同时创建索引时,关键字要换成KEY,并非INDEX,否则也会提示语法错误。

    1.2 操作索引

    ● 查看索引 SHOW INDEX FROM tableName image.png 每个字段的含义

    ● ①Table:当前索引属于那张表。 ● ②Non_unique:目前索引是否属于唯一索引,0代表是的,1代表不是。 ● ③Key_name:当前索引的名字。 ● ④Seq_in_index:如果当前是联合索引,目前字段在联合索引中排第几个。 ● ⑤Column_name:当前索引是位于哪个字段上建立的。 ● ⑥Collation:字段值以什么方式存储在索引中,A表示有序存储,NULL表无序。 ● ⑦Cardinality:当前索引的散列程度,也就是索引中存储了多少个不同的值。 ● ⑧Sub_part:当前索引使用了字段值的多少个字符建立,NULL表示全部。 ● ⑨Packed:表示索引在存储字段值时,以什么方式压缩,NULL表示未压缩, ● ⑩Null:当前作为索引字段的值中,是否存在NULL值,YES表示存在。 ● ⑪Index_type:当前索引的结构(BTREE, FULLTEXT, HASH, RTREE)。 ● ⑫Comment:创建索引时,是否对索引有备注信息。 后续排除问题、性能调优时,可以通过分析其中的Cardinality字段值,如果该值少于数据的实际行数,那目前索引有可能失效 ● 如果新建错了索引,只能删除再重新创建 DROP INDEX indexName ON tableName; ● 指定索引 SELECT * FROM table_name FORCE INDEX(index_name) WHERE .....; 这个关键字的用法是:当一条查询语句在有多个索引可以检索数据时,显式指定一个索引,减少优化器选择索引的耗时。但是如果对业务系统不熟悉,还是得让优化器自己来选择。

    1.3 索引的本质

    数据库是基于磁盘工作的,所有数据都再磁盘上面存储,索引也是数据的一种,同样存储在磁盘上,但是索引最终会以哪种方式进行存储,这是由索引的数据结构来决定的,同时索引机制又是由存储引擎实现的,不同的存储引擎下的索引文件,保存在本地的格式是不同的。 当数据量越少时,创建索引越好 ,因为创建索引时,会基于原有的数据重新在本地创建索引文件,并同时做好排序并与表数据产生映射的关系。

    1.4索引的分类

    ● 数据结构层次划分 ○ B+Tree类型:MySQL中最常用的索引结构,大部分引擎支持,有序 ○ Hash类型:大部分存储引擎都支持,字段值不重复的情况下查询最快,无序 ○ R-Tree类型:MyISAM引擎支持,也就是空间索引的默认结构类型 ○ T-Tree类型:NDB-Cluster引擎支持,主要用于MySQL-Cluster服务中

    B+树和哈希索引是最常见的索引结构,B+Tree是有序的,哈希是无序的。在MySQL中创建索引时,其默认的数据结构就为B+Tree,可以在建表时使用using字段改变索引的数据结构。 CREATE INDEX indexName ON tableName (columnName(length) [ASC|DESC]) USING HASH; ● 字段数量的层次划分 ○ 单列索引 ■ 主键索引 ■ 唯一索引 ■ 普通索引 ○ 多列索引 ■ 联合索引、组合索引、复合索引 、多值索引...很多种叫法,本质即由多个字段组成的索引

    使用多列索引的注意事项:当建立多列索引后,一条SELECT语句,只有当查询条件中了包含了多列索引的第一个字段时,才能使用多列索引 ● 使用一个字段值中的前N个字符创建出的索引,就可以被称为前缀索引 (length指定长度) 前缀索引能够在很大程度上,节省索引文件的存储空间,也能很大程度上提升索引的性能 ● 功能逻辑层次划分 ○ 普通索引、唯一索引、主键索引、全文索引、空间索引 ● 存储方式划分 ○ 聚簇索引:也被称为聚集索引、簇类索引 ■ 逻辑上连续且物理空间上的连续 ○ 非聚簇索引:也叫非聚集索引、非簇类索引、二级索引、辅助索引、次级索引 ■ 逻辑上的连续,物理空间上不连续

    注意:

    1.一张表中只能存在一个聚簇索引,一般都会选用主键作为聚簇索引,其他字段上建立的索引都属于非聚簇索引,或者称之为辅助索引、次级索引。 2.误区:虽然MySQL默认会使用主键上建立的索引作为聚簇索引,但也可以指定其他字段上的索引为聚簇索引,一般聚簇索引要求索引必须是非空唯一索引才行。 3.如果表中就算没有定义主键,InnoDB中会选择一个唯一的非空索引作为聚簇索引,但如果非空唯一索引也不存在,InnoDB隐式定义一个主键来作为聚簇索引(一般适合采用带有自增性的顺序值)。

    ]]>
    2023-06-11T15:45:50+00:00
    https://github.com/HealUP/MyBlog/issues/35🗓️6月份计划2024-04-14T05:46:53.962384+00:00👣 热爱生活,感受当下!加油!🌝
    • 找到一份Java后端的实习岗位
    • 持续跟进学校工作室项目,按时完成交付
    • 熟悉公司业务(尽快上手开发)
    • 备考六级
    • 总结面试经验
    • 持续学习并输出学习blog
    • 算法(贪心+回顾前面总结)
    • redis
    • MySQL进阶
    • Dubbo&Zookeeper
    • 设计模并运用到项目
    • 八股文持续总结输出
    ]]>
    2023-06-02T14:55:22+00:00
    https://github.com/HealUP/MyBlog/issues/33快速排序⭐⭐⭐2024-04-14T05:46:54.077121+00:00理论基础:快速排序是一种常见的排序算法,使用分治的思想来排序一个数组或列表。它的基本思想是选择一个基准数将数组分成两个部分,一部分是小于基准数的,另一部分是大于等于基准数的,然后再对这两部分递归地进行排序

    时间复杂度:nlogn

    具体步骤如下:

    1. 选择一个基准数(pivot),可以选择第一个数、最后一个数、中间的数或者随机数。
    2. 将数组中小于等于基准数的元素放在基准数的左边,大于基准数的元素放在基准数的右边,这一步称为**“划分**”(partition)操作。
    3. 对基准数左边的子数组进行递归排序,对基准数右边的子数组进行递归排序,直到每个子数组的长度为 0 或 1,排序完成。
    class Solution {
        public int[] sortArray(int[] nums) {
            quickSort(nums, 0, nums.length - 1);
            return nums;
        }
        // 快速排序函数
        public static int[] quickSort(int[] nums, int left, int right) {
            if (left >= right) {
                return null;
            }
            int partition = partition(nums, left, right);
            quickSort(nums, left, partition - 1);//在划分位置的左边子区域再划分,直到不可划分left >= right不能进循环条件
            quickSort(nums, partition + 1, right); //在划分位置的右边子区域再划分,直到不可划分left >= right不能进循环条件
            return nums;
        }
    
        public static int partition(int[] nums, int left, int right) {
            int pivot = nums[left];
            while (left < right) {
                while (left < right && nums[right] >= pivot) {
                    right--;
                }
                nums[left] = nums[right];
                while (left < right && nums[left] <= pivot) {
                    left++;
                }
                nums[right] = nums[left];
            }
            nums[left] = pivot; //当前left,right 指向同一个位置了 nums[right] = pivot也行
            return left;// 返回划分的位置
        }
    }
    
    ]]>
    2023-05-24T15:36:17+00:00
    https://github.com/HealUP/MyBlog/issues/32LC 111. 二叉树的最小深度2024-04-14T05:46:54.144506+00:00111. 二叉树的最小深度

    思路:遍历每一层的时候,记录次数;当该节点的左右节点都为空的时候,直接返回当前的深度即可,如果左右节点不为空就加入队列

    class Solution {
        public int minDepth(TreeNode root) {
            if (root == null) {
                return 0;
            }
            Queue<TreeNode> que = new LinkedList<>();
            que.offer(root);
            int min = 0;
            while (!que.isEmpty()) {
                int len = que.size();
                min++;
                for (int i = 0; i < len; i++) {
                    TreeNode node = que.poll();
                    if (node.left == null && node.right == null) {
                        return min;//该节点的左右节点大都为空直接返回当前的深度
                    }
                    //加入该节点的左右节点
                    if (node.left != null) que.offer(node.left);
                    if (node.right != null) que.offer(node.right);
                }
            }
            return min;
        }
    }
    
    ]]>
    2023-05-18T15:42:21+00:00
    https://github.com/HealUP/MyBlog/issues/31LC 117. 填充每个节点的下一个右侧节点指针II2024-04-14T05:46:54.210517+00:00117. 填充每个节点的下一个右侧节点指针II

    该题目和116的区别:就是116题是完全二叉树,这道题不是完全二叉树,题解完全一样的。

    3.9 104. 二叉树的最大深度

    思路:依旧是层序遍历,计算共有多少层即可

    class Solution {
        public int maxDepth(TreeNode root) {
            if (root == null) {
                return 0;
            }
            Queue<TreeNode> que = new LinkedList<>();
            que.offer(root);
            int count = 0;//记录深度
            while (!que.isEmpty()) {
                int len = que.size();
                count++;
                for (int i = 0; i < len; i++) {
                    TreeNode node = que.poll();//弹出并记录
                    //放入节点的左右节点
                    if (node.left != null) que.offer(node.left);
                    if (node.right != null) que.offer(node.right);
                }
            }
            return count;
        }
    }
    
    ]]>
    2023-05-18T15:41:53+00:00
    https://github.com/HealUP/MyBlog/issues/30LC 116. 填充每个节点的下一个右侧节点指针2024-04-14T05:46:54.272082+00:00116. 填充每个节点的下一个右侧节点指针

    思路:

    • 层序遍历 一个while 一个for循环
    • 处理for循环的时候,让下标i小于数组最大下标的每一层的每一个节点指向下一个节点的值(只是查看值,而不弹出,用peek())
    /*
    // Definition for a Node.
    class Node {
        public int val;
        public Node left;
        public Node right;
        public Node next;
    
        public Node() {}
        
        public Node(int _val) {
            val = _val;
        }
    
        public Node(int _val, Node _left, Node _right, Node _next) {
            val = _val;
            left = _left;
            right = _right;
            next = _next;
        }
    };
    */
    
    class Solution {
        public Node connect(Node root) {
            if (root == null) {
                return root;
            }
            Queue<Node> que = new LinkedList<>();
            que.offer(root);
            while (!que.isEmpty()) {
                int len = que.size();//每一层的数量是会改变的,记录下来
                for (int i = 0; i < len; i++) {
                    Node node = que.poll();
                    //连接每一层的节点,当该层的节点的数量大于1就可连接
                    if (i < len - 1) {
                        node.next = que.peek();//不弹出该节点,只是查看
                    }
                    //扩展下一层的节点放入队列中
                    if (node.left != null) que.offer(node.left);
                    if (node.right != null) que.offer(node.right);
                } 
            }
            return root;
        }
    }
    
    ]]>
    2023-05-18T15:41:12+00:00
    https://github.com/HealUP/MyBlog/issues/29LC 515. 在每个树行中找最大值2024-04-14T05:46:54.334777+00:00515. 在每个树行中找最大值

    思路:层序遍历,取每一层的最大值,放到一维数组,最后返回结果集

    Collections.emptyList()//这个方法返回的List是Collections类的一个静态内部类,它继承AbstractList后并没有实现add()、remove()等方法,因此这个返回值List并不能增加删除元素
    
    • 层序遍历
    • 处理每一层的时候找到最大值即可

    用到:

    class Solution {
        public List<Integer> largestValues(TreeNode root) {
            if (root == null) {
                return Collections.emptyList();//直接返回空列表,减少开销
            }
            Queue<TreeNode> que = new LinkedList<>();//创建队列
            List<Integer> res = new ArrayList<>();//结果集
            que.offer(root);//根节点入队
            while (!que.isEmpty()) {
                int len = que.size();//记录当前节点的数量
                int max = Integer.MIN_VALUE;//先赋予int类型的最小值
                for (int i = 0; i < len; i++) {//找每一层的最大值
                    TreeNode node = que.poll();//从队列中取出元素
                    max = Math.max(max, node.val);//找出该层的最大值
                    //放入该节点的左右节点到队列
                    if (node.left != null) que.offer(node.left);
                    if (node.right != null) que.offer(node.right);
                }
                //将每一层的最大值放到结果集
                res.add(max);
            }
            return res;
        }
    }
    
    ]]>
    2023-05-18T15:38:38+00:00
    https://github.com/HealUP/MyBlog/issues/28N叉树的层序遍历2024-04-14T05:46:54.413245+00:00N叉树的层序遍历

    思路:依然是层序遍历,只是节点的孩子有多个了,children也是List类型的,将节点的所有孩子遍历后全部放入队列即可:

    遍历方式

    • 增强for循环
    • 普通for循环
    class Solution {
        public List<List<Integer>> levelOrder(Node root) {
            List<List<Integer>> result = new ArrayList<>();
            Queue<Node> que = new LinkedList<>();
            if (root != null) {
                que.offer(root);
            }
            while (!que.isEmpty()) {
                List<Integer> list = new ArrayList<>();
                int len = que.size();
                for (int i = 0; i < len; i++) {
                    Node node = que.poll();
                    list.add(node.val);//取出节点的值加入到结果集
                    //添加节点的孩子到队列,是Node类型的List
                    List<Node> children = node.children;
                    //遍历孩子,放到队列中
                    // for (int j = 0; j < children.size(); j++) {
                    //     if (children.get(j) != null ) {
                    //         que.offer(children.get(j));//将节点加入到队列
                    //     }
                    // }
                    for (Node child : children) {
                        if (child != null) {
                            que.offer(child);
                        }
                    }
                }
                result.add(list);
            }
            return result;
        }
    }
    

    N叉树(节点)的定义

    // Definition for a Node.
    class Node {
        public int val;
        public List<Node> children;//孩子也是节点类型的
    
        public Node() {}//构造器
    
        public Node(int _val) {//有参构造器
            val = _val;
        }
    
        public Node(int _val, List<Node> _children) {//有参构造器
            val = _val;
            children = _children;
        }
    };
    
    ]]>
    2023-05-18T15:38:00+00:00
    https://github.com/HealUP/MyBlog/issues/27LC 637. 二叉树的层平均值2024-04-14T05:46:54.485983+00:00637. 二叉树的层平均值

    思路:

    • 层次遍历的时候,每一层求一个总和
    • 该层结束,就求这一层的平均值即可
    class Solution {
        public List<Double> averageOfLevels(TreeNode root) {
            Queue<TreeNode> que = new LinkedList<>();
            //判断空
            if (root != null) {
                que.offer(root);
            }
            List<Double> list = new ArrayList<>();
            while (!que.isEmpty()) {
                int len = que.size();
                double sum = 0.0;
                for (int i = 0; i < len; i++) {//这里不能用while判断 会改变len的值
                    TreeNode node = que.poll();//弹出并临时记录
                    sum += node.val;//求和
                    //将该层的平均值存放到一维数组中
                    if (node.left != null) {
                        que.add(node.left);
                    }
                    if (node.right != null) {
                        que.add(node.right);
                    }
                }
                list.add(sum / len);
            }
            return list;
        }
    }
    

    注意:这里不能用while

    不能用 while(len > 0) {
        ...
        len--;
    }
    因为len改变了,就无法计算sum/len了,这点要注意
    
    ]]>
    2023-05-18T15:36:44+00:00
    https://github.com/HealUP/MyBlog/issues/26LC107. 二叉树的层次遍历2024-04-14T05:46:54.567733+00:003.2 107. 二叉树的层次遍历
    class Solution {
        public List<List<Integer>> levelOrderBottom(TreeNode root) {
            /*思路:
            1.广搜得到二维数组的结果
            2.将二维数组反转
             */
            //创建一个结果二维数组
            List<List<Integer>> res = new ArrayList<>();
            Queue<TreeNode> que = new LinkedList<>();
            if (root == null) {
                return res;
            }
            que.offer(root);//根节点放入队列
            while (!que.isEmpty()) {
                //创建一维数组保存当前层的节点
                List<Integer> list = new ArrayList<>();
                //保存当前层的节点的数量
                int len = que.size();
                for (int i = 0; i < len; i++) {
                    //保存当前出队的节点
                    TreeNode node = que.poll();
                    //将其值存入到一维数组
                    list.add(node.val);
                    //将该节点的左右节点入队
                    if (node.left != null) que.offer(node.left);//空节点不入队
                    if (node.right != null) que.offer(node.right);
                }
                //将该层的节点的值存放到一维数组中
                res.add(list);
            }
            //开辟一个二维数组的空间,存放反转后的二维数组的结果
            List<List<Integer>> result = new ArrayList<>();
            for (int i = res.size() - 1; i >= 0; i--) {//数组下标从0开始,最大长度是数组大小减1
                result.add(res.get(i));//get获取对应位置的元素
            }
    
            //返回结果
            return result;
        }
    }
    
    ]]>
    2023-05-18T15:36:11+00:00
    https://github.com/HealUP/MyBlog/issues/25LC 102.二叉树的层序遍历2024-04-14T05:46:54.629816+00:00102. 二叉树的层次遍历

    思路

    • 定义数组和队列:将每一层的结果保存在一个一维数组中,将每一层的一维数组保存在二维数组中 判断队列是否为空

    • 遍历当前层,用size记录当前层的大小,控制当前遍历的次数

    • 加入当前节点的下一层的左右孩子

    • 此时又重新获取了下一层的节点的数量

    /**
     * Definition for a binary tree node.
     * public class TreeNode {
     *     int val;
     *     TreeNode left;
     *     TreeNode right;
     *     TreeNode() {}
     *     TreeNode(int val) { this.val = val; }
     *     TreeNode(int val, TreeNode left, TreeNode right) {
     *         this.val = val;
     *         this.left = left;
     *         this.right = right;
     *     }
     * }
     */
    class Solution {
        public List<List<Integer>> levelOrder(TreeNode root) {
            //创建一个二维数组 存放结果
            List<List<Integer>> res = new ArrayList<>();
            //创建一个队列 
            Queue<TreeNode> que = new LinkedList<TreeNode>();
            //BFS广度优先搜索——使用队列实现
            if (root != null) {
                que.offer(root);
            };
            //广度优先搜索模板
            /*如果不为空队列不为空,就创建一个数组,记录下队列的大小,将队列中的节点的值存入数组*/
            while (!que.isEmpty()) {
                //创建数组
                List<Integer> list = new ArrayList<>();
                //记录当前层数的节点个数 即当前层的队列的大小
                int len = que.size();//队列的元素是会改变的
    
                while (len > 0) {//只处理len个节点
                    //存放取出的节点
                    TreeNode node = que.poll();
                    list.add(node.val);//将节点的值放到数组中(一维数组)
                    //将该节点的左右节点都放到队列中 这样就改变了队列的节点的个数了 
                    if (node.left != null) que.offer(node.left);
                    if (node.right != null) que.offer(node.right);
                    len--;
                }
                //将该层的节点(一维数组)放到二维数组里面
                res.add(list);
            }
            return res;
        }
    }
    
    ]]>
    2023-05-18T15:33:22+00:00
    https://github.com/HealUP/MyBlog/issues/24剑指 Offer 29. 顺时针打印矩阵2024-04-14T05:46:54.715509+00:00[剑指 Offer 29. 顺时针打印矩阵](https://leetcode.cn/problems/shun-shi-zhen-da-yin-ju-zhen-lcof/)

    思路分析:根据题目示例 matrix = [[1,2,3],[4,5,6],[7,8,9]] 的对应输出 [1,2,3,6,9,8,7,4,5] 可以发现,顺时针打印矩阵的顺序是 “从左向右、从上向下、从右向左、从下向上” 循环。

    https://leetcode.cn/problems/shun-shi-zhen-da-yin-ju-zhen-lcof/solution/mian-shi-ti-29-shun-shi-zhen-da-yin-ju-zhen-she-d

    class Solution {
        public int[] spiralOrder(int[][] matrix) {
            if (matrix.length == 0) return new int [0];//矩阵为空,返回空数组即可
            int l = 0, r = matrix[0].length-1, t = 0, b = matrix.length - 1, x = 0;//r 是列长度,即宽度b是行长度,即高度
            int[] res = new int[(r + 1) * (b + 1)];//数组的大小就是二维数组相乘的大小
            while (true) {
                //left to right
                for (int i = l; i <= r; i++) {
                    res[x++] = matrix[t][i];
                }
                //top大于bottom出边界了
                if(++t > b) {
                    break;
                }
                //top to bottom
                for (int i = t; i <= b; i++) {
                    res[x++] = matrix[i][r];
                }
                if (--r < l) {
                    break;
                }
                //right to left
                for (int i = r; i >= l; i--) {
                    res[x++] = matrix[b][i];
                }
                if (--b <t) {
                    break;
                }
                //bottom to top
                for (int i = b; i >= t; i--) {
                    res[x++] = matrix[i][l];
                }
                if (++l > r) {
                    break;
                }
            }
            return res;
        }
    }
    

    小厂笔试第一题

    ]]>
    2023-05-12T11:30:48+00:00
    https://github.com/HealUP/MyBlog/issues/23LC 226.翻转二叉树 ⭐⭐⭐⭐2024-04-14T05:46:54.786738+00:00226. 翻转二叉树

    解决方案包括递归法非递归法:

    • 递归法 实现前中后序遍历
    • 非递归法即迭代法,包括:
      • 深度优先搜索 DFS 使用模拟 实现前中后序遍历 => 基于栈的深搜其实还不好写统一的前中后序遍历,但是有统一的写法,一刷的时候,由于比较赶,先不写。先掌握递归的写法🐛
      • 广度优先搜索 BFS 使用队列模拟 实现层序遍历

    法一:递归法这道题目使用前序遍历和后序遍历都可以,唯独中序遍历不方便,因为中序遍历会把某些节点的左右孩子翻转了两次

    首先确定三要素:

    • 确定递归函数的参数返回值
    • 确定终止条件
    • 确定单层递归的逻辑
    class Solution {
        //递归函数
        public TreeNode invertTree(TreeNode root) {
            //递归法
            if (root == null) {
                return root;
            }
            //直接交换,使用前序遍历
            swapChildren(root);//中
            invertTree(root.left);//左节点放入  反转的时候将左节点的左右子节点翻转
            invertTree(root.right);//右节点放入 
            return root;
        }
        //交换左右节点
        public void swapChildren (TreeNode root) {
            TreeNode temp = root.left;
            root.left = root.right;
            root.right = temp;
        }
    }
    

    法二:迭代法—层序遍历

    思路:层序遍历,每一层分别反转

    class Solution {
        public TreeNode invertTree(TreeNode root) {
           //层序遍历 每一层的节点分别翻转
           if (root == null) {return null;}
           Queue<TreeNode> que = new LinkedList<>();
           que.offer(root);
           while (!que.isEmpty()) {
               int len = que.size();
               for (int i = 0; i < len; i++) {
                   TreeNode node = que.poll();
                   swapChildren(node);
                   if (node.left != null) que.offer(node.left);
                   if (node.right != null) que.offer(node.right);
               }
           }
           return root;
        }
    
        //交换左右节点
        public void swapChildren (TreeNode root) {
            TreeNode temp = root.left;
            root.left = root.right;
            root.right = temp;
        }
    }
    

    ]]>
    2023-05-12T11:28:53+00:00
    https://github.com/HealUP/MyBlog/issues/22LC双周赛 —6300. 最小公共值2024-04-14T05:46:54.865216+00:00##第 96 场

    双周赛 —6300. 最小公共值

    题目链接:https://leetcode.cn/contest/biweekly-contest-96/problems/minimum-common-value/

    自己写的:

    class Solution {
        public int getCommon(int[] nums1, int[] nums2) {
            //哈希法 set集合实现,因为无法判断输入数组的大小 浪费空间会比较大
            //判断临界条件
            // if ( nums1 == null || nums1.length == 0 || nums2 == null || nums2.length == 0) {
            //     return -1;//返回-1
            // }
            
            //存入nums1数组到set集合
            Set<Integer> set = new HashSet<>();
            for (int i : nums1) {
                set.add(i);
            }
            //存放结果集
            Set<Integer> resSet = new HashSet<>();
            //遍历nums2同时判断nums1的集合中否存在这个元素,存在的放入resSet中
            for (int i : nums2) {
                if (set.contains(i)) {
                    resSet.add(i);
                }
            }
            //返回数组的第一个元素
         int[] res = resSet.stream().mapToInt(x -> x).toArray();
            
            if (res != null && res.length != 0) {
                Arrays.sort(res);//升序
                return res[0];
            } else {
                return -1;
            }
            
        }
    }
    

    优化:因为题目告诉我们,它们已经按非降序排序(即已按升序排序)不用将交集放到另外一个集合了,直接第一个找到了就返回该数就好。

    class Solution {
        public int getCommon(int[] nums1, int[] nums2) {
            Set<Integer> set = new HashSet<Integer>();
            for (int num : nums1) {
                set.add(num);
            }
            for (int num2 : nums2) {
                if (set.contains(num2)) {
                    return num;
                }
            }
            return -1;
        }
    }
    
    ]]>
    2023-05-11T07:28:46+00:00
    https://github.com/HealUP/MyBlog/issues/21349. 两个数组的交集🤓🤓2024-04-14T05:46:54.940005+00:00哈希法—set集合实现

    2.1 349. 两个数组的交集

    一些提示:

    • 如果哈希值比较少、特别分散、跨度非常大,使用数组就造成空间的极大浪费!

    • 这道题目没有限制数值的大小,就无法使用数组来做哈希表了

    • 直接使用set 不仅占用空间比数组大,而且速度要比数组慢,set把数值映射到key上都要做hash计算的

    思路分析:简单的说就是,将一个数组转化成set集合,再将另外一个数组转化成set集合前判断是否在第一个集合出现过,出现过的再放到另外一个set结果集中。

    class Solution {
        public int[] intersection(int[] nums1, int[] nums2) {
            //判断临界条件
            if (nums1 == null || nums1.length == 0 || nums2 == null || nums2.length == 0) {
                return new int[0];
            }
            //先将数组nums1存入set集合中,它当然也会去重,set集合中不会出现重复的元素
            Set<Integer> set1 = new HashSet<>();
            //存放结果
            Set<Integer> resSet = new HashSet<>();
            //遍历数组nums1
            for (int i : nums1) {
                set1.add(i);
            }
            //遍历数组nums2
            for (int i : nums2) {
                //判断nums1里面有没有
                if (set1.contains(i)) {
                    resSet.add(i);
                }
            }
    
            //将集合转化为数组
            return resSet.stream().mapToInt(x -> x).toArray();
    /*这个代码使用了Java 8中的流(Stream) API,它提供了一种高效的方式来处理数据。
    具体来说,这句话的各个部分的作用如下:
    resSet.stream():对 resSet 集合进行流操作,返回一个 Stream<Integer> 类型的流。
    .mapToInt(x -> x):对流中的每个元素执行 map 操作,将元素映射成一个 int 类型的值。x -> x 表示的是一个Lambda表达式,它的作用是将元素原封不动地映射成 int 类型。
    .toArray():将流中的所有 int 值存储在一个 int 数组中,然后将该数组作为结果返回。*/
    
    ]]>
    2023-05-11T07:24:00+00:00
    https://github.com/HealUP/MyBlog/issues/20时间复杂度❗2024-04-14T05:46:55.006188+00:00如图所示: image-20230510150615098.png (图来源不明,侵删,抱歉)

    • 常数阶:常数阶的操作数量与输入数据大小 n无关,即不随着 n的变化而变化

    • 对数阶:与指数阶正好相反,后者反映“每轮增加到两倍的情况”,而前者反映“每轮缩减到一半的情况”。对数阶仅次于常数阶,时间增长得很慢,是理想的时间复杂度。

    • 线性阶:常出现于单层循环

    • 线性对数阶:常出现于嵌套循环中,两层循环的时间复杂度分别为 O(log⁡n) 和 O(n) 。

      主流排序算法的时间复杂度都是 O(nlog⁡N) ,例如快速排序、归并排序、堆排序等

    • 平方阶:常出现于嵌套循环,外层循环和内层循环都为 O(n)

    • 指数阶:增长得非常快,在实际应用中一般是不能被接受的。若一个问题使用「暴力枚举」求解的时间复杂度是 O(2^n) ,那么一般都需要使用**「动态规划」或「贪心算法」**等算法来求解

    ]]>
    2023-05-11T07:08:16+00:00
    https://github.com/HealUP/MyBlog/issues/19LC242. 有效的字母异位词🤓🤓2024-04-14T05:46:55.072012+00:00哈希法—数组实现

    思路分析

    使用一个数组记录字符串S出现的次数,若该字母出现了一次,该位置的元素值加一;再遍历另外一个字符串,若出现一次就在数组对应位置的值减一。

    因为字母是按顺序的,ASCII码上,每个字母也是相差1,随便取一个字母减去‘a’,就会得到该字母的下标了,然后在对应的record数组上,让该索引下的值+1

    时间复杂度:O(n)

    空间复杂度:O(1)

    class Solution {
        public boolean isAnagram(String s, String t) {
            //哈希法 用数组来实现
            //定义一个统计字母出现次数的字符串
            int[] record = new int[26];//开辟26个空间即可
    
            for (int i = 0; i < s.length(); i++) {
                record[s.charAt(i) - 'a']++;//因为字母是按顺序的,ASCII码上,每个字母也是相差1,随便取一个字母减去‘a’,就会得到该字母的下标了,然后在对应的record数组上,让该索引下的值+1
            }
            for (int i = 0; i < t.length(); i++) {
                record[t.charAt(i) - 'a']--;
            }
            //只要record数组里面,不是0的,说明该位置的元素在两个字符串中不相等
            for (int count: record) {
                if (count != 0) {
                    return false;
                }
            }
            return true;
        }
    }
    
    ]]>
    2023-05-11T06:58:36+00:00
    https://github.com/HealUP/MyBlog/issues/18LC215. 数组中的第K个最大元素2024-04-14T05:46:55.135569+00:00[215. 数组中的第K个最大元素](https://leetcode.cn/problems/kth-largest-element-in-an-array/)
    class Solution {
        public int findKthLargest(int[] nums, int k) {
            // 快速排序 小的放到右边,大的放到左边即可
            quickSort(nums, 0, nums.length - 1);
            return nums[k - 1];
    
        }
    
        public int[] quickSort(int[] nums, int left, int right) {
            if (left >= right) {
                return null;
            }
            int pivot = partion(nums, left, right);
            quickSort(nums, left, pivot - 1);
            quickSort(nums, pivot + 1,right);
            return nums;
        }
    
        public int partion(int[] nums, int left, int right) {
            int pivot = nums[left];
    
            while (left < right) {
                while (left < right && nums[right] <= pivot) {
                    right--;
                }
                nums[left] = nums[right];
                while (left < right && nums[left] >= pivot) {
                    left++;
                }
                nums[right] = nums[left];
            }
    
            nums[left] = pivot;
            return left;
        }
    }
    
    ]]>
    2023-05-11T06:41:05+00:00
    https://github.com/HealUP/MyBlog/issues/17LC912. 排序数组 2024-04-14T05:46:55.208879+00:00

    理论基础:快速排序是一种常见的排序算法,使用分治的思想来排序一个数组或列表。它的基本思想是选择一个基准数将数组分成两个部分,一部分是小于基准数的,另一部分是大于等于基准数的,然后再对这两部分递归地进行排序。

    好的情况:时间复杂度:nlogn

    具体步骤如下:

    1. 选择一个基准数(pivot),可以选择第一个数、最后一个数、中间的数或者随机数。
    2. 将数组中小于等于基准数的元素放在基准数的左边,大于基准数的元素放在基准数的右边,这一步称为**“划分**”(partition)操作。
    3. 对基准数左边的子数组进行递归排序,对基准数右边的子数组进行递归排序,直到每个子数组的长度为 0 或 1,排序完成。
    class Solution {
        public int[] sortArray(int[] nums) {
            quickSort(nums, 0, nums.length - 1);
            return nums;
        }
        // 快速排序函数
        public static int[] quickSort(int[] nums, int left, int right) {
            if (left >= right) {
                return null;
            }
            int partition = partition(nums, left, right);
            quickSort(nums, left, partition - 1);//在划分位置的左边子区域再划分,直到不可划分left >= right不能进循环条件
            quickSort(nums, partition + 1, right); //在划分位置的右边子区域再划分,直到不可划分left >= right不能进循环条件
            return nums;
        }
    
        public static int partition(int[] nums, int left, int right) {
            int pivot = nums[left];
            while (left < right) {
                while (left < right && nums[right] >= pivot) {
                    right--;
                }
                nums[left] = nums[right];
                while (left < right && nums[left] <= pivot) {
                    left++;
                }
                nums[right] = nums[left];
            }
            nums[left] = pivot; //当前left,right 指向同一个位置了 nums[right] = pivot也行
            return left;// 返回划分的位置
        }
    }
    

    缺点:如果输入的数组是基本有序的,快速排序的效率会受到影响,因为快速排序的时间复杂度在最坏情况下是 O(n^2),即每次划分都只划分出一个子区间。如果输入的数组基本有序,每次划分可能都会划分出极度不平衡的子区间,使得快排退化成冒泡排序。

    双轴快排

    分析:双轴快排是一种改进的快速排序算法,其基本思想是将待排序数组划分成三个区域:小于基准元素的区域、等于基准元素的区域和大于基准元素的区域。与传统快排相比,双轴快排使用两个轴值,分别从两端扫描数组,将数组划分成三个区域。具体流程如下:

    1. 选取两个轴值p和q,p<q,将数组分成左、中、右三个部分,左部分中所有元素均小于p,右部分中所有元素均大于q,中部分中所有元素均介于p和q之间。

    2. 对中部分进行递归排序。

    3. 对左、右部分进行递归排序。

    由于双轴快排采用两个轴值进行划分,因此每次划分可以减少更多的无用比较,从而提高了排序效率。同时,由于其采用了三路划分的思想,可以处理包含大量重复元素的数组。

    双轴快排的时间复杂度为O(nlogn),与传统快排相同,但是实际运行效率更高,尤其是在处理包含大量重复元素的数组时。

    class Solution {
        public int[] sortArray(int[] nums) {
            dualPivotQuickSort(nums, 0, nums.length - 1);
            return nums;
        }
        
        private void dualPivotQuickSort(int[] nums, int left, int right) {
            if (left >= right) {
                return;
            }
            if (nums[left] > nums[right]) {
                swap(nums, left, right);
            }
            int pivot1 = nums[left], pivot2 = nums[right];
            int i = left + 1, lt = left + 1, gt = right - 1;
            while (i <= gt) {
                if (nums[i] < pivot1) {
                    swap(nums, i++, lt++);
                } else if (nums[i] > pivot2) {
                    swap(nums, i, gt--);
                } else {
                    i++;
                }
            }
            swap(nums, left, --lt);
            swap(nums, right, ++gt);
            dualPivotQuickSort(nums, left, lt - 1);
            dualPivotQuickSort(nums, lt + 1, gt - 1);
            dualPivotQuickSort(nums, gt + 1, right);
        }
        
        private void swap(int[] nums, int i, int j) {
            int temp = nums[i];
            nums[i] = nums[j];
            nums[j] = temp;
        }
    }
    
    
    ]]>
    2023-05-11T06:38:11+00:00
    https://github.com/HealUP/MyBlog/issues/16LC 20.有效的括号⭐⭐⭐⭐2024-04-14T05:46:55.284659+00:0020.有效的括号🤓🤓

    • 括号匹配是使用栈解决的经典问题

    思路分析

    分析三种情况:

    • 第一种:左括号多了(栈中遗留了一个右括号),缺右括号
    • 第二种:括号的类型没有匹配上
    • 第三种:没有左括号了,右括号多了;字符串还没遍历,栈就为空了

    代码步骤:

    • 遇到左括号、左花括号、左中括号就往栈中放入相应的右括号、右花括号、右中括号

    剪枝的条件:

    字符串—长度是奇数的话是一定不匹配的

    题解:

    class Solution {
        public boolean isValid(String s) {
            //创建一个栈,存放字符
            Deque<Character> deque = new LinkedList<>();
            char ch;
            for (int i = 0; i < s.length(); i++) {
                ch = s.charAt(i);
                //碰到左括号,就把相应的右括号放入栈中
                if (ch == '(') {
                    deque.push(')');//压入
                } else if (ch == '[') {
                    deque.push(']');
                } else if (ch == '{') {
                    deque.push('}');
                }//处理第三种情况和第二种情况,即第三种:栈为空了,栈没有要去匹配的右括号了
                //第二种:栈里面的右括号和字符串遍历的左括号不匹配
                 else if (deque.isEmpty() || deque.peek() != ch) {
                     return false;
                 } else {
                     deque.pop();//弹出
                 }
            }
            //遍历完字符串了,最后解决第一种情况:就是栈中多出来了右括号,但是字符串已经遍历完了
            //判断最后的栈内为不为空 不为空放的肯定是右括号
            return deque.isEmpty();//不为空返回false
        }
    }
    
    ]]>
    2023-05-10T06:39:17+00:00
    https://github.com/HealUP/MyBlog/issues/15LC 704.二分查找⭐⭐⭐⭐2024-04-14T05:46:55.360348+00:00704.二分查找🤓🤓 思路分析:

    二分查找法是一种在有序数组中查找某一特定元素的搜索算法。搜索过程从数组的中间元素开始,如果中间元素正好是要查找的元素,则搜索过程结束;如果某一特定元素大于或者小于中间元素,则在数组大于或小于中间元素的那一半中查找,而且跟开始一样从中间元素开始比较。如果在某一步骤数组为空,则代表找不到。这种搜索算法每一次比较都使搜索范围缩小一半

    public int binarySearch(int[] nums, int target) {
            // 在区间[left,right]中查找元素,左闭右闭
            int left = 0;
            int right = nums.length - 1;
    
            // 由于是在区间[left,right]中查找
            // 因此当left=right时,区间内还有一个元素需要查找
                while (left <= right) {
                // 计算中间点
                int mid = left + (right-left)/2;
    
                // 如果target == nums[mid]则表示已经找到,返回mid
                if (target == nums[mid]) {
                    return mid;
                    // 如果target < nums[mid],表示目标值可能在左半边
                } else if (target < nums[mid]){
                    // 由于是在左闭右闭的区间[left,right]中查找
                    // 而target < nums[mid],因此mid不再需要考虑
                    // 所以right = mid - 1,即在[left,mid-1]中继续查找
                    right = mid - 1;
    
                    // 如果target > nums[mid],表示目标值可能在右半边
                } else if (target > nums[mid]){
                    // 由于是在左闭右闭的区间[left,right]中查找
                    // 而target > nums[mid],因此mid不再需要考虑
                    // 所以left = mid + 1,即在[mid+1,right]中继续查找
                    left = mid + 1;
                }
            }
    
            // 未找到返回-1
            return -1;
        }
    
    ]]>
    2023-05-10T06:31:49+00:00
    https://github.com/HealUP/MyBlog/issues/14LC 27.移除元素 & 26.删除排序数组中的重复项⭐⭐⭐⭐2024-04-14T05:46:55.433336+00:00快慢指针

    通过一个快指针和慢指针在一个for循环下完成两个for循环的工作。

    • 暴力解法时间复杂度:O(n^2)

    • 双指针时间复杂度:O(n)

    27.移除元素🤓🤓 https://leetcode.cn/problems/remove-element/

    快慢指针:通过一个快指针和慢指针在一个for循环下完成两个for循环的工作

    class Solution {
        public int removeElement(int[] nums, int val) {
            int slowIndex = 0;//定义慢指针
            for (int fastIndex = 0; fastIndex < nums.length; fastIndex++) {
                //快指针去查找数组的值是否等于val 
                if (val != nums[fastIndex]) {
                    //nums[slowIndex++] = nums[fastIndex];
                    nums[slowIndex] = nums[fastIndex];
                    slowIndex++;
                }
            }
            return slowIndex;
        }
    }
    

    26.删除排序数组中的重复项🤓🤓

    思路分析

    利用数组有序的特点,可以通过双指针的方法删除重复元素。定义两个指针 fast 和 slow 分别为快指针和慢指针,快指针表示遍历数组到达的下标位置,慢指针表示下一个不同元素要填入的下标位置,初始时两个指针都指向下标 1。当数组的长度大于0的时候,至少都包含了一个不重复的元素,因此,nums[0]保持原状即可,从下标1开始。

    链接:https://leetcode.cn/problems/remove-duplicates-from-sorted-array/solution/shan-chu-pai-xu-shu-zu-zhong-de-zhong-fu-tudo/

    class Solution {
        public int removeDuplicates(int[] nums) {   
            //判断数组是否为0
            int n = nums.length;
            if (n == 0) {
                return 0;
            }
            int fast = 1, slow = 1;//从1开始 因为数组至少有一个元素是不重复的
            while (fast < n) {//如果整个数组包括0的话,要<=n
                if (nums[fast] != nums[fast - 1]) {
                    nums[slow] = nums[fast];//不相等则记录在慢指针中
                   // slow = slow + 1 ;//先加了再赋值
                    ++slow;
                }
                ++fast;
            }
            return slow;
        }
    }
    
    ]]>
    2023-05-10T06:29:04+00:00
    https://github.com/HealUP/MyBlog/issues/13📌刷题策略2024-04-14T05:46:55.507334+00:00以下是个人总结的刷题方法💡
    • 初刷(一题控制在半小时

      5分钟内没有头绪,直接看题解,尽力去理解,用规范的代码风格,"抄写",不懂也没关系,以后再看。

    • 精刷(一题控制在1小时

      针对初刷未理解的题,尽量全面理解答案的要义,至少能够"默写"出来,调试通过。将解题的思路,以及标准的答案记录到笔记中,笔记做好分类。如果还是未能理解,做好标记。

    • 过题(一题控制在5——10分钟

      按照分类的笔记,大量复习题目。大脑过一遍解题思路,跟正确思路对比是否正确。不需要写题

    • 复刷(一题控制在20分钟

      自言自语分析理解题意,讲清楚自己的思路,写题,检查,一遍跑通!

    • 模拟(一题控制在30分钟

      做一道新题,看看是否能达到复刷的效果,计入笔记。

    ]]>
    2023-05-10T06:22:07+00:00
    https://github.com/HealUP/MyBlog/issues/12于香港中文大学2024-04-14T05:46:55.655683+00:00这次省赛原本是2022年应该举办的,但是由于疫情延迟到了今天,本次的举办地点香港中文大学深圳校区,先附上几张图片吧。 mmexport42474e6a459898e414415f83633a8d1a_1682136901577 20230421_235020

    ]]>
    2023-04-22T10:07:33+00:00
    https://github.com/HealUP/MyBlog/issues/115月份计划🗓️2024-04-14T05:46:55.727934+00:00
  • 算法笔记迁移 & 复习旧题
  • 分类总结八股文
  • 简历项目面试准备
  • 完成工作室项目开发进度
  • 准备软考
  • 完善wyy项目
  • ]]>
    2023-04-18T01:47:17+00:00
    https://github.com/HealUP/MyBlog/issues/10蓝桥杯2024-04-14T05:46:55.800732+00:00这段时间也没怎么刷蓝桥的题了,感觉8号的蓝桥杯要g,也算是预料之内吧。当然,当初报名的初心就是能通过报名算法类的竞赛,提高自己的算法能力,然后大概100天的时间,也刷了LC大概160道(摆烂式刷题),系统学了数据结构等知识,也总结了算法笔记,让自己对曾经不敢接触的东西,有了新的认知。总之,收获还是有的,这300块的报名费,值得,目的算是达到了。希望后面的时间,也要继续做题,冲。

    ]]>
    2023-04-06T01:53:01+00:00
    https://github.com/HealUP/MyBlog/issues/9数据库又被黑了 真佛了...2024-04-14T05:46:55.878512+00:00README_TO_RECOVER_YOUR_DATA

    由于自己练手项目的数据库用的mongodb,想着练手,不重要,当时没做安全考虑,将配置文件上传到了仓库。后来想着还是添加到忽略的文件中,但是不起作用,改得了一次提交,改不了所有的提交记录,由于之前的提交记录仍然存在账号密码,想着应该没人会搞吧,即使搞了也不重要,就没去折腾了。

    好家伙,话音刚落,第二天,拿不到后台数据,发现被黑了,这是第一次 image-20230316112619224

    要我比特币,后来直接回封邮件给他 fk y ** m 当时的处置方法是,重新删掉原来的数据库,再创建mogodb容器,设置账号密码(设的比较简单)

    3.29,前端又拿不到数据了,发现数据库又被黑了 6 ,这一次是另外的人

    3dcb3a76d79e1db8b338fb74b0ed4e2

    还是同样的操作,重建了数据库,这次快多了。设置了比较强的密码!!

    期待下一次!🥲 💤

    ]]>
    2023-03-29T16:46:07+00:00
    https://github.com/HealUP/MyBlog/issues/8报了软考 预言家说:铁定能过!2024-04-14T05:46:55.939176+00:00距离2023.5.27日还有大概两个月⏰

    接下来的日子里,忙项目、背面经、找实习、备考等等,似乎忙不过来了 计划一下,好好渡过,忙有所得,忙有所思 加油吧!

    ]]>
    2023-03-25T10:52:03+00:00
    https://github.com/HealUP/MyBlog/issues/7友情链接格式✔️2024-04-14T05:46:56.013844+00:00显示到友情链接的方法:评论,然后将下面对应部分替换成你的,我点了爱心就可以自动添加啦。 名字:HealUP 链接:https://github.com/HealUP/MyBlog 描述:blog

    ]]>
    2023-03-25T05:15:45+00:00
    https://github.com/HealUP/MyBlog/issues/64月份就要结束了,一些要做的事2024-04-14T05:46:56.078001+00:004月份-4月中旬
    • 总结好项目
    • 投简历
    • 接收工作室项目
    ]]>
    2023-03-24T13:14:38+00:00
    https://github.com/HealUP/MyBlog/issues/4README看起来更顺眼了!😁2024-04-14T05:46:56.154256+00:00参考了大佬们的博客,抽了点时间美化了一下readme,主要做了以下几件事

    • wakatime 数据显示
    1. 常用编辑器上装好插件wakatime
    2. 获取token
    3. github actions 定期更新
    4. 自定义个人的actions
    • 仓库的概览
    • Top language
    • Extra pin 放了简历上的两个项目 花了点时间折腾,之前想着放年贪吃蛇吃贡献砖块的,还不小心给另外的actions给覆盖了,又让我重搞readme,崩溃🥲 又多折腾了会
    ]]>
    2023-03-24T12:30:58+00:00
    https://github.com/HealUP/MyBlog/issues/3给个人PC加了一根16G的内存条,有点起飞的感觉!2024-04-14T05:46:56.226160+00:00

    电脑买的是拯救者的y7000,20款的,当时花了6100左右,想着以后可以自己扩展内存条,并且对硬盘空间要求不是很大,就买了8g的内存,512的硬盘款的,便宜点。具体配置:

    image

    • 原装内存条是8G,一年前买了一根8G的加了进去,当时是220r,老贵了。后来发现写项目跑微服务,内存一下子飙到99%🥲很不流畅,为了提高体验感,让电脑更加流畅点,索性换了16G的进去,现在好多了,同时跑多个服务+开多个软件+网页浏览,大概60%左右,爽,速度明显提高,体验感倍儿好。

    0988cbc17653e4404b389f76f56d4bf

    ]]>
    2023-03-23T15:53:29+00:00
    https://github.com/HealUP/MyBlog/issues/2支持RSS订阅2024-04-14T05:46:56.341838+00:00大家可以将RSS链接添加到阅读器里,方便及时获取到博客的内容,感谢关注~❤️

    ]]>
    2023-03-23T08:16:09+00:00
    https://github.com/HealUP/MyBlog/issues/1GitHub-Blog 诞生2024-04-14T05:46:56.408532+00:00
  • 测试actions

  • 问题 image

  • 问题解决 image

  • over! 把自己Typora 的博客都迁移到这!

  • ]]>
    2023-03-23T03:25:51+00:00