likes
comments
collection
share

查漏补缺第十六期(华为二面)

作者站长头像
站长
· 阅读数 63

前言

目前正在出一个查漏补缺专题系列教程, 篇幅会较多, 喜欢的话,给个关注❤️ ~

本专题主要以Java语言为主, 好了, 废话不多说直接开整吧~

算法题: 奇偶链表 (力扣328题)

给定单链表的头节点 head ,将所有索引为奇数的节点和索引为偶数的节点分别组合在一起,然后返回重新排序的列表。

第一个节点的索引被认为是 奇数 , 第二个节点的索引为 偶数 ,以此类推。

请注意,偶数组和奇数组内部的相对顺序应该与输入时保持一致。

输入: head = [1,2,3,4,5]
输出: [1,3,5,2,4]
输入: head = [2,1,3,5,6,4,7]
输出: [2,3,6,7,1,5,4]
public class Q328 {

    public static class ListNode {
        int val;
        ListNode next;

        ListNode(int val) {
            this.val = val;
            this.next = null;
        }
    }



    public static ListNode oddEvenList(ListNode head) {
        if (head == null || head.next == null) {
            return head;
        }

        ListNode oddHead = head;
        ListNode evenHead = head.next;
        ListNode oddTail = oddHead;
        ListNode evenTail = evenHead;

        while (evenTail != null && evenTail.next != null) {
            oddTail.next = evenTail.next;
            oddTail = oddTail.next;
            evenTail.next = oddTail.next;
            evenTail = evenTail.next;
        }

        oddTail.next = evenHead;
        return oddHead;
    }

    public static void main(String[] args) {
        // Test Case 1: [1,2,3,4,5]
        ListNode head1 = new ListNode(1);
        head1.next = new ListNode(2);
        head1.next.next = new ListNode(3);
        head1.next.next.next = new ListNode(4);
        head1.next.next.next.next = new ListNode(5);
        ListNode result1 = oddEvenList(head1);
        printList(result1); // Expected: [1,3,5,2,4]

        // Test Case 2: [2,1,3,5,6,4,7]
        ListNode head2 = new ListNode(2);
        head2.next = new ListNode(1);
        head2.next.next = new ListNode(3);
        head2.next.next.next = new ListNode(5);
        head2.next.next.next.next = new ListNode(6);
        head2.next.next.next.next.next = new ListNode(4);
        head2.next.next.next.next.next.next = new ListNode(7);
        ListNode result2 = oddEvenList(head2);
        printList(result2); // Expected: [2,3,6,7,1,5,4]
    }

    private static void printList(ListNode head) {
        while (head != null) {
            System.out.print(head.val + " ");
            head = head.next;
        }
        System.out.println();
    }
}

上述代码中,我们使用两个指针oddHeadevenHead来分别表示奇数节点链表的头和偶数节点链表的头。然后,我们使用oddTailevenTail来分别表示奇数节点链表的尾部和偶数节点链表的尾部。

在遍历原始链表时,我们将奇数位置的节点连接到oddTail后面,并将偶数位置的节点连接到evenTail后面。最后,我们将奇数节点链表的尾部连接到偶数节点链表的头部,从而完成重新排序。

时间复杂度为O(n),其中n是链表的节点数 空间复杂度O(1)

Mysql常用的数据类型

MySQL中常用的数据类型包括以下几种:

  1. 整数类型(Integer Types):用于存储整数值,常见的整数类型有:

    • TINYINT:占用1字节,范围为-128到127(有符号)或0到255(无符号)。
    • SMALLINT:占用2字节,范围为-32768到32767(有符号)或0到65535(无符号)。
    • MEDIUMINT:占用3字节,范围为-8388608到8388607(有符号)或0到16777215(无符号)。
    • INT:占用4字节,范围为-2147483648到2147483647(有符号)或0到4294967295(无符号)。
    • BIGINT:占用8字节,范围为-9223372036854775808到9223372036854775807(有符号)或0到18446744073709551615(无符号)。
  2. 浮点数类型(Floating-Point Types):用于存储浮点数值,常见的浮点数类型有:

    • FLOAT:占用4字节,单精度浮点数,范围约为-3.402823466E+38到-1.175494351E-38、0和1.175494351E-38到3.402823466E+38。
    • DOUBLE:占用8字节,双精度浮点数,范围约为-1.7976931348623157E+308到-2.2250738585072014E-308、0和2.2250738585072014E-308到1.7976931348623157E+308。
  3. 定点数类型(Decimal Types):用于存储精确的小数值,常见的定点数类型有:

    • DECIMAL(M, D):占用可变长度,用于存储固定精度的小数,M表示总位数,D表示小数位数。
  4. 字符串类型(String Types):用于存储文本数据,常见的字符串类型有:

    • CHAR(N):固定长度的字符串,最多存储N个字符。
    • VARCHAR(N):可变长度的字符串,最多存储N个字符。
    • TEXT:可变长度的文本字符串,最多存储65535个字符。
  5. 日期和时间类型(Date and Time Types):用于存储日期和时间数据,常见的日期和时间类型有:

    • DATE:存储日期,格式为'YYYY-MM-DD'。
    • TIME:存储时间,格式为'HH:MM:SS'。
    • DATETIME:存储日期和时间,格式为'YYYY-MM-DD HH:MM:SS'。
    • TIMESTAMP:存储日期和时间,范围为'1970-01-01 00:00:01'到'2038-01-19 03:14:07'。
  6. 布尔类型(Boolean Type):用于存储布尔值,常见的布尔类型有:

    • BOOL:存储布尔值,取值为TRUE或FALSE。
    • BOOLEAN:存储布尔值,取值为TRUE或FALSE。
  7. 枚举类型(Enumeration Type):用于存储枚举值,枚举类型定义了一个可选值的列表,常见的枚举类型有:

    • ENUM('value1', 'value2', ...):存储从列表中选择的一个值。
  8. 集合类型(Set Type):用于存储集合值,集合类型定义了一个可选值的列表,常见的集合类型有:

    • SET('value1', 'value2', ...):存储从列表中选择的一个或多个值。

除了上述常见的数据类型外,MySQL还支持其他一些特殊的数据类型,如二进制类型(Binary Types)、JSON类型等,用于存储特定类型的数据。

Java集合框架的主类是什么,HashSet有没有继承Collection

Java集合框架的主类是java.util.Collection

HashSetCollection接口的实现类,它实现了Set接口,因此它同时继承了Collection接口和Set接口。Set接口是Collection接口的子接口,用于表示不允许重复元素的集合。HashSet通过哈希表实现,它不保证元素的顺序,且允许使用null元素。

在继承关系中,HashSet的继承关系如下:

HashSet -> AbstractSet -> AbstractCollection -> Object

可以看到,HashSet直接继承了AbstractSet类,而AbstractSet类又是通过继承AbstractCollection类实现的,最终都继承自Object类。所以,HashSet间接继承了Collection接口。

有了解过哪些排序算法,说说原理

以下是一些经典的排序算法:

  1. 冒泡排序(Bubble Sort):比较相邻元素的大小,并根据需要交换它们的位置,重复这个过程直到整个序列排序完成。

  2. 选择排序(Selection Sort):每次从未排序的部分选择最小(或最大)的元素,并将其放置在已排序部分的末尾,重复这个过程直到整个序列排序完成。

  3. 插入排序(Insertion Sort):将未排序部分的元素逐个插入到已排序部分的合适位置,重复这个过程直到整个序列排序完成。

  4. 快速排序(Quick Sort):选择一个基准元素,将序列分割为两部分,小于基准元素的放在左边,大于基准元素的放在右边,然后对左右两部分递归地进行快速排序。

  5. 归并排序(Merge Sort):将序列分割为较小的子序列,然后递归地排序子序列,最后将排序好的子序列合并成一个有序序列。

  6. 堆排序(Heap Sort):构建一个最大(或最小)堆,然后将堆顶元素与最后一个元素交换,然后重新调整堆使其满足堆的性质,重复这个过程直到整个序列排序完成。

  7. 计数排序(Counting Sort):根据序列中每个元素的计数,统计小于或等于该元素的元素个数,然后根据统计结果将元素放置到正确的位置。

  8. 桶排序(Bucket Sort):将元素分配到不同的桶中,对每个桶中的元素进行排序,最后按照桶的顺序将所有元素合并成一个有序序列。

  9. 基数排序(Radix Sort):根据元素的位数将序列排序,从最低位到最高位依次进行排序,最终得到有序序列。

String能否被继承

在Java中,String类是被final修饰的,因此不能被继承。当一个类被声明为final时,意味着它不能被其他类所继承。

这是因为String类在Java中被设计为不可变(immutable)的,即一旦创建了一个String对象,就不能修改其内容。这种设计有助于字符串的安全性和线程安全性。如果String类可以被继承,那么子类可能会重写String类中的方法,从而可能破坏字符串的不可变性。

因此,为了保持字符串的特性和安全性,Java语言设计者将String类声明为final,禁止其他类继承它。如果需要在String类的基础上进行扩展或自定义,可以通过创建新的类并包含String对象作为成员变量来实现。

Java内存泄露了该如何排查

Java内存泄漏是指在程序中存在不再使用的对象,但由于某些原因(例如对象的引用未被正确释放),导致这些对象无法被垃圾回收器回收,最终导致内存占用过高或内存不足的问题。

以下是一些用于排查Java内存泄漏问题的常用方法:

  1. 使用内存分析工具:使用专业的内存分析工具(如Eclipse Memory Analyzer、VisualVM、MAT等)来检测和分析内存泄漏问题。这些工具可以提供详细的内存使用情况、对象引用关系等信息,帮助定位内存泄漏的源头。

  2. 检查代码:仔细审查代码,查找潜在的内存泄漏点。特别关注以下情况:

    • 长期持有对象的引用,导致无法被垃圾回收。
    • 静态集合或缓存中的对象未被正确移除。
    • 未关闭的IO流或数据库连接。
    • 监听器或回调未正确注销。
    • 大对象或大集合未及时释放。
  3. 内存监控和日志:使用Java的内存监控工具(如JVM自带的jstat命令)监视内存使用情况,并通过日志记录关键的内存分配、回收和对象生命周期等信息。这有助于发现内存占用异常或内存泄漏的迹象。

  4. Heap Dump分析:当发现内存泄漏问题时,生成Heap Dump文件,然后使用内存分析工具来分析该文件。Heap Dump提供了当前Java堆内存的快照,可以查看对象实例、引用关系和内存占用等信息,帮助定位内存泄漏的原因。

  5. 性能测试和压力测试:在进行性能测试和压力测试时,监测系统的内存使用情况。如果发现内存持续增长或内存占用过高,可能存在内存泄漏的问题。

  6. 代码审查和重构:请同事或其他开发者对代码进行审查,从不同的角度来寻找潜在的内存泄漏问题。有时候可能需要对代码进行重构,以避免潜在的内存泄漏风险。

静态代理和动态代理

在Java中,静态代理和动态代理都是实现代理模式的方式,用于实现对对象的间接访问控制。代理模式可以通过代理对象来控制目标对象的访问,并可以在目标对象的方法执行前后执行额外的操作。

静态代理(Static Proxy): 静态代理是在编译期间就已经确定代理类和被代理类的关系,需要手动编写代理类。代理类和被代理类都实现相同的接口,代理类持有被代理类的实例,并在代理类的方法中调用被代理类的方法。静态代理的关键是在代理类中显式地创建被代理类的对象,并在方法调用前后添加额外的逻辑。

静态代理的优点是简单、易于理解和实现,缺点是当被代理类的方法增加或修改时,代理类也需要相应地进行修改。

// 接口:定义被代理对象和代理对象共同的行为
interface Subject {
    void doSomething();
}

// 被代理类
class RealSubject implements Subject {
    public void doSomething() {
        System.out.println("RealSubject is doing something.");
    }
}

// 代理类
class ProxySubject implements Subject {
    private RealSubject realSubject;

    public ProxySubject(RealSubject realSubject) {
        this.realSubject = realSubject;
    }

    public void doSomething() {
        System.out.println("ProxySubject is doing something before RealSubject.");
        realSubject.doSomething();
        System.out.println("ProxySubject is doing something after RealSubject.");
    }
}

// 使用静态代理
public class StaticProxyExample {
    public static void main(String[] args) {
        RealSubject realSubject = new RealSubject();
        ProxySubject proxySubject = new ProxySubject(realSubject);
        proxySubject.doSomething();
    }
}


动态代理(Dynamic Proxy): 动态代理是在运行时动态生成代理类的方式,不需要手动编写代理类。Java中的动态代理机制是通过反射来实现的。代理类在运行时根据指定的接口或类动态生成,代理类的方法会被重定向到调用处理器(InvocationHandler)中的invoke方法。在invoke方法中可以执行额外的逻辑,如在方法调用前后进行一些操作。

Java中提供了java.lang.reflect包来支持动态代理。常用的动态代理类有ProxyInvocationHandlerProxy类用于创建代理类的实例,InvocationHandler接口定义了代理类的调用处理器。

动态代理的优点是可以在运行时动态地生成代理类,无需手动编写代理类,适用于代理多个类或接口的情况。缺点是相对于静态代理,动态代理的实现较为复杂。

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

// 接口:定义被代理对象和代理对象共同的行为
interface Subject {
    void doSomething();
}

// 被代理类
class RealSubject implements Subject {
    public void doSomething() {
        System.out.println("RealSubject is doing something.");
    }
}

// 实现 InvocationHandler 接口的调用处理器类
class ProxyHandler implements InvocationHandler {
    private Object target;

    public ProxyHandler(Object target) {
        this.target = target;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Proxy is doing something before RealSubject.");
        Object result = method.invoke(target, args);
        System.out.println("Proxy is doing something after RealSubject.");
        return result;
    }
}

// 使用动态代理
public class DynamicProxyExample {
    public static void main(String[] args) {
        RealSubject realSubject = new RealSubject();
        InvocationHandler handler = new ProxyHandler(realSubject);

        // 创建动态代理类的实例
        Subject proxySubject = (Subject) Proxy.newProxyInstance(
                realSubject.getClass().getClassLoader(),
                realSubject.getClass().getInterfaces(),
                handler
        );

        proxySubject.doSomething();
    }
}

总结:

静态代理动态代理都是实现代理模式的方式,用于实现对对象的间接访问控制。静态代理需要手动编写代理类,而动态代理是在运行时动态生成代理类。静态代理在编译期间确定代理类和被代理类的关系,动态代理在运行时根据接口或类动态生成代理类。动态代理使用反射机制来实现,在调用处理器中可以添加额外的逻辑。

结束语

大家可以针对自己薄弱的地方进行复习, 然后多总结,形成自己的理解,不要去背~

本着把自己知道的都告诉大家,如果本文对您有所帮助,点赞+关注鼓励一下呗~

相关文章

项目源码(源码已更新 欢迎star⭐️)

往期设计模式相关文章

设计模式项目源码(源码已更新 欢迎star⭐️)

Kafka 专题学习

项目源码(源码已更新 欢迎star⭐️)

ElasticSearch 专题学习

项目源码(源码已更新 欢迎star⭐️)

往期并发编程内容推荐

推荐 SpringBoot & SpringCloud (源码已更新 欢迎star⭐️)

博客(阅读体验较佳)

转载自:https://juejin.cn/post/7249381456049012795
评论
请登录