算法随笔(一)

一、油车最远距离

每辆车每跑 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);
    }
}
    
相关推荐
晚安里2 小时前
JVM相关 4|JVM调优与常见参数(如 -Xms、-Xmx、-XX:+PrintGCDetails) 的必会知识点汇总
java·开发语言·jvm·后端·算法
THMAIL3 小时前
机器学习从入门到精通 - 集成学习核武器:随机森林与XGBoost工业级应用
人工智能·python·算法·随机森林·机器学习·集成学习·sklearn
要做朋鱼燕3 小时前
【C++】迭代器详解与失效机制
开发语言·c++·算法
一支鱼3 小时前
leetcode-6-正则表达式匹配
算法·leetcode·typescript
闪电麦坤955 小时前
数据结构:图的表示 (Representation of Graphs)
数据结构·算法·图论
利以檀本人(梦泽不忙)6 小时前
#T1359. 围成面积
c++·程序人生·算法
胡萝卜3.06 小时前
【LeetCode&数据结构】设计循环队列
数据结构·算法·leetcode·队列·循环队列
徐归阳6 小时前
数组本身的深入解析
数据结构·c++·算法
白榆!7 小时前
string类的实现
开发语言·c++·算法