• 九章算法强化


    第一节:follow up问题

    1.两数之和 II

    --给一组整数,问能找出多少对整数,他们的和大于一个给定的目标值。

    解析:与two sum类似,先排序,再两指针。

     1 class Solution {
     2 public:
     3     /**
     4      * @param nums: an array of integer
     5      * @param target: an integer
     6      * @return: an integer
     7      */
     8     int twoSum2(vector<int> &nums, int target) {
     9         // Write your code here
    10         sort(nums.begin(), nums.end());
    11         int left = 0; 
    12         int right = nums.size() - 1;
    13         int ans = 0;
    14         while (left < right)
    15         {
    16             if (nums[left] + nums[right] > target)
    17             {
    18                 ans += right - left;
    19                 right--;
    20             } else 
    21             {
    22                 left++;
    23             }
    24         }
    25         return ans;
    26     }
    27 };
    View Code

    2.三角形计数

    --给定一个整数数组,在该数组中,寻找三个数,分别代表三角形三条边的长度,问,可以寻找到多少组这样的三个数来组成三角形?

    解析:two sumII的follow up ,卡在怎么同时证明三个数任意两个之和大于第三个数,其实数组排序了之后,只要证明两个小的数之和大于大的数就可以了。

     1 class Solution {
     2 public:
     3     /**
     4      * @param S: A list of integers
     5      * @return: An integer
     6      */
     7     int triangleCount(vector<int> &S) {
     8         // write your code here
     9         sort(S.begin(), S.end());
    10         int ans = 0;
    11         for (int i = 2; i < S.size(); i++) 
    12         {
    13             int left = 0;
    14             int right = i - 1;
    15             while (left < right)
    16             {
    17                 if (S[left] + S[right] > S[i])
    18                 {
    19                     ans += right - left;
    20                     right--;
    21                 } else
    22                 {
    23                     left++;
    24                 }
    25             }
    26         }
    27         return ans;
    28     }
    29 };
    View Code

    3.排序矩阵中的从小到大第k个数

    --在一个排序矩阵中找从小到大的第 k 个整数。排序矩阵的定义为:每一行递增,每一列也递增。

    解析:优先级队列往右和下两个方向遍历,记住两个方向遍历的小技巧,还有上下左右四个方向遍历的技巧,int posX = {0,1,0,-1};int posY = {-1,0,1,0};

     1 public class Solution {
     2     /**
     3      * @param matrix: a matrix of integers
     4      * @param k: an integer
     5      * @return: the kth smallest number in the matrix
     6      */
     7     public int kthSmallest(int[][] matrix, int k) {
     8         // write your code here
     9         if (matrix == null || k < 1) {
    10             return 0;
    11         }
    12         int m = matrix.length;
    13         int n = matrix[0].length;
    14         if (m == 0 || n == 0) {
    15             return 0;
    16         }
    17         boolean[][] visited = new boolean[m][n];
    18         PriorityQueue<Element> pq = new PriorityQueue<>(k, new MyComparator());
    19         pq.offer(new Element(0, 0, matrix[0][0]));
    20         visited[0][0] = true;
    21         //向右和向下两个方向遍历
    22         int[] posX = {0,1};
    23         int[] posY = {1,0};
    24         
    25         for (int i = 0; i < k - 1; i++) {
    26             Element cur = pq.poll();
    27             for (int j = 0; j < 2; j++) {
    28                 int x_next = cur.x + posX[j];
    29                 int y_next = cur.y + posY[j];
    30                 if (x_next < m && y_next < n && !visited[x_next][y_next]) {
    31                     pq.offer(new Element(x_next, y_next, matrix[x_next][y_next]));
    32                     visited[x_next][y_next] = true;
    33                 }
    34             }
    35         }
    36         return pq.poll().val;
    37     }
    38     public class Element {
    39         int x = 0;
    40         int y = 0;
    41         int val = 0;
    42         public Element(int x, int y, int val) {
    43             this.x = x;
    44             this.y = y;
    45             this.val = val;
    46         }
    47     }
    48     public class MyComparator implements Comparator<Element> {
    49         public int compare(Element e1, Element e2) {
    50             return e1.val - e2.val;
    51         }
    52     }
    53 }
    View Code

    4.排序矩阵中的从小到大第k个数follow up ---两个排序数组和的第k小

    --给定两个排好序的数组 AB,定义集合 sum = a + b ,求 sum 中第k小的元素。

    解析:这题跟第3题其实是一样的,给出 A = [1,7,11] B = [2,4,6],可以得出和的表格

    AB 2 4 6
    1 3 5 7
    7 9 11 13
    11 13 16 17

     

     

     

     

    其实和的矩阵就是第三题的行列递增的矩阵,所以解法与第三题是相同的。

    第二节-- 高级数据结构

    1.并查集

    并查集的两个基本操作,查询与合并。

    查询--找到这个节点的最终父节点,使用带压缩路径的递归,可以做到O(1)平均时间复杂度。

    合并--合并两个集合,方法是将两个集合的根结点连接起来,O(1)平均时间复杂度。

    并查集原生题

    a.connecting graph 

    题意:

    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:
    1. connect(a, b), add an edge to connect node a and node b. 
    2.query(a, b)`, check if two nodes are connected
    
    
    样例
    5 // n = 5
    query(1, 2) return false
    connect(1, 2)
    query(1, 3) return false
    connect(2, 4)
    query(1, 4) return true
    View Code

    解法:

     1 public class ConnectingGraph { 
     2     int[] father;
     3     public ConnectingGraph(int n) {
     4         // initialize your data structure here.
     5         father = new int[n + 1];
     6         for (int i = 1; i <= n; i++) {
     7             father[i] = i;
     8         }
     9     }
    10 
    11     public void connect(int a, int b) {
    12         // Write your code here
    13         int fa = find(a);
    14         int fb = find(b);
    15         if (fa != fb) {
    16             father[fa] = fb;
    17         }
    18     }
    19         
    20     public boolean  query(int a, int b) {
    21         // Write your code here
    22         if (find(a) == find(b)) {
    23             return true;
    24         }
    25         return false;
    26     }
    27     public int find(int a) {
    28         // Write your code here
    29         if (father[a] == a) {
    30             return a;
    31         }
    32         return father[a] = find(father[a]);
    33     }
    34 }
    View Code

    b.connecting graph II

    题意:

    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:
    1. connect(a, b), an edge to connect node a and node b
    2. query(a), Returns the number of connected component nodes which include node a.
    
    样例
    5 // n = 5
    query(1) return 1
    connect(1, 2)
    query(1) return 2
    connect(2, 4)
    query(1) return 3
    connect(1, 4)
    query(1) return 3
    View Code

    解法:

     1 public class ConnectingGraph2 {
     2 
     3     int[] father;
     4     int[] size;
     5     public ConnectingGraph2(int n) {
     6         // initialize your data structure here.
     7         father = new int[n + 1];
     8         size = new int[n + 1];
     9         for (int i = 1; i <= n; i++) {
    10             father[i] = i;
    11             size[i] = 1;
    12         }
    13     }
    14 
    15     public void connect(int a, int b) {
    16         // Write your code here
    17         int fa = find(a);
    18         int fb = find(b);
    19         if (fa != fb) {
    20             father[fa] = fb;
    21             size[fb] += size[fa];
    22         }
    23     }
    24         
    25     public int query(int a) {
    26         // Write your code here
    27         return size[find(a)];
    28     }
    29     public int find(int a) {
    30         if (father[a] == a) {
    31             return a;
    32         }
    33         return father[a] = find(father[a]);
    34     }
    35 }
    View Code

    c.connecting graph III

    题意:

    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:
    1. connect(a, b), an edge to connect node a and node b
    2. query(), Returns the number of connected component in the graph
    
    样例
    5 // n = 5
    query() return 5
    connect(1, 2)
    query() return 4
    connect(2, 4)
    query() return 3
    connect(1, 4)
    query() return 3
    View Code

    解法:

     1 public class ConnectingGraph3 {
     2 
     3     int[] father;
     4     int count;
     5     public ConnectingGraph3(int n) {
     6         father = new int[n + 1];
     7         count = n;
     8         for (int i = 1; i <= n; i++) {
     9             father[i] = i;
    10         }
    11     }
    12 
    13     public void connect(int a, int b) {
    14         int root_a = find(a);
    15         int root_b = find(b);
    16         if (root_a != root_b) {
    17             father[root_a] = root_b;
    18             count--;
    19         }
    20     }
    21         
    22     public int query() {
    23         return count;
    24     }
    25     public int find(int x) {
    26         if (father[x] == x) {
    27             return x;
    28         }
    29         return father[x] = find(father[x]);
    30     }
    31 }
    View Code

     衍生题--Number of Islands II

    题意:

    给定 n,m,分别代表一个2D矩阵的行数和列数,同时,给定一个大小为 k 的二元数组A。起初,2D矩阵的行数和列数均为 0,即该矩阵中只有海洋。二元数组有 k 个运算符,每个运算符有 2 个整数 A[i].x, A[i].y,你可通过改变矩阵网格中的[A[i].x,[A[i].y] 来将其由海洋改为岛屿。请在每次运算后,返回矩阵中岛屿的数量。
    
     注意事项
    0 代表海,1 代表岛。如果两个1相邻,那么这两个1属于同一个岛。我们只考虑上下左右为相邻。
    
    样例
    给定 n = 3, m = 3, 二元数组 A = [(0,0),(0,1),(2,2),(2,1)].
    
    返回 [1,1,2,2].
    View Code

    解法:其实就是求集合的个数,然后在新的岛屿生成后,将上下左右的集合合并就好。

     1 /**
     2  * Definition for a point.
     3  * class Point {
     4  *     int x;
     5  *     int y;
     6  *     Point() { x = 0; y = 0; }
     7  *     Point(int a, int b) { x = a; y = b; }
     8  * }
     9  */
    10 public class Solution {
    11     /**
    12      * @param n an integer
    13      * @param m an integer
    14      * @param operators an array of point
    15      * @return an integer array
    16      */
    17     private int[] father;
    18     public List<Integer> numIslands2(int n, int m, Point[] operators) {
    19         // Write your code here
    20         if (n == 0 || m == 0 || operators == null || operators.length == 0) {
    21             return new ArrayList<>();
    22         }
    23         father = new int[n * m];
    24         for (int i = 0; i < n * m; i++) {
    25             father[i] = -1;
    26         }
    27         int num = 0;
    28         int len = operators.length;
    29         int[] posX = {0, 1, -1, 0};
    30         int[] posY = {1, 0, 0, -1};
    31         List<Integer> ans = new ArrayList<>();
    32         for (int i = 0; i < len; i++) {
    33             int x = operators[i].x;
    34             int y = operators[i].y;
    35             if (father[x * m + y] != -1) {
    36                 ans.add(num);
    37                 continue;
    38             }
    39             father[x * m + y] = x * m + y;
    40             num++;
    41             for (int j = 0; j < 4; j++) {
    42                 int nX = x + posX[j];
    43                 int nY = y + posY[j];
    44                 if (nX >= 0 && nX < n && nY >= 0 && nY < m && father[nX * m + nY] != -1) {
    45                     num = union(x * m + y, nX * m + nY, num);
    46                 }
    47             }
    48             ans.add(num);
    49         }
    50         return ans;
    51     }
    52     public int find(int a) {
    53         if (father[a] == a) {
    54             return a;
    55         }
    56         return father[a] = find(father[a]);
    57     }
    58     public int union(int a, int b, int num) {
    59         int fa = find(a);
    60         int fb = find(b);
    61         if (fa != fb) {
    62             father[fa] = fb;
    63             num--;
    64         }
    65         return num;
    66     }
    67 }
    View Code

     判断图是否是树

    题意:

    给出 n 个节点,标号分别从 0 到 n - 1 并且给出一个 无向 边的列表 (给出每条边的两个顶点), 写一个函数去判断这张`无向`图是否是一棵树
    
     注意事项
    
    你可以假设我们不会给出重复的边在边的列表当中. 无向边 [0, 1] 和 [1, 0] 是同一条边, 因此他们不会同时出现在我们给你的边的列表当中。
    
    
    样例
    给出n = 5 并且 edges = [[0, 1], [0, 2], [0, 3], [1, 4]], 返回 true.
    
    给出n = 5 并且 edges = [[0, 1], [1, 2], [2, 3], [1, 3], [1, 4]], 返回 false.
    View Code

    --图是连通图,并且不存在环。那么这个图是树

    解法:并查集。并查集用来判断是否存在环,同时记下连通块的个数,最后只剩一个连通块并且没有环,则就是树。

     1 public class Solution {
     2     /**
     3      * @param n an integer
     4      * @param edges a list of undirected edges
     5      * @return true if it's a valid tree, or false
     6      */
     7     int[] father = null;
     8     public boolean validTree(int n, int[][] edges) {
     9         // Write your code here
    10         if (edges == null || n == 0) {
    11             return false;
    12         }
    13         father = new int[n];
    14         for (int i = 0; i < n; i++) {
    15             father[i] = i;
    16         }
    17         int count = n;
    18         for (int i = 0; i < edges.length; i++) {
    19             int fa = find(edges[i][0]);
    20             int fb = find(edges[i][1]);
    21             //fa == fb存在环
    22             if (fa == fb) {
    23                 return false;
    24             }
    25             father[fa] = fb;
    26             count--;
    27         }
    28         //count==1说明是连通
    29         return count == 1;
    30     }
    31     public int find(int x) {
    32         if (father[x] == x) {
    33             return x;
    34         }
    35         return father[x] = find(father[x]);
    36     }
    37 }
    View Code

    2.trie tree

    1.Implement trie tree

    --实现trie tree

    解法:这里用hashmap+非递归的方法,其他实现见http://www.cnblogs.com/fisherinbox/p/6073183.html

     1 /**
     2  * Your Trie object will be instantiated and called as such:
     3  * Trie trie = new Trie();
     4  * trie.insert("lintcode");
     5  * trie.search("lint"); will return false
     6  * trie.startsWith("lint"); will return true
     7  */
     8 class TrieNode {
     9     // Initialize your data structure here.
    10     HashMap<Character,TrieNode> children = null; 
    11     boolean wordEnd;
    12     public TrieNode() {
    13         children = new HashMap<>();
    14         wordEnd = false;
    15     }
    16 }
    17 
    18 public class Trie {
    19     private TrieNode root;
    20 
    21     public Trie() {
    22         root = new TrieNode();
    23     }
    24 
    25     // Inserts a word into the trie.
    26     public void insert(String word) {
    27         if (word == null || word.length() == 0) {
    28             return;
    29         }
    30         TrieNode cur = root;
    31         for (int i = 0; i < word.length(); i++) {
    32             TrieNode next = null;
    33             if (cur.children.containsKey(word.charAt(i))) {
    34                 next = cur.children.get(word.charAt(i));
    35             } else {
    36                 next = new TrieNode();
    37                 cur.children.put(word.charAt(i), next);
    38             }
    39             if (i == word.length() - 1) {
    40                 next.wordEnd = true;
    41             }
    42             cur = next;
    43         }
    44     }
    45 
    46     // Returns if the word is in the trie.
    47     public boolean search(String word) {
    48         if (word == null || word.length() == 0) {
    49             return false;
    50         }
    51         TrieNode cur = root;
    52         for (int i = 0; i < word.length(); i++) {
    53             if (!cur.children.containsKey(word.charAt(i))) {
    54                 return false;
    55             }
    56             if (i == word.length() - 1 && cur.children.get(word.charAt(i)).wordEnd) {
    57                 return true;
    58             }
    59             cur = cur.children.get(word.charAt(i));
    60         }
    61         return false;
    62     }
    63 
    64     // Returns if there is any word in the trie
    65     // that starts with the given prefix.
    66     public boolean startsWith(String prefix) {
    67         if (prefix == null || prefix.length() == 0) {
    68             return false;
    69         }
    70         TrieNode cur = root;
    71         for (int i = 0; i < prefix.length(); i++) {
    72             if (!cur.children.containsKey(prefix.charAt(i))) {
    73                 return false;
    74             }
    75             cur = cur.children.get(prefix.charAt(i));
    76         }
    77         return true;
    78     }
    79 }
    View Code

     2.单词搜索II

    题意:

    给出一个由小写字母组成的矩阵和一个字典。找出所有同时在字典和矩阵中出现的单词。一个单词可以从矩阵中的任意位置开始,可以向左/右/上/下四个相邻方向移动。
    
    您在真实的面试中是否遇到过这个题? Yes
    样例
    给出矩阵:
    doaf
    agai
    dcan
    和字典:
    {"dog", "dad", "dgdg", "can", "again"}
    
    返回 {"dog", "dad", "can", "again"}
    View Code

    解法:利用trie 树+dfs,先将字典中的单词插入到trie tree中,再dfs遍历矩阵,这样的好处是避免无效的遍历,利用trie tree可以判断当前位置的字符是否在trie tree的当前节点的孩子节点中,如果在就往下遍历,不在就无需遍历了。输出结果不能有重复的单词。

     1 public class Solution {
     2     /**
     3      * @param board: A list of lists of character
     4      * @param words: A list of string
     5      * @return: A list of string
     6      */
     7     public ArrayList<String> wordSearchII(char[][] board, ArrayList<String> words) {
     8         // write your code here
     9         if (board == null || words == null) {
    10             return new ArrayList<>();
    11         }
    12         TrieTree trie = new TrieTree();
    13         for (String word : words) {
    14             trie.insert(word);
    15         }
    16         Set<String> set = new HashSet<>();
    17         ArrayList<String> ans = new ArrayList<>();
    18         StringBuffer sb = new StringBuffer();
    19         for (int i = 0; i < board.length; i++) {
    20             for (int j = 0; j < board[0].length; j++) {
    21                 if (trie.root.children.containsKey(board[i][j])) {
    22                     helper(board, i, j, trie.root, set, sb);
    23                 }
    24             }
    25         }
    26         for (String word : set) {
    27             ans.add(word);
    28         }
    29         return ans;
    30     }
    31     public void helper(char[][] board, int i, int j, TrieNode cur, Set<String> ans, StringBuffer sb) {
    32         if (i < 0 || i >= board.length || j < 0 || j >= board[0].length || board[i][j] == '#') {
    33             return;
    34         }
    35         char curChar = board[i][j];
    36         board[i][j] = '#';
    37         if (cur.children.containsKey(curChar)) {
    38             sb.append(curChar);
    39             if (cur.children.get(curChar).wordEnd) {
    40                 ans.add(new String(sb));
    41             }
    42             cur = cur.children.get(curChar);
    43             helper(board, i + 1, j, cur, ans, sb);
    44             helper(board, i - 1, j, cur, ans, sb);
    45             helper(board, i, j - 1, cur, ans, sb);
    46             helper(board, i, j + 1, cur, ans, sb);
    47             sb.deleteCharAt(sb.length() - 1);
    48         }
    49         board[i][j] = curChar;
    50     }
    51 }
    52 class TrieNode {
    53     HashMap<Character, TrieNode> children = null;
    54     boolean wordEnd;
    55     public TrieNode() {
    56         children = new HashMap<>(); 
    57         wordEnd = false;
    58     }
    59 }
    60 class TrieTree {
    61     TrieNode root = null;
    62     public TrieTree() {
    63         root = new TrieNode();
    64     }
    65     public void insert(String word) {
    66         if (word == null || word.length() == 0) {
    67             return ;
    68         }
    69         TrieNode cur = root;
    70         for (int i = 0; i < word.length(); i++) {
    71             if (!cur.children.containsKey(word.charAt(i))) {
    72                 cur.children.put(word.charAt(i), new TrieNode());
    73             } 
    74             cur = cur.children.get(word.charAt(i));
    75             if (i == word.length() - 1) {
    76                 cur.wordEnd = true;
    77             }
    78         }
    79     }
    80 }
    View Code

     第三节 高级数据结构II

    1.Trapping Rain Water

    题意:

    Given n non-negative integers representing an elevation map where the width of each bar is 1, compute how much water it is able to trap after raining.

    Trapping Rain Water

    Example

    Given [0,1,0,2,1,0,1,3,2,1,2,1], return 6.

    解法:两指针解法,分别初始化位头和尾,始终移动高度较小的一端,并且在移动的同时,计算储水量。较小的高度是储水量的上界(木桶原理类似),在移动的同时保存一个储水上界,遇到较低的高度则计算高度差则是储水量,遇到较高的高度则更新储水上界为这个高度。

     1 public class Solution {
     2     /**
     3      * @param heights: an array of integers
     4      * @return: a integer
     5      */
     6     public int trapRainWater(int[] heights) {
     7         // write your code here
     8         if (heights == null || heights.length < 3) {
     9             return 0;
    10         }
    11         int left = 0;
    12         int right = heights.length - 1;
    13         int cur = Math.min(heights[left], heights[right]);
    14         int ans = 0;
    15         while(left < right) {
    16             if (heights[left] <= heights[right]) {
    17                 ans += Math.max(cur - heights[left], 0);
    18                 cur = Math.max(cur, heights[left]);
    19                 left++;
    20             } else {
    21                 ans += Math.max(cur - heights[right], 0);
    22                 cur = Math.max(cur, heights[right]);
    23                 right--;
    24             }
    25         }
    26         return ans;
    27     }
    28 }
    View Code

    2. Trapping Rain Water II--pq

    题意:

    Given n x m non-negative integers representing an elevation map 2d where the area of each cell is 1 x 1, compute how much water it is able to trap after raining.

    样例

    例如,给定一个 5*4 的矩阵: 

    [
      [12,13,0,12],
      [13,4,13,12],
      [13,8,10,12],
      [12,13,12,12],
      [13,13,13,13]
    ]
    

    返回 14.

    解法:pq ,与上一题不同,这题立体化了。我们需要由外向内遍历,并且使用pq来给柱子高度排序,以最小高度向内灌水,遇到高度比当前最小高度小的,则将当前高度替换实际高度加入pq中。注意pq中存的是自定义类,因为要保存位置信息。可以将已经加入pq中的单元格值至为-1,用来去重,并且可以保证是由外向内遍历的。

     1 public class Solution {
     2     /**
     3      * @param heights: a matrix of integers
     4      * @return: an integer
     5      */
     6     public int trapRainWater(int[][] heights) {
     7         // write your code here
     8         if (heights == null || heights.length < 3 || heights[0].length < 3) {
     9             return 0;
    10         }
    11         int m = heights.length;
    12         int n = heights[0].length;
    13         PriorityQueue<Cell> pq = new PriorityQueue<Cell>(2*(m+n),new MyComparator());
    14         for (int i = 0; i < n; i++) {
    15             pq.offer(new Cell(0, i, heights[0][i]));
    16             pq.offer(new Cell(m - 1, i, heights[m - 1][i]));
    17             heights[0][i] = -1;
    18             heights[m - 1][i] = -1;
    19         }
    20         for (int i = 1; i < m - 1; i++) {
    21             pq.offer(new Cell(i, 0, heights[i][0]));
    22             pq.offer(new Cell(i, n - 1, heights[i][n - 1]));
    23             heights[i][0] = -1;
    24             heights[i][n - 1] = -1;
    25         }
    26         int[] dx = {0, 1, -1, 0};
    27         int[] dy = {1, 0, 0, -1};
    28         int ans = 0;
    29         while (!pq.isEmpty()) {
    30             Cell cur = pq.poll();
    31             for (int i = 0; i < 4; i++) {
    32                 int nx = cur.x + dx[i];
    33                 int ny = cur.y + dy[i];
    34                 if (nx >= 0 && nx < m && ny >= 0 && ny < n && heights[nx][ny] != -1) {
    35                     ans += Math.max(0, cur.val - heights[nx][ny]);
    36                     pq.offer(new Cell(nx, ny, Math.max(cur.val, heights[nx][ny])));
    37                     heights[nx][ny] = -1;
    38                 }
    39             }
    40         }
    41         return ans;
    42     }
    43 public class Cell{
    44     int x;
    45     int y;
    46     int val;
    47     public Cell(int x, int y, int val) {
    48         this.x = x;
    49         this.y = y;
    50         this.val = val;
    51     }
    52 }
    53 public class MyComparator implements Comparator<Cell> {
    54     public int compare(Cell c1, Cell c2) {
    55         return c1.val - c2.val;
    56     }
    57 }
    58 };
    View Code

    3.实时数据流的中位数

    思路:用一个最大堆和一个最小堆。将数据流平均分成两个部分,最大堆中的数都小于最小堆中的数,当前数据流个数是偶数的时候,中位数则是最大堆与最小堆堆顶元素之和/2;当前数据流个数是奇数的时候,中位数是最大堆的堆顶。所以当数据流个数是偶数时,最大堆与最小堆数据个数相等,反之,最大堆的数据个数比最小堆多一个。

    1.当最大堆与最小堆长度相等时,新来一个数据,本应该加入最大堆,但是如果这个数据比最小堆的最小值要大,就要加入最小堆,再将最小堆的堆顶元素pop出来加入最大堆中。

    2.当最大堆长度大于最小堆时,新来一个数据,本应该加入最小堆,但是如果这个数据比最大堆最大值要小,则应该加入最大堆,并且将最大堆的最大值pop加入最小堆。

    通过上面两步保证最大堆的所有元素小于最小堆。

    插入时间复杂度为O(logn),获取中位数的时间复杂度为O(1);

    代码使用c++写的,注意priority_queue的pop函数返回void,并不会返回数值。最小堆的实现没有自定义比较器,而是将数值取负号放入最大堆,相当于形成了一个最小堆。这时候用long类型就比较安全,因为最小负整数加负号之后会越界int的。

     1 class MedianFinder {
     2     priority_queue<long> small, large;
     3 public:
     4     /** initialize your data structure here. */
     5     MedianFinder() {
     6         
     7     }
     8     
     9     void addNum(int num) {
    10         if (small.size() == large.size()) {
    11             if (large.size() != 0 && num > -large.top()) {
    12                 large.push(-num);
    13                 small.push(-large.top());
    14                 large.pop();
    15             } else {
    16                 small.push(num);
    17             }
    18         }
    19         if (small.size() > large.size()) {
    20             if (num < small.top()) {
    21                 small.push(num);
    22                 large.push(- small.top());
    23                 small.pop();
    24             } else {
    25                 large.push(- num);
    26             }
    27         }
    28     }
    29     
    30     double findMedian() {
    31         return small.size() > large.size() ? small.top() : (small.top() - large.top()) / 2.0;
    32     }
    33 };
    34 
    35 /**
    36  * Your MedianFinder object will be instantiated and called as such:
    37  * MedianFinder obj = new MedianFinder();
    38  * obj.addNum(num);
    39  * double param_2 = obj.findMedian();
    40  */
    View Code

    4.滑动窗口的中位数

    题意:

    给定一个包含 n 个整数的数组,和一个大小为 k 的滑动窗口,从左到右在数组中滑动这个窗口,找到数组中每个窗口内的中位数。(如果数组个数是偶数,则在该窗口排序数字后,返回第 N/2 个数字。)
    样例
    对于数组 [1,2,7,8,5], 滑动大小 k = 3 的窗口时,返回 [2,7,7]
    最初,窗口的数组是这样的:
    
    [ | 1,2,7 | ,8,5] , 返回中位数 2;
    
    接着,窗口继续向前滑动一次。
    
    [1, | 2,7,8 | ,5], 返回中位数 7;
    
    接着,窗口继续向前滑动一次。
    
    [1,2, | 7,8,5 | ], 返回中位数 7;
    View Code

    解法:与上一题类似,利用两个堆,但是这里是滑动窗口,所以每一步需要删除掉出窗口的元素,而pq的删除操作时间复杂度是O(n),不适合,这里用treeset,内部结构式平衡二叉搜索树,删除操作时间复杂度是O(logn).

     1 public class Solution {
     2     /**
     3      * @param nums: A list of integers.
     4      * @return: The median of the element inside the window at each moving.
     5      */
     6     public ArrayList<Integer> medianSlidingWindow(int[] nums, int k) {
     7         // write your code here
     8         if (nums == null || nums.length == 0 ||  k <= 0) {
     9             return new ArrayList<Integer>();
    10         }
    11         ArrayList<Integer> ans = new ArrayList<>();
    12         TreeSet<Node> minHeap = new TreeSet<>();
    13         TreeSet<Node> maxHeap = new TreeSet<>();
    14         int size = (k + 1) / 2;
    15         for (int i = 0; i < k - 1; i++) {
    16             add(minHeap, maxHeap, size, new Node(i, nums[i]));
    17         }
    18         for (int i = k - 1; i < nums.length; i++) {
    19             add(minHeap, maxHeap, size, new Node(i, nums[i]));
    20             ans.add(maxHeap.last().val);
    21             remove(minHeap, maxHeap, new Node(i - k + 1, nums[i - k + 1]));
    22         }
    23         return ans;
    24     }
    25     public void add(TreeSet<Node> minHeap, TreeSet<Node> maxHeap, int size, Node node) {
    26         if (maxHeap.size() < size) {
    27             maxHeap.add(node);
    28         } else {
    29             minHeap.add(node);
    30         }
    31         if (maxHeap.size() == size) {
    32             if (minHeap.size() > 0 && maxHeap.last().val > minHeap.first().val) {
    33                 Node min = minHeap.first();
    34                 Node max = maxHeap.last();
    35                 minHeap.remove(min);
    36                 maxHeap.remove(max);
    37                 minHeap.add(max);
    38                 maxHeap.add(min);
    39             }
    40         }
    41     }
    42     public void remove(TreeSet<Node> minHeap, TreeSet<Node> maxHeap, Node node) {
    43         if (minHeap.contains(node)) {
    44             minHeap.remove(node);
    45         } else {
    46             maxHeap.remove(node);
    47         }
    48     }
    49     public class Node implements Comparable<Node>{
    50         int pos = 0;
    51         int val = 0;
    52         public Node (int pos, int val) {
    53             this.pos = pos;
    54             this.val = val;
    55         }
    56         public int compareTo(Node other) {
    57             if (this.val == other.val) {
    58                 return this.pos - other.pos;
    59             }
    60             return this.val - other.val;
    61         }
    62     }
    63 }
    View Code

    5.滑动窗口的最大值

    思路:利用双端队列。队列头部始终保存当前滑动窗口的最大值。遍历数组,如果当前数值大于队列尾部数值,则删除所有小于当前数字的队列尾部数字,并在尾部加入当前数值,如果当前数值小于队列尾部,则加入尾部。如果当前索引号与队列头部索引号之差大于等于滑动窗口大小k,则删除头部,因为头部已经不属于当前滑动窗口。注意队列中存的是数值的索引号。

     1 class Solution {
     2 public:
     3     vector<int> maxSlidingWindow(vector<int>& nums, int k) {
     4         vector<int> max;
     5         if (nums.size() < k || k < 1) {
     6             return max;
     7         }
     8         deque<int> dq;
     9         for (int i = 0; i < k; i++) {
    10             while (!dq.empty() && nums[i] > nums[dq.back()]) {
    11                 dq.pop_back();
    12             }
    13             dq.push_back(i);
    14         }
    15         max.push_back(nums[dq.front()]);
    16         for (int i = k; i < nums.size(); i++) {
    17             while (!dq.empty() && nums[i] >= nums[dq.back()]) {
    18                 dq.pop_back();
    19             }
    20             if (!dq.empty() && dq.front() <= i - k) {
    21                 dq.pop_front();
    22             }
    23             dq.push_back(i);
    24             max.push_back(nums[dq.front()]);
    25         }
    26         return max;
    27     }
    28 };
    View Code
  • 相关阅读:
    内部类
    多重继承关系初始化顺序及初始化
    String
    Error
    算法:插入排序
    算法:冒泡排序
    算法:选择排序
    注册Activity
    java变量的作用域和基本数据类型转换
    java数据类型
  • 原文地址:https://www.cnblogs.com/fisherinbox/p/6952808.html
Copyright © 2020-2023  润新知