九章算法高级班笔记2.数据结构(上)

并查集 Union Find cs3k.com

并查集是这样一种数据机构:
A, B, C三我的在Microsoft工做, 给他们三我的每一个人都安个指针指向Microsoft. 这个指针相对于你工做单位的腰牌,表明你所属的阵营, 说明你是这个阵营的小弟.
D, E, F, J四我的在Linkedin工做, 给这四我的每一个人都安个腰牌指针指向大哥Linkedin.
*****pic2.1java

这个指针腰牌有神马用呢? 就是假设A在饭桌上遇到了F, A一看对放腰牌是L公司, F一看对方腰牌是M公司, 就都知道对方是否是本身一家公司的. 又来我的D, D的腰牌是L公司, 这下F和D也当即就知道他俩是同事.

*********pic2.2
node

这时候, 发生了一件事儿, 就是M公司Microsoft把L公司Linkedin收购了. 可是Linkedin也不小, 底下一撮子的小弟, 一个个改腰牌好麻烦. 而后公司想了个办法, D, E, F, J的大哥不变, 依然由Linkedin担当, 可是Linkedin要百Microsoft当大哥. 这样饭桌上即便聊起来, A能够看下E的腰牌, 噢, 你大哥是L啊, 是我大哥M手下L管的小弟, 咱俩仍是一个阵营哒~
******pic2.3c++

这里, 咱们能够不用M和L这个公司名字, 直接在公司的全部员工里, 选个权利最大的作表明, 好比M公司的A, B, C我就选了B (Bill Gates), L公司呢选J, 你们的大哥就分别是这两我的:
*****pic2.4面试

这时候呢, 若是公司之间合并收购, 就让J拜B当大哥, 你们就是一伙儿的啦
pic2.5*************express

并查集的精髓即三个操做:

1. 初始化

初始化后每个元素的父亲节点是它自己,即本身是本身的大哥(也能够根据状况而变), 举个栗子:数组

private int[] father = null;
    public Initialization(int n) {
        // initialize your data structure here.
        father = new int[n + 1];
        for (int i = 1; i <= n; ++i)
            father[i] = i;
    }

2. 查找

查找一个元素所在的集合,其精髓是找到这个元素的大哥!这个才是并查集判断和合并的最终依据。
判断两个元素是否属于同一集合,只要看他们是否是最终都归一个大哥管, 模板以下:app

private int find(int x) {
        if (father[x] == x) {
            return x;
        }
        return father[x] = find(father[x]);
    }

3. 合并

合并两个不相交集合就是让一伙的老大哥拜另外一伙儿的老大哥为大哥, 模板为:ide

public void connect(int a, int b) {
        // Write your code here
        int root_a = find(a);
        int root_b = find(b);
        if (root_a != root_b)
            father[root_a] = root_b;
    }

路径压缩和时间复杂度:

无路径压缩是这样的:wordpress

private int find(int x) {
        if (father[x] == x) {
            return x;
        }
        return find(father[x]);
    }

有路径压缩是函数

private int find(int x) {
        if (father[x] == x) {
            return x;
        }
        return father[x] = find(father[x]);
    }

其中路径压缩是这样的原理:
假设A->B->Z->Y->W这样的路径, 咱们想查询A的老大哥, 须要从A走过B, Z, Y到W, 途中通过5个点, B查询通过4个点… 每次这样好麻烦:
因而咱们在递归回溯的过程当中, 把每层的father大哥都变成最大的大哥:

****pic

而后咱们就由链条型多步找大哥变为众星捧月型一步找大哥:

enter image description here

其中:

private int find(int x) {
     if (father[x] == x) {
         return x;
     }
     return father[x] = find(father[x]);
 }

的时间复杂度第一次是O(n), 可是屡次下来是log*n (见https://en.wikipedia.org/wiki/Iterated_logarithm), 证实略

enter image description here
由于1-5都很小, 因此find基本上是O(1)的操做.
若是find是O(1), 那么union也是O(1)时间复杂度的操做.

Connecting Graph

cs3k.com

Given n nodes in a graph labeled from 1 to n. There is no edges in the graph at beginning.

You need to support the following method:

connect(a, b), add an edge to connect node a and node b`.
query(a, b), check if two nodes are connected.

这道题很容易直接想到bfs, bfs的遍历, 时间复杂度是O(kn), 其中k是操做次数.
若是用并查集的方法, 时间复杂度每次操做都是O(1), 因此总共是O(k).

public class ConnectingGraph { 

    private int[] father = null;

    private int find(int x) {
        if (father[x] == x) {
            return x;
        }
        return father[x] = find(father[x]);
    }

    public ConnectingGraph(int n) {
        // initialize your data structure here.
        father = new int[n + 1];
        for (int i = 1; i <= n; ++i)
            father[i] = i;
    }

    public void connect(int a, int b) {
        // Write your code here
        int root_a = find(a);
        int root_b = find(b);
        if (root_a != root_b)
            father[root_a] = root_b;
    }
        
    public boolean  query(int a, int b) {
        // Write your code here
        int root_a = find(a);
        int root_b = find(b);
        return root_a == root_b;
    }
}

Connecting Graph II

cs3k.com

Given n nodes in a graph labeled from 1 to n. There is no edges in the graph at beginning.

You need to support the following method:

connect(a, b), an edge to connect node a and node b
query(a), Returns the number of connected component nodes which include node a.

public class ConnectingGraph2 { 

    private int[] father = null;
    private int[] size = null;

    private int find(int x) {
        if (father[x] == x) {
            return x;
        }
        return father[x] = find(father[x]);
    }

    public ConnectingGraph2(int n) {
        // initialize your data structure here.
        father = new int[n + 1];
        size = new int[n + 1];
        for (int i = 1; i <= n; ++i) {
            father[i] = i;
            size[i] = 1;
        }
    }

    public void connect(int a, int b) {
        // Write your code here
        int root_a = find(a);
        int root_b = find(b);
        if (root_a != root_b) {
            father[root_a] = root_b;
            size[root_b] += size[root_a];
        }
    }
        
    public int query(int a) {
        // Write your code here
        int root_a = find(a);
        return size[root_a];
    }
}

Connecting Graph问题的总结

cs3k.com

并查集原生操做:

  1. 查询两个元素是否在同一个集合内
  2. 合并两个元素所在的集合

并查集的派生操做:

  1. 查询某个元素所在集合的元素个数
  2. 查询当前集合的个数

Number of Islands

Given a boolean 2D matrix, 0 is represented as the sea, 1 is represented as the island. If two 1 is adjacent, we consider them in the same island. We only consider up/down/left/right adjacent.

Find the number of islands.
这道题, 用bfs或者dfs的时候, k个操做, 时间复杂度是O(kmn), 其中m是行数, n是列数.
可是其实每次操做, 咱们并不须要遍历整张图, 只须要在每一个点附近的局部作处理就行, 这种bfs/dfs下的局部处理, 能够想到并查集. 须要作的的事是: 把其余的操做,转化成查找和合并这两件事情.

值得注意的是, 二位矩阵转一维有个公式:
二位矩阵中的(x, y)某点若是想变为一维矩阵中的某个位置, id转化公式以下, 其中m为二维矩阵的列数:

(x, y) ---> ID :     ID = x * m + y
ID ----> (x, y):     x = ID / m
                     y = ID % m

这道题的题解:

class UnionFind { 

    private int[] father = null;
    private int count;

    private int find(int x) {
        if (father[x] == x) {
            return x;
        }
        return father[x] = find(father[x]);
    }

    public UnionFind(int n) {
        // initialize your data structure here.
        father = new int[n];
        for (int i = 0; i < n; ++i) {
            father[i] = i;
        }
    }

    public void connect(int a, int b) {
        // Write your code here
        int root_a = find(a);
        int root_b = find(b);
        if (root_a != root_b) {
            father[root_a] = root_b;
            count --;
        }
    }
        
    public int query() {
        // Write your code here
        return count;
    }
    
    public void set_count(int total) {
        count = total;
    }
}

public class Solution {
    /**
     * @param grid a boolean 2D matrix
     * @return an integer
     */
    public int numIslands(boolean[][] grid) {
        int count = 0;
        int n = grid.length;
        if (n == 0)
            return 0;
        int m = grid[0].length;
        if (m == 0)
            return 0;
        UnionFind union_find = new UnionFind(n * m);
        
        int total = 0;
        for(int i = 0;i < grid.length; ++i)
            for(int j = 0;j < grid[0].length; ++j)
            if (grid[i][j])
                total ++;
    
        union_find.set_count(total);
        for(int i = 0;i < grid.length; ++i)
            for(int j = 0;j < grid[0].length; ++j) {
             if (grid[i][j]) {
                if (i > 0 && grid[i - 1][j]) {
                    union_find.connect(i * m + j, (i - 1) * m + j);
                }
                if (i  0 && grid[i][j - 1]) {
                    union_find.connect(i * m + j, i * m + j - 1);
                }
                if (j < m - 1 && grid[i][j + 1]) {
                    union_find.connect(i * m + j, i * m + j + 1);
                }
            }
        return union_find.query();
    }
}

此道题相似于把大象装冰箱, 一共分三步:

  1. 初始化
  2. 检查双下左右四个方向
  3. 合并岛屿减count
    时间复杂度为: O(mN + 4k), 其中k为操做的次数, 小于bfs/dfs的O(mN*k)

Trie树

cs3k.com

Implement Trie

enter image description here

Trie树的三步走:

  1. 创建根的须 char
  2. 须实体化 hashmap
  3. 记录结尾 hasWord

C++的implementation:

  1. 装trie的指针的数组 Trie *children[26]
  2. 记录结尾 is_end
  3. 构造函数复制1和2

snipaste_20171122_121717

 

class TrieNode {
    // Initialize your data structure here.
    char c;
    HashMap children = new HashMap();
    boolean hasWord;
    
    public TrieNode(){
        
    }
    
    public TrieNode(char c){
        this.c = c;
    }
}

public class Trie {
    private TrieNode root;

    public Trie() {
        root = new TrieNode();
    }

    // Inserts a word into the trie.
    public void insert(String word) {
        TrieNode cur = root;
        HashMap curChildren = root.children;
        char[] wordArray = word.toCharArray();
        for(int i = 0; i < wordArray.length; i++){
            char wc = wordArray[i];
            if(curChildren.containsKey(wc)){
                cur = curChildren.get(wc);
            } else {
                TrieNode newNode = new TrieNode(wc);
                curChildren.put(wc, newNode);
                cur = newNode;
            }
            curChildren = cur.children;
            if(i == wordArray.length - 1){
                cur.hasWord= true;
            }
        }
    }

    // Returns if the word is in the trie.
    public boolean search(String word) {
        if(searchWordNodePos(word) == null){
            return false;
        } else if(searchWordNodePos(word).hasWord) 
          return true;
          else return false;
    }

    // Returns if there is any word in the trie
    // that starts with the given prefix.
    public boolean startsWith(String prefix) {
        if(searchWordNodePos(prefix) == null){
            return false;
        } else return true;
    }
    
    public TrieNode searchWordNodePos(String s){
        HashMap children = root.children;
        TrieNode cur = null;
        char[] sArray = s.toCharArray();
        for(int i = 0; i < sArray.length; i++){
            char c = sArray[i];
            if(children.containsKey(c)){
                cur = children.get(c);
                children = cur.children;
            } else{
                return null;
            }
        }
        return cur;
    }
}

Hash VS Trie

O(1)不是表明一个字符, O(1)表明整个字符串

Hash                  Trie
构建      O(n)                   O(n)
查询      O(1)                   O(1)

对于 a, aa, aaa, aaaa的状况

Hash                  Trie
存储     10个a                 5个a节点
可用操做  有/无查询             有/无/前缀查询
代码量   1行                   75~100行

因此选择hash缘由是代码量小, 可是涉及到前缀查询的时候, 考虑trie树

Add and Search Word

cs3k.com

Design a data structure that supports the following two operations: addWord(word) and search(word)

search(word) can search a literal word or a regular expression string containing only letters a-z or …

A . means it can represent any one letter.

若是用dfs在矩阵里面一个一个字符遍历, 时间复杂度至少为O(mn * mn).
dfs+trie树就只须要遍历在trie树里面有前缀的, 因此会快
注意不能用.做为trie树的第27叉.

class TrieNode {

    public TrieNode[] children;
    public boolean hasWord;
    
    public TrieNode() {
        children = new TrieNode[26];
        for (int i = 0; i < 26; ++i)
            children[i] = null;
        hasWord = false;
    }
}


public class WordDictionary {
    private TrieNode root;
 
    public WordDictionary(){
        root = new TrieNode();
    }
 
    // Adds a word into the data structure.
    public void addWord(String word) {
        // Write your code here
        TrieNode now = root;
        for(int i = 0; i < word.length(); i++) {
            Character c = word.charAt(i);
            if (now.children[c - 'a'] == null) {
                now.children[c - 'a'] = new TrieNode();
            }
            now = now.children[c - 'a'];
        }
        now.hasWord = true;
    }
    
    boolean find(String word, int index, TrieNode now) {
        if(index == word.length()) {
            return now.hasWord;
        }
        
        Character c = word.charAt(index);
        if (c == '.') {
            for(int i = 0; i < 26; ++i) 
            if (now.children[i] != null) {
                if (find(word, index+1, now.children[i]))
                    return true;
            }
            return false;
        } else if (now.children[c - 'a'] != null) {
            return find(word, index+1, now.children[c - 'a']);  
        } else {
            return false;
        }
    }
    
    // Returns if the word is in the data structure. A word could
    // contain the dot character '.' to represent any one letter.
    public boolean search(String word) {
        // Write your code here
        return find(word, 0, root);
    }
}

c++的实现:

snipaste_20171122_122729snipaste_20171122_122750

Word Search II

Given a matrix of lower alphabets and a dictionary. Find all words in the dictionary that can be found in the matrix. A word can start from any position in the matrix and go left/right/up/down to the adjacent position.

三步走:

  1. 字典建成trie树
  2. 用dfs的方法遍历矩阵,同时在Trie上搜索前缀是否存在
  3. 查询全部Trie里面有可能出现的字符

注意:

  1. trie树创建要选空间占地面积小的字典, 而不是矩阵, 由于若是用矩阵的话, 还要dfs遍历矩阵, 太麻烦
  2. 要有一个查询走没走过的board[ ][ ]
 Notice
  • There are at least 1 and at most 1000 words.
  • All words will have the exact same length.
  • Word length is at least 1 and at most 5.
  • Each word contains only lowercase English alphabet a-z.

能够问面试官的clarify的问题:

  1. size
  2. length
  3. lowercase / uppercase
public class Solution {
    /**
     * @param board: A list of lists of character
     * @param words: A list of string
     * @return: A list of string
     */
         
    class TrieNode {
        String s;
         boolean isString;
         HashMap subtree;
         public TrieNode() {
            // TODO Auto-generated constructor stub
             isString = false;
             subtree = new HashMap();
             s = "";
         }
    };


    class TrieTree{
        TrieNode root ;
        public TrieTree(TrieNode TrieNode) {
            root = TrieNode;
        }
        public void insert(String s) {
            TrieNode now = root;
            for (int i = 0; i < s.length(); i++) {
                if (!now.subtree.containsKey(s.charAt(i))) {
                    now.subtree.put(s.charAt(i), new TrieNode());
                }
                now  =  now.subtree.get(s.charAt(i));
            }
            now.s = s;
            now.isString  = true;
        }
        public boolean find(String s){
            TrieNode now = root;
            for (int i = 0; i < s.length(); i++) {
                if (!now.subtree.containsKey(s.charAt(i))) {
                    return false;
                }
                now  =  now.subtree.get(s.charAt(i));
            }
            return now.isString ;
        }
    };

    public int []dx = {1, 0, -1, 0};
    public int []dy = {0, 1, 0, -1};
    
    public void search(char[][] board, int x, int y, TrieNode root, List ans) {    
        if(root.isString == true)
        {
            // -------这行很重要 由于每一个结束都是靠它下面一层递归加string进结果
            // 而它下面的递归可能进去不少次,而且dad这种palindrome原本也会重复-----------------------
            if(!ans.contains(root.s)){
                ans.add(root.s);
            }
        }
        if(x = board.length || y = board[0].length || board[x][y]==0 || root == null)
            return ;
        if(root.subtree.containsKey(board[x][y])){
            for(int i = 0; i < 4; i++){
                char now = board[x][y];
                board[x][y] = 0;
                search(board, x+dx[i], y+dy[i], root.subtree.get(now), ans);
                board[x][y] = now;
            }
        }
        
    }
    
    public List wordSearchII(char[][] board, List words) {
        List ans = new ArrayList();
        
        TrieTree tree = new TrieTree(new TrieNode());
        for(String word : words){
            tree.insert(word);
        }
        for(int i = 0; i < board.length; i++){
            for(int j = 0; j < board[i].length; j++){
                search(board, i, j, tree.root, ans);
            }
        }
        return ans;
        // write your code here
        
    }
    
}

Word Squares

cs3k.com

Given a set of words without duplicates, find all word squares you can build from them.

A sequence of words forms a valid word square if the kth row and column read the exact same string, where 0 ≤ k < max(numRows, numColumns).

For example, the word sequence [“ball”,“area”,“lead”,“lady”] forms a word square because each word reads the same both horizontally and vertically.

b a l l
a r e a
l e a d
l a d y

假设单词平均长度为m, 单词个数为n, 用简单的dfs咱们须要的时间复杂度是A(m*n), A表明排列.可是不要气馁, 写程序不是一步登天, 咱们先想蠢办法, 而后一步一步优化. 杨过的路子很差走,我们学郭靖, 一点点来.

由于有前缀相关的, 因此想到trie树, 总共主要有两步骤:

  1. 找前缀
  2. 验证可否放进去
public class Solution {
     class TrieNode {
        List startWith;
        TrieNode[] children;

        TrieNode() {
            startWith = new ArrayList();
            children = new TrieNode[26];
        }
    }

    class Trie {
        TrieNode root;

        Trie(String[] words) {
            root = new TrieNode();
            for (String w : words) {
                TrieNode cur = root;
                for (char ch : w.toCharArray()) {
                    int idx = ch - 'a';
                    if (cur.children[idx] == null)
                        cur.children[idx] = new TrieNode();
                    cur.children[idx].startWith.add(w);
                    cur = cur.children[idx];
                }
            }
        }

        List findByPrefix(String prefix) {
            List ans = new ArrayList();
            TrieNode cur = root;
            for (char ch : prefix.toCharArray()) {
                int idx = ch - 'a';
                if (cur.children[idx] == null)
                    return ans;

                cur = cur.children[idx];
            }
            ans.addAll(cur.startWith);
            return ans;
        }
    }

    public List wordSquares(String[] words) {
        List ans = new ArrayList();
        if (words == null || words.length == 0)
            return ans;
        int len = words[0].length();
        Trie trie = new Trie(words);
        List ansBuilder = new ArrayList();
        for (String w : words) {
            ansBuilder.add(w);
            search(len, trie, ans, ansBuilder);
            ansBuilder.remove(ansBuilder.size() - 1);
        }

        return ans;
    }

    private void search(int len, Trie tr, List ans,
            List ansBuilder) {
        if (ansBuilder.size() == len) {
            ans.add(new ArrayList(ansBuilder));
            return;
        }

        int idx = ansBuilder.size();
        StringBuilder prefixBuilder = new StringBuilder();
        for (String s : ansBuilder)
            prefixBuilder.append(s.charAt(idx));
        List startWith = tr.findByPrefix(prefixBuilder.toString());
        for (String sw : startWith) {
            ansBuilder.add(sw);
            search(len, tr, ans, ansBuilder);
            ansBuilder.remove(ansBuilder.size() - 1);
        }
    }
}

这个在搜索type ahead上面常用到.

Segment Tree

cs3k.com

线段树是一个二叉树结构, 它能作全部heap能作的操做,而且能够logn时间查找到某个区间的最大和最小值

Heap        Segment Tree
        push    O(logn)        O(logn)
        pop     O(logn)        O(logn)
        top     O(1)           O(1)
        modify   X             O(logn)

线段树的三个操做

  1. Query
  2. Build
  3. Modify

线段树的性质:

1. 二叉树
2. Parent 区间被平分
  Leftson = 左区间
  Rightson = 右区间
  叶子节点不可分
3. 每一个节点表示区间(叶子节点表明一个元素)

Segment Tree Query

cs3k.com

For an integer array (index from 0 to n-1, where n is the size of this array), in the corresponding SegmentTree, each node stores an extra attribute max to denote the maximum number in the interval of the array (index from start to end).

Design a query method with three parameters root, start and end, find the maximum number in the interval [start, end] by the given root of segment tree.

Notice

It is much easier to understand this problem if you finished Segment Tree Build first.
Example
For array [1, 4, 2, 3], the corresponding Segment Tree is:

[0, 3, max=4]
             /             \
      [0,1,max=4]        [2,3,max=3]
      /         \        /         \
[0,0,max=1] [1,1,max=4] [2,2,max=2], [3,3,max=3]

query(root, 1, 1), return 4

query(root, 1, 2), return 4

query(root, 2, 3), return 3

query(root, 0, 2), return 4

节点区间和要查找区间的关系分四种状况:
1.节点区间包含查找区间—>查找区间递归向下
2.节点区间不相交于查找区间 ->查找区间中止搜索
3.节点区间相交不包含于查找区间->查找区间分裂成两段区间,一段于被节点区间包含,另外一段不相交
4. 节点区间相等于查找区间—> 返回值查找的结果

查询最多LOGN层,时间复杂度为O(LOGN)

cs3k.com

public class Solution {
    /**
     *@param root, start, end: The root of segment tree and 
     *                         an segment / interval
     *@return: The maximum number in the interval [start, end]
     */
    public int query(SegmentTreeNode root, int start, int end) {
        // write your code here
        if(start == root.start && root.end == end) { // 相等 
            return root.max;
        }
        
        
        int mid = (root.start + root.end)/2;
        int leftmax = Integer.MIN_VALUE, rightmax = Integer.MIN_VALUE;
        // 左子区
        if(start <= mid) {
            if( mid < end) { // 分裂 
                leftmax =  query(root.left, start, mid);
            } else { // 包含 
                leftmax = query(root.left, start, end);
            }
            // leftmax = query(root.left, start, Math.min(mid,end));
        }
        // 右子区
        if(mid < end) { // 分裂 3
            if(start <= mid) {
                rightmax = query(root.right, mid+1, end);
            } else { //  包含 
                rightmax = query(root.right, start, end);
            }
            //rightmax = query(root.right, Math.max(mid+1,start), end);
        }  
        // else 就是不相交
        return Math.max(leftmax, rightmax);
    }
}

Segment Tree Build

cs3k.com

The structure of Segment Tree is a binary tree which each node has two attributes start and end denote an segment / interval.

start and end are both integers, they should be assigned in following rules:

The root’s start and end is given by build method.
The left child of node A has start=A.left, end=(A.left + A.right) / 2.
The right child of node A has start=(A.left + A.right) / 2 + 1, end=A.right.
if start equals to end, there will be no children for this node.
Implement a build method with two parameters start and end, so that we can create a corresponding segment tree with every node has the correct start and end value, return the root of this segment tree.

Clarification
Segment Tree (a.k.a Interval Tree) is an advanced data structure which can support queries like:

which of these intervals contain a given point
which of these points are in a given interval
See wiki:
Segment Tree
Interval Tree

Example
Given start=0, end=3. The segment tree will be:

[0,  3]
         /        \
      [0,  1]     [2, 3]
      /    \       /    \
  [0, 0]  [1, 1] [2, 2] [3, 3]

Given start=1, end=6. The segment tree will be:

[1,  6]
         /        \
     [1,  3]      [4,  6]
     /     \      /     \
  [1, 2]   [3,3]  [4, 5]   [6,6]
   /  \           /   \
[1,1]  [2,2]    [4,4] [5,5]

口诀:

自上而下递归分裂
自下而上回溯更新

时间复杂度为O(N):

一共logn层, 每层的元素个数是分别是
[1, 2, 4, 8…n]

O   = 1+2+4+...+n
        = (2^(logn+1) -1)/ (1 - 2)
        = 2n
        = n
public class Solution {
    /**
     *@param start, end: Denote an segment / interval
     *@return: The root of Segment Tree
     */
    public SegmentTreeNode build(int start, int end) {
        // write your code here
        if(start > end) {  // check core case
            return null;
        }
       
        SegmentTreeNode root = new SegmentTreeNode(start, end);
       
        if(start != end) {
            int mid = (start + end) / 2;
            root.left = build(start, mid);
            root.right = build(mid+1, end);
           
            // root.max = Math.max(root.left.max, root.right.max);
        }
        return root;
    }
}

Segment Tree Build II

The structure of Segment Tree is a binary tree which each node has two attributes start and end denote an segment / interval.

start and end are both integers, they should be assigned in following rules:

The root’s start and end is given by build method.
The left child of node A has start=A.left, end=(A.left + A.right) / 2.
The right child of node A has start=(A.left + A.right) / 2 + 1, end=A.right.
if start equals to end, there will be no children for this node.
Implement a build method with a given array, so that we can create a corresponding segment tree with every node value represent the corresponding interval max value in the array, return the root of this segment tree.
Clarification
Segment Tree (a.k.a Interval Tree) is an advanced data structure which can support queries like:

which of these intervals contain a given point
which of these points are in a given interval
See wiki:
Segment Tree
Interval Tree

Example
Given [3,2,1,4]. The segment tree will be:

[0,  3] (max = 4)
                       /              \
         [0,  1] (max = 3)         [2, 3]  (max = 4)
           /        \                 /             \
[0, 0](max = 3)  [1, 1](max = 2)  [2, 2](max = 1) [3, 3] (max = 4)
public class Solution {
    /**
     *@param A: a list of integer
     *@return: The root of Segment Tree
     */
    public SegmentTreeNode build(int[] A) {
        // write your code here
        return buildTree(0, A.length - 1, A);
    }

    public SegmentTreeNode buildTree(int start, int end, int[] A) {
        if (start > end)
            return null;

        if (start == end) {
            return new SegmentTreeNode(start, end, A[start]);
        }
        SegmentTreeNode node = new SegmentTreeNode(start, end, A[start]);
        int mid = (start + end) / 2;
        node.left = this.buildTree(start, mid, A);
        node.right = this.buildTree(mid + 1, end, A);
        if (node.left != null && node.left.max > node.max)
            node.max = node.left.max;
        if (node.right != null && node.right.max > node.max)
            node.max = node.right.max;
        return node;
    }
}

c++ implementation:

/** * Definition of SegmentTreeNode: * class SegmentTreeNode { * public: * int start, end, max; * SegmentTreeNode *left, *right; * SegmentTreeNode(int start, int end, int max) { * this->start = start; * this->end = end; * this->max = max; * this->left = this->right = NULL; * } * } */ class Solution { public: /** *@param A: a list of integer *@return: The root of Segment Tree */ SegmentTreeNode * build(vector<int>& A) { // write your code here return buildTree(0, A.size()-1, A); } SegmentTreeNode * buildTree(int start, int end, vector<int>& A) { if (start > end) return NULL; SegmentTreeNode * node = new SegmentTreeNode(start, end, A[start]); if (start == end) return node; int mid = (start + end) / 2; node->left = buildTree(start, mid, A); node->right = buildTree(mid+1, end, A); if (node->left && node->left->max > node->max) node->max = node->left->max; if (node->right && node->right->max > node->max) node->max = node->right->max; return node; } };

Segment Tree Modify

For a Maximum Segment Tree, which each node has an extra value max to store the maximum value in this node’s interval.

Implement a modify function with three parameter root, index and value to change the node’s value with [start, end] = [index, index] to the new given value. Make sure after this change, every node in segment tree still has the max attribute with the correct value.

Notice

We suggest you finish problem Segment Tree Build and Segment Tree Query first.

Have you met this question in a real interview? Yes
Example
For segment tree:

[1, 4, max=3]
                /                \
    [1, 2, max=2]                [3, 4, max=3]
   /              \             /             \
[1, 1, max=2], [2, 2, max=1], [3, 3, max=0], [4, 4, max=3]

if call modify(root, 2, 4), we can get:

[1, 4, max=4]
                /                \
    [1, 2, max=4]                [3, 4, max=3]
   /              \             /             \
[1, 1, max=2], [2, 2, max=4], [3, 3, max=0], [4, 4, max=3]

or call modify(root, 4, 0), we can get:

[1, 4, max=2]
                /                \
    [1, 2, max=2]                [3, 4, max=0]
   /              \             /             \
[1, 1, max=2], [2, 2, max=1], [3, 3, max=0], [4, 4, max=0]

注意

1. heap没法对元素进行修改;hash heap和平衡二叉树能够
2. modify最多查找logn层,时间复杂度是logn
3. 口诀:自上而下递归查找
        自下而上回溯更新
4. 以数组下标来创建线段树
public class Solution {
    /**
     *@param root, index, value: The root of segment tree and 
     *@ change the node's value with [index, index] to the new given value
     *@return: void
     */
    public void modify(SegmentTreeNode root, int index, int value) {
        // write your code here
        if(root.start == index && root.end == index) { // 查找到
            root.max = value;
            return;
        }
        
        // 查询
        int mid = (root.start + root.end) / 2;
        if(root.start <= index && index <=mid) {
            modify(root.left, index, value);
        }
        
        if(mid < index && index <= root.end) {
            modify(root.right, index, value);
        }
        //更新
        root.max = Math.max(root.left.max, root.right.max);
    }
}

Interval Minimum Number

Given an integer array (index from 0 to n-1, where n is the size of this array), and an query list. Each query has two integers [start, end]. For each query, calculate the minimum number between index start and end in the given array, return the result list.

Notice

We suggest you finish problem Segment Tree Build, Segment Tree Query and Segment Tree Modify first.

Have you met this question in a real interview? Yes
Example
For array [1,2,7,8,5], and queries [(1,2),(0,4),(2,4)], return [2,1,5]

class SegmentTreeNode {
    public int start, end, min;
    public SegmentTreeNode left, right;
    public SegmentTreeNode(int start, int end, int min) {
          this.start = start;
          this.end = end;
          this.min = min;
          this.left = this.right = null;
    }
}
public class Solution {
    /**
     *@param A, queries: Given an integer array and an query list
     *@return: The result list
     */
    public SegmentTreeNode build(int start, int end, int[] A) {
        // write your code here
        if(start > end) {  // check core case
            return null;
        }
        
        SegmentTreeNode root = new SegmentTreeNode(start, end, Integer.MAX_VALUE);
        
        if(start != end) {
            int mid = (start + end) / 2;
            root.left = build(start, mid, A);
            root.right = build(mid+1, end, A);
            
            root.min = Math.min(root.left.min, root.right.min);
        } else {
            root.min = A[start];
        }
        return root;
    }
    public int query(SegmentTreeNode root, int start, int end) {
        // write your code here
        if(start == root.start && root.end == end) { // 相等 
            return root.min;
        }
        
        
        int mid = (root.start + root.end)/2;
        int leftmin = Integer.MAX_VALUE, rightmin = Integer.MAX_VALUE;
        // 左子区
        if(start <= mid) {
            if( mid < end) { // 分裂 
                leftmin =  query(root.left, start, mid);
            } else { // 包含 
                leftmin = query(root.left, start, end);
            }
        }
        // 右子区
        if(mid < end) { // 分裂 3
            if(start <= mid) {
                rightmin = query(root.right, mid+1, end);
            } else { //  包含 
                rightmin = query(root.right, start, end);
            } 
        }  
        // else 就是不相交
        return Math.min(leftmin, rightmin);
    }
    
    public ArrayList<Integer> intervalMinNumber(int[] A, 
                                                ArrayList<Interval> queries) {
        // write your code here
        SegmentTreeNode root = build(0, A.length - 1, A);
        ArrayList ans = new ArrayList<Integer>();
        for(Interval in : queries) {
            ans.add(query(root, in.start, in.end));
        }
        return ans;
    }
}

线段树小总结

线段树的三大操做

1.Query    O(log(n))
2.Build    O(n)
3.Modify   O(log(n))

线段树的应用:

1. Sum
2. Maximum/ Minimum
3. Count

构建形式:

cs3k.com

1. 下标做为创建区间
2. 值做为创建区间

本节小结:

  1. union find (union O(1)  find O(1)):用来查询两个元素在不在一个集合,合并两个元素所在集合,某个元素所在集合元素个数,当前集合个数。dfs/bfs的局部操做可用union find
  2. trie (build O(n)   searchO(1) 1表明整个字符串): 三要素:须,is_end, constructor。有前缀的时候想trie树
  3. segment tree (push, pop和heap不支持的modify, query都是O(logn), top O(1), build O(n)): 五要素:start, end , min, *left, *right。区间极值
相关文章
相关标签/搜索