gitweixin
  • 首页
  • 小程序代码
    • 资讯读书
    • 工具类
    • O2O
    • 地图定位
    • 社交
    • 行业软件
    • 电商类
    • 互联网类
    • 企业类
    • UI控件
  • 大数据开发
    • Hadoop
    • Spark
    • Hbase
    • Elasticsearch
    • Kafka
    • Flink
    • 数据仓库
    • 数据挖掘
    • flume
    • Kafka
    • Hive
    • shardingsphere
    • solr
  • 开发博客
    • Android
    • php
    • python
    • 运维
    • 技术架构
    • 数据库
  • 程序员网赚
  • bug清单
  • 量化投资
  • 在线查询工具
    • 去行号
    • 在线时间戳转换工具
    • 免费图片批量修改尺寸在线工具
    • SVG转JPG在线工具

年度归档2024

精品微信小程序开发门户,代码全部亲测可用

  • 首页   /  
  • 2024
Java, 面试 12月 29,2024

java多线程的实现的各种方法?

​

在 Java 中,实现多线程主要有以下几种方法:

  • 继承 Thread 类:通过创建一个继承自 Thread 类的子类,并重写 run () 方法来定义线程的执行逻辑。然后创建该子类的实例,并调用 start () 方法启动线程。例如,以下代码创建了一个简单的线程类:

class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("线程正在运行");
    }
}

public class Main {
    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.start();
    }
}
  • 实现 Runnable 接口:创建一个实现 Runnable 接口的类,并重写 run () 方法。然后创建该类的实例,并将其作为参数传递给 Thread 类的构造函数,最后调用 start () 方法启动线程。例如:

class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("线程正在运行");
    }
}

public class Main {
    public static void main(String[] args) {
        MyRunnable runnable = new MyRunnable();
        Thread thread = new Thread(runnable);
        thread.start();
    }
}
  • 使用 Callable 和 Future 接口:Callable 接口类似于 Runnable 接口,但它可以返回一个结果。Future 接口用于获取 Callable 任务的结果。可以通过创建一个实现 Callable 接口的类,并重写 call () 方法来定义线程的执行逻辑和返回结果。然后使用 ExecutorService 提交 Callable 任务,并通过 Future 获取任务的结果。例如:

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

class MyCallable implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        return 1 + 1;
    }
}

public class Main {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService executor = Executors.newSingleThreadExecutor();
        Future<Integer> future = executor.submit(new MyCallable());
        Integer result = future.get();
        System.out.println("结果:" + result);
        executor.shutdown();
    }
}
  • 使用线程池:线程池可以管理和复用线程,减少线程创建和销毁的开销。可以通过使用 ExecutorService 接口和其实现类来创建线程池,并提交任务到线程池中执行。例如:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Main {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(5);
for (int i = 0; i < 10; i++) { executor.execute(() -> {
System.out.println(“线程正在运行”);
});
}
executor.shutdown();
}
}

作者 east
Java, 面试 12月 29,2024

面试必问题:单例模式的5种写法

​

单例模式主要有以下几种分类:

  • 饿汉式单例:在类加载时就创建单例对象,优点是实现简单,线程安全,缺点是如果单例对象在程序运行过程中一直未被使用,会造成资源浪费。例如:
public class Singleton {
    private static Singleton instance = new Singleton();

    private Singleton() {}

    public static Singleton getInstance() {
        return instance;
    }
}
  • 懒汉式单例:在第一次调用 getInstance 方法时才创建单例对象,优点是可以延迟加载,节省资源,但是在多线程环境下需要考虑线程安全问题。例如:

public class Singleton {
    private static Singleton instance;

    private Singleton() {}

    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}
  • 双重检查锁单例:在懒汉式单例的基础上,通过双重检查锁机制来提高性能并保证线程安全。例如:

public class Singleton {
    private volatile static Singleton instance;

    private Singleton() {}

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}
  • 静态内部类单例:利用静态内部类的特性来实现单例,既保证了线程安全,又实现了延迟加载。例如:

public class Singleton {
    private Singleton() {}

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

    public static Singleton getInstance() {
        return SingletonHolder.instance;
    }
}
  • 枚举单例:通过枚举类型来实现单例,不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。例如:

public enum Singleton {
    INSTANCE;

    public void doSomething() {
        // 单例对象的方法
    }
}

在多线程环境下,保证单例模式的线程安全可以采用以下几种方法:

  • 使用 synchronized 关键字:在获取单例对象的方法上添加 synchronized 关键字,如懒汉式单例中的 getInstance 方法,这样可以保证在同一时刻只有一个线程能够访问该方法,从而避免多个线程同时创建单例对象。但是这种方式会降低性能,因为每次调用该方法都需要获取锁。
  • 双重检查锁机制:在懒汉式单例的基础上,通过双重检查锁机制来减少同步的开销。在第一次检查实例是否为 null 时,如果不为 null,则直接返回实例,不需要获取锁;只有在第一次检查为 null 时,才进入同步块再次检查并创建实例。同时,需要将实例变量声明为 volatile,以保证可见性和禁止指令重排序。
  • 静态内部类:利用静态内部类的特性,在类加载时不会立即初始化内部类,只有在第一次调用 getInstance 方法时,才会加载并初始化内部类,从而创建单例对象。由于类加载过程是线程安全的,所以这种方式可以保证单例的线程安全,并且实现了延迟加载。
  • 枚举:枚举类型在 Java 中是天然的单例,因为 Java 虚拟机在加载枚举类时会保证只有一个实例存在,并且枚举类的实例是线程安全的,所以可以通过枚举来实现单例模式,避免了复杂的线程安全处理。

​

作者 east
bug清单, 大数据开发 12月 25,2024

解决idea运行scala代码报错:scala: No ‘scala-library*.jar’ in Scala compiler classpath in Scala SDK Maven: org.scala-lang:scala-library:2.11.12

这个错误信息表明,Scala 编译器的 classpath 中缺少必要的 scala-library*.jar 文件,特别是 Scala SDK 所需的 scala-library 版本 2.11.12。错误发生的原因通常是因为项目的构建配置缺失或错误,导致 Scala 编译器无法找到正确的库文件。

分析原因:

  1. Maven 配置问题: 错误信息中提到 Maven: org.scala-lang:scala-library:2.11.12,这表明你的项目在使用 Maven 来管理依赖。可能是 pom.xml 文件中的 Scala 依赖配置不正确,导致缺少 scala-library JAR 文件。
  2. IDE 配置问题: 另一个可能的原因是 IntelliJ IDEA 中的 Scala SDK 配置不完整或错误。Scala SDK 包括了编译器和库,如果 SDK 配置不正确,IDE 就无法正确找到必要的库文件。

解决方案:

1. 检查 pom.xml 文件中的 Scala 依赖

确保 pom.xml 中包含了正确版本的 Scala 依赖。如果没有,请添加类似下面的配置:

<dependency>
    <groupId>org.scala-lang</groupId>
    <artifactId>scala-library</artifactId>
    <version>2.11.12</version>
</dependency>

如果你使用的是其他 Scala 版本(如 2.13 或 3.x),需要替换为相应的版本号。

2. 确保项目中配置了正确的 Scala 编译器和 SDK

  • 在 IntelliJ IDEA 中,检查你是否已经配置了 Scala SDK。
    • 打开 File -> Project Structure -> Modules -> 选择你的模块 -> Dependencies,确保选择了正确的 Scala SDK。
    • 你也可以在 Project Structure 中检查 Scala SDK 的版本是否与你项目中使用的版本匹配。

3. 更新或重新下载 Scala 库

  • 在 IntelliJ IDEA 中,尝试通过右键点击项目根目录并选择 Maven -> Reimport 来重新加载依赖。
  • 如果仍然无法解决问题,可以尝试删除 ~/.m2/repository/org/scala-lang/scala-library/ 下的对应版本文件,然后重新构建项目。

如果上面的方法都没办法解决,可以删除
File>>Project Structure>>Libraries中删除默认的scala编译library,替换成本地的scala-sdk 。

首先在Global Libraries中添加本地scala-sdk

Modules -> 选择你的模块 -> Dependencies,确保选择本地 Scala SDK。


作者 east
Java 12月 22,2024

大厂面试手撕面试题:反转链表中前 K 个节点(亲测可用的java实现)

我们需要反转链表中前 k 个节点,且可能会有多个这样的反转段(每 k 个节点为一组)。如果链表的剩余部分不满 k 个节点,我们就不对其进行反转,直接保留原样。

解题思路:

  1. 链表结构:
    • 我们有一个单链表,定义为 ListNode 类型。每个节点包含一个值 val 和指向下一个节点的指针 next。
    • 目标是反转链表中前 k 个节点,并对每一组长度为 k 的节点进行反转,直到链表遍历完。
  2. 关键步骤:
    • 判断当前段是否满 k 个节点: 在反转之前,我们必须确认当前的节点数是否足够 k 个。如果不足,直接跳过该段,保持原链表顺序。
    • 反转当前 k 个节点: 如果当前段正好有 k 个节点,我们就进行反转。
    • 连接反转后的链表: 反转后的链表需要和之前的链表正确连接,以保证链表结构的完整性。
  3. 具体步骤:
    • 维护一个 dummy 虚拟节点,它的 next 指针指向链表头。这样可以避免链表头部的特殊情况处理,使得头节点的反转与其他节点反转保持一致。
    • 使用 prevGroupEnd 指针来标记上一个反转段的最后一个节点,它在反转完成后需要连接到当前反转段的最后一个节点。
    • 对每一组长度为 k 的节点进行反转。具体做法是使用 prev 和 curr 两个指针,通过迭代的方式完成每组的反转。
    • 完成一组反转后,更新 prevGroupEnd 和 head,继续处理下一组。
  4. 终止条件:
    • 当链表遍历完,或者剩下的节点不足 k 个时,停止反转操作。

代码详解:

public class Solution {
    
    public ListNode reverseKGroup(ListNode head, int k) {
        // 如果链表为空或 k 为 1,直接返回头节点
        if (head == null || k == 1) return head;
        
        // 创建一个虚拟节点,用于简化操作
        ListNode dummy = new ListNode(0);
        dummy.next = head;
        
        // prevGroupEnd 用于指向前一组反转后链表的末尾节点
        ListNode prevGroupEnd = dummy;
        
        // 当前头节点 head
        while (head != null) {
            // 找到当前组的最后一个节点
            ListNode groupEnd = head;
            int count = 1;
            // 计算当前组的节点数量
            while (groupEnd != null && count < k) {
                groupEnd = groupEnd.next;
                count++;
            }
            
            // 如果当前组的节点数不足 K 个,不需要反转,直接返回
            if (groupEnd == null) break;
            
            // 反转当前组的节点
            ListNode nextGroupStart = groupEnd.next;  // 保存下一个组的起始节点
            ListNode prev = nextGroupStart;  // prev 初始指向下一组的起始节点
            ListNode curr = head;  // curr 初始指向当前组的头节点
            
            // 反转当前组的节点
            while (curr != nextGroupStart) {
                ListNode temp = curr.next;  // 暂存当前节点的下一个节点
                curr.next = prev;  // 将当前节点的 next 指向 prev(反转)
                prev = curr;  // prev 更新为当前节点
                curr = temp;  // curr 更新为下一个节点
            }
            
            // 将上一组的尾节点连接到当前反转后的组的头节点
            prevGroupEnd.next = groupEnd;
            // 当前组的头节点(反转后)需要指向下一组的起始节点
            head.next = nextGroupStart;
            
            // 更新 prevGroupEnd 和 head,准备处理下一组
            prevGroupEnd = head;
            head = nextGroupStart;
        }
        
        return dummy.next; // 返回新的链表头节点
    }

    // 辅助方法:打印链表
    public void printList(ListNode head) {
        while (head != null) {
            System.out.print(head.val + " ");
            head = head.next;
        }
        System.out.println();
    }

    public static void main(String[] args) {
        Solution solution = new Solution();
        
        // 创建一个链表 1 -> 2 -> 3 -> 4 -> 5
        ListNode head = new ListNode(1);
        head.next = new ListNode(2);
        head.next.next = new ListNode(3);
        head.next.next.next = new ListNode(4);
        head.next.next.next.next = new ListNode(5);
        
        int k = 3;
        
        // 输出原链表
        System.out.print("Original List: ");
        solution.printList(head);
        
        // 调用反转链表前 K 个节点的方法
        ListNode newHead = solution.reverseKGroup(head, k);
        
        // 输出反转后的链表
        System.out.print("List after reversing first " + k + " nodes: ");
        solution.printList(newHead);
    }
}

详细解析:

  1. 虚拟节点 dummy:
    • dummy 节点的作用是简化链表头部的操作,避免对链表头的特殊处理。通过将 dummy.next 设置为链表的头节点,我们可以统一处理链表的反转和连接。
  2. 变量 prevGroupEnd:
    • prevGroupEnd 用于连接每次反转后组的最后一个节点。初始化时,它指向虚拟节点 dummy。
    • 在每次反转完成后,prevGroupEnd 被更新为当前反转组的头节点 head。
  3. 变量 groupEnd 和 count:
    • groupEnd 用于记录当前反转组的最后一个节点。如果当前组不足 k 个节点,则不进行反转。
    • count 用来计算当前组的节点数。如果 count 达到 k,则进行反转操作。
  4. 反转操作:
    • 反转时,通过三个指针 prev(指向反转后的部分),curr(当前处理的节点),和 temp(暂存当前节点的下一个节点)来完成每一组的节点反转。
    • 完成反转后,将 prevGroupEnd 的 next 指向当前反转组的尾节点 groupEnd,并将 head.next 指向剩余部分的起始节点。
  5. 循环和终止条件:
    • 每次反转 k 个节点后,将 head 更新为剩余部分的起始节点,继续进行下一组的处理。
    • 当剩余节点不足 k 个时,循环终止。

时间复杂度:

  • O(n): 每个节点会被访问并反转一次,总时间复杂度为 O(n),其中 n 是链表的节点数。

空间复杂度:

  • O(1): 只使用了常数的额外空间,除了输入的链表本身。

示例:

对于链表 1 -> 2 -> 3 -> 4 -> 5 和 k = 3,执行反转操作后,链表将变为 3 -> 2 -> 1 -> 4 -> 5。

作者 east
Java 12月 22,2024

大厂面试手撕面试题:删除字符串中的指定字符(亲测可用的java实现)

要求:给定一个字符串 s 和一个字符 t,要求删除字符串中所有出现的字符 t,并返回处理后的新字符串。

Java代码实现:

public class Solution {
    public static String removeCharacter(String s, char t) {
        // 使用 StringBuilder 来构建结果字符串
        StringBuilder result = new StringBuilder();

        // 遍历原字符串,拼接不等于 t 的字符
        for (int i = 0; i < s.length(); i++) {
            if (s.charAt(i) != t) {
                result.append(s.charAt(i));
            }
        }

        // 返回结果字符串
        return result.toString();
    }

    public static void main(String[] args) {
        String s = "hello world";
        char t = 'o';
        
        String result = removeCharacter(s, t);
        System.out.println(result); // 输出 "hell wrld"
    }
}

代码解释:

  1. removeCharacter 方法:
    • 输入:一个字符串 s 和一个字符 t。
    • 使用 StringBuilder 来构建新的字符串,这样在处理大量字符时会更加高效。
    • 遍历原字符串 s,将不等于 t 的字符添加到 StringBuilder 中。
    • 最终通过 toString() 返回结果。
  2. main 方法:
    • 示例:给定字符串 "hello world",删除字符 'o' 后,结果是 "hell wrld"。

时间复杂度:

  • 遍历一次字符串,时间复杂度为 O(n),其中 n 是字符串 s 的长度。

空间复杂度:

  • 由于使用了 StringBuilder 来存储新的字符串,空间复杂度为 O(n),其中 n 是字符串 s 的长度。
作者 east
Java, 面试 12月 22,2024

大厂面试手撕面试题:一个字符串中最长的没有重复字符的子串(亲测可用的java实现)

要解决 “求解一个字符串中最长的没有重复字符的子串” 的问题,可以使用 滑动窗口(Sliding Window)算法。这个算法通过维护一个动态窗口来跟踪当前没有重复字符的子串,然后逐步扩展窗口来找到最长的子串。

思路:

  1. 使用两个指针,left 和 right,表示当前窗口的左右边界。
  2. 通过哈希集合(HashSet 或 HashMap)来存储窗口中的字符,确保每个字符在窗口内唯一。
  3. 从左到右遍历字符串,每次右指针right向右移动,检查字符是否已经在窗口中存在。如果存在,就将左指针left向右移动,直到窗口中没有重复字符。
  4. 更新最长子串的长度。

Java实现:

import java.util.HashSet;

public class LongestSubstring {
    public static int lengthOfLongestSubstring(String s) {
        // 哈希集合用于存储窗口中的字符
        HashSet<Character> set = new HashSet<>();
        
        // 定义左右指针和最大长度
        int left = 0;
        int maxLength = 0;
        
        // 右指针向右移动,遍历字符串
        for (int right = 0; right < s.length(); right++) {
            // 如果右指针指向的字符在集合中,说明有重复字符
            // 移动左指针直到去除重复字符
            while (set.contains(s.charAt(right))) {
                set.remove(s.charAt(left));
                left++;
            }
            
            // 将当前字符加入集合
            set.add(s.charAt(right));
            
            // 更新最大长度
            maxLength = Math.max(maxLength, right - left + 1);
        }
        
        return maxLength;
    }

    public static void main(String[] args) {
        String s = "abcabcbb";
        System.out.println("The length of the longest substring without repeating characters is: " + lengthOfLongestSubstring(s));
    }
}

解释:

  1. HashSet<Character> set = new HashSet<>();:
    • 用于存储当前窗口内的字符,确保窗口内的字符唯一。
  2. left 和 right 指针:
    • right指针用来遍历字符串。
    • left指针控制窗口的起始位置,确保窗口内没有重复字符。
  3. 滑动窗口的调整:
    • 当遇到重复字符时,移动left指针,直到窗口内不再有重复字符。
    • 每次移动时,都会更新maxLength,保持最大窗口的长度。
  4. 时间复杂度:
    • 最坏情况下,left 和 right 每个指针最多各走一次字符串,因此时间复杂度是 O(n),其中 n 是字符串的长度。

示例输出:

对于输入 "abcabcbb",输出结果是:

The length of the longest substring without repeating characters is: 3 

该子串是 "abc",其长度为 3。

作者 east
Java 12月 22,2024

大厂面试手撕面试题:冒泡排序(亲测可用的java实现)

冒泡排序(Bubble Sort)是一种简单的排序算法。它通过重复遍历待排序的列表,比较相邻元素并交换它们的位置,直到整个列表排序完成。每次遍历都会把当前未排序部分的最大值“冒泡”到数组的最后。

思路:

  1. 从数组的第一个元素开始,逐个比较相邻的元素。
  2. 如果当前元素大于下一个元素,交换它们的位置。
  3. 每一轮遍历后,最大的元素会被“冒泡”到数组的末尾。
  4. 每次遍历的长度逐渐减少,因为已经排序好的元素已经“浮到”数组的末尾。
  5. 直到没有需要交换的元素时,排序完成。

冒泡排序的优化:

  • 如果在某次遍历中没有进行交换,说明数组已经是有序的,可以提前结束排序,避免不必要的遍历。

Java 完整代码实现:

public class BubbleSort {
    public static void main(String[] args) {
        // 测试数组
        int[] arr = {5, 2, 9, 1, 5, 6};
        
        // 调用冒泡排序
        bubbleSort(arr);
        
        // 打印排序后的数组
        System.out.println("Sorted array:");
        for (int num : arr) {
            System.out.print(num + " ");
        }
    }
    
    // 冒泡排序方法
    public static void bubbleSort(int[] arr) {
        int n = arr.length;
        
        // 外层循环控制排序的轮数
        for (int i = 0; i < n - 1; i++) {
            boolean swapped = false;  // 标记是否发生了交换
            
            // 内层循环进行相邻元素比较和交换
            for (int j = 0; j < n - 1 - i; j++) {
                if (arr[j] > arr[j + 1]) {
                    // 交换 arr[j] 和 arr[j + 1]
                    int temp = arr[j];
                    arr[j] = arr[j + 1];
                    arr[j + 1] = temp;
                    swapped = true;
                }
            }
            
            // 如果没有发生交换,说明数组已经有序,提前结束
            if (!swapped) {
                break;
            }
        }
    }
}

代码解释:

  1. 主方法 (main):初始化一个整数数组并调用 bubbleSort 方法进行排序。排序后打印数组。
  2. 冒泡排序方法 (bubbleSort):
    • 外层循环控制排序的轮次,每一轮确保将最大的元素“冒泡”到末尾。
    • 内层循环执行相邻元素的比较和交换。如果当前元素比下一个元素大,则交换。
    • swapped 是一个标记,用来检查在某一轮遍历中是否发生了交换。如果某轮没有交换,说明数组已经是有序的,可以提前结束排序。

时间复杂度:

  • 最坏情况下,时间复杂度为 O(n^2),即当数组是逆序时。
  • 最好情况下(当数组已经是有序的),时间复杂度为 O(n),因为只会进行一次遍历且没有发生任何交换。

空间复杂度:

  • O(1),因为该算法是原地排序,不需要额外的空间。
作者 east
Java 12月 22,2024

大厂面试手撕面试题:快速排序(亲测可用的java实现)

快速排序(Quick Sort)是一种常用的排序算法,采用分治法(Divide and Conquer)进行排序。其基本思路是通过选择一个基准元素(pivot),将待排序的数组分成两部分,一部分所有元素都小于基准元素,另一部分所有元素都大于基准元素。然后递归地对这两部分继续进行排序,最终达到排序整个数组的效果。

快速排序的步骤:

  1. 选择基准元素:选择数组中的一个元素作为基准元素(常见的选择有第一个元素、最后一个元素、随机选择等)。
  2. 分区操作:将数组分成两部分,小于基准的放左边,大于基准的放右边。基准元素最终的位置已经确定。
  3. 递归排序:对基准元素左侧和右侧的子数组进行递归调用快速排序,直到子数组的大小为1或0,排序完成。

时间复杂度:

  • 最佳情况:O(n log n),发生在每次分割时都能平衡地分成两部分。
  • 最坏情况:O(n^2),当数组已经有序或反向有序时,每次选择的基准元素都可能是最小或最大的元素,从而导致不均匀的分割。
  • 平均情况:O(n log n),在大多数情况下,快速排序的时间复杂度表现良好。

空间复杂度:

  • 快速排序是原地排序,只需要 O(log n) 的栈空间来存储递归调用的状态。
  • 空间复杂度主要取决于递归的深度,最坏情况下是 O(n),但平均情况下是 O(log n)。

快速排序的Java实现代码:

public class QuickSort {

    // 主函数:调用快速排序
    public static void quickSort(int[] arr) {
        if (arr == null || arr.length < 2) {
            return;
        }
        quickSortHelper(arr, 0, arr.length - 1);
    }

    // 快速排序的核心递归函数
    private static void quickSortHelper(int[] arr, int left, int right) {
        if (left < right) {
            // 分区操作,返回基准元素的正确位置
            int pivotIndex = partition(arr, left, right);
            // 递归对基准元素左侧和右侧的子数组排序
            quickSortHelper(arr, left, pivotIndex - 1);
            quickSortHelper(arr, pivotIndex + 1, right);
        }
    }

    // 分区操作,返回基准元素的最终位置
    private static int partition(int[] arr, int left, int right) {
        // 选择最右边的元素作为基准元素
        int pivot = arr[right];
        int i = left - 1; // i 指向比基准小的元素区域的最后一个元素
        for (int j = left; j < right; j++) {
            if (arr[j] < pivot) {
                // 交换 arr[i + 1] 和 arr[j]
                i++;
                swap(arr, i, j);
            }
        }
        // 将基准元素放到正确位置
        swap(arr, i + 1, right);
        return i + 1; // 返回基准元素的索引
    }

    // 交换数组中两个元素的位置
    private static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

    // 主函数入口,打印排序后的结果
    public static void main(String[] args) {
        int[] arr = {3, 6, 8, 10, 1, 2, 1};
        System.out.println("Original array: ");
        printArray(arr);
        
        quickSort(arr);

        System.out.println("Sorted array: ");
        printArray(arr);
    }

    // 打印数组的辅助函数
    private static void printArray(int[] arr) {
        for (int num : arr) {
            System.out.print(num + " ");
        }
        System.out.println();
    }
}

代码解析:

  • quickSort:是快速排序的入口方法,接收一个数组作为参数,检查数组是否为空或大小小于2,若满足条件则无需排序,直接返回。
  • quickSortHelper:是一个递归函数,通过分区操作将数组划分为左右两部分,然后递归地对这两部分继续排序。
  • partition:分区操作方法,它选择数组的最后一个元素作为基准元素,并通过两端的指针进行交换,将小于基准的元素放到左边,大于基准的元素放到右边。最终,基准元素被放到正确位置并返回其索引。
  • swap:交换数组中两个元素的辅助函数。
  • printArray:打印数组内容的辅助函数。

复杂度分析:

  • 时间复杂度:
    • 最佳情况:O(n log n),当每次分区都能将数组平衡地分成两部分。
    • 最坏情况:O(n^2),当每次分区都选到数组的极端元素(如最小或最大),导致每次只能减少一个元素的排序,递归的深度为 n。
    • 平均情况:O(n log n),是快速排序最常见的表现。
  • 空间复杂度:
    • 最好和平均情况下:O(log n),由于递归的深度受限于树的高度。
    • 最坏情况下:O(n),当数组已经是排序好的,或者每次选到的基准元素极端时,递归的深度为 n。
作者 east
Java 12月 22,2024

大厂面试手撕面试题:堆排序(亲测可用的java实现)

堆排序(Heap Sort)是一种基于比较的排序算法,利用堆这种数据结构来实现排序。堆是一种完全二叉树,通常用数组来表示。堆排序分为两个步骤:构建最大堆和不断将堆顶元素与末尾元素交换,再进行堆调整。

思路:

  1. 构建最大堆:首先要将数组转化为一个最大堆。最大堆的特点是父节点的值大于等于其左右子节点的值。通过调整从最后一个非叶子节点开始,向上调整整个堆。
  2. 堆排序过程:
    • 将堆顶元素(最大值)与堆的最后一个元素交换。
    • 然后将堆的大小减1,调用堆调整操作(heapify)恢复堆的性质,继续重复上述过程,直到堆的大小为1。

时间复杂度:

  • 构建堆:O(n)。
    • 从最后一个非叶子节点开始调整,每次调整的时间是 O(log n),一共进行 n/2 次调整,因此时间复杂度是 O(n)。
  • 堆排序过程:每次需要调整堆结构,进行 n 次交换,每次调整堆的时间复杂度是 O(log n),所以堆排序的时间复杂度是 O(n log n)。

空间复杂度:

  • 堆排序是原地排序算法,它不需要额外的空间来存储数据。时间复杂度是 O(1)。

Java代码实现:

public class HeapSort {

    // 堆化操作,维护堆的性质
    public static void heapify(int[] arr, int n, int i) {
        int largest = i; // 初始化父节点为最大值
        int left = 2 * i + 1; // 左子节点
        int right = 2 * i + 2; // 右子节点

        // 如果左子节点比父节点大
        if (left < n && arr[left] > arr[largest]) {
            largest = left;
        }

        // 如果右子节点比父节点大
        if (right < n && arr[right] > arr[largest]) {
            largest = right;
        }

        // 如果最大值不是父节点,交换并继续堆化
        if (largest != i) {
            int temp = arr[i];
            arr[i] = arr[largest];
            arr[largest] = temp;

            // 递归堆化
            heapify(arr, n, largest);
        }
    }

    // 堆排序
    public static void heapSort(int[] arr) {
        int n = arr.length;

        // 构建最大堆
        for (int i = n / 2 - 1; i >= 0; i--) {
            heapify(arr, n, i);
        }

        // 一个个地把最大元素移动到数组末尾
        for (int i = n - 1; i >= 1; i--) {
            // 将当前堆的根节点与末尾元素交换
            int temp = arr[0];
            arr[0] = arr[i];
            arr[i] = temp;

            // 调整堆
            heapify(arr, i, 0);
        }
    }

    // 输出数组
    public static void printArray(int[] arr) {
        for (int num : arr) {
            System.out.print(num + " ");
        }
        System.out.println();
    }

    // 主函数
    public static void main(String[] args) {
        int[] arr = {12, 11, 13, 5, 6, 7};

        System.out.println("Original array:");
        printArray(arr);

        heapSort(arr);

        System.out.println("Sorted array:");
        printArray(arr);
    }
}

代码解析:

  1. heapify:这是一个递归函数,用于调整堆。每次比较父节点与左右子节点的大小,确保父节点是最大值。如果不是,则交换并继续对调整后的子树进行堆化。
  2. heapSort:
    • 第一部分是构建最大堆。从数组的最后一个非叶子节点开始,逐个堆化直到根节点。
    • 第二部分是排序,将堆顶元素与最后一个元素交换,然后调整堆,重复此过程,直到数组完全排序。
  3. printArray:用来输出数组的内容,便于调试和查看结果。

示例输出:

Original array: 12 11 13 5 6 7  
Sorted array: 5 6 7 11 12 13

作者 east
Java 12月 22,2024

大厂面试手撕面试题:归并排序(亲测可用的java实现)

归并排序(Merge Sort)是一种典型的分治算法,它将一个大问题分解成多个小问题,逐步解决,再合并结果,最终得到排序后的序列。

归并排序的思路:

  1. 分解(Divide):将待排序的数组分成两半,递归地对这两半分别进行归并排序。
  2. 解决(Conquer):对分解出的子数组继续进行排序,直到每个子数组只有一个元素(因为一个元素自然是有序的)。
  3. 合并(Combine):将排序后的子数组合并成一个有序的大数组。合并是归并排序的核心步骤。

归并排序的时间复杂度与空间复杂度:

  1. 时间复杂度:
    • 归并排序的时间复杂度是 O(nlogn),无论是最坏情况、最佳情况还是平均情况。这里的 n 是待排序元素的数量,logn 是因为每次递归将数组分成两半,直到数组大小为1。
    • 每一层递归的合并操作是线性时间复杂度O(n),递归深度是logn,所以总的时间复杂度是 O(nlogn)。
  2. 空间复杂度:
    • 归并排序需要额外的空间来存储合并过程中的中间结果,空间复杂度为O(n)。
    • 在递归调用过程中,每次递归调用栈的深度是logn,但归并过程中需要额外的存储空间来存储中间的合并结果,所以总空间复杂度是O(n)。

归并排序的Java实现:

public class MergeSort {
    
    // 归并排序函数
    public static void mergeSort(int[] arr) {
        if (arr == null || arr.length < 2) {
            return;
        }
        // 调用辅助函数进行递归排序
        mergeSortHelper(arr, 0, arr.length - 1);
    }

    // 递归的归并排序方法
    private static void mergeSortHelper(int[] arr, int left, int right) {
        if (left < right) {
            int mid = left + (right - left) / 2;  // 防止溢出
            mergeSortHelper(arr, left, mid);       // 排序左半部分
            mergeSortHelper(arr, mid + 1, right);  // 排序右半部分
            merge(arr, left, mid, right);          // 合并两部分
        }
    }

    // 合并两个有序子数组
    private static void merge(int[] arr, int left, int mid, int right) {
        // 创建临时数组存储合并结果
        int[] temp = new int[right - left + 1];
        int i = left, j = mid + 1, k = 0;

        // 合并两个有序子数组
        while (i <= mid && j <= right) {
            if (arr[i] <= arr[j]) {
                temp[k++] = arr[i++];
            } else {
                temp[k++] = arr[j++];
            }
        }

        // 如果左半部分有剩余元素,直接复制到temp数组
        while (i <= mid) {
            temp[k++] = arr[i++];
        }

        // 如果右半部分有剩余元素,直接复制到temp数组
        while (j <= right) {
            temp[k++] = arr[j++];
        }

        // 将临时数组的内容拷贝回原数组
        System.arraycopy(temp, 0, arr, left, temp.length);
    }

    // 测试归并排序
    public static void main(String[] args) {
        int[] arr = {38, 27, 43, 3, 9, 82, 10};
        System.out.println("排序前: ");
        printArray(arr);

        mergeSort(arr);

        System.out.println("排序后: ");
        printArray(arr);
    }

    // 打印数组
    public static void printArray(int[] arr) {
        for (int num : arr) {
            System.out.print(num + " ");
        }
        System.out.println();
    }
}

代码解析:

  1. mergeSort 函数:是外部调用的接口,接收待排序的数组。首先检查数组的有效性(如果为空或长度小于2直接返回)。
  2. mergeSortHelper 函数:是递归的核心部分,负责将数组分成两部分,分别递归排序,直到分割到只有一个元素为止。
  3. merge 函数:这是合并的关键部分,它接受三个参数:left, mid, right,分别表示左部分的起始索引,右部分的结束索引。合并操作会创建一个临时数组,按顺序将两个已排序的子数组合并到这个临时数组中,然后再将其复制回原数组。
  4. printArray 函数:打印数组元素的辅助函数。

测试:

给定输入数组 {38, 27, 43, 3, 9, 82, 10},归并排序后的输出应为:

排序前:  38 27 43 3 9 82 10  
排序后: 3 9 10 27 38 43 82
作者 east
Java 12月 22,2024

大厂面试手撕面试题:二分查找(亲测可用的java实现)

二分查找是一种在有序数组中查找特定元素的高效算法。其基本思路是通过逐步缩小查找范围来找到目标元素。每次将查找范围的中间元素与目标元素进行比较,根据比较结果决定继续查找左半部分还是右半部分。

二分查找的实现思路:

  1. 输入:一个有序数组 arr 和一个目标值 target。
  2. 初始化:设置两个指针,left 和 right,分别指向数组的左右两端(即 left = 0,right = arr.length - 1)。
  3. 循环查找:
    • 计算中间位置 mid = left + (right - left) / 2。
    • 如果 arr[mid] == target,则找到了目标值,返回 mid(即元素的索引)。
    • 如果 arr[mid] < target,说明目标值可能在右半部分,将 left 移动到 mid + 1。
    • 如果 arr[mid] > target,说明目标值可能在左半部分,将 right 移动到 mid - 1。
  4. 退出条件:如果 left 超过了 right,说明数组中没有目标元素,返回 -1。

时间复杂度与空间复杂度分析:

  • 时间复杂度:O(log n),每次查找将问题的规模减少一半,因此时间复杂度为对数级别。
  • 空间复杂度:O(1),使用常数空间,只需要几个额外的变量(left、right 和 mid)来存储指针,没有使用额外的空间。

Java 完整代码实现:

public class BinarySearch {

    // 二分查找函数
    public static int binarySearch(int[] arr, int target) {
        int left = 0;
        int right = arr.length - 1;

        // 进行二分查找
        while (left <= right) {
            // 计算中间索引
            int mid = left + (right - left) / 2;

            // 检查中间元素是否为目标值
            if (arr[mid] == target) {
                return mid; // 找到目标,返回其索引
            }

            // 如果目标值大于中间元素,则调整搜索范围为右半部分
            if (arr[mid] < target) {
                left = mid + 1;
            }
            // 如果目标值小于中间元素,则调整搜索范围为左半部分
            else {
                right = mid - 1;
            }
        }

        // 目标值不存在于数组中,返回 -1
        return -1;
    }

    // 主函数,用于测试
    public static void main(String[] args) {
        int[] arr = {1, 3, 5, 7, 9, 11, 13, 15, 17, 19};
        int target = 7;
        
        // 调用二分查找方法
        int result = binarySearch(arr, target);
        
        if (result != -1) {
            System.out.println("目标元素 " + target + " 在数组中的索引是: " + result);
        } else {
            System.out.println("目标元素 " + target + " 不在数组中");
        }
    }
}

代码解释:

  • binarySearch 方法:接收一个有序整数数组 arr 和一个目标值 target。通过不断调整左右指针,最终找到目标值并返回其索引。如果没有找到目标元素,则返回 -1。
  • main 方法:用于测试 binarySearch 方法。数组 arr 是已排序的,target 是我们要查找的目标值。

样例输出:

目标元素 7 在数组中的索引是: 3
作者 east
Java 12月 22,2024

大厂面试手撕面试题:数组的全排列(亲测可用的java实现)

在面试中,关于数组的全排列问题是一个经典的考察题目,能够检验候选人的递归思维、回溯算法的理解和实现能力。

思路:

要获取一个数组的全排列,我们可以利用回溯算法。具体来说,回溯算法通过递归的方式逐步生成排列,在每一步都将一个元素加入排列中,然后在下一步递归中排除已选元素,回溯的时候撤销选择,尝试其他可能。

步骤:

  1. 递归生成排列:
    • 使用一个辅助数组来记录当前的排列。
    • 对于每个位置,我们尝试填充每一个可能的元素,并递归地填充后续的位置。
    • 使用回溯的方式,在完成一个排列后,撤回当前选择,继续尝试其他可能性。
  2. 交换元素:
    • 通过交换数组中的元素来生成排列,而不是额外使用空间存储状态。这样可以减少空间复杂度。

时间复杂度:

  • 生成全排列的时间复杂度是 O(n!),因为每个元素都需要和其他元素交换一遍,排列的总数为 n!。

空间复杂度:

  • 空间复杂度是 O(n),因为递归调用栈的深度是 n(每次递归深度为数组的长度),且我们只需要常数空间来交换数组元素。

Java 代码实现:

import java.util.ArrayList;
import java.util.List;

public class Permutations {

    // 主函数,返回所有的全排列
    public List<List<Integer>> permute(int[] nums) {
        List<List<Integer>> result = new ArrayList<>();
        backtrack(nums, new ArrayList<>(), result, new boolean[nums.length]);
        return result;
    }

    // 回溯函数,生成排列
    private void backtrack(int[] nums, List<Integer> current, List<List<Integer>> result, boolean[] used) {
        // 当当前排列的长度等于nums的长度时,说明找到了一个全排列
        if (current.size() == nums.length) {
            result.add(new ArrayList<>(current));
            return;
        }

        // 遍历nums数组中的每个元素
        for (int i = 0; i < nums.length; i++) {
            // 如果该元素已经被使用过,则跳过
            if (used[i]) continue;

            // 做选择,标记当前元素为已使用
            used[i] = true;
            current.add(nums[i]);

            // 递归生成剩余的排列
            backtrack(nums, current, result, used);

            // 撤销选择,回溯
            used[i] = false;
            current.remove(current.size() - 1);
        }
    }

    // 测试主函数
    public static void main(String[] args) {
        Permutations solution = new Permutations();
        int[] nums = {1, 2, 3};
        List<List<Integer>> result = solution.permute(nums);

        // 打印结果
        for (List<Integer> perm : result) {
            System.out.println(perm);
        }
    }
}

代码解析:

  1. 主函数 permute(int[] nums):
    • 该函数会返回所有的全排列。我们初始化一个空的 result 列表,并调用 backtrack 函数来生成排列。
  2. 回溯函数 backtrack(int[] nums, List<Integer> current, List<List<Integer>> result, boolean[] used):
    • current 存储当前的排列结果。
    • used 数组记录每个元素是否已经被选择过。
    • 每次递归,我们将一个没有被使用的元素添加到 current 中,并在递归完成后回溯,撤销当前选择。
  3. 时间复杂度和空间复杂度:
    • 时间复杂度:O(n!),因为要生成n个元素的所有排列,总共有n!个排列,每个排列需要O(n)的时间来生成。
    • 空间复杂度:O(n),递归调用栈深度最大为n。

测试示例:

对于输入 nums = [1, 2, 3],输出应为:

[1, 2, 3]
[1, 3, 2]
[2, 1, 3]
[2, 3, 1]
[3, 1, 2]
[3, 2, 1]
作者 east

1 2 … 15 下一个

关注公众号“大模型全栈程序员”回复“小程序”获取1000个小程序打包源码。回复”chatgpt”获取免注册可用chatgpt。回复“大数据”获取多本大数据电子书

标签

AIGC AI创作 bert chatgpt github GPT-3 gpt3 GTP-3 hive mysql O2O tensorflow UI控件 不含后台 交流 共享经济 出行 图像 地图定位 外卖 多媒体 娱乐 小程序 布局 带后台完整项目 开源项目 搜索 支付 效率 教育 日历 机器学习 深度学习 物流 用户系统 电商 画图 画布(canvas) 社交 签到 联网 读书 资讯 阅读 预订

官方QQ群

小程序开发群:74052405

大数据开发群: 952493060

近期文章

  • 详解Python当中的pip常用命令
  • AUTOSAR如何在多个供应商交付的配置中避免ARXML不兼容?
  • C++thread pool(线程池)设计应关注哪些扩展性问题?
  • 各类MCAL(Microcontroller Abstraction Layer)如何与AUTOSAR工具链解耦?
  • 如何设计AUTOSAR中的“域控制器”以支持未来扩展?
  • C++ 中避免悬挂引用的企业策略有哪些?
  • 嵌入式电机:如何在低速和高负载状态下保持FOC(Field-Oriented Control)算法的电流控制稳定?
  • C++如何在插件式架构中使用反射实现模块隔离?
  • C++如何追踪内存泄漏(valgrind/ASan等)并定位到业务代码?
  • C++大型系统中如何组织头文件和依赖树?

文章归档

  • 2025年6月
  • 2025年5月
  • 2025年4月
  • 2025年3月
  • 2025年2月
  • 2025年1月
  • 2024年12月
  • 2024年11月
  • 2024年10月
  • 2024年9月
  • 2024年8月
  • 2024年7月
  • 2024年6月
  • 2024年5月
  • 2024年4月
  • 2024年3月
  • 2023年11月
  • 2023年10月
  • 2023年9月
  • 2023年8月
  • 2023年7月
  • 2023年6月
  • 2023年5月
  • 2023年4月
  • 2023年3月
  • 2023年1月
  • 2022年11月
  • 2022年10月
  • 2022年9月
  • 2022年8月
  • 2022年7月
  • 2022年6月
  • 2022年5月
  • 2022年4月
  • 2022年3月
  • 2022年2月
  • 2022年1月
  • 2021年12月
  • 2021年11月
  • 2021年9月
  • 2021年8月
  • 2021年7月
  • 2021年6月
  • 2021年5月
  • 2021年4月
  • 2021年3月
  • 2021年2月
  • 2021年1月
  • 2020年12月
  • 2020年11月
  • 2020年10月
  • 2020年9月
  • 2020年8月
  • 2020年7月
  • 2020年6月
  • 2020年5月
  • 2020年4月
  • 2020年3月
  • 2020年2月
  • 2020年1月
  • 2019年7月
  • 2019年6月
  • 2019年5月
  • 2019年4月
  • 2019年3月
  • 2019年2月
  • 2019年1月
  • 2018年12月
  • 2018年7月
  • 2018年6月

分类目录

  • Android (73)
  • bug清单 (79)
  • C++ (34)
  • Fuchsia (15)
  • php (4)
  • python (43)
  • sklearn (1)
  • 云计算 (20)
  • 人工智能 (61)
    • chatgpt (21)
      • 提示词 (6)
    • Keras (1)
    • Tensorflow (3)
    • 大模型 (1)
    • 智能体 (4)
    • 深度学习 (14)
  • 储能 (44)
  • 前端 (4)
  • 大数据开发 (488)
    • CDH (6)
    • datax (4)
    • doris (30)
    • Elasticsearch (15)
    • Flink (78)
    • flume (7)
    • Hadoop (19)
    • Hbase (23)
    • Hive (40)
    • Impala (2)
    • Java (71)
    • Kafka (10)
    • neo4j (5)
    • shardingsphere (6)
    • solr (5)
    • Spark (99)
    • spring (11)
    • 数据仓库 (9)
    • 数据挖掘 (7)
    • 海豚调度器 (10)
    • 运维 (34)
      • Docker (3)
  • 小游戏代码 (1)
  • 小程序代码 (139)
    • O2O (16)
    • UI控件 (5)
    • 互联网类 (23)
    • 企业类 (6)
    • 地图定位 (9)
    • 多媒体 (6)
    • 工具类 (25)
    • 电商类 (22)
    • 社交 (7)
    • 行业软件 (7)
    • 资讯读书 (11)
  • 嵌入式 (70)
    • autosar (63)
    • RTOS (1)
    • 总线 (1)
  • 开发博客 (16)
    • Harmony (9)
  • 技术架构 (6)
  • 数据库 (32)
    • mongodb (1)
    • mysql (13)
    • pgsql (2)
    • redis (1)
    • tdengine (4)
  • 未分类 (6)
  • 程序员网赚 (20)
    • 广告联盟 (3)
    • 私域流量 (5)
    • 自媒体 (5)
  • 量化投资 (4)
  • 面试 (14)

功能

  • 登录
  • 文章RSS
  • 评论RSS
  • WordPress.org

All Rights Reserved by Gitweixin.本站收集网友上传代码, 如有侵犯版权,请发邮件联系yiyuyos@gmail.com删除.