Arts 第九周(5/13 ~ 5/19)

ARTS是什麼?
Algorithm:每週至少作一個leetcode的算法題;
Review:閱讀並點評至少一篇英文技術文章;
Tip:學習至少一個技術技巧;
Share:分享一篇有觀點和思考的技術文章。java


Algorithm

LeetCode 407. Trapping Rain Water IInode

題目思路分析
算法

在 1 個 2 維的矩陣中,每一個格子都有其高度,問這個 2 維矩陣可以盛多少的水。首先咱們分析,格子可以盛水的必要條件是其周圍存在格子比當前格子高,這樣水纔可以被框得住,可是仔細一想,最外圍的格子怎麼辦?它們是存不了水的,能夠把最外圍的格子想象成圍欄,它們的做用就是保證裏面格子的水不會流出來,因此咱們就得先考慮這些格子,它們的高度直接決定了內部格子的蓄水量,可是這些格子也有局部性,一個格子的長短並不會影響矩陣當中全部的格子,可是它會影響與其相鄰的格子,那麼咱們就須要有一個考慮的順序,那就是優先考慮最外層最短的格子,因爲每一個格子都會影響到其周圍的格子,內部格子也須要列入考慮範圍,每次咱們都考慮最短的格子,而後看其周圍有沒有沒考慮過的比它還短的格子,因而就有了考慮的前後順序:數據庫

  1. 考慮最外層格子
  2. 選出最外層最短的格子
  3. 考慮該格子與其相鄰的內部格子是否能盛水,並把這個內部格子也歸入考慮範圍
  4. 在考慮範圍內的全部格子中選出最短的格子,重複步驟 3

這裏須要注意的是,每次歸入考慮範圍的格子是加了水以後的高度,而不是以前的高度,緣由想一下應該不難理解。另外就是可使用了 「堆」 這個數據結構來幫助實現尋找 「當前考慮範圍內最短的格子」 這個操做步驟。api

參考代碼緩存

private class Pair {
    int x, y, h;
    Pair(int x, int y, int h) {
        this.x = x;
        this.y = y;
        this.h = h;
    }
}

private int[] dirX = {0, 0, -1, 1};
private int[] dirY = {-1, 1, 0, 0};

public int trapRainWater(int[][] heightMap) {
    if (heightMap.length == 0 || heightMap[0].length == 0) {
        return 0;
    }
    
    int m = heightMap.length;
    int n = heightMap[0].length;
    
    PriorityQueue<Pair> pq = new PriorityQueue<>(new Comparator<Pair>() {
        @Override
        public int compare(Pair a, Pair b) {
            return a.h - b.h;
        }
    });
    
    boolean[][] visited = new boolean[m][n];
    
    for (int i = 0; i < n; ++i) {
        pq.offer(new Pair(0, i, heightMap[0][i]));
        pq.offer(new Pair(m - 1, i, heightMap[m - 1][i]));
        
        visited[0][i] = true;
        visited[m - 1][i] = true;
    }
    
    for (int i = 1; i < m - 1; ++i) {
        pq.offer(new Pair(i, 0, heightMap[i][0]));
        pq.offer(new Pair(i, n - 1, heightMap[i][n - 1]));
        
        visited[i][0] = true;
        visited[i][n - 1] = true;
    }
    
    int result = 0;
    while (!pq.isEmpty()) {
        Pair cur = pq.poll();
        
        for (int k = 0; k < 4; ++k) {
            int curX = cur.x + dirX[k];
            int curY = cur.y + dirY[k];
            
            if (curX < 0 || curY < 0 || curX >= m || curY >= n || visited[curX][curY]) {
                continue;
            }
            
            if (heightMap[curX][curY] < cur.h) {
                result += cur.h - heightMap[curX][curY];
            }
            
            pq.offer(new Pair(curX, curY, 
                              Math.max(heightMap[curX][curY], cur.h)));
            visited[curX][curY] = true;
        }
    }
    
    return result;
}
複製代碼

Review

一篇關於 Node.js 中項目代碼結構的文章:
安全

Bulletproof node.js project architecture服務器

做者的有幾個觀點我以爲仍是很值得借鑑的:數據結構

  • 使用三層結構
    controller、service、model 分別處理 REST 請求、邏輯處理、以及數據庫操做,這樣的好處是 controller 中不會有邏輯操做,每一個函數的任務都很清晰、明確,代碼會更加的簡潔,並且程序出 bug,定位問題也會更高效
  • 利用 JS 中的發送和監聽機制來處理業務
    舉個例子,當咱們在服務器端新建一個 user 帳號時,這個 「新建」 操做可能會涉及到查找記錄、建立帳號、初始化信息、發送郵件通知等等,若是這些東西都在一個模塊中進行,不免會使這個模塊中的代碼邏輯變得很複雜。咱們能夠把這些東西分紅一系列的小監聽組件(XXX.on(...)),這樣新建操做只須要 emit 「新建」 這個事件,相關的組件都會被觸發,這樣在主代碼邏輯中會更加地清晰,並且這些小的監聽組件也能夠被重複使用
  • 使用注入依賴(Dependency Injection)的方式來組織代碼
    node.js 中能夠考慮使用 typedi 這個庫來實現注入依賴
  • 考慮環境變量和config文件結合的方式來存儲私密資料
    把相似密碼、數據庫 IP 一類重要的信息直接寫在功能代碼中或者普通文件中,都不是一個安全的方式,Node.js 使用 process.env 設置環境變量的方式存儲這些重要的私密信息,咱們能夠考慮使用 dotenv 這個庫來幫助在 .env 隱藏文件中定義這些私密信息,可是這裏有一點很差的是沒有辦法整合歸類,因而考慮把這些環境變量在export 到一個 config 文件中整合歸類,這樣,在咱們寫代碼的時候藉助 IDE 的功能補全能夠快速地找到對應的環境變量
  • 不要把全部的邏輯都放在同一個函數或者文件中
    有一個清晰的設計理念就是,「一個組件只作一件事情」,這樣的代碼纔是可測試的代碼,代碼的重用率纔會提升,也是更便於他人理解的

做者同時在文章的開頭給出了他以爲不錯的一個文件結構:
src
    app.js # App entry point
    api # Express route controllers for all the endpoints of the app
    config # Environment variables and configuration related stuff
    jobs # Jobs definitions for agenda.js
    loaders # Split the startup process into modules
    models # Database models
    services # All the business logic is here
    subscribers # Event handlers for async task
    types # Type declaration files (d.ts) for Typescriptapp


Tip

最近在學 Python,總結一些列表、元組、集合、字典的使用注意事項:

列表和元組

  • 列表是動態的,元組是靜態的,列表可變,元組不可變
  • 列表和元組都支持負數索引和切片操做
  • 列表根據 over allocate 原則去進行擴充
  • 同等大小的列表會比元組多 16 個字節,緣由在於存儲指針和記錄最大容量的變量
  • 新建元組會比新建列表更高效,緣由在於 Python 的垃圾回收機制對於不用的、大小不是太大的元組會進行緩存,等到新建元組的時候就不須要再向操做系統請求開闢內存空間,直接使用這些緩存的空間便可
  • 一些經常使用的內置函數
    • count
    • index
    • sort # 原地排序
    • reverse # 原地反轉
    • sorted # 排序後返回排序好的新列表/元組
    • reversed # 反轉後返回排序好的新列表/元組

集合和字典

  • 集合和字典的初始化均可以使用 {}
  • 能夠直接使用 == 判斷兩個字典或者集合中內容是否徹底相同
  • 使用 value in set/dict 來判斷一個 key 是否存在於集合或者字典中
  • 能夠考慮使用 get(key, default) 來獲取對應值而不產生報錯
  • 集合使用 remove 刪除元素,add 添加元素,字典使用 pop 刪除元素,注意 pop 在集合中是刪除最後一個元素,可是集合原本就是無序的,最好不要使用這個函數
  • 對集合進行排序會返回一個列表,對字典進行排序會返回一個含有二元組的列表
  • 字典和集合在底層實現中和 Java 的 HashMap 和 HashSet 相似,可是處理衝突的解決方法有所不一樣,遇到衝突時會繼續尋找,直到找到爲止,而不是使用鏈表或者樹的結構

Share

此次繼續來積累算法知識,此次看看深度優先搜索,經過它,咱們能夠發現不少高級的算法,將學過的東西創建聯繫、融會貫通也是一件很是有意義的事情。

從簡單二叉樹問題從新來看深度優先搜索

相關文章
相關標籤/搜索