本篇给出了几种考察一些数据结构的理解的题型。
Min Stack
Min Stack除了能实现普通stack的操作之外,还要求能返回stack中的最小值,所有的操作都要在O(1)的时间内完成。看到题目也许能想到stack本身的所有操作也是可以在O(1)的时间内完成的。那么这道题目其实使用了两个stack,其中一个保存当前数组中最小的数,每存入一个数,两个stack都push进一个,这样就不用保存最小数是前几个数的最小数了,而pop时,两个stack都进行pop即可。并且这样就可以使求最小值的操作的事件复杂度也为O(1)了。
public class MinStack {
private Stack<Integer> stack;
private Stack<Integer> minStack;
public MinStack() {
stack = new Stack<Integer>();
minStack = new Stack<Integer>();
}
public void push(int number) {
stack.push(number);
if (minStack.isEmpty()) {
minStack.push(number);
} else {
minStack.push(Math.min(number, minStack.peek()));
}
}
public int pop() {
minStack.pop();
return stack.pop();
}
public int min() {
return minStack.peek();
}
}
Implement Queue by Two Stacks
题目要求使用两个stack来实现一个queue,并且时间复杂度为O(1)。相信不用我介绍,大家也明白stack与queue的区别是,stack是先进后出而queue是先进先出,用两个stack可以很轻松的实现一个queue,难点在于时间复杂度。如果想操作的均摊复杂度为O(1),我们当进行push操作时使用stack1,而pop时使用stack2,然后当stack2为空时,我们才把stack1中的元素push到stack2中,如此并不影响元素按先进先出顺序出队列,同时,每个元素只被“倒”了一次,所以符合题目的均摊时间复杂度为O(1)。
public class Queue {
private Stack<Integer> stack1;
private Stack<Integer> stack2;
public Queue() {
stack1 = new Stack<Integer>();
stack2 = new Stack<Integer>();
}
private void stack2ToStack1() {
while (! stack2.empty()) {
stack1.push(stack2.pop());
}
}
public void push(int number) {
stack2.push(number);
}
public int pop() {
if (stack1.empty() == true) {
this.stack2ToStack1();
}
return stack1.pop();
}
public int top() {
if (stack1.empty() == true) {
this.stack2ToStack1();
}
return stack1.peek();
}
}
Word Search
题目为给出一个二维board板(可以看成是词典)上,判定某个字符串(单词)是否在其中出现。其实这道题不是考察的对数据结构的理解,而是搜索,按题目很容易就能看出来,使用DFS是最容易的。
public class Solution {
public boolean exist(char[][] board, String word) {
if(board == null || board.length == 0)
return false;
if(word.length() == 0)
return true;
for(int i = 0; i< board.length; i++){
for(int j=0; j< board[0].length; j++){
if(board[i][j] == word.charAt(0)){
boolean rst = find(board, i, j, word, 0);
if(rst)
return true;
}
}
}
return false;
}
private boolean find(char[][] board, int i, int j, String word, int start){
if(start == word.length())
return true;
if (i < 0 || i>= board.length ||
j < 0 || j >= board[0].length || board[i][j] != word.charAt(start)){
return false;
}
board[i][j] = '#'; // should remember to mark it
boolean rst = find(board, i-1, j, word, start+1)
|| find(board, i, j-1, word, start+1)
|| find(board, i+1, j, word, start+1)
|| find(board, i, j+1, word, start+1));
board[i][j] = word.charAt(start);
return rst;
}
}
Word Search II
这道题其实的考察是trie树,trie树定义其实并不是很难,它的每个节点下面都有可能有n个子节点,但是最多只能由26个节点(这道题中),它将所有的信息按照一定规律保存起来,然后我们需要对其中内容进行搜索的时候,就可以较快速的得到答案,类似于hash表。需要注意的是trie树的查找的时间复杂度为O(1)而不是O(n)。在我们这道题中,创建一个trie树,将词典中的单词逐一插入到trie树中,其中每个单词的结尾字母做一个标记,并且标注是哪个单词的。这样做可以减少创建trie树的复杂度和时间,如果使用broad词典中信息来创建trie树则会花费大量的时间,因为trie树本身就是适合于查找的。然后从broad词典的起点开始每个点都进行“上下左右”的深搜,找到单词则保存到结果中即可。
class TrieNode {
String s;
boolean isString;
HashMap<Character, TrieNode> subtree;
public TrieNode() {
// TODO Auto-generated constructor stub
isString = false;
subtree = new HashMap<Character, TrieNode>();
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, ArrayList<String> ans, String res) {
if(root.isString == true)
{
if(!ans.contains(root.s)){
ans.add(root.s);
}
}
if(x < 0 || x >= board.length || y < 0 || 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, res);
board[x][y] = now;
}
}
}
public ArrayList<String> wordSearchII(char[][] board, ArrayList<String> words) {
ArrayList<String> ans = new ArrayList<String>();
TrieTree tree = new TrieTree(new TrieNode());
for(String word : words){
tree.insert(word);
}
String res = "";
for(int i = 0; i < board.length; i++){
for(int j = 0; j < board[i].length; j++){
search(board, i, j, tree.root, ans, res);
}
}
return ans;
}
LRU Cache
LRU缓存是最近最少使用原则,这个缓存机制是当缓存块不够使用时将最近最少使用的数据从缓存中删除。所以,我们使用一个链表来保存数据的访问顺序,这样当缓存不够用时就可以知道需要直接删掉哪些数据了。链表我们并不用经常的维护,所以并不是很费时间,而我们使用一个HashMap来保存缓存数据。
public class LRUCache {
private class Node{
Node prev;
Node next;
int key;
int value;
public Node(int key, int value) {
this.key = key;
this.value = value;
this.prev = null;
this.next = null;
}
}
private int capacity;
private HashMap<Integer, Node> hs = new HashMap<Integer, Node>();
private Node head = new Node(-1, -1);
private Node tail = new Node(-1, -1);
public LRUCache(int capacity) {
this.capacity = capacity;
tail.prev = head;
head.next = tail;
}
public int get(int key) {
if( !hs.containsKey(key)) {
return -1;
}
// remove current
Node current = hs.get(key);
current.prev.next = current.next;
current.next.prev = current.prev;
// move current to tail
move_to_tail(current);
return hs.get(key).value;
}
public void set(int key, int value) {
if( get(key) != -1) {
hs.get(key).value = value;
return;
}
if (hs.size() == capacity) {
hs.remove(head.next.key);
head.next = head.next.next;
head.next.prev = head;
}
Node insert = new Node(key, value);
hs.put(key, insert);
move_to_tail(insert);
}
private void move_to_tail(Node current) {
current.prev = tail.prev;
tail.prev = current;
current.prev.next = current;
current.next = tail;
}
}
Max Tree
Max Tree类似于最大堆,最大的元素在上面。而左右子树的根为被父节点分成两部分中的最大元素。怎么取最大的元素是这道题的难点。如果递归去找,时间复杂度会达到O(n^2),那我们使用一个栈来解决,画出栈分析可以看出,一个元素被插入到的父节点是它左边最大值和右边最大值中的较小者。那么按栈来保存需要被插入的节点即可。
public static TreeNode maxTree(int[] A) {
Stack<TreeNode> stack = new Stack<TreeNode>();
TreeNode root = null;
for (int i = 0; i <= A.length; i++) {
TreeNode right = i == A.length ? new TreeNode(Integer.MAX_VALUE)
: new TreeNode(A[i]);
while (!stack.isEmpty()) {
if (right.val > stack.peek().val) {
TreeNode nodeNow = stack.pop();
if (stack.isEmpty()) {
right.left = nodeNow;
} else {
TreeNode left = stack.peek();
if (left.val > right.val) {
right.left = nodeNow;
} else {
left.right = nodeNow;
}
}
} else
break;
}
stack.push(right);
}
return stack.peek().left;
}
Largest Rectangle in Histogram
这道题给定一组高度值,以这组高度值宽度为1画出柱状图,求图中矩形面积最大的值。能形成矩形,矩形的高一定是图中“最矮”的高度值,宽度为能“够到”的数组的index连续个数。类似于上道题的思路,我们使用一个栈来寻找一个数左边的最小值,查看现有能形成的最大矩形,但是我们并不是对每个值都进行这个操作,而是对一个数的右边的数如果小于它本身,那么他就该计算了,此时计算矩形可以得到目前的最大矩形值。
public int largestRectangleArea(int[] height) {
if (height == null || height.length == 0) {
return 0;
}
Stack<Integer> stack = new Stack<Integer>();
int max = 0;
for (int i = 0; i <= height.length; i++) {
int curt = (i == height.length) ? -1 : height[i];
while (!stack.isEmpty() && curt <= height[stack.peek()]) {
int h = height[stack.pop()];
int w = stack.isEmpty() ? i : i - stack.peek() - 1;
max = Math.max(max, h * w);
}
stack.push(i);
}
return max;
}