算法 | 一周刷完《劍指Offer》 Day6:第61~66題

寫在前面

上一篇:算法 | 一周刷完《劍指Offer》 Day5:第50~60題


Day6:第60~66題

6天搞定!感覺還是有點進步的。

  • T61. 序列化二叉樹
  • T62. 二叉搜索樹的第k個結點
  • T63. 數(shù)據(jù)流中的中位數(shù)
  • T64. 滑動窗口的最大值
  • T65. 矩陣中的路徑
  • T66. 機器人的運動范圍

T61. 序列化二叉樹

題目描述

請實現(xiàn)兩個函數(shù),分別用來序列化和反序列化二叉樹

解題思路

基本的序列化思想就可以。
此題每個人方法都不一樣,需要注意的是:
1.對于二叉樹來說,節(jié)點為null時最好采用一個字符來替代表示(這里用的 # 字符);
2.該題二叉樹的value為int型,在序列化成字符串時最好用字符分隔開(這里用的逗號),不然int和String轉(zhuǎn)換過程中容易出錯。

    public String Serialize(TreeNode root) {
        if(root == null) return "";
        
        StringBuilder sb = new StringBuilder();
        serialize(root, sb);
        
        return sb.toString();
    }
    
    private void serialize(TreeNode root, StringBuilder sb) {
        if(root == null) {
            sb.append("#,");
            return;
        }
        
        sb.append(root.val);
        sb.append(",");
        serialize(root.left, sb);
        serialize(root.right, sb);
    }
       
    public TreeNode Deserialize(String str) {
        if(str == null || str.length() == 0) return null;

        String[] strs = str.split(",");
        index = 0;
        return deserialize(strs);
    }
    
    private int index;
    
    private TreeNode deserialize(String[] strs) {
        if(!strs[index].equals("#")) {
            TreeNode node = new TreeNode(Integer.parseInt(strs[index]));
            index ++;
            node.left = deserialize(strs);
            node.right = deserialize(strs);
            return node;
        } else {
            index ++;
        }
        return null;
    }

T62. 二叉搜索樹的第k個結點

題目描述

給定一棵二叉搜索樹,請找出其中的第k小的結點。例如, (5,3,7,2,4,6,8) 中,按結點數(shù)值大小順序第三小結點的值為4。

解題思路

注意題意。二叉搜索樹:左子結點 < 根結點 < 右子結點
因此對二叉搜索樹進行中序遍歷,得到的即為從小到大排序序列。

    private ArrayList<TreeNode> list = new ArrayList<>();
    
    public TreeNode KthNode(TreeNode pRoot, int k) {//二叉搜索樹,中序遍歷即為從小到大的排序
        if(pRoot == null || k <= 0) return null;
        
        inOrder(pRoot);
        
        TreeNode kthNode = null;
        
        if(k <= list.size()) {
            kthNode = list.get(k - 1);
        }
        
        return kthNode;
    }
    
    private void inOrder(TreeNode root) {//中序遍歷
        if(root == null) return;
        
        //左
        if(root.left != null) inOrder(root.left);
        //根
        list.add(root);
        //右
        if(root.right != null) inOrder(root.right);
    }

T63. 數(shù)據(jù)流中的中位數(shù)

題目描述

如何得到一個數(shù)據(jù)流中的中位數(shù)?如果從數(shù)據(jù)流中讀出奇數(shù)個數(shù)值,那么中位數(shù)就是所有數(shù)值排序之后位于中間的數(shù)值。如果從數(shù)據(jù)流中讀出偶數(shù)個數(shù)值,那么中位數(shù)就是所有數(shù)值排序之后中間兩個數(shù)的平均值。我們使用Insert()方法讀取數(shù)據(jù)流,使用GetMedian()方法獲取當前讀取數(shù)據(jù)的中位數(shù)。

解題思路

畫了張草圖(如上)。這是一個比較巧妙的方法,用到了PriorityQueue,一個能自動排序的隊列,排序方式也可以自定義。解題思路寫在注釋里了,對著代碼和圖片容易理解一些。

    private int count = 0;//計數(shù),判斷奇偶
    //使用自動排序的PriorityQueue,兩個堆詳情見圖片
    //小頂推:默認從小到大排序,堆頂為最小數(shù),用于存儲后半部分較大的數(shù),堆頂用于計數(shù)中位數(shù)
    private PriorityQueue<Integer> minHeap = new PriorityQueue<>();
    //大頂堆:聲明從大到小排序,堆頂為最大數(shù),用于存儲前半部分較小的數(shù),堆頂用于計數(shù)中位數(shù)
    private PriorityQueue<Integer> maxHeap = new PriorityQueue<>(new Comparator<Integer>() {
        @Override
        public int compare(Integer arg0, Integer arg1) {
            return arg1 - arg0;
        }
    });
    
    public void Insert(Integer num) {
        //分奇偶次插入,一半入小頂堆,一半入大頂堆,保證兩個堆數(shù)量一半一半
        if(count % 2 == 0) {//注意這里0為第1個數(shù),所以插入時奇數(shù)次對應的count為偶數(shù)
            //奇數(shù)次插入時,最終入小頂堆
            //注意插入時是先入大頂堆,由大頂堆排序后取大頂堆最大的插入小頂堆
            
            //插入大頂堆
            maxHeap.offer(num);
            //取大頂堆堆頂
            int maxInMaxHead = maxHeap.poll();
            //堆頂插入小頂堆
            minHeap.offer(maxInMaxHead);
        } else {
            //偶數(shù)次插入時,最終入大頂堆
            //原理同上
            
            //插入小頂堆
            minHeap.offer(num);
            //取小頂堆堆頂
            int minInMinHead = minHeap.poll();
            //堆頂入大頂堆
            maxHeap.offer(minInMinHead);
        }
        
        count ++;
    }

    @SuppressWarnings("deprecation")
    public Double GetMedian() {
        //由于先入小頂堆,小頂堆數(shù)量總是比大頂堆多1或相等
        //所以這里,count為奇次時,插入了奇數(shù)次,中位數(shù)是小頂堆堆頂
        //count為偶次時,插入了偶數(shù)次,中位數(shù)是兩堆堆頂平均數(shù)
        if(count % 2 == 0) {
            return new Double(minHeap.peek() + maxHeap.peek()) / 2;
        } else {
            return new Double(minHeap.peek());
        }
    }

T64. 滑動窗口的最大值

題目描述

給定一個數(shù)組和滑動窗口的大小,找出所有滑動窗口里數(shù)值的最大值。例如,如果輸入數(shù)組{2,3,4,2,6,2,5,1}及滑動窗口的大小3,那么一共存在6個滑動窗口,他們的最大值分別為{4,4,6,6,6,5}; 針對數(shù)組{2,3,4,2,6,2,5,1}的滑動窗口有以下6個: {[2,3,4],2,6,2,5,1}, {2,[3,4,2],6,2,5,1}, {2,3,[4,2,6],2,5,1}, {2,3,4,[2,6,2],5,1}, {2,3,4,2,[6,2,5],1}, {2,3,4,2,6,[2,5,1]}。

解題思路

使用到了Deque,一個雙端隊列,可從兩端彈出,用來模擬窗口,儲存當前窗口顯示的元素在num數(shù)組的下標。每當移動窗口時,要判斷隊列頭是否超出窗口范圍,同時要將新加入的隊尾的數(shù)與隊中最大數(shù)比較,小則直接退出,大則退出最大數(shù)。(這樣做實質(zhì)上始終只有一個最大元素在窗口中)

    public ArrayList<Integer> maxInWindows(int[] num, int size) {
        ArrayList<Integer> result = new ArrayList<>();
        
        if(num == null || num.length == 0 || size == 0 || size > num.length) {
            return result;
        }
        
        //雙端隊列,可從兩端彈出,模擬窗口,用于存當前窗口最大值在num數(shù)組下標
        Deque<Integer> deque = new ArrayDeque<>();
        
        for(int i = 0; i < num.length; i ++) {
            if(!deque.isEmpty() && (i - deque.peekFirst()) >= size) {//當隊列頭已超出窗口范圍時移除隊列頭
                deque.pollFirst();
            }
            while(!deque.isEmpty() && num[i] >= num[deque.peekLast()]) {//如果,要進隊列的新的數(shù)比隊列尾部大,移除隊尾,直到隊列中只剩最大的一個數(shù)
                deque.pollLast();
            }
            deque.offer(i);
            if(i >= (size - 1)) {//過濾前幾個,當遍歷到第一個窗口大小時開始存結果
                result.add(num[deque.peekFirst()]);
            }
        }
        
        return result;
    }

T65. 矩陣中的路徑

題目描述

請設計一個函數(shù),用來判斷在一個矩陣中是否存在一條包含某字符串所有字符的路徑。路徑可以從矩陣中的任意一個格子開始,每一步可以在矩陣中向左,向右,向上,向下移動一個格子。如果一條路徑經(jīng)過了矩陣中的某一個格子,則之后不能再次進入這個格子。 例如 a b c e s f c s a d e e 這樣的3 X 4 矩陣中包含一條字符串"bcced"的路徑,但是矩陣中不包含"abcb"路徑,因為字符串的第一個字符b占據(jù)了矩陣中的第一行第二個格子之后,路徑不能再次進入該格子。

解題思路

動態(tài)規(guī)劃應用題,思路是邊走邊找,具體見注釋。

注意
定義輔助數(shù)組,用于記錄矩陣中各位置是否被訪問過;
二維數(shù)組在一維數(shù)組形式中的表示:index = i * cols + j;
此題求多種解,記得回溯。

    public boolean hasPath(char[] matrix, int rows, int cols, char[] str) {
        int[] flag = new int[matrix.length];//輔助數(shù)組,用于記錄矩陣中各位置是否被訪問過
        
        for(int i = 0; i < rows; i ++) {
            for(int j = 0; j < cols; j ++) {
                int index = i * cols + j;//二維數(shù)組在一維數(shù)組形式中的表示
                
                if(matrix[index] == str[0]) {//找到str在矩陣的開始位置
                    if(findPath(matrix, rows, cols, i, j, str, 0, flag)) {
                        return true;
                    }
                }
            }
        }
        
        return false;
    }
    
    //動態(tài)規(guī)劃求解
    private boolean findPath(char[] matrix, int rows, int cols, int i, int j, 
                            char[] str, int strIndex, 
                            int[] flag) {
        int index = i * cols + j;
        if(i < 0 || i >= rows || j < 0 || j >= cols //數(shù)組越界
            || matrix[index] != str[strIndex] //矩陣該點與字符串相應位置值不同
            || flag[index] == -1) {//矩陣該點已使用過
            return false;
        }
        if(strIndex == str.length - 1) {//該點符合要求且已是字符串最后一位
            return true;
        }
        
        flag[index] = -1;
        //對該點上下左右遞歸求解
        if(findPath(matrix, rows, cols, i - 1, j, str, strIndex + 1, flag)
            || findPath(matrix, rows, cols, i + 1, j, str, strIndex + 1, flag)
            || findPath(matrix, rows, cols, i, j - 1, str, strIndex + 1, flag)
            || findPath(matrix, rows, cols, i, j + 1, str, strIndex + 1, flag)) {
            return true;//任一符合即可
        }
        
        flag[index] = 0;//記得回溯時將該點標記為未使用過
        
        return false;
    }

T66. 機器人的運動范圍

題目描述

地上有一個m行和n列的方格。一個機器人從坐標0,0的格子開始移動,每一次只能向左,右,上,下四個方向移動一格,但是不能進入行坐標和列坐標的數(shù)位之和大于k的格子。 例如,當k為18時,機器人能夠進入方格(35,37),因為3+5+3+7 = 18。但是,它不能進入方格(35,38),因為3+5+3+8 = 19。請問該機器人能夠達到多少個格子?

解題思路

動態(tài)規(guī)劃應用題,同上一題,相比要簡單很多,具體見注釋。

    public int movingCount(int threshold, int rows, int cols) {
        int[] flags = new int[rows * cols];//輔助數(shù)組,用于記錄矩陣中各位置是否被訪問過
        
        return moving(threshold, rows, cols, 0, 0, flags);
    }
    
    //動態(tài)規(guī)劃
    private int moving(int k, int rows, int cols,
                        int i, int j, int[] flags) {
        int index = i * cols + j;
        if(i < 0 || i >= rows || j < 0 || j >= cols || flags[index] == -1) {//數(shù)組越界或該點被訪問過
            return 0;
        }
        
        int sum = 0;
        int tmp = i;
        while(tmp != 0) {//橫坐標各位和
            sum += tmp % 10;
            tmp /= 10;
        }
        tmp = j;
        while(tmp != 0) {//縱坐標各位和
            sum += tmp % 10;
            tmp /= 10;
        }
        
        if(sum <= k) {
            flags[index] = -1;//訪問過,與上題不同的是,這里不需回溯
            int steps = 1;//計入當前點
            //對該點右邊和下邊的點遞歸求解,左邊與上邊在前面的迭代過程中已訪問過,無需再次訪問
            steps += moving(k, rows, cols, i + 1, j, flags);
            steps += moving(k, rows, cols, i, j + 1, flags);
            return steps;
        }
        
        return 0;
    }

項目地址https://github.com/JohnnyJYWu/offer-Java

上一篇算法 | 一周刷完《劍指Offer》 Day5:第50~60題

希望這篇文章對你有幫助~

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

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容