算法随笔(一)

一、油车最远距离

每辆车每跑 1 公里消耗 1 升油,油箱容量 12 升;车与车之间可随时无损互相加油,被"弃置"的车不必返回。问最远能让一辆车开多远。

思路

让多辆车一起走,在合适的位置把其中一辆的油"喂"给其他车,使剩下的车都满箱,然后把这辆空车留在原地不再前进。

当有 m 辆车一起走时,总耗油是 m 升/公里。要凑到正好"淘汰"1 辆(即净消耗一个满箱 C升),应走公里。依次减少车数,直到只剩 1 辆独自前进。

当我们这C=12升、一共四辆车

  1. 4 车同行 12/4=3 公里,在 3 公里处把油分给其余三辆使其满箱,留下一辆空车停下。

  2. 3 车再走 12/3=41 公里,到 7 公里处再淘汰一辆。

  3. 2 车再走 12/2=6 公里,到 13 公里处再淘汰一辆。

  4. 剩下 1 车最后在13公里处再独走 12/1=12公里。

所以最后的公里数是25公里

这里有一个一般化结论

这种题应该不好用程序穷尽,让这些车能返回补油就是一个小学奥数题,没想到小学奥数题都是这种程度了吗?

二、基于父节点关联的扁平节点列表,构建树形结构并实现可视化打印

一个ArrayList<TreeNode>,每个节点仅存储「自身 ID」和「父节点 ID」(无直接子节点列表),节点结构如下:

java 复制代码
// 核心节点结构(仅含父子关联必要字段)
class TreeNode {
    private String id;       // 节点唯一标识
    private String parentId; // 父节点ID(根节点为null)
    private String name;     // 节点名称(用于打印展示)
    // 构造器、getter、toString略
}

列表长这样

java 复制代码
        List<TreeNode> nodes = new ArrayList<>();
        nodes.add(new TreeNode("1", null, "技术部"));       // 根节点:技术部(无父节点)
        nodes.add(new TreeNode("2", "1", "前端组"));        // 技术部的子节点:前端组
        nodes.add(new TreeNode("3", "1", "后端组"));        // 技术部的子节点:后端组
        nodes.add(new TreeNode("4", "2", "PC前端组"));     // 前端组的子节点:PC前端组
        nodes.add(new TreeNode("5", "2", "移动端组"));     // 前端组的子节点:移动端组
        nodes.add(new TreeNode("6", "3", "Java组"));       // 后端组的子节点:Java组
        nodes.add(new TreeNode("7", "3", "大数据组"));     // 后端组的子节点:大数据组
        nodes.add(new TreeNode("8", null, "产品部"));       // 根节点:产品部(无父节点)
        nodes.add(new TreeNode("9", "8", "产品设计组"));   // 产品部的子节点:产品设计组

要求输出入下

java 复制代码
=== 部门层级(空格缩进) ===
技术部
    前端组
        PC前端组
        移动端组
    后端组
        Java组
        大数据组
产品部
    产品设计组

思路

我最初的思路是 用一个map 把 每个节点的子节点存储起来,

  • 键(Key):父节点 ID
  • 值(Value):该父节点对应的所有子节点列表(List<TreeNode>

然后用递归的方式也就是树的深度有限遍历打印

但是在做这个算法的时候时间有限,没手搓出来,感觉快了,但是看了AI给的方法,感觉用代码优雅的写出来还是很有难度的。

完整代码加注释

java 复制代码
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 树状结构打印工具类(空格缩进版)
 * 功能:将扁平存储的节点列表(通过parentId关联父子关系)转换为树状结构,
 * 并仅通过空格缩进的方式在控制台打印层级关系,不使用特殊符号
 */
public class TreeSpacePrinter {

    /**
     * 树节点实体类
     * 存储节点的唯一标识、父节点标识及节点名称,用于构建树状结构
     */
    static class TreeNode {
        private String id;         // 节点唯一ID(用于标识当前节点)
        private String parentId;   // 父节点ID(用于关联父节点,根节点为null)
        private String name;       // 节点名称(打印时展示的内容)

        /**
         * 构造方法:初始化节点的核心属性
         * @param id 节点唯一标识
         * @param parentId 父节点标识(根节点传null)
         * @param name 节点展示名称
         */
        public TreeNode(String id, String parentId, String name) {
            this.id = id;
            this.parentId = parentId;
            this.name = name;
        }

        // Getter方法:供工具类获取节点属性
        public String getId() { return id; }
        public String getParentId() { return parentId; }
        public String getName() { return name; }

        /**
         * 重写toString方法:打印节点时直接展示名称
         * 避免默认打印对象地址,简化输出内容
         */
        @Override
        public String toString() { return name; }
    }

    /**
     * 构建父节点到子节点的映射关系
     * 作用:将扁平的节点列表转换为"父节点ID→子节点列表"的映射,
     * 便于快速查找某个节点的所有子节点,避免多次遍历列表
     * @param nodeList 原始扁平节点列表
     * @return 父节点ID为键,对应子节点列表为值的Map
     */
    private static Map<String, List<TreeNode>> buildTreeMap(List<TreeNode> nodeList) {
        // 初始化Map:键为父节点ID,值为该父节点下的所有子节点
        Map<String, List<TreeNode>> treeMap = new HashMap<>();
        
        // 遍历所有节点,将节点添加到其父节点对应的子列表中
        for (TreeNode node : nodeList) {
            // computeIfAbsent:若父节点ID对应的列表不存在,则创建空列表;存在则直接获取
            // 然后将当前节点添加到该列表中,完成父子关联
            treeMap.computeIfAbsent(node.getParentId(), k -> new ArrayList<>()).add(node);
        }
        return treeMap;
    }

    /**
     * 查找所有根节点
     * 根节点定义:父节点ID为null,或在节点列表中不存在对应父节点的节点
     * @param nodeList 原始扁平节点列表
     * @return 所有根节点组成的列表
     */
    private static List<TreeNode> findRootNodes(List<TreeNode> nodeList) {
        List<TreeNode> rootNodes = new ArrayList<>();
        // 提取所有节点的ID到列表,用于快速判断父节点是否存在
        List<String> allNodeIds = nodeList.stream().map(TreeNode::getId).toList();

        // 遍历节点,筛选出根节点
        for (TreeNode node : nodeList) {
            String parentId = node.getParentId();
            // 根节点条件:父节点ID为null,或父节点ID不在所有节点ID中(父节点不存在)
            if (parentId == null || !allNodeIds.contains(parentId)) {
                rootNodes.add(node);
            }
        }
        return rootNodes;
    }

    /**
     * 递归打印节点及其子节点(核心打印方法)
     * 通过控制空格缩进量体现节点层级,每层缩进固定数量的空格
     * @param node 当前需要打印的节点
     * @param treeMap 父节点到子节点的映射(用于获取当前节点的子节点)
     * @param depth 当前节点的层级深度(根节点为0,子节点深度+1)
     */
    private static void printNode(TreeNode node, Map<String, List<TreeNode>> treeMap, int depth) {
        // 计算缩进字符串:每层缩进4个空格(可根据需要修改空格数量调整缩进幅度)
        String indent = "    ".repeat(depth);
        // 打印当前节点:缩进+节点名称(缩进量由深度决定,体现层级)
        System.out.println(indent + node);

        // 获取当前节点的所有子节点(从映射中根据节点ID查询)
        List<TreeNode> children = treeMap.get(node.getId());
        // 若没有子节点,直接返回(递归终止条件)
        if (children == null || children.isEmpty()) {
            return;
        }

        // 遍历所有子节点,递归打印(子节点深度=当前节点深度+1)
        for (TreeNode child : children) {
            printNode(child, treeMap, depth + 1);
        }
    }

    /**
     * 对外暴露的树状打印入口方法
     * 封装了树构建、根节点查找和打印的完整流程
     * @param nodeList 待打印的扁平节点列表
     */
    public static void printTree(List<TreeNode> nodeList) {
        // 边界处理:若节点列表为空,直接提示
        if (nodeList == null || nodeList.isEmpty()) {
            System.out.println("节点列表为空,无需打印");
            return;
        }

        // 1. 构建父节点到子节点的映射
        Map<String, List<TreeNode>> treeMap = buildTreeMap(nodeList);
        // 2. 查找所有根节点
        List<TreeNode> rootNodes = findRootNodes(nodeList);

        // 3. 从根节点开始打印(根节点深度为0)
        for (TreeNode root : rootNodes) {
            printNode(root, treeMap, 0);
        }
    }

    /**
     * 测试方法:创建示例节点列表并验证打印效果
     * 模拟实际业务中的层级数据(如部门结构、菜单列表等)
     */
    public static void main(String[] args) {
        // 1. 构建测试用的扁平节点列表(模拟从数据库查询的结果)
        List<TreeNode> nodes = new ArrayList<>();
        nodes.add(new TreeNode("1", null, "技术部"));       // 根节点:技术部(无父节点)
        nodes.add(new TreeNode("2", "1", "前端组"));        // 技术部的子节点:前端组
        nodes.add(new TreeNode("3", "1", "后端组"));        // 技术部的子节点:后端组
        nodes.add(new TreeNode("4", "2", "PC前端组"));     // 前端组的子节点:PC前端组
        nodes.add(new TreeNode("5", "2", "移动端组"));     // 前端组的子节点:移动端组
        nodes.add(new TreeNode("6", "3", "Java组"));       // 后端组的子节点:Java组
        nodes.add(new TreeNode("7", "3", "大数据组"));     // 后端组的子节点:大数据组
        nodes.add(new TreeNode("8", null, "产品部"));       // 根节点:产品部(无父节点)
        nodes.add(new TreeNode("9", "8", "产品设计组"));   // 产品部的子节点:产品设计组

        // 2. 打印提示信息并执行树状打印
        System.out.println("=== 部门层级结构(空格缩进展示) ===");
        printTree(nodes);
    }
}
    
相关推荐
江畔柳前堤8 小时前
大语言模型分布式训练:从并行策略到万卡工程的系统梳理
人工智能·分布式·深度学习·算法·目标检测·机器学习·语言模型
Forever Nore10 小时前
LeetCode 4 寻找两个正序数组的中位数 - 二分
算法·leetcode
罗西的思考11 小时前
【OpenClaw具身硬件】MiniClaw 阅读笔记---(1)基础
人工智能·算法·机器学习
蛋先生DX12 小时前
大模型参数存储格式揭秘:BF不是男朋友
深度学习·算法·llm
爱跳舞的烤冷面12 小时前
自学嵌入式第22天(数据结构——哈希)
数据结构·算法·哈希算法
猎嘤一号13 小时前
博弈论(Game Theory)的理论、算法与工程
人工智能·算法·安全·博弈论
月光船幽幽13 小时前
影子模式下保护 logits 不被修改
人工智能·python·算法
马拉AI13 小时前
腾讯开源 Agent 记忆系统,AI“换对话就忘”的问题有了新解法(附安装使用教程)
人工智能·算法·开源·科研
地平线开发者15 小时前
征程6工具链模型X86推理方式说明
算法
地平线开发者15 小时前
【征程6】校准量化中HistogramObserver解析
算法