算法修煉之路—【字符串】Leetcode 606根據二叉樹創建字符串

題目描述

你需要採用前序遍歷的方式,講一個二叉樹轉換成一個由括號和整數組成的字符串。

空節點則用一對空括號"()"表示。而且你需要省略所有不影響字符串與原始二叉樹之間的一對一映射關係的空括號對。

示例1:

輸入: 二叉樹:[1, 2, 3, 4]
在這裏插入圖片描述
輸出: “1(2(4))(3)”
解釋: 原本將是“1(2(4)())(3())”,在省略不必要的空括號之後,它將是"1(2(4))(3)"。

示例2:

輸入: 二叉樹:[1, 2, 3, null, 4]
在這裏插入圖片描述
輸出: “1(2()(4))(3)”
解釋: 同示例1 相似,除了我們不能省略第一個對括號來中斷輸入和輸出之間的一對一映射關係。

思路分析

遞歸

當左右節點存在情況不同時,如何阻止字符串排列,左右節點可分爲以下五種情況:

  1. 當前節點爲null時,return "";
  2. 左右節點均無:return String.valueOf(root.val);
  3. 只存在左節點:return root.val + "(" + solution(root.left) + ")";
  4. 只存在右節點: return root.val + "()(" + solution(root.left) + ")";
  5. 左右節點均存在:return root.val + "(" + solution(root.left) + ")(" + solution(root.right) + ")";

我們可以看到當左節點爲null時,是否將其展示爲空括號(),取決於右節點是否存在,故我們將情況 3,4 合併爲一類;其他兩類爲情況1 左右節點不存在情況2 只存在左節點.
代碼有:

代碼1 遞歸解法

        if (root == null) {
            return "";
        }
        
        if(root.right == null){
            if(root.left == null)
                return String.valueOf(root.val);
            else
                return root.val
                    	+ "("
                    	+ solutionRecursive(root.left)
                    	+ ")";
        }        

        // root.left != null && root.right != null
        return root.val
                + "("
                + solutionRecursive(root.left)
                + ")("
                + solutionRecursive(root.right)
                + ")";

迭代

迭代的解法相較於遞歸式解法會稍顯複雜,但是會更有利於思維訓練;且遞歸在數據體量較大情況下往往因爲使用棧空間過大而更具有理論研究價值(實際多數據業務場景下使用較少),故我們這裏給出迭代的解法和思路。

這道題雖是帶有字符串的標籤,但更大程度是考察二叉樹的遍歷,有一些基礎的小夥伴知道其通過稍微改動二叉樹的迭代前序遍歷解法即可實現本題要求。

傳統的迭代式需要藉助兩個輔助容器:Stack來存儲遍歷的非空節點;HashSet來判斷是否訪問過此節點。我們這裏還需要藉助StringBuilder來保存返回結果,且在遍歷時,我們不缺分根節點,當返回結果時我們直接取去掉首位括號的中間字符串,即(1(2()(4))(3)).subString(1, len - 1),即爲返回結果1(2()(4))(3),這裏給出核心代碼:

Stack<TreeNode> stack = new Stack<>();
HashSet<TreeNode> visitedSet = new HashSet<>();
StringBuilder res = new StringBuiler();

stack.push(root);

while(!stack.empty()){
	TreeNode node = stack.peek();
	if(visitedSet.contains(node)){
		stack.pop();
		res.append(")");
	}else{
		visitedSet.add(node);
		res.append("(" + node.val);
		
		if(node.left == null && node.right != null)
			res.append("()");
			
		if(node.right != null) // attention here !!!!
			stack.push(node.right);
		if(node.left!= null)
			stack.push(node.left);
	}	
}

return res.subString(1, res.length() - 1);

這裏需要注意的是,訪問新節點時,先判斷右節點是否存在,因爲根據棧先進後出 的數據訪問特點,我們需要先判斷左節點,之後是右節點,故先將右節點判存後入棧。

解題代碼

1. 遞歸

    public static String solutionRecursive(TreeNode root) {
        if (root == null) {
            return "";
        }
        
        if(root.right == null){
            if(root.left == null)
                return String.valueOf(root.val);
            else
                return root.val
                        + "("
                        + solutionRecursive(root.left)
                        + ")";
        }        

        // root.left != null && root.right != null
        return root.val
                + "("
                + solutionRecursive(root.left)
                + ")("
                + solutionRecursive(root.right)
                + ")";

    }

2. 迭代

    public static String solutionIterative(TreeNode root){
        if(root == null) return "";
        
        StringBuilder res = new StringBuilder();
        HashSet<TreeNode> visitedSet = new HashSet<>();
        Stack<TreeNode> stack = new Stack<>();
        
        stack.push(root);
        
        while(!stack.empty()){
            TreeNode node = stack.peek();
            if(visitedSet.contains(node)){
                res.append(")");
                stack.pop();
            }else{
                visitedSet.add(node);
                res.append("(" + node.val);
                
                if(node.left == null && node.right != null)                       
                    res.append("()");
                if(node.right != null)
                    stack.push(node.right);
                if(node.left != null)
                     stack.push(node.left);                
                
            }
        }
        return res.substring(1, res.length() - 1);
        
        
    }

複雜度分析

兩種解法的複雜度相同,我們統一分析:

  1. 遞歸

時間複雜度: 我們對二叉樹進行了一次遍歷,故時間複雜度爲O(n);
空間複雜度: 藉助輔助容器 res ,故空間複雜度爲O(n);

  1. 迭代
    時間複雜度: 時間複雜度爲O(n);
    空間複雜度: 我們這裏藉助了stack, visited,以及返回結果res,最壞情況下爲3n,故空間複雜度爲O(n);

Github源碼

完整可運行文件請訪問GitHub

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章