算法随笔(一)
一、油车最远距离
每辆车每跑 1 公里消耗 1 升油,油箱容量 12 升;车与车之间可随时无损互相加油,被“弃置”的车不必返回。问最远能让一辆车开多远。
思路
让多辆车一起走,在合适的位置把其中一辆的油“喂”给其他车,使剩下的车都满箱,然后把这辆空车留在原地不再前进。
当有 m 辆车一起走时,总耗油是 m 升/公里。要凑到正好“淘汰”1 辆(即净消耗一个满箱 C升),应走公里。依次减少车数,直到只剩 1 辆独自前进。
当我们这C=12升、一共四辆车
4 车同行 12/4=3 公里,在 3 公里处把油分给其余三辆使其满箱,留下一辆空车停下。
3 车再走 12/3=41 公里,到 7 公里处再淘汰一辆。
2 车再走 12/2=6 公里,到 13 公里处再淘汰一辆。
剩下 1 车最后在13公里处再独走 12/1=12公里。
所以最后的公里数是25公里
这里有一个一般化结论
这种题应该不好用程序穷尽,让这些车能返回补油就是一个小学奥数题,没想到小学奥数题都是这种程度了吗?
二、基于父节点关联的扁平节点列表,构建树形结构并实现可视化打印
一个ArrayList<TreeNode>
,每个节点仅存储「自身 ID」和「父节点 ID」(无直接子节点列表),节点结构如下:
// 核心节点结构(仅含父子关联必要字段)
class TreeNode {private String id; // 节点唯一标识private String parentId; // 父节点ID(根节点为null)private String name; // 节点名称(用于打印展示)// 构造器、getter、toString略
}
列表长这样
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", "产品设计组")); // 产品部的子节点:产品设计组
要求输出入下
=== 部门层级(空格缩进) ===
技术部前端组PC前端组移动端组后端组Java组大数据组
产品部产品设计组
思路
我最初的思路是 用一个map 把 每个节点的子节点存储起来,
- 键(Key):父节点 ID
- 值(Value):该父节点对应的所有子节点列表(
List<TreeNode>
)
然后用递归的方式也就是树的深度有限遍历打印
但是在做这个算法的时候时间有限,没手搓出来,感觉快了,但是看了AI给的方法,感觉用代码优雅的写出来还是很有难度的。
完整代码加注释
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方法:打印节点时直接展示名称* 避免默认打印对象地址,简化输出内容*/@Overridepublic 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);}
}