LeetCode 863. 二叉树中所有距离为 K 的结点 (树转图,邻接表)/ 641. 设计循环双端队列 / 622. 设计循环队列

LeetCode 专栏收录该内容
139 篇文章 0 订阅

863. 二叉树中所有距离为 K 的结点

2021.7.28每日一题

题目描述

给定一个二叉树(具有根结点 root), 一个目标结点 target ,和一个整数值 K 。

返回到目标结点 target 距离为 K 的所有结点的值的列表。 答案可以以任何顺序返回。

示例 1:

输入:root = [3,5,1,6,2,0,8,null,null,7,4], target = 5, K = 2
输出:[7,4,1]
解释:
所求结点为与目标结点(值为 5)距离为 2 的结点,
值分别为 7,4,以及 1

在这里插入图片描述

注意,输入的 “root” 和 “target” 实际上是树上的结点。
上面的输入仅仅是对这些对象进行了序列化描述。

提示:

给定的树是非空的。
树上的每个结点都具有唯一的值 0 <= node.val <= 500 。
目标结点 target 是树上的结点。
0 <= K <= 1000.

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/all-nodes-distance-k-in-binary-tree
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

如果把target节点当做根节点的话,就可以很方便的找到距离为k的节点。
那么用一个哈希表把所有节点的父节点存储起来
然后由target节点开始层序遍历
可以向三个方向遍历,用一个set存储已经被遍历过的节点,如果没有被遍历过,就扩展,直到k层

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    Map<Integer, TreeNode> map;
    public List<Integer> distanceK(TreeNode root, TreeNode target, int k) {
        //如果把target节点当做根节点的话,就可以很方便的找到距离为k的节点
        //那么用一个哈希表把所有节点的父节点存储起来
        //然后由target节点开始层序遍历?
        //可以向三个方向遍历,如果和传入的方向不同,就遍历,直到k层
        map = new HashMap<>();
        dfs(root);
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(target);
        List<Integer> res = new LinkedList<>();
        if(k == 0){
            res.add(target.val);
            return res;
        }
        Set<Integer> set = new HashSet<>();
        set.add(target.val);
        int deep = 0;
        while(!queue.isEmpty()){
            deep++;
            int size = queue.size();
            while(size-- > 0){
                TreeNode node = queue.poll();
                if(node.left != null && !set.contains(node.left.val)){
                    queue.add(node.left);
                    set.add(node.left.val);
                    if(deep == k){
                        res.add(node.left.val);
                    }
                }
                if(node.right != null && !set.contains(node.right.val)){
                    queue.add(node.right);
                    set.add(node.right.val);
                    if(deep == k){
                        res.add(node.right.val);
                    }
                }
                if(map.containsKey(node.val) && !set.contains(map.get(node.val).val)){
                    queue.add(map.get(node.val));
                    set.add(map.get(node.val).val);
                    if(deep == k){
                        res.add(map.get(node.val).val);
                    }
                }
            }
        }
        return res;
    }

    public void dfs(TreeNode root){
        if(root == null){
            return;
        }
        if(root.left != null)
            map.put(root.left.val, root);
        if(root.right != null)
            map.put(root.right.val, root);
        dfs(root.left);
        dfs(root.right);
    }
}

深度优先搜索
如果来源节点和要走的方向不同,就往这个方向走,否则就不能往这个方向走

class Solution {
    Map<Integer, TreeNode> parents = new HashMap<Integer, TreeNode>();
    List<Integer> ans = new ArrayList<Integer>();

    public List<Integer> distanceK(TreeNode root, TreeNode target, int k) {
        // 从 root 出发 DFS,记录每个结点的父结点
        findParents(root);

        // 从 target 出发 DFS,寻找所有深度为 k 的结点
        findAns(target, null, 0, k);

        return ans;
    }

    public void findParents(TreeNode node) {
        if (node.left != null) {
            parents.put(node.left.val, node);
            findParents(node.left);
        }
        if (node.right != null) {
            parents.put(node.right.val, node);
            findParents(node.right);
        }
    }

    public void findAns(TreeNode node, TreeNode from, int depth, int k) {
        if (node == null) {
            return;
        }
        if (depth == k) {
            ans.add(node.val);
            return;
        }
        if (node.left != from) {
            findAns(node.left, node, depth + 1, k);
        }
        if (node.right != from) {
            findAns(node.right, node, depth + 1, k);
        }
        if (parents.get(node.val) != from) {
            findAns(parents.get(node.val), node, depth + 1, k);
        }
    }
}


作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/all-nodes-distance-k-in-binary-tree/solution/er-cha-shu-zhong-suo-you-ju-chi-wei-k-de-qbla/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

早上来了看了三叶姐建图的方法,晚上想的时候,完全没有想到和图有什么关系,这次又好像明白了点什么
然后建图,邻接表,我想着就是那种二维数组的形式,但是又觉得树里面的节点大小不固定,当然可以开超过最大限制范围的数组,但是那样建表的话后面遍历又很慢
然后就看三叶姐怎么建图的,她没有写具体思路,但是根据我的理解是这样的
(看不太懂,已经求问了,明白了再来补充)
(中午收到大佬回复,来写一下理解)

首先需要将这个邻接表理解成一个链表的形式,意思是如果父节点a连接着子树b和c的话,建立的就是a->b->c的一个链表(b,c哪个在前看插入的顺序)
he是邻接表的表头,下标 i 代表是节点的值,而存储的值val是当前节点指向相邻节点的一个指针idx,e[idx]是链表节点的val,ne是next的意思,就是说下一个节点指向哪里
而每次插入也是相当于链表的头插法
he[]数组初始化为-1
比如要建立父节点a连着b和c的邻接表:
首先传入a,b。第一步,e[idx]=b,意思是idx指针指向了b这个节点,next[idx]也就是b的下一个节点,现在没有,也就是-1,然后he[a]=idx,也就是说,a节点的指针是idx,这样就将a和b连接起来,a->b
再传入a和c的时候,第一步e[idx2]=c,意思是idx2指针指向了c这个节点,
第二步,ne[idx2]=he[a],此时he[a]等于的是idx指针,那么这句话的意思是next[idx2]=idx,意思是idx2的下一个指针是idx,相当于把c插在了头部
第三步,he[a]=idx2,意思是a通过idx2指针能找到c(e[idx2]=c),然后next[idx2]=idx,就是说通过next数组可以找到相连的后继节点,也就是指针idx,然后再通过e[idx]=b找到b节点,如果再有next[idx],那么next[idx]=-1,表示走到头了,没有节点了
这样就形成了a->c->b一个链表,将a和b、c连接到了一起

贴上三叶姐和回复我的小姐姐的解释:
在这里插入图片描述

	int N = 1010, M = N * 2;
    int[] he = new int[N], e = new int[M], ne = new int[M];
    int idx;
    void add(int a, int b) {
        e[idx] = b;
        ne[idx] = he[a];
        he[a] = idx++;
    }

自己理解代码

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {

    int N = 501;    //节点值最大为500
    int M = N * 4;  //每个节点最多两个边
    int[] head = new int[N];    //下标表示头节点的值,数值表示头节点的指针,需要赋初值
    int[] next = new int[M];    //表示指针的下一个指向
    int[] edge = new int[M];    //表示指针指向的节点数值
    int idx = 0;                //表示边的下标,也可以看做指针
    //为节点a指向节点b
    public void add(int a, int b){
        //首先,将当前指针和新加入数值相连,相当于新的头节点的指针指向了新的节点
        edge[idx] = b;
        //然后next指针,也就是新节点的指针,指向原来头节点的指针
        next[idx] = head[a];
        //最后将头节点的值和新的头节点指针配对
        head[a] = idx++;
    }
    boolean[] used = new boolean[N];
    public List<Integer> distanceK(TreeNode root, TreeNode target, int k) {
        //学习一下邻接表的写法,顺便再做一下这个题
        List<Integer> res = new ArrayList<>();
        Arrays.fill(head, -1);   
        dfs(root);          //建图
        used[target.val] = true;
        //然后从target开始深度搜索,
        check(target.val, k, 0, res);
        return res;
    }

    //cur表示当前层
    public void check(int root, int k, int cur, List<Integer> list){
        if(cur == k){
            list.add(root);
        }
        //根据邻接表,遍历
        //首先i等于第一个指针,然后每次只是移动指针,下一个指针就是next[i]
        for(int i = head[root]; i != -1; i = next[i]){
            //取出指针指向的值
            int temp = edge[i];
            if(used[temp])
                continue;
            used[temp] = true;
            check(temp, k, cur + 1, list);
        }
    }


    public void dfs(TreeNode root){
        if(root == null)
            return;
        if(root.left != null){
            add(root.left.val, root.val);
            add(root.val, root.left.val);
            dfs(root.left);
        }
        if(root.right != null){
            add(root.right.val, root.val);
            add(root.val, root.right.val);
            dfs(root.right);
        }
    }
}

641. 设计循环双端队列

题目描述

设计实现双端队列。
你的实现需要支持以下操作:

MyCircularDeque(k):构造函数,双端队列的大小为k。
insertFront():将一个元素添加到双端队列头部。 如果操作成功返回 true。
insertLast():将一个元素添加到双端队列尾部。如果操作成功返回 true。
deleteFront():从双端队列头部删除一个元素。 如果操作成功返回 true。
deleteLast():从双端队列尾部删除一个元素。如果操作成功返回 true。
getFront():从双端队列头部获得一个元素。如果双端队列为空,返回 -1。
getRear():获得双端队列的最后一个元素。 如果双端队列为空,返回 -1。
isEmpty():检查双端队列是否为空。
isFull():检查双端队列是否满了。
示例:

MyCircularDeque circularDeque = new MycircularDeque(3); // 设置容量大小为3
circularDeque.insertLast(1); // 返回 true
circularDeque.insertLast(2); // 返回 true
circularDeque.insertFront(3); // 返回 true
circularDeque.insertFront(4); // 已经满了,返回 false
circularDeque.getRear(); // 返回 2
circularDeque.isFull(); // 返回 true
circularDeque.deleteLast(); // 返回 true
circularDeque.insertFront(4); // 返回 true
circularDeque.getFront(); // 返回 4

提示:

所有值的范围为 [1, 1000]
操作次数的范围为 [1, 1000]
请不要使用内置的双端队列库。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/design-circular-deque
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

开了会员,上周力扣发邮件推荐了这个题,是有什么暗示吗哈哈

双向链表很好实现,单链表在队尾删除的时候可能会有点问题,需要遍历,还是用数组来写
创建数组时,使数组长度比要求的长度大1,即空一格
然后使left指向数据开头位置,right指向最后一个数据的后一个位置,这个是为了方便判断队列为空,或者队列为满
队列为空,则left==right;队列为满,则(right+1)%len == left
如果直接模拟的话,判断条件会比较复杂

class MyCircularDeque {
    //链表很好实现,还是用数组来做
    //还是空一个位置
    int len;
    int[] deque;
    int left;
    int right;
    /** Initialize your data structure here. Set the size of the deque to be k. */
    public MyCircularDeque(int k) {
        len = k + 1;
        deque = new int[len];
        left = 0;
        right = 0;
    }
    
    /** Adds an item at the front of Deque. Return true if the operation is successful. */
    public boolean insertFront(int value) {
        if(isFull())
            return false;
        left = (left - 1 + len) % len;
        deque[left] = value;
        return true;
    }
    
    /** Adds an item at the rear of Deque. Return true if the operation is successful. */
    public boolean insertLast(int value) {
        if(isFull())
            return false;
        deque[right] = value;
        right = (right + 1) % len;
        return true;
    }
    
    /** Deletes an item from the front of Deque. Return true if the operation is successful. */
    public boolean deleteFront() {
        if(isEmpty())
            return false;
        left = (left + 1) % len;
        return true;
    }
    
    /** Deletes an item from the rear of Deque. Return true if the operation is successful. */
    public boolean deleteLast() {
        if(isEmpty())
            return false;
        right = (right - 1 + len) % len;
        return true;
    }
    
    /** Get the front item from the deque. */
    public int getFront() {
        if(isEmpty())
            return -1;
        return deque[left];
    }
    
    /** Get the last item from the deque. */
    public int getRear() {
        if(isEmpty())
            return -1;
        return deque[(right - 1 + len) % len];
    }
    
    /** Checks whether the circular deque is empty or not. */
    public boolean isEmpty() {
        return left == right;
    }
    
    /** Checks whether the circular deque is full or not. */
    public boolean isFull() {
        return (right + 1) % len == left;
    }
}

/**
 * Your MyCircularDeque object will be instantiated and called as such:
 * MyCircularDeque obj = new MyCircularDeque(k);
 * boolean param_1 = obj.insertFront(value);
 * boolean param_2 = obj.insertLast(value);
 * boolean param_3 = obj.deleteFront();
 * boolean param_4 = obj.deleteLast();
 * int param_5 = obj.getFront();
 * int param_6 = obj.getRear();
 * boolean param_7 = obj.isEmpty();
 * boolean param_8 = obj.isFull();
 */

622. 设计循环队列

题目描述

设计你的循环队列实现。 循环队列是一种线性数据结构,其操作表现基于 FIFO(先进先出)原则并且队尾被连接在队首之后以形成一个循环。它也被称为“环形缓冲器”。

循环队列的一个好处是我们可以利用这个队列之前用过的空间。在一个普通队列里,一旦一个队列满了,我们就不能插入下一个元素,即使在队列前面仍有空间。但是使用循环队列,我们能使用这些空间去存储新的值。

你的实现应该支持如下操作:

MyCircularQueue(k): 构造器,设置队列长度为 k 。
Front: 从队首获取元素。如果队列为空,返回 -1 。
Rear: 获取队尾元素。如果队列为空,返回 -1 。
enQueue(value): 向循环队列插入一个元素。如果成功插入则返回真。
deQueue(): 从循环队列中删除一个元素。如果成功删除则返回真。
isEmpty(): 检查循环队列是否为空。
isFull(): 检查循环队列是否已满。

示例:

MyCircularQueue circularQueue = new MyCircularQueue(3); // 设置长度为 3
circularQueue.enQueue(1); // 返回 true
circularQueue.enQueue(2); // 返回 true
circularQueue.enQueue(3); // 返回 true
circularQueue.enQueue(4); // 返回 false,队列已满
circularQueue.Rear(); // 返回 3
circularQueue.isFull(); // 返回 true
circularQueue.deQueue(); // 返回 true
circularQueue.enQueue(4); // 返回 true
circularQueue.Rear(); // 返回 4

提示:

所有的值都在 0 至 1000 的范围内;
操作数将在 1 至 1000 的范围内;
请不要使用内置的队列库。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/design-circular-queue
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

官解的方法,是存储了首部的指针和当前元素的个数
还有一种实现是建一个容量加1的数组,right指针指向的是末尾加1的位置

class MyCircularQueue {
    //用数组实现一下,空出一个位置
    int[] queue;
    int left;   //指向队列头部
    int right;  //尾部加1的位置,添加到队尾
    int len;
    public MyCircularQueue(int k) {
        len = k + 1;
        queue = new int[k + 1];
        left = 0;   
        right = 0;
    }
    
    public boolean enQueue(int value) {
        if(isFull())
            return false;
        queue[right] = value;
        right = (right + 1) % len;
        return true;
    }
    
    public boolean deQueue() {
        if(isEmpty())
            return false;
        left = (left + 1) % len;
        return true;
    
    }
    
    public int Front() {
        if(isEmpty())
            return -1;
        return queue[left];
    }
    
    public int Rear() {
        if(isEmpty())
            return -1;
        return queue[(right - 1 + len) % len];
        
    }
    
    public boolean isEmpty() {
        return left == right ? true : false;
    }
    
    public boolean isFull() {
        return (right + 1) % len == left ? true : false;
    }
}

/**
 * Your MyCircularQueue object will be instantiated and called as such:
 * MyCircularQueue obj = new MyCircularQueue(k);
 * boolean param_1 = obj.enQueue(value);
 * boolean param_2 = obj.deQueue();
 * int param_3 = obj.Front();
 * int param_4 = obj.Rear();
 * boolean param_5 = obj.isEmpty();
 * boolean param_6 = obj.isFull();
 */

官解竟然给了一个加锁的实现,惊呆了,第一次见

class MyCircularQueue {
    //官解竟然给了一个线程安全的实现,惊呆了!!!

    private Node head, tail;
    private int count;
    private int capacity;
    // Additional variable to secure the access of our queue
    private ReentrantLock queueLock = new ReentrantLock();

    /** Initialize your data structure here. Set the size of the queue to be k. */
    public MyCircularQueue(int k) {
        this.capacity = k;
    }

    /** Insert an element into the circular queue. Return true if the operation is successful. */
    public boolean enQueue(int value) {
        // ensure the exclusive access for the following block.
        queueLock.lock();
        try {
        if (this.count == this.capacity)
            return false;

        Node newNode = new Node(value);
        if (this.count == 0) {
            head = tail = newNode;
        } else {
            tail.nextNode = newNode;
            tail = newNode;
        }
        this.count += 1;

        } finally {
        queueLock.unlock();
        }
        return true;
    }
}

  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 打赏
    打赏
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 深蓝海洋 设计师:CSDN官方博客 返回首页

打赏作者

Zephyr丶J

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值