Java非常规手写代码题

文章目录

    • 1、写三种单例模式的实现方式
    • 2、编号为 1-n 的循环报 1-3,报道 3 的出列,求最后一人的编号
    • 3、写两个线程打印 1-n,⼀个线程打印奇数,⼀个线程打印偶数
    • 4、LRU 缓存实现
    • 5、用Java 实现栈
    • 6、加权轮询算法的实现
    • 7、手写死锁
    • 8、快速排序
    • 9、生产者消费者

1、写三种单例模式的实现方式

1、枚举

简单高效,无需加锁,线程安全,可以避免通过反射破坏枚举单例

public enum Singleton {
    INSTANCE;
    public void doSomething(String str) {
    	System.out.println(str);
     }
}

2、静态内部类

  • 当外部类 Singleton 被加载的时候,并不会创建静态内部类SingletonInner 的实例对象
  • 只有当调用getInstance() 方法时, SingletonInner 才会被加载,此时才会创建单例对象 INSTANCE
  • INSTANCE 的唯⼀性、创建过程的线程安全性,都由 JVM 来保证
  • 无需加锁,线程安全,并且支持延时加载
public class Singleton {
    // 私有化构造⽅法
    private Singleton() {
     }
    // 对外提供获取实例的公共⽅法
    public static Singleton getInstance() {
    	return SingletonInner.INSTANCE;
     }
    // 定义静态内部类
    private static class SingletonInner{
    	private final static Singleton INSTANCE = new Singleton();
     }
}

3、双重检验锁

public class Singleton {
    private volatile static Singleton uniqueInstance;
    // 私有化构造⽅法
    private Singleton() {}
    public static Singleton getUniqueInstance() {
        //先判断对象是否已经实例过,没有实例化过才进⼊加锁代码
        if (uniqueInstance == null) {
        //类对象加锁
            synchronized (Singleton.class) {
                if (uniqueInstance == null) {
                	uniqueInstance = new Singleton();
                }
            }
        }
        return uniqueInstance;
    }
}

uniqueInstance = new Singleton(); 三步执行:

  1. 为 uniqueInstance 分配内存空间
  2. 初始化uniqueInstance
  3. 将uniqueInstance指向分配的内存地址

2、编号为 1-n 的循环报 1-3,报道 3 的出列,求最后一人的编号

约瑟夫环问题

  1. 要求出最后留下的那个⼈的编号
  2. 求全过程,即要算出每轮出局的人

公式: (f(n - 1, k) + k - 1) % n + 1

f(n,k) 表示 n 个人报数,每次报数报 到 k 的人出局,最终最后一个人的编号

public class Josephus {
    // 定义递归函数
    public static int f(int n, int k) {
        // 如果只有⼀个⼈,则返回 1
        if (n == 1) {
        	return 1;
        }
        return (f(n - 1, k) + k - 1) % n + 1;
        }
    public static void main(String[] args) {
        int n = 10;
        int k = 3;
        System.out.println("最后留下的那个⼈的编号是:" + f(n, k));
    }
}

3、写两个线程打印 1-n,⼀个线程打印奇数,⼀个线程打印偶数

  1. 线程的等待/通知机制( wait() 和 notify() )
  2. 信号量Semaphore
package com.mys.leetcode;

/**
 * @author mys
 * @date 2023/10/26 20:32
 */
public class ParityPrinter {
    private int max; // 总打印次数
    private int number = 1; // 打印次数
    private boolean odd; // 是否该打印奇数

    public ParityPrinter(int max) {
        this.max = max;
    }

    /**
     * 打印奇数
     */
    public synchronized void printOdd() {
        while (number < 100) {
            // 如果当前应该打印偶数,就等待
            while (odd) {
                try {
                    wait();
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
            System.out.println(Thread.currentThread().getName() + ":" + number);
            number ++;

            odd = true;
            notify(); // 唤醒另一个线程
        }
    }

    /**
     * 打印偶数
     */
    public synchronized void printEven() {
        while (number < 100) {
            // 如果当前应该打印偶数,就等待
            while (!odd) {
                try {
                    wait();
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
            System.out.println(Thread.currentThread().getName() + ":" + number);
            number ++;

            odd = false;
            notify(); // 唤醒另一个线程
        }
    }

    public static void main(String[] args) {
        ParityPrinter printer = new ParityPrinter(100);
        Thread t1 = new Thread(printer::printOdd);
        Thread t2 = new Thread(printer::printEven);
        t1.start();
        t2.start();
    }
}
Thread-0:1
Thread-1:2
Thread-0:3
Thread-1:4
Thread-0:5
Thread-1:6
Thread-0:7
Thread-1:8

4、LRU 缓存实现

LRU:最近最少使用

适用场景:频繁访问(缓存、页面置换)、有局部性、数据分布均匀、缓存容量适中

使用LinkedHashMap实现LRUCache

LinkedHashMap内部维护了双链表,增加了元素的顺序信息

removeEldestEntry:是否移除老数据

public class LRUCache<K, V> extends LinkedHashMap<K, V> {
    private int capacity;
    public LRUCache(int capacity) {
        // LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder)
        super(capacity, 0.75f, true);
        this.capacity = capacity;
    }

    @Override
    protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
    // 当缓存元素个数超过容5时,移除最老的元素
        return size() > capacity;
    }

    public static void main(String[] args) {
        // 创建一个容量为3的LRU缓存
        LRUCache<Integer, String> lruCache = new LRUCache<>(3);
        // 添加数据
        lruCache.put(1, "one");
        lruCache.put(2, "two");
        lruCache.put(3, "three");
        // 此时缓存为:{1=One, 2=Two, 3=Three}
        System.out.println(lruCache);

        // 访问某个元素,使其成为最近访问元素
        String value = lruCache.get(2);
        // 此时缓存为:{1=One, 3=Three, 2=Two}
        System.out.println(lruCache);

        // 添加新数据,触发淘汰
        lruCache.put(4, "Four");
        // 此时缓存为:{3=Three, 2=Two, 4=Four}
        System.out.println(lruCache);
    }
}
{1=One, 2=Two, 3=Three}
{1=One, 3=Three, 2=Two}
{3=Three, 2=Two, 4=Four}

5、用Java 实现栈

public class Stack {
    private int[] arr;
    private int top;

    public Stack(int capacity) {
        arr = new int[capacity];
        top = -1;
    }

    public void push(int element) {
        if (top == arr.length - 1) {
            throw new IllegalStateException("栈已满");
        }
        top ++;
        arr[top] = element;
    }

    public int pop() {
        if (isEmpty()) {
            throw new IllegalStateException("栈为空");
        }
        int element = arr[top];
        top --;
        return element;
    }

    public int top() {
        if (isEmpty()) {
            throw new IllegalStateException("栈为空");
        }
        return arr[top];
    }

    public boolean isEmpty() {
        return top == -1;
    }

    public int size() {
        return top + 1;
    }
}

6、加权轮询算法的实现

加权轮询: 它通过给不同的服务器分配不同的权重来实现请求的均衡分发

  1. 为服务器分配初始权重,权重越高,可以处理更高的请求
  2. 新请求到达,负载均衡器选择权重最高的机器处理请求
  3. 处理之后,服务器权重减去一个设定好的值,减小权重
  4. 所有服务器权重为0,重新赋初值
class Server {
    private String name;
    private int weight;

    public Server(String name, int weight) {
        this.name = name;
        this.weight = weight;
    }

    public String getName() {
        return name;
    }

    public int getWeight() {
        return weight;
    }
}
public class WeightedRoundRobin {
    /*
    1. 为服务器分配初始权重,权重越高,可以处理更高的请求
    2. 新请求到达,负载均衡器选择权重最高的机器处理请求
    3. 处理之后,服务器权重减去一个设定好的值,减小权重
    4. 所有服务器权重为0,重新赋初值
     */

    private List<Server> servers;
    private int currentIndex;

    public WeightedRoundRobin(List<Server> servers) {
        this.servers = servers;
        currentIndex = 0;
    }

    // 根据服务器权重,选择下一个服务器
    public Server getNextServer() {
        int totalWeight = calculateTotalWeight(); // 所有服务器总权重
        int maxWeight = findMaxWeight(); // 所有服务器中的最大权重
        int gcd = calculateGCD(); // 所有权重的最大公约数
        // 根据最大权重遍历
        while (true) {
            currentIndex = (currentIndex + 1) % servers.size();
            if (currentIndex == 0) {
                maxWeight -= gcd;
                if (maxWeight <= 0) {
                    maxWeight = findMaxWeight();
                    if (maxWeight == 0) {
                        return null; // 所有服务器的权重都为0,无法分配请求
                    }
                }
            }
            if (servers.get(currentIndex).getWeight() >= maxWeight) {
                return servers.get(currentIndex);
            }
        }
    }

    // 计算所有服务器总权重
    private int calculateTotalWeight() {
        int totalWeight = 0;
        for (Server server : servers) {
            totalWeight += server.getWeight();
        }
        return totalWeight;
    }

    // 找到所有服务器中的最大权重
    private int findMaxWeight() {
        int maxWeight = 0;
        for (Server server : servers) {
            if (server.getWeight() > maxWeight) {
                maxWeight = server.getWeight();
            }
        }
        return maxWeight;
    }

    // 计算所有权重的最大公约数
    private int calculateGCD() {
        int weightsLength = servers.size();
        int[] weights = new int[weightsLength];
        for (int i = 0; i < weightsLength; i ++) {
            weights[i] = servers.get(i).getWeight();
        }
        return findGCD(weights, weightsLength);
    }

    // 找多个权值的最大公约数
    private int findGCD(int[] weights, int n) {
        int result = weights[0];
        for (int i = 0; i < n; i ++) {
            result = gcd(result, weights[i]);
        }
        return result;
    }

    // 计算两个数的最大公约数
    private int gcd(int a, int b) {
        if (b == 0) {
            return a;
        } else {
            return gcd(b, a % b);
        }
    }

    public static void main(String[] args) {
        List<Server> servers = new ArrayList<>();
        servers.add(new Server("Server1", 3));
        servers.add(new Server("Server2", 5));
        servers.add(new Server("Server3", 1));
        WeightedRoundRobin wrr = new WeightedRoundRobin(servers);
        // 模拟请求分发
        for (int i = 0; i < 10; i++) {
            Server server = wrr.getNextServer();
            System.out.println("Request " + (i + 1) + " sent to: " + server.getName());
        }
    }
}

7、手写死锁

定义、四个必要条件、避免死锁、银行家算法、安全状态

public class DeadLockDemo {
    private static Object resource1 = new Object();
    private static Object resource2 = new Object();

    public static void main(String[] args) {
        new Thread(() -> {
            synchronized (resource1) {
                System.out.println(Thread.currentThread() + " get resource1");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread() + " wait resource2");
                synchronized (resource2) {
                    System.out.println(Thread.currentThread() + " get resource2");
                }
            }
        }, "线程1").start();

        new Thread(() -> {
            synchronized (resource2) {
                System.out.println(Thread.currentThread() + " get resource2");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread() + " wait resource1");
                synchronized (resource1) {
                    System.out.println(Thread.currentThread() + " get resource1");
                }
            }
        }, "线程2").start();
    }
}
Thread[线程1,5,main] get resource1
Thread[线程2,5,main] get resource2
Thread[线程1,5,main] wait resource2
Thread[线程2,5,main] wait resource1

8、快速排序

  1. 确定分界点:q[l] q[(l+r)/2] q[r] 随机
  2. 调整范围:x放右边
  3. 递归:处理左右两端

代码思想:两指针同时往中间走,若i指向的数x,指针前移。完成之后指针指向的数swap,继续进行下一次循环

public void quickSort(int[] nums, int l, int r) {
    if (l >= r) return;
    int i = l - 1, j = r + 1, x = nums[(l + r) / 2];
    while (i < j) {
        do i ++; while (nums[i] < x);
        do j --; while (nums[j] > x);
        if (i < j) {
            int tmp = nums[i];
            nums[i] = nums[j];
            nums[j] = tmp;
        }
    }
    quickSort(nums, l, j);
    quickSort(nums, j + 1, r);
}

思想2:

  1. 从后往前走,找比x更小的数,放在low位置;
  2. 从前往后走,找比x更大的数,放在high位置
  3. 当low>=high,一轮循环结束,确定了x的位置,以x为中心,将数组分为左右两边,左右两边的数组继续循环

eg: 5 1 7 3 1 6 9 4 x=5

第一趟:4 1 1 3 5 6 9 7 确定了5;x1=4, x2=6

第二趟:3 1 1 4 5 6 9 7 确定了4,5,6;x1=3, x2=9

第三趟:1 1 3 4 5 6 7 9

9、生产者消费者

问题:如何确保⽣产者不会往满了的数据缓冲区继续添加数据,而消费者也不会从空的缓冲区取数据——引入⼀个共享的缓冲区

Procuder

public class Producer implements Runnable{
    private final BlockingQueue<Integer> queue;
    private final int MAX_SIZE = 5; // 缓冲区最大容量

    public Producer(BlockingQueue<Integer> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        try {
            for (int i = 1; i <= 10; i ++) {
                produce(i); // 生产
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

    private void produce(int item) throws InterruptedException {
        synchronized (queue) {
            while (queue.size() == MAX_SIZE) {
                System.out.println("缓冲区已满,生产者等待");
                queue.wait(); // 冲区已满,生产者等待
            }
            queue.put(item); // 生产者放入缓冲区
            System.out.println("生产物品:" + item);
            queue.notifyAll(); // 唤醒所有等待的消费者
        }
    }
}

Comsumer

public class Consumer implements Runnable{
    private final BlockingQueue<Integer> queue;
    public Consumer(BlockingQueue<Integer> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        try {
            while (true) {
                consume(); // 消费物品
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

    private void consume() throws InterruptedException {
        synchronized (queue) {
            while (queue.isEmpty()) {
                System.out.println("缓冲区空,消费者等待");
                queue.wait(); // 缓冲区空,消费者等待
            }
            int item = queue.take(); // 从缓冲区取物品
            System.out.println("消费者消费:" + item);
            queue.notifyAll(); //  唤醒所有等待的生产者
        }
    }
}

Test

public class ProducerConsumerExample {
    public static void main(String[] args) {
        BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(5);
        Producer producer = new Producer(queue);
        Consumer consumer = new Consumer(queue);
        Thread producerThread = new Thread(producer);
        Thread consumerThread = new Thread(consumer);
        producerThread.start();
        consumerThread.start();
    }
}
生产物品:1
生产物品:2
生产物品:3
生产物品:4
生产物品:5
缓冲区已满,生产者等待
消费者消费:1
消费者消费:2
消费者消费:3
消费者消费:4
消费者消费:5
缓冲区空,消费者等待

你可能感兴趣的:(LeetCode,java,leetcode)