当前位置: 首页 > news >正文

Android面试之算法总结

一、什么是 LRU 缓存?

LRU(Least Recently Used)即最近最少使用算法,是一种常用的缓存淘汰策略。其核心思想是:当缓存容量已满时,优先淘汰最久未被访问的数据,以保证缓存始终存储高频访问的热点数据。

LRU 缓存需要满足以下核心操作:

  • get(key):获取指定键的值,若存在则返回并更新其访问顺序
  • put(key, value):插入或更新键值对,若容量不足则淘汰最久未使用的键

二、LRU 缓存的两种实现方式

import java.util.HashMap;
import java.util.Map;

// 实现 LRU 缓存机制的类
class LRUCache {
    // 双向链表节点类,用于存储键值对
    private class DLinkedNode {
        // 键
        int key;
        // 值
        int value;
        // 指向前一个节点的引用
        DLinkedNode prev;
        // 指向后一个节点的引用
        DLinkedNode next;

        // 无参构造函数
        DLinkedNode() {
        }

        // 带键值参数的构造函数
        DLinkedNode(int key, int value) {
            this.key = key;
            this.value = value;
        }
    }

    // 缓存的容量
    private int capacity;
    // 当前缓存中元素的数量
    private int size;
    // 哈希表,用于快速查找键对应的节点
    private Map<Integer, DLinkedNode> cache = new HashMap<>();
    // 双向链表的头节点,虚拟节点,不存储实际数据
    private DLinkedNode head;
    // 双向链表的尾节点,虚拟节点,不存储实际数据
    private DLinkedNode tail;

    // 构造函数,初始化缓存容量、大小、头节点、尾节点
    public LRUCache(int capacity) {
        this.capacity = capacity;
        this.size = 0;
        head = new DLinkedNode();
        tail = new DLinkedNode();
        // 初始化双向链表,头节点的下一个节点是尾节点
        head.next = tail;
        // 尾节点的前一个节点是头节点
        tail.prev = head;
    }

    // 根据键获取值,如果键存在则将对应的节点移到链表头部并返回值,不存在则返回 -1
    public int get(int key) {
        DLinkedNode node = cache.get(key);
        if (node == null) {
            return -1;
        }
        // 将访问的节点移动到链表头部
        moveToHead(node);
        return node.value;
    }

    // 插入或更新键值对
    public void put(int key, int value) {
        DLinkedNode node = cache.get(key);
        if (node == null) {
            // 若键不存在,创建新节点
            DLinkedNode newNode = new DLinkedNode(key, value);
            // 将新节点存入哈希表
            cache.put(key, newNode);
            // 将新节点添加到链表头部
            addToHead(newNode);
            // 缓存元素数量加 1
            ++size;
            if (size > capacity) {
                // 若缓存元素数量超过容量,移除链表尾部节点
                DLinkedNode removed = removeTail();
                // 从哈希表中移除对应的键值对
                cache.remove(removed.key);
                // 缓存元素数量减 1
                --size;
            }
        } else {
            // 若键已存在,更新节点的值
            node.value = value;
            // 将该节点移动到链表头部
            moveToHead(node);
        }
    }

    // 将节点添加到双向链表头部
    private void addToHead(DLinkedNode node) {
        // 新节点的前一个节点指向头节点
        node.prev = head;
        // 新节点的下一个节点指向原头节点的下一个节点
        node.next = head.next;
        // 原头节点下一个节点的前一个节点指向新节点
        head.next.prev = node;
        // 头节点的下一个节点指向新节点
        head.next = node;
    }

    // 从双向链表中移除指定节点
    private void removeNode(DLinkedNode node) {
        // 该节点前一个节点的下一个节点指向该节点的下一个节点
        node.prev.next = node.next;
        // 该节点下一个节点的前一个节点指向该节点的前一个节点
        node.next.prev = node.prev;
    }

    // 将指定节点移动到双向链表头部
    private void moveToHead(DLinkedNode node) {
        // 先从链表中移除该节点
        removeNode(node);
        // 再将该节点添加到链表头部
        addToHead(node);
    }

    // 移除双向链表的尾部节点
    private DLinkedNode removeTail() {
        // 获取尾部节点的前一个节点(即实际要移除的节点)
        DLinkedNode res = tail.prev;
        // 从链表中移除该节点
        removeNode(res);
        return res;
    }
}    

一、问题描述

给定一个包含 K 个有序链表的数组,要求将这些链表合并为一个有序链表:

// 定义一个解决方案类
class Solution {
    /**
     * 合并多个有序链表
     * @param lists 包含多个有序链表的数组
     * @return 合并后的有序链表
     */
    public ListNode mergeKLists(ListNode[] lists) {
        // 调用递归方法,从数组的第一个元素开始,到最后一个元素结束
        return mergeKLists(lists, 0, lists.length);
    }

    /**
     * 合并从 lists[i] 到 lists[j - 1] 的链表
     * @param lists 包含多个有序链表的数组
     * @param i 起始索引
     * @param j 结束索引(不包含)
     * @return 合并后的有序链表
     */
    private ListNode mergeKLists(ListNode[] lists, int i, int j) {
        // 计算当前要合并的链表数量
        int m = j - i;
        // 如果要合并的链表数量为 0,说明输入的数组为空,返回 null
        if (m == 0) {
            return null; 
        }
        // 如果要合并的链表数量为 1,无需合并,直接返回该链表
        if (m == 1) {
            return lists[i]; 
        }
        // 递归合并左半部分的链表
        ListNode left = mergeKLists(lists, i, i + m / 2); 
        // 递归合并右半部分的链表
        ListNode right = mergeKLists(lists, i + m / 2, j); 
        // 最后把左半和右半合并后的链表进行合并
        return mergeTwoLists(left, right); 
    }

    /**
     * 合并两个有序链表
     * @param list1 第一个有序链表
     * @param list2 第二个有序链表
     * @return 合并后的有序链表
     */
    private ListNode mergeTwoLists(ListNode list1, ListNode list2) {
        // 创建一个哨兵节点,简化代码逻辑,避免处理头节点为空的情况
        ListNode dummy = new ListNode(); 
        // cur 指针指向新链表的末尾,用于构建新链表
        ListNode cur = dummy; 
        // 当两个链表都不为空时,比较两个链表当前节点的值
        while (list1 != null && list2 != null) {
            if (list1.val < list2.val) {
                // 如果 list1 的当前节点值较小,将其添加到新链表中
                cur.next = list1; 
                // list1 指针后移
                list1 = list1.next; 
            } else { 
                // 相等的情况加哪个节点都是可以的,这里将 list2 的当前节点添加到新链表中
                cur.next = list2; 
                // list2 指针后移
                list2 = list2.next; 
            }
            // cur 指针后移,指向新链表的末尾
            cur = cur.next; 
        }
        // 拼接剩余链表,将未遍历完的链表直接连接到新链表的末尾
        cur.next = list1 != null ? list1 : list2; 
        // 返回哨兵节点的下一个节点,即合并后的链表的头节点
        return dummy.next; 
    }
}

// 定义链表节点类
class ListNode {
    int val;
    ListNode next;
    ListNode() {}
    ListNode(int val) { this.val = val; }
    ListNode(int val, ListNode next) { this.val = val; this.next = next; }
}

希望能对你有帮助!!!

感谢观看!!!

相关文章:

  • Azure SDK 使用指南
  • 如何保障kafka的数据不会重复消费呢,如何防止漏掉呢
  • Es结合kibana查询
  • PyTorch量化技术教程:第一章 PyTorch基础入门
  • 如何在 Windows 上安装并使用 Postman?
  • 问题:md文档转换word,html,图片,excel,csv
  • SICAR标准 汽车焊装生产线触摸屏操作说明
  • LeetCode 第25、27、28题
  • 动态合并任意连续相同行
  • Linux 创建用户和用户组,设置主目录
  • vue中实现元素在界面中自由拖动
  • Flink介绍与安装
  • 4.(vue3.x+vite)接入echarts
  • 前端工程化开篇
  • Redis 如何保证数据一致性:从原理到实践的全面解析
  • 【Flutter入门】1. 从零开始的flutter跨平台开发之旅(概述、环境搭建、第一个Flutter应用)
  • 基于微信小程序的仓储管理系统+论文源码调试
  • Linux程序性能分析
  • 蓝之洋科技以AI智能制造引领变革,推动移动电源产业迈向高端智能化!
  • vue创建子组件步骤及注意事项
  • 经济日报评论员:拧紧“带头过紧日子”的制度螺栓
  • 文化厚度与市场温度兼具,七猫文学为现实题材作品提供土壤
  • 国家外汇管理局:4月货物贸易项下跨境资金净流入649亿美元
  • 陈龙带你观察上海生物多样性,纪录片《我的城市邻居》明播出
  • 这位中国电影早期的全能奇才,90年前唱响国歌
  • 国家统计局:消费对我国经济增长的拉动有望持续增长