九章算法强化
第一节: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 };
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 };
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 }
4.排序矩阵中的从小到大第k个数follow up ---两个排序数组和的第k小
--给定两个排好序的数组 A, B,定义集合 sum = a + b ,求 sum 中第k小的元素。
解析:这题跟第3题其实是一样的,给出 A = [1,7,11]
B = [2,4,6],可以得出和的表格
A\B | 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
解法:
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 }
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
解法:
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 }
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
解法:
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 }
衍生题--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].
解法:其实就是求集合的个数,然后在新的岛屿生成后,将上下左右的集合合并就好。
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 }
判断图是否是树
题意:
给出 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.
--图是连通图,并且不存在环。那么这个图是树
解法:并查集。并查集用来判断是否存在环,同时记下连通块的个数,最后只剩一个连通块并且没有环,则就是树。
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 }
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 }
2.单词搜索II
题意:
给出一个由小写字母组成的矩阵和一个字典。找出所有同时在字典和矩阵中出现的单词。一个单词可以从矩阵中的任意位置开始,可以向左/右/上/下四个相邻方向移动。 您在真实的面试中是否遇到过这个题? Yes 样例 给出矩阵: doaf agai dcan 和字典: {"dog", "dad", "dgdg", "can", "again"} 返回 {"dog", "dad", "can", "again"}
解法:利用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 }
第三节 高级数据结构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.
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 }
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 };
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 */
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;
解法:与上一题类似,利用两个堆,但是这里是滑动窗口,所以每一步需要删除掉出窗口的元素,而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 }
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 };