您的位置:首页 > 其它

有趣的题目系列一:实现具有最大值、最小值、中间值的栈和队列

2013-10-08 21:22 477 查看
番外知识:Java反射详解

番外二:

<?>是1.5的新特性,泛型
如果是?表示可以放Object类型以及他的子类

如果是String 表示只能接收String以及他的子类。
Class<?> c表示这个Class可以放任意的类,?表示object(所有类都隐性从Object继承的)
Class<String> c 只能接收String和他的子类(本文中的<E>也是这种情况)
Class c和Class<?>c性质是一样的

===========================================

题目来源:

1)http://blog.csdn.net/bigheadzzy/article/details/8002253

2)http://blog.csdn.net/yangzhongblog/article/details/12391959

文章中的大部分题目描述、代码均参考自上面二处,加以自己的一些理解。

-------------------------------------------------------------------切割-------------------------------------------------------------------------

Works Applications的笔试题:

要求实现下面两个接口,实现要求:运行速度快,每个操作时间复杂度不能相差太大。

一,实现Immutable的FIFO队列。也就是说要求入队和出队不会改变原来的队列。

package jp.co.worksap.recruiting;

public interface ExamImmutableQueue<E> {
public ExamImmutableQueue<E> enqueue(E e);
public ExamImmutableQueue<E> dequeue();
public E peek();
public int size();
}


二,实现可以查找最大、最小和中间值的FIFO队列。

public interface ExamPeekableQueue<E extends Comparable<E>> {

public void enqueue(E e);
public E dequeue();
public E peekMedian();
public E peekMaximum();
public E peekMinimum();
public int size();
}

接口一要求实现的API有:入队、出队、峰值和大小

接口二要求实现的有:入队、出队、中间值、最小值、最大值和大小

仔细对比,发现接口一和接口二的入队和出队的返回值是不同的。

原作者的代码是这样子的(我自己敲了一遍):

import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.NoSuchElementException;
import java.util.Stack;

public class ExamImmutableQueueImpl<E> implements ExamImmutableQueue<E>{
/*根据题目的要求是要求入队和出队均不影响原队列,所以这里用了final*/
private final E[] queue;
private final int size;

public ExamImmutableQueueImpl(){//构造函数不需要标注<E>
queue = null;
size = 0;
}
public ExamImmutableQueueImpl(E[] queue){
this.queue = queue;
size = queue.length;
}
@Override
public ExamImmutableQueue<E> enqueue(E e) {
// TODO Auto-generated method stub
if(e == null)
throw new IllegalArgumentException();
//需要考虑的是在内部变量插入一个新的元素,如何返回一个新的ExamImmutableQueueImpl对象
//E[] clone = new E[size + 1];Java不可以定义泛型数组,ArrayList或者LinkedList是已经成型的泛型数组
/*
* 可以考虑的一个方法
* ArrayList<E> arr = new ArrayList<E>();
E[] clone = (E[]) arr.toArray();
*/
@SuppressWarnings("unchecked")
E[] clone = (E[])Array.newInstance(e.getClass(), size + 1);
clone[0] = e;
if(size != 0)
System.arraycopy(queue, 0, clone, 1, size);
return new ExamImmutableQueueImpl<E>(clone);
}

@Override
public ExamImmutableQueue<E> dequeue() {
// TODO Auto-generated method stub
//////////////////////////////////
/*题目要求的是FIFO,由入队我们得知新元素将被置在内部数组的首位,那删除应该从后面开始删除*/
if(size == 0)
throw new NoSuchElementException();
//E[] clone = (E[]) new Object[size - 1]; 用Object取代也是一种好方法
@SuppressWarnings("unchecked")
E[] clone = (E[]) new Object[size - 1];
System.arraycopy(queue, 0, clone, 0, size - 1);
return new ExamImmutableQueueImpl<E>(clone);
}

@Override
public E peek() {
// TODO Auto-generated method stub
if(size == 0)
throw new NoSuchElementException();
return (E) queue[size - 1];
}

@Override
public int size() {
// TODO Auto-generated method stub
return this.size;
}

}


第二题:作者的代码:

package jp.co.worksap.recruiting;

import java.util.Collections;
import java.util.LinkedList;
import java.util.NoSuchElementException;

public class ExamPeekableQueueImpl<E extends Comparable<E>> implements
ExamPeekableQueue<E> {

private LinkedList<SortedNode> objList = new LinkedList<SortedNode>();
private SortedNode head = null;
private SortedNode tail = null;

class SortedNode implements Comparable<SortedNode>{
public E e;
public SortedNode next;
public SortedNode(E e, SortedNode sn) {
this.e = e;
next = sn;
}
@Override
public int compareTo(SortedNode sn) {
// TODO Auto-generated method stub
return this.e.compareTo(sn.e);
}
}

private void testNull() {
if (objList.isEmpty()) {
head = null;
tail = null;
throw new NoSuchElementException("No element");
}
}

@Override
public void enqueue(E e) {
// TODO Auto-generated method stub
if (e == null)
throw new NoSuchElementException("Null object to eunqueue");

if (head == null) {
SortedNode sn = new SortedNode(e, null);
head = sn;
tail = sn;
objList.add(sn);
}
else {
SortedNode sn = new SortedNode(e, null);
tail.next = sn;
tail = sn;
objList.add(sn);
Collections.sort(objList);
}
}

@Override
public E dequeue() {
// TODO Auto-generated method stub
testNull();
SortedNode sn = head;
head = head.next;
objList.remove(sn);
Collections.sort(objList);
return sn.e;
}

@Override
public E peekMedian() {
// TODO Auto-generated method stub
testNull();
return objList.get(objList.size()/2).e;
}

@Override
public E peekMaximum() {
// TODO Auto-generated method stub
testNull();
return objList.getLast().e;
}

@Override
public E peekMinimum() {
// TODO Auto-generated method stub
testNull();
return objList.getFirst().e;
}

@Override
public int size() {
// TODO Auto-generated method stub
return objList.size();
}

}


个人感觉是使用的策略较为原始。

下面是另一篇博文的转载:

在研究“如何实现具有最大值、最小值和中间值的栈和队列”前,我们先考虑以下问题,然后由此过度到题目问题。

1)如何用两个栈实现队列

2)如何用两个队列实现栈

3)如何实现包含获取最小值函数getMin()的栈

4)如何实现包含获取中间值函数getMedian()的栈

5)如何实现包含获取最小值函数getMin()的队列


1 如何用两个栈实现队列

在研究问题前,我们可以用2个栈模拟一下具体操作过程,可以总结出以下规律:

入队:元素插入stack1;

出队:如果stack2中为空,先将stack1中元素入栈stack2,然后再将stack2的栈顶元素出栈。否则直接将stack2中元素出栈。

队列为空:stack1和stack2同时为空

队列大小:为stack1和stack2大小之和

具体过程见下图(图来自《剑指offer》)



Java实现代码如下

[java] view
plaincopy

public class QueueByStack<E extends Comparable<E>>  {  

      

    private LinkedList stack1=null;  

    private LinkedList stack2=null;  

      

    //constructor  

    public QueueByStack(){  

        stack1=new LinkedList();  

        stack2=new LinkedList();  

    }//end QueueByStack  

          

    public void insert(E e){  

          stack1.addLast(e);  

    }//end insert()  

      

    public E remove(){  

        if(!isEmpty()){  

            if(stack2.isEmpty()){  

                stack1Tostack2();  

            }//end if  

  

            return (E)stack2.removeLast();  

        }//end if  

        else{  

            System.out.println("queue is empty");  

            return null;  

        }//end else  

    }//end remove  

      

    //将stack1中元素入栈stack2  

    private void stack1Tostack2(){  

        while(!stack1.isEmpty()){  

            stack2.addLast(stack1.removeLast());  

        }//end while      

    }//end stack1ToStack2()  

      

    public boolean isEmpty(){  

        return stack1.isEmpty() && stack2.isEmpty();  

    }//end isEmpty()  

      

    public int size(){  

        return (stack1.size()+stack2.size());  

    }//end size()  

  

    /** 

     * @param args 

     */  

    public static void main(String[] args) {  

        QueueByStack q=new QueueByStack();  

        q.insert(1);  

        q.insert(2);  

        q.insert(3);  

        System.out.println(q.remove());  

        System.out.println(q.remove());  

        q.insert(4);  

        System.out.println(q.remove());  

        System.out.println(q.remove());  

    }  

}  


2 如何用两个队列实现栈

还是先用2个队列模拟栈的出栈和入栈过程,可以得出以下规律:

入栈:压入非空的那个队列

出栈:将非空队列中的n-1个元素压入空的队列中,然后将第n个元素出栈。

具体过程见下图(图来自《剑指offer》)




3 如何实现包含获取最小值函数的栈

问题:定义栈的数据结构,请在该类型中实现一个能够得到栈的最小元素的getMin函数。在该栈中,调用getMin、push及pop的时间复杂度都是O(1).

思路:用一个辅助栈stack2记住每次入栈stack1的当前最小值:在stack1入栈时,往stack2中加入当前最小值;stack1元素出栈时,stack2也出栈一个元素。最小值从stack2中获取及栈顶元素。


4 如何实现包含获取中间值函数的栈

如果能对栈中元素进行排序,那么排序好的中间值即为所求。问题3和问题4的具体代码如下,代码中同时实现了获取栈的最大值、最小值和中间值

[java] view
plaincopy

public class Stack<E extends Comparable<E>> {  

      

    private LinkedList<E> heartStack=new LinkedList<E>();   

    private LinkedList<E> curMinStack=new LinkedList<E>(); //辅助栈,用于记录当前最小值  

    private LinkedList<E> curMaxStack=new LinkedList<E>(); //辅助栈,用于记录当前最大值  

      

    public void push(E e){  

           heartStack.push(e);  

           //当前最小值入栈curMinStack  

           E currentMin=curMinStack.peek();  

           if(currentMin.compareTo(e)>0){  

               curMinStack.push(e);  

           }//end if  

           else  

               curMinStack.push(currentMin);  

             

           //当前最大值入栈curMinStack  

           E currentMax=curMinStack.peek();  

           if(currentMax.compareTo(e)<0){  

               curMaxStack.push(e);  

           }//end if  

           else  

               curMaxStack.push(currentMax);  

    }//end push()  

      

    public E pull(){  

        if(isEmpty())  

            return null;  

        else{  

            E e=heartStack.poll();  

            curMinStack.poll();  

            curMaxStack.poll();  

            return e;  

        }//end else  

    }//end pull()  

      

    public E getMax(){  

        return curMaxStack.peek();  

    }//end getMax()  

      

    public E getMin(){  

        return curMinStack.peek();  

    }//end getMin()  

      

    public int size(){  

        return heartStack.size();  

    }//end size()  

      

    public boolean isEmpty(){  

        return heartStack.isEmpty();  

    }//end isEmpty()  

      

    public E getMedian(){  

        E[] e=(E[]) heartStack.toArray();  

        Arrays.sort(e);  

        return e[e.length/2];  

    }//end getMedian()  

      

    public E[] toArray(){  

        return (E[])heartStack.toArray();  

    }//end toArray()  

}  


5 如何实现包含获取最小值函数的队列

问题:定义队列的数据结构,请在该类型中实现一个能够得到队列的最小元素的getMin函数。在该队列中,调用getMin、insert及remove的时间复杂度都是O(1).

思路1:用最小堆实现优先队列,获取最小值时间复杂度为O(nlogn),但优先队列只能获取最小值,remove获取的不是先入队的元素。

思路2:

如果能用栈有效地实现队列,而栈的获取最小值的操作又很容易实现,那么队列的获取最小值的操作也很容易完成。

因为上面可用2个栈实现栈的min函数,而用2个栈可以实现队列。所以可以用已实现了获取最小值的栈stack1和stack2实现队列,而整个队列的最小值从min(stack1.getMin(),stack2.getMin())中获取。具体实现代码见问题6中代码。


6 如何实现具有最大值、最小值和中间值的栈和队列

问题5解决了用O(1)时间获取栈的最小值,那么解决最大值的问题也迎刃而解。对于获取队列的中间值,可以将队列中所有元素排序,然后获取排序后的中间值。

具体实现如下(代码中的stack类为上述问题4中已实现的可以获取最大值和最小值的栈):

[java] view
plaincopy

public class ExmaPeekableQueue<E extends Comparable<E>>  implements IExamPeekableQueue{  

    Stack stack1=new Stack();  

    Stack stack2=new Stack();  

      

    @Override  

    public void enqueue(Comparable e) {  

        stack1.push(e);       

    }//end enqueue()  

  

    @Override  

    public Comparable<E> dequeue() {  

        if(stack2.isEmpty()){  

            stack2.push(stack1.pull());  

        }//end if  

          

        return stack2.pull();  

    }//end dequeue()  

  

    @Override  

    public Comparable<E> peekMedian() {  

        Comparable[] arr=null; //用于存储队列中当前元素的数组  

          

        if(stack1.isEmpty()){  

            arr=stack2.toArray();  

        }//end if  

        else if(stack2.isEmpty()){  

            arr=stack1.toArray();  

        }//end if  

        else{  

            arr=new Comparable[size()];  

            Comparable[] arrE1=stack1.toArray();  

            Comparable[] arrE2=stack2.toArray();  

              

            //将2个栈中的元素复制到一个数组中  

            int i=0;  

            for(;i<stack1.size();i++){  

                arr[i]=arrE1[i];  

            }//end for  

              

            for(int j=0;j<stack2.size();j++){  

                arr[++i]=arrE2[j];  

            }//end for  

        }//end else  

              

        Arrays.sort(arr);  

        return arr[arr.length/2];  

    }//end peekMedian()  

  

    @Override  

    public Comparable peekMaximum() {  

        Comparable max1=stack1.getMax();  

        Comparable max2=stack2.getMax();  

          

        if(max1.compareTo(max2)>0){  

            return max1;  

        }//end if  

        else  

            return max2;  

    }  

  

    @Override  

    public Comparable<E> peekMinimum() {  

        Comparable min1=stack1.getMin();  

        Comparable min2=stack2.getMin();  

          

        if(min1.compareTo(min2)>0){  

            return min2;  

        }//end if  

        else  

            return min1;  

    }//end peekMinimum()  

  

    @Override  

    public int size() {  

        return (stack1.size()+stack2.size());  

    }//end size()  

}//end class  

========================

当然如果知识为了实现栈的最大值最小值中间值功能,而不要求使用队列,那完全可以使用内部栈解决。

在上面的博文中,使用两个队列(数组)实现的栈的先入后出功能,

可以有两种实现模式:

当在Array1 输入1 2 3 之后要删除3 则需要借助Array2 -> Array1:空  Array2为 1 2

这个时候新来的元素应该插入到Array1 还是Array2呢。

如果是插入到Array2 则,若后面需要删除某元素,则必须整个数组移到Array1

如果是插入到Array1 则只需要将后面输入的移到Array2。这是个较为优化的方案。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐