算法体系-11 第十一节:二叉树基本算法(上)

news2025/1/11 0:46:12

一 两链表相交

1.1 题目描述

给定两个可能有环也可能无环的单链表,头节点head1和head2。请实现一个函数,如果两个链表相交,请返回相交的 第一个节点。如果不相交,返回null

【要求】

如果两个链表长度之和为N,时间复杂度请达到O(N),额外空间复杂度 请达到O(1)。

1.2 判断一个单链表是否有环

情况 :假如一个单链表,给你一个头节点,如果有环的话返回第一个入环的节点,如果无环的话返回null?

1.2.1 方案一 容器的办法 Set<>集合

1.2.1.1 假如链表无环,走到null的时候,在hashset里面都没有找到重复的

假如链表是有环的,遍历链表的判断当前链表每个节点,判断当前节点是否在hashset里面,如果在就说明有环,如果不在将该节点放入set里面,如果遍历到最后一个null后m都没找到有在hash里面的节点说明无环

1.2.1.2 代码
//快慢指针
 public boolean hasCycle(ListNode head) {
        if (head == null) {
            return false;
        }
 
        ListNode slow = head;
        ListNode fast = head.next;
 
        while (slow != fast) {
            if (fast == null || fast.next == null) {
                return false;
            }
 
            slow = slow.next;
            fast = fast.next.next;
        }
 
        return true;
    }
}
/**
     * 通过Set集合记录值的方式,如果有重复的数据,就代表有环
     * @param node
     * @return
     */
    private boolean hasCycle2(Node node) {
        Set<Node> nodeSet = new HashSet<>();
        //此字段仅用来记录遍历次数
        int traverseCount = 0;
        while (node != null) {
            if (nodeSet.contains(node)) {
                Log.d(TAG, "hasCycle2==>有环...traverseCount="+traverseCount);
                return true;
            }
            traverseCount ++;
            Log.d(TAG, "hasCycle2==>traverseCount="+traverseCount);
            nodeSet.add(node);
            node = node.next;
        }
        Log.d(TAG, "hasCycle2==>无环");
        return false;
    }
1.2.1.3 总结

链表的环可能有很多,但单链表只有一个next指针,不会出现分叉的情况

1.2.2 使用快慢指针遍历链表

思路:使用快慢指针遍历链表,快指针一旦走到

null,则该单链表一定无环(快指针一次走两步),如果存在环的话,快指针与慢指针一定会在环上相遇。

当快慢指针相遇时,让快指针重新指向初始节点;慢指针原地不变;两个指针都每次走一步,一定会在链表的入环节点相遇

1.2.2.1 分析

情况一 如果快指针走到null了说明无环

如下情况图解找到相遇的点,但不是相交的入口节点,再做图二的解析,当相遇的时候,快指针去到链表的头,慢指针留在原地继续走,这个时候快慢指针都只走一步,这样再次往前走的话他两一定会在入口节点相遇,这个相遇的点就为入口的交点

1.2.2.2 代码
public static class Node {
    public int value;  
      public Node next;    
      public Node(int data) {
       this.value = data;   
        }
}
// 找到链表第一个入环节点,如果无环,返回null
    public static Node getLoopNode(Node head) {
        if (head == null || head.next == null || head.next.next == null) {
            return null;
        }
        // n1 慢  n2 快
        Node slow = head.next; // n1 -> slow
        Node fast = head.next.next; // n2 -> fast
        while (slow != fast) {
            if (fast.next == null || fast.next.next == null) {
                return null;
            }
            fast = fast.next.next;
            slow = slow.next;
        }
        // slow fast  相遇
        fast = head; // n2 -> walk again from head
        while (slow != fast) {
            slow = slow.next;
            fast = fast.next;
        }
        return slow;
    }

1.2.2.3 总结

快慢指针找链表入环的节点

1、找到相遇点

2、快指针去到链表头再都分别往前走就能找到

1.3 本题分析

情况一 两个链表都无环的情况-存在相交,

情况二 如果两个链表一个无环,一个有环-不存在相交,因为相交后只存在一个next,只有一个有环需要两个next

情况三 两个都有环 的情况,这个环在相交节点后成一个环

情况一

情况二

情况三两个链表有环的情况

1.3.1 情况一 两个链表都无环的情况-存在相交,

1.3.1.1 hashset

先判断两个链表都没环的情况,先通过上面的方法判断每个链表是否有环,再利用hashset,先将一个链表放入hashset里面,再遍历宁一个链表是否有节点在haseset里面

1.3.1.2 二不使用hashset

分析

先都让两个无环的链表走到最后,看是最后一个节点的内存地址是否相等,不相等一定不相交,end1==end2那么他们是一定相交的,end1和end2是他们相交的最后一个节点,要求的是第一个相交的节点,让长的一个走先他多出来的节点,再让短的和他们一起走,当有相等的时候那么这个点就是他们相交的第一个节点;

代码

// 如果两个链表都无环,返回第一个相交节点,如果不想交,返回null
    public static Node noLoop(Node head1, Node head2) {
        if (head1 == null || head2 == null) {
            return null;
        }
        Node cur1 = head1;
        Node cur2 = head2;
        int n = 0;
        while (cur1.next != null) {
            n++;
            cur1 = cur1.next;
        }
        while (cur2.next != null) {
            n--;
            cur2 = cur2.next;
        }
       //判断是否相交
        if (cur1 != cur2) {
            return null;
        }
        // n  :  链表1长度减去链表2长度的值
        cur1 = n > 0 ? head1 : head2; // 谁长,谁的头变成cur1
        cur2 = cur1 == head1 ? head2 : head1; // 谁短,谁的头变成cur2
        n = Math.abs(n);
        while (n != 0) {
            n--;
            cur1 = cur1.next;
        }
        while (cur1 != cur2) {
            cur1 = cur1.next;
            cur2 = cur2.next;
        }
        return cur1;
    }

1.3.2 情况二

分析 如果两个链表一个无环,一个有环-不存在相交,因为相交后只存在一个next,要一个有环需要两个next

1.3.3 情况三

情况三 两个都有环 的情况,这个环在相交节点后成一个环

当loop1 和loop2相等的化就是情况三的第二种

当loop1 和loop2不相等的化就是情况三的第一种和第三种

1.3.3.1 loop1 == loop2

当loop1 和loop2相等的化、话就是情况三的第二种,求第一个交点

分析 当loop1 和loop2终止点时,就和情况一中的求无环链表的第一个交点一样

1.3.3.2 loop1 != loop2

当loop1 和loop2不相等的化就是情况三的第一种和第二种

分析 loop1环节点开始,我转一圈的过程中如果越到loop2说明是情况三, loop1 和 loop2都是他两的相交节点

没有越到说明是情况1,没有相交节点情况1返回null

1.3.3 代码
// 两个有环链表,返回第一个相交节点,如果不想交返回null
    public static Node bothLoop(Node head1, Node loop1, Node head2, Node loop2) {
        Node cur1 = null;
        Node cur2 = null;
        if (loop1 == loop2) {
            cur1 = head1;
            cur2 = head2;
            int n = 0;
            while (cur1 != loop1) {
                n++;
                cur1 = cur1.next;
            }
            while (cur2 != loop2) {
                n--;
                cur2 = cur2.next;
            }
            cur1 = n > 0 ? head1 : head2;
            cur2 = cur1 == head1 ? head2 : head1;
            n = Math.abs(n);
            while (n != 0) {
                n--;
                cur1 = cur1.next;
            }
            while (cur1 != cur2) {
                cur1 = cur1.next;
                cur2 = cur2.next;
            }
            return cur1;
        } else {
            cur1 = loop1.next;
            while (cur1 != loop1) {
                if (cur1 == loop2) {
                    return loop1;
                }
                cur1 = cur1.next;
            }
            return null;
        }
    }
1.3.4 主函数调用
public static Node getIntersectNode(Node head1, Node head2) {
    if (head1 == null || head2 == null) {
       return null;    }
    Node loop1 = getLoopNode(head1);   
     Node loop2 = getLoopNode(head2);   
      if (loop1 == null && loop2 == null) {
       return noLoop(head1, head2);   
        }
    if (loop1 != null && loop2 != null) {
       return bothLoop(head1, loop1, head2, loop2);   
        }
    return null;
    }

二 二叉树的先序、中序、后序遍历

2.1 递归实现

2.1.1 前中后遍历的讲解

先序 中序 后序 都可以由以下函数改出来,把打印放在第一次,第二次,第三次就是对应的前中后遍历;

递归序

充分理解递归点过程,根据递归的实现原理,        

2.1.2 代码

package class10;

public class Code02_RecursiveTraversalBT {

    public static class Node {
        public int value;
        public Node left;
        public Node right;

        public Node(int v) {
            value = v;
        }
    }

    public static void f(Node head) {
        if (head == null) {
            return;
        }
        // 1
        f(head.left);
        // 2
        f(head.right);
        // 3
    }

    // 先序打印所有节点
    public static void pre(Node head) {
        if (head == null) {
            return;
        }
        System.out.println(head.value);
        pre(head.left);
        pre(head.right);
    }

    public static void in(Node head) {
        if (head == null) {
            return;
        }
        in(head.left);
        System.out.println(head.value);
        in(head.right);
    }

    public static void pos(Node head) {
        if (head == null) {
            return;
        }
        pos(head.left);
        pos(head.right);
        System.out.println(head.value);
    }

    public static void main(String[] args) {
        Node head = new Node(1);
        head.left = new Node(2);
        head.right = new Node(3);
        head.left.left = new Node(4);
        head.left.right = new Node(5);
        head.right.left = new Node(6);
        head.right.right = new Node(7);

        pre(head);
        System.out.println("========");
        in(head);
        System.out.println("========");
        pos(head);
        System.out.println("========");

    }

}

2.2 非递归实现-

2.2.1 前序遍历

2.2.1.1 分析
2.2.1.2 代码
public static class Node {
        public int value;
        public Node left;
        public Node right;

        public Node(int v) {
            value = v;
        }
    }

    public static void pre(Node head) {
        System.out.print("pre-order: ");
        if (head != null) {
            Stack<Node> stack = new Stack<Node>();
            stack.add(head);
            while (!stack.isEmpty()) {
                head = stack.pop();
                System.out.print(head.value + " ");
                if (head.right != null) {
                    stack.push(head.right);
                }
                if (head.left != null) {
                    stack.push(head.left);
                }
            }
        }
        System.out.println();
    }

2.2.2 中序遍历


2.2.2.1 分析

2.2.2.2 代码
public static void in(Node cur) {
        System.out.print("in-order: ");
        if (cur != null) {
            Stack<Node> stack = new Stack<Node>();
            //cur == null 还需要继续保证往下,通过!stack.isEmpty()
            while (!stack.isEmpty() || cur != null) {
                if (cur != null) {
                    stack.push(cur);
                    cur = cur.left;
                    //1、假如上面的最后一个节点 cur = d.left;
                } else {
                    //2、cur == null ,弹出  cur = stack.pop(); 为d,
                    //3、找cur = cur.right;还是空,还是来到这个循环,
                    //4、弹出的就是b了,cur = stack.pop();为e了继续往下走
                    //5、e的左右都为空再弹出的就是a了
                    cur = stack.pop();
                    System.out.print(cur.value + " ");
                    cur = cur.right;
                }
            }
        }
        System.out.println();
    }

2.2.3 后续遍历

2.2.3.1 在先序遍历的基础上进行修改, 先改出一个头右左去压栈(压栈的时候先左再右,弹出来就对了),从新栈出来就是左右头就是后续遍历

先在压栈头左右基础上改出一个压栈为头右左的形式,弹出的时候不立马打印,而是先压入一个栈里面最后再弹出就是后续遍历

左右头

2.2.3.2 代码
public static void pos1(Node head) {
        System.out.print("pos-order: ");
        if (head != null) {
            Stack<Node> s1 = new Stack<Node>();
            Stack<Node> s2 = new Stack<Node>();
            s1.push(head);
            while (!s1.isEmpty()) {
                head = s1.pop(); // 头 右 左
                s2.push(head);
                if (head.left != null) {
                    s1.push(head.left);
                }
                if (head.right != null) {
                    s1.push(head.right);
                }
            }
            // 左 右 头
            while (!s2.isEmpty()) {
                System.out.print(s2.pop().value + " ");
            }
        }
        System.out.println();
    }

扩展见代码 用一个栈来实现

三 附加题 X 祖先节点 交集 后边再看

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1531718.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

什么是GPU云服务器?2024腾讯云GPU云服务器全解析!

腾讯云GPU服务器是提供GPU算力的弹性计算服务&#xff0c;腾讯云GPU服务器具有超强的并行计算能力&#xff0c;可用于深度学习训练、科学计算、图形图像处理、视频编解码等场景&#xff0c;腾讯云百科txybk.com整理腾讯云GPU服务器租用价格表、GPU实例优势、GPU解决方案、GPU软…

名词【语法笔记】

1.名词分为几大类 2.每一类&#xff0c;又有几个小类&#xff0c;以及所需要注意什么

python中字典相关知识点总结

1.字典的定义 字典&#xff1a;在Python中&#xff0c;字典是一系列键-值对。每个键都与一个值相关联&#xff0c;程序员可以通过键来访问与之相关联的值。 实际举例&#xff1a; student{name:xincun,age:18} 通过实例我们可以发现&#xff0c;键-值对是两个相关联的值。指…

3.20作业

1.创建一个工人信息库&#xff0c;包含工号&#xff08;主键&#xff09;&#xff0c;姓名&#xff0c;年龄&#xff0c;薪资 CREATE TABLE work (id int, name char, age int,money float); 2.添加三条工人信息&#xff08;可以完整信息&#xff0c;也可以非完整信息&#xff…

Twincat实现电机控制

不仅是控制系统的核心部分&#xff0c;而且能够将任何基于PC的系统转换为一个带有PLC、NC、CNC和机器人实时操作系统的实时控制系统。TwinCAT软件在工业自动化领域具有广泛的应用&#xff0c;特别是在机器人关节电机控制方面!!! 在机器人关节电机控制方面&#xff0c;TwinCAT通…

【C语言基础篇】字符串处理函数(二)strcpy的介绍及模拟实现

目录 一、strcpy介绍 函数原型&#xff1a; 函数功能&#xff1a; 函数参数&#xff1a; 函数返回值&#xff1a; 二、strcpy模拟实现 代码&#xff1a; 测试&#xff1a; 个人主页&#xff1a; 倔强的石头的博客 系列专栏 &#xff1a;C语言指南 C语言刷题系列…

JeePlus低代码开发平台存在SQL注入漏洞

漏洞描述 JeePlus低代码开发平台存在SQL注入漏洞 fofa语句 app"JeePlus" 漏洞复现 打开页面 构造payload GET /a/sys/user/validateMobile?mobile1%27and1%3D%28updatexml%281%2Cconcat%280x7e%2C%28selectmd5%281%29%29%2C0x7e%29%2C1%29%29and%271%27%3D%271…

六、循环结构

在python当中有两类循环结构&#xff1a;for循环和while循环 一、遍历for循环 for循环首先判断遍历对象中是否有元素&#xff0c;在依次遍历 for循环常与range&#xff08;&#xff09;函数使用 for i in range(1,10,):#range()函数依次遍历1~10但不包括10print(i,end ) p…

账号+密码+图片验证码认证

账号密码图片验证码认证 实现步骤 实现账号密码认证&#xff0c;执行流程如下 第一步: 对于验证码服务工程的生成验证码图片的接口在网关处需要放行,否则页面无法获取生成的验证码图片 /**临时放行所有请求 /auth/**认证服务地址 /content/open/**内容管理公开访问文件接口 …

【计算机视觉】Gaussian Splatting源码解读补充(二)

第一部分 目录 三、前向传播&#xff08;渲染&#xff09;&#xff1a;submodules/diff-gaussian-rasterization/cuda_rasterizer/forward.cu预备知识&#xff1a;CUDA编程基础 三、前向传播&#xff08;渲染&#xff09;&#xff1a;submodules/diff-gaussian-rasterization/c…

软件工程导论画图题汇总:期末+复试

文章目录 一、数据模型&#xff1a;实体联系图&#xff08;E-R图&#xff09;二、行为模型&#xff1a;状态转换图三、功能模型&#xff1a;数据流图四、数据字典五、系统流程图六、层次图七、HIPO图八、结构图九、程序流程图十、盒图十一、PAD图十二、判定表、判定树 一、数据…

Vue2(四):Vue监测数据的原理

一、先来看一个问题 添加一个按钮点击更新马冬梅的信息&#xff1a; <button click"gengxin">点击更新马冬梅的信息</button> methods:{gengxin(){this.person[1].name马老师,this.person[1].age50,this.person[1].sex男}} 下面这种方式就不能奏效&a…

数据库系统概论-第5章 数据库完整性

5.1 实体完整性 5.2 参照完整性 5.3 用户定义完整性 5.4 完整性约束命名子句 5.5 域中的完整性限制 5.6 断言 5.7 触发器 5.8 小结

STM32CubeIDE基础学习-EXTI外部中断实验

STM32CubeIDE基础学习-EXTI外部中断实验 文章目录 STM32CubeIDE基础学习-EXTI外部中断实验前言第1章 硬件介绍第2章 工程配置2.1 工程外设配置部分2.2 生成工程代码部分 第3章 代码编写第4章 实验现象总结 前言 中断概念&#xff1a;让CPU打断正在执行的程序&#xff0c;进而去…

第8关:删除P表中所有的记录

任务描述 删除P表中所有的记录 相关知识 零件表P由零件代码&#xff08;PNO&#xff09;、零件名(PNAME)、颜色(COLOR)、重量(WEIGHT)组成&#xff1b; P表如下图&#xff1a; 现已构建P表&#xff0c;结构信息如下&#xff1a; 开始你的任务吧&#xff0c;祝你成功 USE my…

spring MVC是如何找到html文件并返回的?

Spring MVC 搜索路径 启动一个SpringBoot项目时&#xff0c;访问http://localhost:8080&#xff0c;对于SpringMVC&#xff0c;它会默认把这段url看成http://localhost:8080/index.html&#xff0c;所以这两个url是等价的。 .html, .css, .js, .img …都是静态资源文件&#x…

直播预约丨《袋鼠云大数据实操指南》No.1:从理论到实践,离线开发全流程解析

近年来&#xff0c;新质生产力、数据要素及数据资产入表等新兴概念犹如一股强劲的浪潮&#xff0c;持续冲击并革新着企业数字化转型的观念视野&#xff0c;昭示着一个以数据为核心驱动力的新时代正稳步启幕。 面对这些引领经济转型的新兴概念&#xff0c;为了更好地服务于客户…

6.如何判断数据库搜索是否走索引?

判断是否使用索引搜索 索引在数据库中是一个不可或缺的存在&#xff0c;想让你的查询结果快准狠&#xff0c;还是需要索引的来帮忙&#xff0c;那么在mongo中如何判断搜索是不是走索引呢&#xff1f;通常使用执行计划&#xff08;解释计划、Explain Plan&#xff09;来查看查询…

Linux之缓冲区与C库IO函数简单模拟

缓冲区 首先, 我们对缓冲区最基本的理解, 是一块内存, 用户提供的缓冲区就是用户缓冲区, C标准库提供的就是C标准库提供的缓冲区, 操作系统提供的就是操作系统缓冲区, 它们都是一块内存. 为什么要有缓冲区? 先举个生活中的例子, 我们寄快递的时候往往是去驿站寄快递, 而不是…

Spring MVC文件下载配置

版权声明 本文原创作者&#xff1a;谷哥的小弟作者博客地址&#xff1a;http://blog.csdn.net/lfdfhl 文件下载 在Spring MVC中通常利用commons-io实现文件下载&#xff0c;示例代码如下&#xff1a; Controller RequestMapping("......") public class DownloadC…