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

寫在前面

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


Day6:第60~66題

6天搞定!感覺還是有點(diǎn)進(jìn)步的。

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

T61. 序列化二叉樹

題目描述

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

解題思路

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

    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個(gè)結(jié)點(diǎn)

題目描述

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

解題思路

注意題意。二叉搜索樹:左子結(jié)點(diǎn) < 根結(jié)點(diǎn) < 右子結(jié)點(diǎn)
因此對(duì)二叉搜索樹進(jìn)行中序遍歷,得到的即為從小到大排序序列。

    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ù)

題目描述

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

解題思路

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

    private int count = 0;//計(jì)數(shù),判斷奇偶
    //使用自動(dòng)排序的PriorityQueue,兩個(gè)堆詳情見圖片
    //小頂推:默認(rèn)從小到大排序,堆頂為最小數(shù),用于存儲(chǔ)后半部分較大的數(shù),堆頂用于計(jì)數(shù)中位數(shù)
    private PriorityQueue<Integer> minHeap = new PriorityQueue<>();
    //大頂堆:聲明從大到小排序,堆頂為最大數(shù),用于存儲(chǔ)前半部分較小的數(shù),堆頂用于計(jì)數(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) {
        //分奇偶次插入,一半入小頂堆,一半入大頂堆,保證兩個(gè)堆數(shù)量一半一半
        if(count % 2 == 0) {//注意這里0為第1個(gè)數(shù),所以插入時(shí)奇數(shù)次對(duì)應(yīng)的count為偶數(shù)
            //奇數(shù)次插入時(shí),最終入小頂堆
            //注意插入時(shí)是先入大頂堆,由大頂堆排序后取大頂堆最大的插入小頂堆
            
            //插入大頂堆
            maxHeap.offer(num);
            //取大頂堆堆頂
            int maxInMaxHead = maxHeap.poll();
            //堆頂插入小頂堆
            minHeap.offer(maxInMaxHead);
        } else {
            //偶數(shù)次插入時(shí),最終入大頂堆
            //原理同上
            
            //插入小頂堆
            minHeap.offer(num);
            //取小頂堆堆頂
            int minInMinHead = minHeap.poll();
            //堆頂入大頂堆
            maxHeap.offer(minInMinHead);
        }
        
        count ++;
    }

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

T64. 滑動(dòng)窗口的最大值

題目描述

給定一個(gè)數(shù)組和滑動(dòng)窗口的大小,找出所有滑動(dòng)窗口里數(shù)值的最大值。例如,如果輸入數(shù)組{2,3,4,2,6,2,5,1}及滑動(dòng)窗口的大小3,那么一共存在6個(gè)滑動(dòng)窗口,他們的最大值分別為{4,4,6,6,6,5}; 針對(duì)數(shù)組{2,3,4,2,6,2,5,1}的滑動(dòng)窗口有以下6個(gè): {[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,一個(gè)雙端隊(duì)列,可從兩端彈出,用來模擬窗口,儲(chǔ)存當(dāng)前窗口顯示的元素在num數(shù)組的下標(biāo)。每當(dāng)移動(dòng)窗口時(shí),要判斷隊(duì)列頭是否超出窗口范圍,同時(shí)要將新加入的隊(duì)尾的數(shù)與隊(duì)中最大數(shù)比較,小則直接退出,大則退出最大數(shù)。(這樣做實(shí)質(zhì)上始終只有一個(gè)最大元素在窗口中)

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

T65. 矩陣中的路徑

題目描述

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

解題思路

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

注意
定義輔助數(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;
    }
    
    //動(dòng)態(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] //矩陣該點(diǎn)與字符串相應(yīng)位置值不同
            || flag[index] == -1) {//矩陣該點(diǎn)已使用過
            return false;
        }
        if(strIndex == str.length - 1) {//該點(diǎn)符合要求且已是字符串最后一位
            return true;
        }
        
        flag[index] = -1;
        //對(duì)該點(diǎn)上下左右遞歸求解
        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;//記得回溯時(shí)將該點(diǎn)標(biāo)記為未使用過
        
        return false;
    }

T66. 機(jī)器人的運(yùn)動(dòng)范圍

題目描述

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

解題思路

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

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

項(xiàng)目地址https://github.com/JohnnyJYWu/offer-Java

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

希望這篇文章對(duì)你有幫助~

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

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

  • 前言 2. 實(shí)現(xiàn) Singleton 3. 數(shù)組中重復(fù)的數(shù)字 4. 二維數(shù)組中的查找 5. 替換空格 6. 從尾到...
    Observer_____閱讀 3,181評(píng)論 0 1
  • 說明: 本文中出現(xiàn)的所有算法題皆來自??途W(wǎng)-劍指Offer在線編程題,在此只是作為轉(zhuǎn)載和記錄,用于本人學(xué)習(xí)使用,不...
    秋意思寒閱讀 1,223評(píng)論 1 1
  • 本文是我自己在秋招復(fù)習(xí)時(shí)的讀書筆記,整理的知識(shí)點(diǎn),也是為了防止忘記,尊重勞動(dòng)成果,轉(zhuǎn)載注明出處哦!如果你也喜歡,那...
    波波波先森閱讀 4,385評(píng)論 0 23
  • 寫在前面 本系列包含《劍指Offer》66道算法題,預(yù)計(jì)一周刷完,這是第二篇。系列匯總:劍指Offer 66題 J...
    機(jī)鹽Johnny閱讀 2,103評(píng)論 0 4
  • 01 這是一個(gè)寫作的人比以往任何一個(gè)時(shí)代都多的時(shí)代,很多人懷著夢(mèng)想走入大學(xué)的文學(xué)創(chuàng)作課堂,更有難以計(jì)數(shù)的人活躍在網(wǎng)...
    徽姑娘_b76e閱讀 419評(píng)論 0 5

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