大数据面试(java)题库汇总

文章目录

大数据面试

1. HDFS读写流程?

1.1 HDFS写流程

在这里插入图片描述
1.clinet上传200M文件,client根据HDFS配置信息对文件进行拆分block1,block2
2.client 通知NameNode block1,block2存储,副本三份
3-4. NameNode进行权限等校验,根据内部算法合理的计算出block存储的dataNode节点,并将存储信息返回给client
5. client根据NameNode返回的信息先后进行block1,block2文件上传,在上传的过程中,其他两个副本存储是同步进行的,dataNode存储成功会通知nameNode已存储成功,所有的dataNode存储成功之后,nameNode通知client整个写流程结束。

1.2 HDFS读流程

在这里插入图片描述
1.Client执行:hadoop fs -get /spark/file.txt 获取文件
2.Client 通知nameNode获取文件的行为,nameNode从原数据获取文件的存储信息block0,,block1信息
3.Client 获取到存储信息直接从dataNode获取,获取成功后自动合并block,下载完成结束。

2 HDFS HA 架构

在这里插入图片描述

  1. 通过zookeeper组件,启动两个线程进行节点监控,存在宕机,自动切换
  2. nameNode主备之间数据,有同步机制

3 小文件给hadoop带来的瓶劲问题

3.1 造成问题

  1. 在处理批量文件,小文件太多,会造成多次的磁盘IO,大量的mapReduce,task资源的注册与销毁的开销(hadoop一个文件的处理正常对饮给一个mapReduce,task),造成文件读取时间远远大于数据消费处理的时间,从而导致这集群效率降低。
  2. 造成多个任务,作业处于等待,等待资源释放
  3. 小文件存储信息,都会以元数据的信息存储到nameNode。一方面,每条元数据信息会占用200字节左右存储空间,小文件过多,会提高内存的使用率。另一反面,集群启动,nameNode会从磁盘重新加载元数据,会大大的提高集群启动时间,重而导致在启动的过程当中无法提交或者处理任务。

3.2 IO问题,性能问题如何解决?

4 flink 组件,作业提交流程?

4.1 组件?

  1. dispatcher:它为应用提交提供了 REST 接口。Dispatcher 也会启动一个 Web UI,用来方便地展示和监控作业执行的信息。Dispatcher 在架构中可能并不是必需的,这取决于应用提交运行的方式。
  2. jobManager:JobManager 会先接收到要执行的应用程序,这个应用程序会包括:作业图(JobGraph)、逻辑数据流图(logical dataflow graph)和打包了所有的类、库和其它资源的 JAR 包,而在运行过程中,JobManager 会负责所有需要中央协调的操作,比如说检查点(checkpoints)的协调。
  3. resourcemanager:负责管理任务管理器(TaskManager)的插槽(slot),ResourceManager 还负责终止空闲的 TaskManager,释放计算资源。
  4. taskManager: 旗下有slots,作用是工作执行者。

4.2 任务提交流程?

4.2.1 standalone提交流程?

在这里插入图片描述

  1. app提交要执行的应用及参数。
  2. dispatcher启动并提到应用到jobManager
  3. jobManager对其提交的jar,参数分析生成作业图,逻辑数据流图及其需要的插槽资源等,将信息提交给resourceManager申请资源
    4-6. resourceManager向taskmanager发出指令,启动申请相应的资源,无足够的资源则等待,等待超时job提交失败。
  4. taskManager提供足够的资源给jobManager,等待jobManage分配任务。
4.2.1 YARN提交流程?

在这里插入图片描述

  1. client 向hdfs上传jar和配置信息
  2. 向Yaen 的ResourceManager申请启动JobManager资源,yarn通知NodeManager启动ApplicationMaster,启动jobManager并加载jar包和配置文件,jobManager分析生成作业图,数据流图和启动需要的资源。
  3. jobManager向Yanr的resourceManager申请资源
  4. resourceManager通知NodeManager创建container启动taskManager资源
  5. TaskManager启动后向 JobManager 发送心跳包,并等待 JobManager 向其分配任务。

4.3 flink如何优化?

  1. taskManager.numberOfTaskSlots 配置与服务器cpu一致,将cpu利用率提高到最高
  2. 提高核心任务job的并行度,提高服务器资源利用
  3. taskManager内存分配,根据实际的数据量,计算分析内存最高的使用率进行配置。
  4. 根据ExecutionGraph对任务链(算子链)做相关拆分优化

java 篇

1 反射概念

在程序运行时状态中,通过反射可以知道该类的所有属性和方法;对任意一个对象,通过反射可以调用其属性和方法;通过动态获取信息以及动态获取对象的方法功能称之为反射机制。

1.1 如何获取反射?

// 1 方式1
Class<?> clazz = Class.forName("类全路径")
// 2 方式2
Object obj = new Object();
Class<?> clazz =  obj.getClass();
Class<?> clazz =  Class.getClass();

1.2 反射的应用,手写ORMMapper 框架

2 多线程

2.1 创建线程的方式?共同点异同点?

// 1. 继承Thread
// 2. 实现Runnable
  1. 共同点:都要重写run方法,通过start启动
  2. 异同点:Thread本身就实现了Runnable,可以简单理解Thread是一个Runnable的加强类

2.2 主线程获取子线程的返回值?

  1. 主线程等待,直到获取到值。(不推荐)
  2. 使用线程的Thread.join()方法,阻塞等待线程使用在继续执行。(不推荐使用)
  3. Callbale接口,FetureTask机制
public static main(String[] args) throw Exception {
  FetureTask<String> fetureTask = new FetureTask<String>(new Callable<String>() {
    // 业务逻辑
    return "字符串";
  });
 new Thread(fetureTask).start();
 // fetureTask .get() 方法调用会阻塞,等待线程执行完成
 System.out.println(fetureTask .get());
}
  1. Callbale接口,ExecutorServer线程池

2.3 synchronized

synchronized:可以解决线程处理共享数据(共享对象)造成的并发问题。synchronized锁的释放机制:当一个线程获取锁,需要等到线程抛出异常或者线程执行完毕才会释放锁,线程获取锁的期间,其他线程不能操作当前对象。
简单理解:synchronized修饰的方法是对对象加锁,修饰的static方法是对类加锁。

2.4 线程wati(),notifyAll().notify()

wati:线程等待
notifyAll:唤醒其他所有线程
notify:唤醒其他线程中的一个
注意:notify,notifyAll,notify需要在synchronized关键字中使用,防止异常现象产生

2.5 synchronized与可重入锁ReenTrantLock区别?

  1. 从实现的角度:Synchronized是依赖于JVM实现的,而ReenTrantLock是JDK实现的
  2. 性能发面:Synchronized与ReenTrantLock差不多;在两者均可用情况,官方建议使用Synchronized
  3. 功能上:Synchronized的使用比较方便简洁,并且由编译器去保证锁的加锁和释放;ReenTrantLock需要手动开启与释放
  4. 灵活度:ReenTrantLock比Synchronized灵活

3 JVM(JDK1.8)

3.1 自定义classLoader

3.2 Class.forName 与 ClassLoader区别loadClass区别

类的装载过程

  1. 加载:ClassLoader加载class字节码文件,生成class文件
  2. 链接:校验,准备,解析,分配指针
  3. 初始化:构造方法执行,初始化变量,静态代码块执行等

ClassLoader的loadClass:只完成了第一步,生成class
Class.forName:完成类装载的整个过程

3.3 JVM 内存结构

3.4 JVM 垃圾回收机制

  1. 判断对象是否是垃圾回收的标准
  2. 判断对象是否是垃圾回收的算法

在这里插入图片描述

3.5 谈谈你所了解的垃圾回收机制?

  1. 标记-清除算法

    缺点:碎片化严重

  2. 复制算法
    在这里插入图片描述
    优点:无碎片化,简单高效
    缺点:可使用的内存变少了(只剩50%),不适合用于老年代对象存储,原因:老年代数据过多,对象内存的重新复制分配排序低效

  3. 标记-整理算法
    与标记-清除算法类似,不同点:标记-整理算法将被标记为垃圾的对象清除,将存活的对象进行整合顺序排序。此算法解决了:内存碎片化的问题,同时也解决了复制算法内存只能使用50%,但是缺点在处理耗时,适用于对象存活率高的场景:老生代。

  4. 分代收集算法
    在这里插入图片描述

  • 内存模型如上图,新创建的对象会添加到eden/from中的一个或者是eden/to中的一个。每经过一次GC,均会将eden及from的内存复制到to模块中。
  • 新生代使用复制算法,老生代使用标记清楚/标记整理算法
  • 当JVM在GC时,整个应用程序会停止工作stop the world),优化JVM主要目的是加快GC

3.6 JVM 新生代->老年代需要几次GC?

默认15次,新生代默认分配的内存占1/3,老年代默认分配的内存2/3。
内存分配模型如:
在这里插入图片描述

3.7 JVM 垃圾收集器

  1. 概念
    在这里插入图片描述
    jdk8 默认是G1。
  2. 垃圾回收器的搭配
    在这里插入图片描述

4 其他

4.1 volatile作用

解决多线程中共享变量的可见性,以及指令防止指令重排序的问题。
https://blog.csdn.net/qq_45376284/article/details/113486716

4.2 实现简单的线程池?

import com.google.common.collect.Lists;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;

public class MyThreadPool {

    private final AtomicInteger ctl = new AtomicInteger(0);

    // 默认线程池大小
    private static final int DEFAULT_THREAD_NUM = 5;

    // 执行最大任务
    private static final int DEFAULT_MAX_TASK = 20;

    // 队列
    private BlockingQueue<Runnable> queueRunnable;

    // 当前线程池的线程数
    private int current_thread_num;

    // 当前线程池最大中任务数
    private int current_max_task_num;

    // 工作的线程
    private Set<RunThread> workThread;

    static MyThreadPool newFixThreadPool(Integer threadNum, Integer maxTask) {
        return new MyThreadPool(threadNum, maxTask);
    }

    private MyThreadPool(Integer taskNum, Integer maxTask) {
        if (Objects.isNull(taskNum) || taskNum <= 0) {
            this.current_thread_num = DEFAULT_THREAD_NUM;
        } else {
            this.current_thread_num = taskNum;
        }
        if (Objects.isNull(maxTask) || maxTask <= 0) {
            this.current_max_task_num = DEFAULT_MAX_TASK;
        } else {
            this.current_max_task_num = maxTask;
        }
        this.queueRunnable = new ArrayBlockingQueue<>(this.current_max_task_num);

        this.workThread = new HashSet<>();

        for (int i = 0; i < current_thread_num; i++) {
            RunThread runThread = new RunThread("Thread pool-" + ctl.incrementAndGet());
            runThread.start();
            this.workThread.add(runThread);
        }


    }

    public void submit(Runnable runnable) {
        if (Objects.isNull(runnable)) {
            return;
        }
        queueRunnable.add(runnable);
    }

    public void submit(List<Runnable> runnableList) {
        if (CollectionUtils.isEmpty(runnableList)) {
            return;
        }
        if(runnableList.size() > this.current_max_task_num) {
            System.out.println("任务超标");
            return;
        }
        queueRunnable.addAll(runnableList);
    }


    private final class RunThread extends Thread {

        private String threadName;

        public RunThread(String name) {
            this.threadName = name;
        }

        @Override
        public void run() {
            try {
                for (;;) {
                    Runnable runnable = queueRunnable.take();
                    if (Objects.nonNull(runnable)) {
                        System.out.println(threadName);
                        runnable.run();
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * @describe 测试
     * @param args
     */
    public static void main(String[] args) {
        int num = 20;
        List<Runnable> runnableList = Lists.newArrayList();
        MyThreadPool myThreadPool = MyThreadPool.newFixThreadPool(2, 20);
        for (int i = 0; i < num; i++) {
            runnableList.add(new TestThread(i));
        }
        myThreadPool.submit(runnableList);
    }


    public static class TestThread implements Runnable {

        private int x;

        public TestThread(int x) {
            this.x = x;
        }

        @Override
        public void run() {
            System.out.println("TestThread:" + x);
        }
    }

}

4.3 多线程 实现生产者与消费者?

  1. TestThread
import org.springframework.util.CollectionUtils;

import java.util.Objects;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class TestThread {


    public static void main(String[] args) {

        QueueInfo queueInfo = new QueueInfo();
        producer producer = new producer(queueInfo);
        producer produce1 = new producer(queueInfo);
        consumer consumer = new consumer(queueInfo);
        consumer consumer1 = new consumer(queueInfo);
        consumer consumer2 = new consumer(queueInfo);
        producer.start();
        consumer.start();
        consumer1.start();
        consumer2.start();
        produce1.start();

    }

    public static class producer extends Thread {

        private QueueInfo queueInfo;

        public producer(QueueInfo queueInfo) {
            this.queueInfo = queueInfo;
        }

        @Override
        public void run() {
            super.run();
            ReentrantLock reentrantLock = this.queueInfo.getReentrantLock();
            Condition condition = this.queueInfo.getCondition();
            // 1. 获取锁 未获取 注释状态
            while (true) {
                reentrantLock.lock();
                try {
                    // 如果队列满了,阻塞
                    while (Objects.equals(queueInfo.size(), queueInfo.getMAX())) {
                        System.out.println(super.getName() + ":队列已满:" + queueInfo.size() + ",等待消费");
                        Thread.sleep(2000);
                        condition.await();
                    }
                    this.queueInfo.addQueue(super.getName());
                    Thread.sleep(10);
                    // 唤醒其他线程
                    condition.signal();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    reentrantLock.unlock();
                }
            }


        }
    }


    public static class consumer extends Thread {

        private QueueInfo queueInfo;

        public consumer(QueueInfo queueInfo) {
            this.queueInfo = queueInfo;
        }

        @Override
        public void run() {
            super.run();
            ReentrantLock reentrantLock = this.queueInfo.getReentrantLock();
            Condition condition = this.queueInfo.getCondition();
            // 消费逻辑
            // 1. 获取锁 未获取 注释状态

            while (true) {
                // 加锁消费
                reentrantLock.lock();
                try {
                    // 如果队列无消息,通知生产数据
                    while(CollectionUtils.isEmpty(this.queueInfo)) {
                        System.out.println(super.getName() + ":已消费完成,等待生产者生产消息");
                        condition.await();
                    }
                    this.queueInfo.poolQueue(super.getName());
                    Thread.sleep(80);
                    // 消费完成 唤醒其他生产者或者消费者
                    condition.signal();
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    // 释放锁
                    reentrantLock.unlock();
                }
            }


        }
    }


}

  1. QueueInfo
import java.util.LinkedList;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class QueueInfo extends LinkedList<String> {

    private final AtomicInteger atomicInteger = new AtomicInteger(0);

    private Integer MAX = 20;

    // 引入可重入锁
    private final ReentrantLock reentrantLock = new ReentrantLock();

    private final Condition condition = reentrantLock.newCondition();

    public ReentrantLock getReentrantLock() {
        return reentrantLock;
    }

    public Condition getCondition() {
        return condition;
    }

    public void addQueue(String threadName) {
        int i = atomicInteger.incrementAndGet();
        super.add(i + "");
        System.out.println(threadName + ": 生产了一条消息:" + i);

    }

    public void poolQueue(String threadName) {
        String poll = super.poll();
        System.out.println(threadName + ": 消费消息:" + poll);
    }

    public AtomicInteger getAtomicInteger() {
        return atomicInteger;
    }

    public Integer getMAX() {
        return MAX;
    }

    public void setMAX(Integer MAX) {
        this.MAX = MAX;
    }
}

4.4 分布式锁

4.4.1 分布式锁常见的应用场景
4.4.2 分布式锁的实现方式

redis,zookeeper

4.5 动态代理是什么?如何实现?

4.6 java中有哪些集合?这些集合的差别是什么?List与LinkList区别?

4.7 hasMap什么时候生成数组链表?

4.8 SpringMvc的处理流程?

4.9 SpringBoot与Spring,SpringMvc区别?

4.10 SpringBoot新增了哪些新特性的注解?

4.11 SpringBoot 对象循环引用的问题?如何解决?

4.12 什么是反射?

反射可以获取类的属性,方法,继承关系,可以动态的获取这些信息或者是执行操作。

4.13 什么是泛型?

泛型可以作为类或者是接口存储的对象或者是其描述的对象

4.14 对线程的理解

线程是一个程序执行过程中所需要的工作者。它由操作系统管理调度所有的线程,将线程分配到任何可用的cpu。线程初始化完成,通过调用run开始执行。当线程执行结束,会被回收。

4.15 如何优化锁

  1. 减少程序加锁的时间,只用在有现成安全的程序/代码块上。
  2. 减小所的颗粒度,比如tableMap(大锁)到concurrtHashMap锁使用差异(分段锁/颗粒锁)。
  3. 根据读写功能划分使用什么锁,写使用写锁,读使用读书。

5 剑指offer算法题

5.1 从尾到头打印链表

在这里插入图片描述

  1. 方式1:简易代码,耗时相对长,内存使用少
    思路:递归将
/**
*    public class ListNode {
*        int val;
*        ListNode next = null;
*
*        ListNode(int val) {
*            this.val = val;
*        }
*    }
*
*/
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.Collections;

public class Solution {
    List<Integer> list = new ArrayList<Integer>();

    public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
        def(listNode);
        Collections.reverse(list);
        return (ArrayList<Integer>) list;
    }

    // 递归方法取值
    private void def(ListNode listNode) {
        if (Objects.nonNull(listNode)) {
            list.add(listNode.val);
            if(listNode.next != null) {
                def(listNode.next);
            }
        }
    }

}

结果:
在这里插入图片描述

5.2 反转链表

在这里插入图片描述
1, 思路:该提逻辑相对比较绕
在这里插入图片描述

/*
public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}*/
public class Solution {
        public ListNode ReverseList(ListNode head) {

        if(head==null)
            return null;
        ListNode pre = null;
        ListNode next = null;
        while(head!=null){
           next = head.next;
           head.next = pre;
           pre = head;
           head = next;
        }
        return pre;
    }
}

5.3 合并两个排序的链表

在这里插入图片描述
在这里插入图片描述

  1. 思路1:简单粗暴,将链表转成两个list,合并后通过stream升序排序,排序结束后循环创建链表关系,最后输出
/*
public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}*/

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
public class Solution {
      List<ListNode> mergeList = new ArrayList<>();
    ListNode resultNode = null;

    public ListNode Merge(ListNode list1, ListNode list2) {
        List<ListNode> mergeList1 = anz(list1);
        List<ListNode> mergeList2 = anz(list2);
        mergeList1.addAll(mergeList2);
        List<ListNode> mergeList = mergeList1.stream().map(node -> {
            node.next = null;
            return node;
        })
                .sorted((n, s) -> {
                    return n.val - s.val;
                }).collect(Collectors.toList());

        for (int i = 0; i < mergeList.size(); i++) {
            ListNode listNode = mergeList.get(i);
            if (i == 0) {
                resultNode = listNode;
            }
            int nex = i + 1;
            if (nex < mergeList.size()) {
                listNode.next = mergeList.get(nex);
            }
        }
        return resultNode;

    }

    public List<ListNode> anz(ListNode head) {
        List<ListNode> resultList = new ArrayList<>();
        if (Objects.isNull(head)) {
            return resultList;
        }
        while (head != null) {
            resultList.add(head);
            if (head.next == null) {
                break;
            }
            head = head.next;
        }
        return resultList;

    }

}
  1. 思路2:同时不断遍历两个链表,取出小的追加到新的头节点后,直至两者其中一个为空
/*
public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}*/
public class Solution {
       public ListNode Merge(ListNode list1, ListNode list2) {

        ListNode resultNode = new ListNode(0);
        ListNode currNode = resultNode;

        while (list1 != null || list2 != null) {
            // 两个有序得链表,存在空直接返回另一个链表得剩余节点
            if(list1 == null) {
                currNode.next = list2;
                break;
            }
            if(list2 == null) {
                currNode.next = list1;
                break;
            }
            if (list1.val <= list2.val) {
                currNode.next = list1;
                list1 = list1.next;
            } else {
                currNode.next = list2;
                list2 = list2.next;
            }
            currNode = currNode.next;
        }

        return resultNode.next;
    }

}

5.4 两个链表的第一个公共结点

在这里插入图片描述
在这里插入图片描述

/*
public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}*/
public class Solution {
    public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
        if (pHead1 == null || pHead2 == null) {
            return null;
        }
        ListNode currNode = null;
        while (pHead1 != null) {
            if (def(pHead1, pHead2)) {
                currNode = pHead1;
                break;
            }
            // 下一个
            pHead1 = pHead1.next;
        }
        return currNode;
    }

    // 递归
    private boolean def(ListNode pHead1, ListNode pHead2) {
        while (pHead2 != null) {
            if (compare(pHead1, pHead2)) {
                return true;
            }
            pHead2 = pHead2.next;
        }
        return false;
    }

    public boolean compare(ListNode pHead1, ListNode pHead2) {
        while (pHead1 != null && pHead2 != null) {
            if (pHead1.val != pHead2.val) {
                return false;
            }
            pHead1 = pHead1.next;
            pHead2 = pHead2.next;
        }
        if (pHead1 == null && pHead2 == null) {
            return true;
        }
        return false;
    }
}

5.5 链表中倒数最后k个结点

在这里插入图片描述
在这里插入图片描述

  1. 思路1:list有序队列反转获取
import java.util.*;

/*
 * public class ListNode {
 *   int val;
 *   ListNode next = null;
 *   public ListNode(int val) {
 *     this.val = val;
 *   }
 * }
 */
import java.util.ArrayList;
import java.util.List;

public class Solution {

    List<ListNode> list = new ArrayList<>();

    public ListNode FindKthToTail (ListNode pHead, int k) {
        while (pHead != null) {
            list.add(pHead);
            pHead = pHead.next;
        }

        // 获取倒数k个
        if(k > list.size() || k == 0){
            return null;
        }
        // 1 2 3 4 5  5
        return list.get(list.size() - k);
    }
}
  1. 思路2:压栈做法
import java.util.*;

/*
 * public class ListNode {
 *   int val;
 *   ListNode next = null;
 *   public ListNode(int val) {
 *     this.val = val;
 *   }
 * }
 */
import java.util.Stack;

// 压Stack
public class Solution {
    Stack<ListNode> stack = new Stack<>();

    public ListNode FindKthToTail(ListNode pHead, int k) {

        if (k == 0) {
            return null;
        }
        while (pHead != null) {
            stack.add(pHead);
            pHead = pHead.next;
        }

        // 获取倒数k个
        if (k > stack.size() || k == 0) {
            return null;
        }
        ListNode resNode = null;
        while (k > 0) {
            k--;
            resNode = stack.pop();
        }
        return resNode;
    }

}

5.6 链表中环的入口结点

在这里插入图片描述
在这里插入图片描述

  1. 思路:利用set的特性,存在第一个重复直接返回
/*
 public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}
*/
import java.util.HashSet;
import java.util.Set;
public class Solution {

    Set<Integer> set = new HashSet<>();

    public ListNode EntryNodeOfLoop(ListNode pHead) {

        while (pHead != null) {
            if (!set.add(pHead.val)) {
                return pHead;
            }
            pHead = pHead.next;

        }
        return null;
    }
}

5.7 复杂链表的复制

在这里插入图片描述
在这里插入图片描述

  1. 思路:首先遍历主节点,将新节点存储到Map集合中。随机节点后续循环做映射
/*
public class RandomListNode {
    int label;
    RandomListNode next = null;
    RandomListNode random = null;

    RandomListNode(int label) {
        this.label = label;
    }
}
*/
import java.util.*;
public class Solution {
    RandomListNode root = new RandomListNode(-1);
    Map<RandomListNode, RandomListNode> map = new HashMap<>();

    public RandomListNode Clone(RandomListNode pHead) {
        if (pHead == null) {
            return null;
        }
        RandomListNode currNode = root;
        // 1. 先复制单节点
        while (pHead != null) {
            // 复制节点
            RandomListNode newNode = new RandomListNode(pHead.label);
            currNode.next = newNode;
            currNode = currNode.next;
            map.put(pHead, currNode);
            pHead = pHead.next;
        }

        // 2. 节点映射
        for (RandomListNode keyNode : map.keySet()) {
            RandomListNode randomListNode = map.get(keyNode);
            randomListNode.random = map.get(keyNode.random);
        }
        return root.next;
    }
}

5.8 删除链表中重复的结点

在这里插入图片描述

  1. 思路:通过set,queue,map结合循环处理
/*
 public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}
*/
import java.util.*;
public class Solution {
  

    Set<Integer> set = new HashSet();

    // 存储所有节点 去重
    Queue<ListNode> queue1 = new LinkedList<>();
    // 存储重复节点
    Map<Integer, Boolean> map = new HashMap<>();



    public ListNode deleteDuplication(ListNode pHead) {

        if (pHead == null) {
            return null;
        }

        while (pHead != null) {
            // success 没有重复
            if (set.add(pHead.val)) {
                queue1.add(pHead);
            } else {
                map.put(pHead.val, true);
            }
            pHead = pHead.next;
        }

        ListNode resultNode = new ListNode(0);
        ListNode currNode = resultNode;

        for (ListNode node: queue1) {
            if(map.containsKey(node.val)) {
                continue;
            }
            node.next = null;
            currNode.next = node;
            currNode = currNode.next;
        }

        return resultNode.next;
    }
}

5.9 二叉树的深度

在这里插入图片描述

  1. 思路:递归算出左右节点的最大深度,返回最大值
/**
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;

    public TreeNode(int val) {
        this.val = val;

    }

}
*/
public class Solution {
    public int TreeDepth(TreeNode root) {

        if (root == null) {
            return 0;
        }
        return def(root, 1);

    }

    private int def(TreeNode node, int i) {
        // 当前节点为null 或者 左右节点均为null 表示到达最深度
        if (node == null|| (node.right == null && node.left == null)) {
            return i;
        }
        i++;
        int lef = def(node.left, i);
        int rig = def(node.right, i);
        return lef > rig ? lef : rig;
    }
}

5.10 按之字形顺序打印二叉树

在这里插入图片描述

  1. 思路:将树的每一层看作队列,从左开始输出,偶数倒序
import java.util.ArrayList;

/*
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;

    public TreeNode(int val) {
        this.val = val;

    }

}
*/
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.Queue;
public class Solution {
   
       public ArrayList<ArrayList<Integer>> Print(TreeNode pRoot) {
        if(pRoot == null) {
            return new ArrayList<>();
        }
        Queue<TreeNode> q = new LinkedList<>();
        ArrayList<ArrayList<Integer>> res = new ArrayList<>();
        // 奇数左=>右 偶从右=>左
        int index = 1;
        q.add(pRoot);
        while (!q.isEmpty()) {
            // 当前有几个节点
            ArrayList<Integer> lineList = new ArrayList<>();
            int max = q.size();
            for (int i = 0; i < max; i++) {
                TreeNode currNode = q.poll();
                lineList.add(currNode.val);
                // 左到右设置节点
                if (currNode.left != null) {
                    q.add(currNode.left);
                }
                if (currNode.right != null) {
                    q.add(currNode.right);
                }
            }
            res.add(lineList);
            if (index % 2 == 0) {
                Collections.reverse(lineList);
            }
            index++;
        }
        return res;
    }
}

5.11 动态规划-跳台阶问题

在这里插入图片描述

  1. 思路:公式法
    f(n)=f(n-1)+f(n-2) n > 2
    f(1) = 1
    f(2)=2
public class Solution {

    public int jumpFloor(int n) {

        if(n == 1) {
            return 1;
        }

        if(n == 2) {
            return 2;
        }

        return jumpFloor(n - 1) + jumpFloor(n-2);

    }
}

5.12 动态规划-跳台阶扩展问题

在这里插入图片描述

  1. 思路; 公式法
    f(n)=2*f(n-1) n>2
    f(1)=1

public class Solution {


    public int jumpFloorII(int n) {

        if(n == 1) {
            return 1;
        }

        return 2*jumpFloorII(n - 1);

    }

}
  • 0
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值