leetCode進階算法題+解析(十四)

不同的二叉搜索樹2

題目:給定一個整數 n,生成所有由 1 ... n 為節(jié)點所組成的二叉搜索樹。

示例:
輸入: 3
輸出:
[
[1,null,3,2],
[3,2,null,1],
[3,1,null,null,2],
[2,1,3],
[1,null,2,null,3]
]


題目截圖

思路:其實這個題感覺又是個回溯啊。。首先選擇第一個元素,然后因為是搜索二叉樹,所以遵循左小右大。感覺上比較麻煩,我先去實現下看看
做完回來了,這個題怎么說呢,不是一個回溯問題,之前是我想復雜了。其實不用回溯。但是原理上是類似的。回溯是指添加,遞歸,然后退回上一步。而這道題好一點的是沒有一個list可以專門用來存儲是否添加過某元素了。而是因為數字是確定好的范圍的,所以可以直接用當前值前面,當前值后面來自動判斷左右樹。剛說有點類似回溯的也是這點。每一個值都可能是跟節(jié)點,所以要每一種情況都走一次遞歸。同樣其實子樹的節(jié)點也都是每一個值都有可能。所以還是遞歸套遞歸。我感覺我表達能力是個大問題,可能沒說明白。我直接貼代碼:

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public List<TreeNode> generateTrees(int n) {       
        if(n==0) return new ArrayList<TreeNode>();
        return dfs(1,n);

    }
    public  List<TreeNode> dfs(int start,int end){
        List<TreeNode> res = new ArrayList<TreeNode>();
        if(start>end){
            res.add(null);
            return res;
        }
        for(int i = start;i<=end;i++){
            List<TreeNode> left = dfs(start,i-1);
            List<TreeNode> right = dfs(i+1,end);
            for(TreeNode subLeft :left){
                for(TreeNode subRight:right){
                    TreeNode node =new TreeNode(i);
                    node.left = subLeft;
                    node.right = subRight;
                    res.add(node);
                }
            }
        } 
        return res;
    }    
}

簡單說兩句,重點是dfs方法中的for循環(huán),第一層for循環(huán),是把當前i作為根節(jié)點,然后找出左子樹的所有可能性和右子樹的所有可能性。這塊就用到了遞歸,因為左子樹和右子數作為單獨的樹,其實也是要遍歷確定子樹的“根節(jié)點”的。最后雙層for循環(huán)可以把所有子樹的可能情況都考慮到。反正情況就是這樣。
剛剛看了性能排行第一的代碼,就是多了點判斷,我貼出來參考下:

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public List<TreeNode> generateTrees(int n) {
        List<TreeNode> ans=new ArrayList<TreeNode>();
        if(n==0) return ans;
        return getAns(1,n);
    }

    public List<TreeNode> getAns(int start,int end){
        List<TreeNode> ans=new ArrayList<TreeNode>();
        if(start>end){
            //沒有數字的時候返回n
            ans.add(null);
            return ans;
        }

        if(start==end){
            //只有一個數字的時候直接加入
            TreeNode tree=new TreeNode(start);
            ans.add(tree);
            return ans;
        }

        for(int i=start;i<=end;i++){
            //得到所有的左子樹和右子樹
            List<TreeNode> leftTrees=getAns(start,i-1);
            List<TreeNode> rightTrees=getAns(i+1,end);
            for(TreeNode leftTree:leftTrees){
                //分別進行組合,將其分別連接到root兩側
                for(TreeNode rightTree:rightTrees){
                    TreeNode root=new TreeNode(i);
                    root.left=leftTree;
                    root.right=rightTree;
                    ans.add(root);
                }
            }
        }
        return ans;

    }
}

然后只要理解了就簡單,我反正一開始陷入了回溯的牛角尖,琢磨半天也沒寫出來,后來靈機一動才摸索出來了。這道題只要思路對了代碼不難,然后就這樣了,直接下一題。

不同的二叉搜索樹

題目:給定一個整數 n,求以 1 ... n 為節(jié)點組成的二叉搜索樹有多少種?

示例:
輸入: 3
輸出: 5
解釋:
給定 n = 3, 一共有 5 種不同結構的二叉搜索樹:


題目截圖

思路:說真的,這兩道題有毛病吧?哪怕都出了也應該這個1在前面啊,我都做出2了還有必要做1????就是上面的代碼,返回值是res.size()就好了。我去粘貼提交下。好像不太多,我看題目,這個連樹的結構都莫有了,,,貌似這個種類應該是有簡單算法的。直接可以拋出樹的結構直接數字相加,,我還是去試試代碼吧。
很好,超時了。。。但是我確信我的代碼沒問題。。哈哈,先附上截圖吧:

超時

然后為什么超時。。。我不知道啊,還有什么更簡單的算法么?我現在明白為啥2放在1前面了,都是套路啊。。哎。這道題感覺考點都變了,我再尋思尋思。
唔,我這里用的遞歸,問題應該就出在這里,遞歸超時,有一種遞歸的高效版:動態(tài)規(guī)劃——用數組記錄中間過程的遞歸。
我記得時間復雜度是n方 和n的區(qū)別。我去試試這道題用動歸怎么解。而且我覺得這道題確實這么做是有問題的,因為 比如說三個節(jié)點能組成的方式是固定的。所以如果前面三個后面三個,只算一次就ok了,而我這段代碼是要重復算的,這個是受了上一道題的影響,這里不需要列出來怎么排列,我從頭開始想想怎么做吧。
好了,果然是dp問題,我直接貼代碼:

class Solution {
    public int numTrees(int n) {
        if(n<=1) return n;
        //幾個元素能組成多少中選擇的字典
        int[] d = new int[n+1];
        //沒有元素或只有一個元素只有1中可能,
        d[0] = 1;
        d[1] = 1;
        for(int i = 2;i<=n;i++){
            //以j為根節(jié)點,求出i個元素有多少種排列
            //這里只是計數,從0開始則小于i,從1開始小于等于i
            for(int j = 0;j<i;j++){        
                //因為j是從0開始,所以j直接代表下標(從1開始要j-1。同理后面是i-j)        
                d[i] += d[j]*d[i-j-1]; 
            }
        }
        return d[n];
    }
}

因為這個題我沒啥思路,是一點點測試理解的,所以注釋我感覺寫的比較清楚了。。沒啥可講的。就是一個用數組記錄中間過程的遞歸。如果看不懂這個或者說不太明白,建議去補習動態(tài)規(guī)劃的知識。下一題了。

驗證二叉搜索樹

題目:給定一個二叉樹,判斷其是否是一個有效的二叉搜索樹。
假設一個二叉搜索樹具有如下特征:
節(jié)點的左子樹只包含小于當前節(jié)點的數。
節(jié)點的右子樹只包含大于當前節(jié)點的數。
所有左子樹和右子樹自身必須也是二叉搜索樹。

題目截圖

思路:這道題有點送分啊。。。感覺做過類似的呢,,我有個大膽的想法。前序遍歷,結果不是遞增就是false。。不知道想法對不對呢。我去試試。
哈哈哈哈,我這個大膽的想法就這么實現了,有點意思啊。。。
我直接貼出第一版本的代碼:

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public boolean isValidBST(TreeNode root) {
        List<Integer> list = new ArrayList<Integer>();
        test(root,list);
        for(int i = 0;i<list.size()-1;i++){
            if(list.get(i+1)<=list.get(i)) return false;
        }
        return true;
    }
    public void test(TreeNode root,List<Integer> list){
        if(root==null) return;
        test(root.left,list);
        list.add(root.val);
        test(root.right,list);        
    }
}

講真,leetcode中中等難度最簡單的題目,沒有之一。
然后這個性能不是很好,因為我本來只是想嘗試下思路對不對,,估計是可以優(yōu)化的,我目前想到的優(yōu)化點就是不用遍歷完二叉樹再判斷了,可以在遍歷的時候就判斷。新加元素小于等于前一個元素,直接false。。我去處理下。
emmmm....怎么說呢,改是改完了,運行也對了,就是性能還是2ms,一點沒長進。。。我還是把代碼貼上來吧,也算是個思路 :

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    List<Integer> list;
    public boolean isValidBST(TreeNode root) {
        list = new ArrayList<Integer>();
        return test(root);
    }
    public boolean test(TreeNode root){
        if(root==null) return true;        
        boolean l = test(root.left);
        if(list.size()!=0 && root.val<= list.get(list.size()-1)) return false;
        list.add(root.val);
        boolean r = test(root.right);  
        return l && r;      
    }
}

感覺憑我一己之力已經難以優(yōu)化了,我直接去看性能排行第一的代碼吧。
我直接貼代碼:

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public boolean isValidBST(TreeNode root) {
        return isBST(root,Long.MIN_VALUE,Long.MAX_VALUE);
    }
    public boolean isBST(TreeNode node,long min,long max){
        if(node==null){
            return true;
        }
        if(node.val<=min||node.val>=max){
            return false;
        }
        return isBST(node.left,min,node.val)&&isBST(node.right,node.val,max);
    }
}

差不多就是遍歷過程中判斷,只不過我用了list,人家沒用。。。我感覺我是受了第一個遍歷完判斷的思路的影響。。??戳巳思业拇a恍然大悟豁然開朗,我覺得我也應該能想出來啊。。。哎
思路:然后今天的筆記就記到這里,如果稍微幫到你了記得點個喜歡點個關注!打算從今天開始正常刷題啦,反正閑著也是閑著。也祝大家工作順順利利!生活平平安安健健康康!

?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容