【牛客算法】某司面试算法题:循环右移二叉树

一、算法题描述

1.1 算法描述

现有一棵n个节点构成的二叉树,请你将每一层的节点向右循环位移k位。某层向右位移一位(即k=1)的含义为:

  1. 若当前节点为左孩子节点,会变成当前节点的双亲节点右孩子节点

  2. 若当前节点为右儿子,会变成当前节点的双亲节点右边相邻兄弟节点左孩子节点。(如果当前节点的双亲节点已经是最右边的节点了,则会变成双亲节点同级的最左边的节点左孩子节点)

  3. 该层的每一个节点同时进行一次位移。

  4. 是从最下面的层开始位移,位移完每一层之后,再向上,直到根节点,位移完毕。

如果从最后一层开始对该二叉树的每一层循环位移k位。以下方二叉树为例,k=1:

c 复制代码
			 1
			 / \
			2   3
			   / \
			  4   5

位移最后一层,5变成2的左孩子节点,4变成3的右孩子节点,如下图:

c 复制代码
			    1
			   / \
			  2   3
			 /     \
			5       4

再位移倒数第二层,3变成1的左孩子节点,2变成1的右孩子的节点,它们的孩子节点随着一起位移,如下图:

c 复制代码
		      1
		     / \
		    3   2
		    \   /
		     4 5

根节点没有双亲节点,不用位移,位移完毕

现在给你这棵二叉树,请你返回循环右移k位后的二叉树。

1.2 示例

1.2.1 示例1

输入

c 复制代码
{1,2,3,#,#,4,5},1

输出

c 复制代码
{1,3,2,#,4,5}

说明

c 复制代码
解释见题面描述。     

1.2.1 示例2

输入

c 复制代码
{1,2,3,4},2

输出

c 复制代码
{1,2,3,#,#,4}

说明

c 复制代码
      1
     / \
    2   3
   /
  4



	变为



      1
     / \
    2   3
       /
      4

1.2.3 示例3

输入

c 复制代码
{1,#,3,4,5},1

输出

c 复制代码
{1,3,#,5,4}

说明

c 复制代码
    1
     \
      3
     / \
    4   5


变为


    1
     \
      3
     / \
    5   4


变为

        1
       /
      3
     / \
    5   4

1.4 备注

树的节点个数在[1,10^5]之间,且保证该树上每个节点的编号不同,节点编号并非按顺序排列,1≤k≤100

1.5 提供的代码

java 复制代码
import java.util.*;

/*
 * public class TreeNode {
 *   int val = 0;
 *   TreeNode left = null;
 *   TreeNode right = null;
 *   public TreeNode(int val) {
 *     this.val = val;
 *   }
 * }
 */

public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 
     * @param root TreeNode类 
     * @param k int整型 
     * @return TreeNode类
     */
    public TreeNode cyclicShiftTree (TreeNode root, int k) {
        // write code here
    }
}

二、算法实现

2.1 算法思路

  1. 层序遍历 :使用队列进行BFS(层次遍历),同时记录每个节点在每一层的位置信息,包括父节点位置、当前节点位置以及是否为左孩子或右孩子。将这些信息存储在levels数组中,每个元素是一个列表,包含该层所有节点的信息。

  2. 位移操作:从最底层开始,逐层向上进行位移操作。对于每一层,首先计算出这一层需要位移的步数,然后根据位移步数调整每个节点的左右子节点指针。

  3. 指针重构 :在位移过程中,需要重新构建每个节点的左右子节点指针。这可以通过遍历levels数组中的每层节点,并根据它们的父节点位置和位移步数来确定新的子节点位置。

  4. 更新指针:在位移操作完成后,需要更新上一层的节点指针,将当前层的节点连接到上一层的相应节点上。

  5. 处理边界条件:在位移过程中,需要注意处理边界条件,例如当位移步数超过当前层节点数时,需要对步数进行取模操作。

2.2 算法实现

java 复制代码
import java.util.*;

/*
 * TreeNode类定义
 * public class TreeNode {
 *   int val = 0;
 *   TreeNode left = null;
 *   TreeNode right = null;
 *   public TreeNode(int val) {
 *     this.val = val;
 *   }
 * }
 */

public class Solution {
    
    // Node类封装了节点的层次信息,便于后续重构指针
    class Node {
        int parentIndex;    // 父节点在当前层的位置
        int currentIndex;   // 当前节点在该层的索引位置
        int flag;           // 标记当前节点是左孩子(0)还是右孩子(1)
        TreeNode currentNode;  // 当前节点的引用
        
        // 构造方法
        public Node(int parentIndex, int currentIndex, int flag, TreeNode currentNode) {
            this.parentIndex = parentIndex;
            this.currentIndex = currentIndex;
            this.flag = flag;
            this.currentNode = currentNode;
        }
    }
    
    public TreeNode cyclicShiftTree(TreeNode root, int k) {
        if (root == null) return null;  // 空树处理
        
        // 存储每层的节点信息,用于后续循环位移和指针重构
        ArrayList<ArrayList<Node>> levels = new ArrayList<>();
        Queue<Node> queue = new LinkedList<>();
        
        // 初始化:根节点加入队列
        queue.offer(new Node(0, 0, 0, root));
        
        // `parentNum`记录当前层节点数,`nextNum`记录下一层节点数
        int parentNum = 1, nextNum = 0;
        ArrayList<Node> tempLevel = new ArrayList<>();  // 当前层的节点列表

        // 层次遍历:记录每层节点的结构信息
        while (!queue.isEmpty()) {
            Node node = queue.poll();
            tempLevel.add(node);  // 将节点加入当前层的列表
            --parentNum;

            // 添加左子节点到队列
            if (node.currentNode.left != null) {
                queue.offer(new Node(node.currentIndex, nextNum++, 0, node.currentNode.left));
            }
            // 添加右子节点到队列
            if (node.currentNode.right != null) {
                queue.offer(new Node(node.currentIndex, nextNum++, 1, node.currentNode.right));
            }

            // 当前层遍历完毕,将其加入到levels,并更新下一层
            if (parentNum == 0) {
                parentNum = nextNum;
                nextNum = 0;
                levels.add(tempLevel);
                tempLevel = new ArrayList<>();
            }
        }

        // 从树的最底层开始,逐层循环右移并更新左右子节点指针
        int depth = levels.size() - 1;
        for (int i = depth; i >= 1; --i) {  // 从最底层向上逐层处理
            ArrayList<Node> parentLevel = levels.get(i - 1);
            int parentLevelSize = parentLevel.size();

            // 清空上一层每个节点的左右子节点,准备重新分配指针
            for (Node parent : parentLevel) {
                parent.currentNode.left = null;
                parent.currentNode.right = null;
            }

            // 计算当前层的位移步数
            int move = k % (2 * parentLevelSize);  // 控制位移在有效范围内
            tempLevel = levels.get(i);

            // 根据位移更新每个节点的左右子节点指针
            for (Node node : tempLevel) {
                // 计算目标父节点位置和孩子标记(左孩子或右孩子)
                int targetParentIndex = node.flag == 0
                    ? (node.parentIndex + move / 2) % parentLevelSize
                    : (node.parentIndex + (move + 1) / 2) % parentLevelSize;
                
                int targetChildFlag = node.flag == 0 ? move % 2 : (move + 1) % 2;
                
                // 获取目标父节点
                Node targetParent = parentLevel.get(targetParentIndex);

                // 根据targetChildFlag更新子节点指针
                if (targetChildFlag == 0) {
                    targetParent.currentNode.left = node.currentNode;
                } else {
                    targetParent.currentNode.right = node.currentNode;
                }
            }
        }

        return root;  // 返回根节点,树的结构已完成位移并更新
    }
}

2.3 算法复杂度分析

  1. 时间复杂度:算法的时间复杂度主要取决于树的节点数n以及位移步数k。在最坏情况下,我们需要对每个节点进行位移操作,因此时间复杂度为O(n * k)。然而,由于位移操作的步数k通常远小于树的节点数n,因此实际的时间复杂度通常接近O(n)。

  2. 空间复杂度:算法的空间复杂度主要取决于树的高度h和每层的节点数。在最坏情况下,我们可能需要存储所有层的节点信息,因此空间复杂度为O(n),其中n为树的节点数。

相关推荐
Watermelo6173 分钟前
详解js柯里化原理及用法,探究柯里化在Redux Selector 的场景模拟、构建复杂的数据流管道、优化深度嵌套函数中的精妙应用
开发语言·前端·javascript·算法·数据挖掘·数据分析·ecmascript
乐之者v9 分钟前
leetCode43.字符串相乘
java·数据结构·算法
A懿轩A1 小时前
C/C++ 数据结构与算法【数组】 数组详细解析【日常学习,考研必备】带图+详细代码
c语言·数据结构·c++·学习·考研·算法·数组
古希腊掌管学习的神1 小时前
[搜广推]王树森推荐系统——矩阵补充&最近邻查找
python·算法·机器学习·矩阵
云边有个稻草人1 小时前
【优选算法】—复写零(双指针算法)
笔记·算法·双指针算法
半盏茶香1 小时前
在21世纪的我用C语言探寻世界本质 ——编译和链接(编译环境和运行环境)
c语言·开发语言·c++·算法
忘梓.2 小时前
解锁动态规划的奥秘:从零到精通的创新思维解析(3)
算法·动态规划
️南城丶北离2 小时前
[数据结构]图——C++描述
数据结构··最小生成树·最短路径·aov网络·aoe网络
✿ ༺ ོIT技术༻2 小时前
C++11:新特性&右值引用&移动语义
linux·数据结构·c++
tinker在coding4 小时前
Coding Caprice - Linked-List 1
算法·leetcode