Java面试13|算法

Java写算法时常用的函数:

Stack

void push(E e):将给定元素”压入”栈中。存入的元素会在栈首。即:栈的第一个元素 
E pop():将栈首元素删除并返回。

Queue

boolean offer(E e):将元素追加到队列末尾,若添加成功则返回true。 
E poll():从队首删除并返回该元素。 
E peek():返回队首元素,但是不删除

Deque是双端队列,有Stack和Queue的所有方法。

队首操作:

push、peek、pop

队尾操作:

add、offer、peekLast、popLast

字符串操作:

toCharArray()转换为char数组

charAt(index)取字符串中索引为index的字符

1、冒泡排序

for(int i=0;i<n;i++){
   for(int j=0;j<n-1-i;j++){
        if(temp[j]>temp[j+1]){
           int t=temp[j];
           temp[j]=temp[j+1];
           temp[j+1]=t;
        }
   }
}

2、快速排序

public void quicksort(int[] array,int left,int right){
		if(left<right){
			int key = array[left];
			int low = left;
			int high = right;

			while(low<high){
				while(low<high && array[high]>=key){
					high--;
				}
				array[low] = array[high];
				while(low<high && array[low]<=key){
					low++;
				}
				array[high] = array[low];
			}
			array[low] = key;
	        quicksort(array,left,low-1);
	        quicksort(array,low+1,right);
		}
	}

  

3、查找子字符串出现的第一个索引位置

类似于Java的indexof()方法的实现,如下:

static int indexOf(char[] source, char[] target) {

		char first = target[0];
		int max = (source.length - target.length);

		for (int i = 0; i <= max; i++) {
			/* Look for first character. */
			if (source[i] != first) {
				while (++i <= max && source[i] != first)
					;
			}

			/* Found first character, now look at the rest of v2 */
			if (i <= max) {
				int j = i + 1;
				int end = j + target.length - 1;
				for (int k = 1; j < end && source[j] == target[k]; j++, k++)
					;

				if (j == end) {
					/* Found whole string. */
					return i;
				}
			}
		}
		return -1;
	}

4、分层打印二叉树并在第一层输出换行

public void PrintFromTopToBottom(TreeNode root) {
		TreeNode currentNode = root;

		int first = 1;
		int second = 0;
		while (currentNode != null) {

			if (currentNode.left != null) {
				queue.add(currentNode.left);
				second++;
			}
			if (currentNode.right != null) {
				queue.add(currentNode.right);
				second++;
			}

			first--;
			System.out.print(currentNode.val + " ");
			if (first == 0) {
				System.out.println(" ");
				first = second;
				second = 0;
			}

			currentNode = queue.poll();
		}
	}

  

 

5、一致性hash

一致性hash算法可以解决容错性和扩展性的问题。

系统中增加更多的虚拟节点,可以更好的解负载均衡问题。

public class Shard<S> {     // S类封装了机器节点的信息 ,如name、password、ip、port等   

    private TreeMap<Long, S> circle;  // 将整个hash值空间组成一个虚拟的环
    private List<S> shards;           // 真实机器节点
    private final int NODE_NUM = 100; // 每个机器节点关联的虚拟节点个数
    private final HashFunction hashFunction;  // 选择一个碰撞率低的hash()函数

    public Shard(List<S> shards,HashFunction hashFunction) {
        super();
        this.shards = shards;
        this.hashFunction = hashFunction;
        init();
    }  

    private void init() {  // 初始化一致性hash环
    	circle = new TreeMap<Long, S>();
        for (int i = 0; i<shards.size(); ++i) { // 每个真实机器节点都需要关联虚拟节点
            final S shardInfo = shards.get(i);
            add(shardInfo);
        }
    }  

    public void add(S node) {
		for (int i = 0; i < NODE_NUM; i++) {
			// 虚拟节点用一些特定的hash值来替代,这样形成了hash值到真实节点的映射
			circle.put(hashFunction.hash(node.toString() + i), node);
		}
	}

	public void remove(S node) {
		for (int i = 0; i < NODE_NUM; i++) {
			// 移除真实节点下对应的所有虚拟节点(特定的一些hash值)
			circle.remove(hashFunction.hash(node.toString() + i));
		}
	}

    public S getShardInfo(String key) {
//        SortedMap<Long, S> tail = circle.tailMap(hash(key)); // 沿环的顺时针找到一个虚拟节点
//        if (tail.size() == 0) {
//            return circle.get(circle.firstKey());
//        }
//        return tail.get(tail.firstKey()); // 返回该虚拟节点对应的真实机器节点的信息
    	if (circle.isEmpty()) {
			return null;
		}
		Long hash = hashFunction.hash(key);

		// 如果当前hash值没有定位到虚拟节点
		if (!circle.containsKey(hash)) {
			SortedMap<Long, S> tailMap = circle.tailMap(hash);
			hash = tailMap.isEmpty() ? circle.firstKey() : tailMap.firstKey();
		}

		return circle.get(hash);
    }
}

  

class Machine {
	String ip;
	String name;

	public Machine(String ip, String name) {
		this.ip = ip;
		this.name = name;
	}

	public String getIp() {
		return ip;
	}

	public void setIp(String ip) {
		this.ip = ip;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
}

public class Test {
	public static void main(String[] args) {
		Machine a = new Machine("192.168.0.1", "a");
		Machine b = new Machine("192.168.0.2", "b");
		Machine c = new Machine("192.168.0.3", "c");

		List<Machine> list = Arrays.asList(a, b, c);
		Map<String, Integer> map = new HashMap<String, Integer>();

		Shard<Machine> mcs = new Shard<Machine>(list, new HashFunction());

		// 存储0到2000个数,看存储在各个机器上的数的数量是否大致均匀
		for (int i = 0; i < 2000; i++) {
			String key = i + "";
			Machine m = mcs.getShardInfo(key);
			if (map.get(m.getIp()) == null) {
				map.put(m.getIp(), 0);
			} else {
				map.put(m.getIp(), (int) map.get(m.getIp()) + 1);
			}
		}

		Iterator<Entry<String, Integer>> iterator = map.entrySet().iterator();
		while (iterator.hasNext()) {
			Entry<String, Integer> entry = iterator.next();
			System.out.println(entry.getKey() + "/" + entry.getValue());
		}

	}
}

  

某次运行后的结果如下:

192.168.0.2/599
192.168.0.1/698
192.168.0.3/700

6、LRU最近最少使用算法

要效率的话使用hash搜索,要实现最近最少的话就用双向链表

public class LRUCache {  

    private int cacheSize;
    private HashMap<Object, Entry> nodes; // 缓存容器 ,为了提高查询速度需要这个结构
    private int currentSize;
    private Entry first; // 链表头
    private Entry last;  // 链表尾  

    static class Entry {
        Entry prev;
        Entry next;
        Object key;
        Object value;
    }  

    public LRUCache(int i) {
        currentSize = 0;
        cacheSize = i;
        nodes = new HashMap<Object, Entry>(i);
    }  

    /**
     * 获取缓存中对象,并把它放在最前面
     */
    public Entry get(Object key) {
        Entry node = nodes.get(key);
        if (node != null) {
            moveToHead(node);
            return node;
        } else {
            return null;
        }
    }  

    /**
     * 添加 entry到hashtable, 并把entry
     */
    public void put(Object key, Object value) {
        //先查看hashtable是否存在该entry, 如果存在,则只更新其value
        Entry node = nodes.get(key);  

        if (node == null) {
            //缓存容器是否已经超过大小.
            if (currentSize >= cacheSize) {
                nodes.remove(last.key);
                removeLast();
            } else {
                currentSize++;
            }
            node = new Entry();
        }
        node.value = value;
        //将最新使用的节点放到链表头,表示最新使用的.
        moveToHead(node);
        nodes.put(key, node);
    }  

    /**
     * 将entry删除, 注意:删除操作只有在cache满了才会被执行
     */
    public void remove(Object key) {
        Entry node = nodes.get(key);
        //在链表中删除
        if (node != null) {
            if (node.prev != null) {
                node.prev.next = node.next;
            }
            if (node.next != null) {
                node.next.prev = node.prev;
            }
            if (last == node)
                last = node.prev;
            if (first == node)
                first = node.next;
        }
        //在hashtable中删除
        nodes.remove(key);
    }  

    /**
     * 删除链表尾部节点,即使用最后 使用的entry
     */
    private void removeLast() {
        //链表尾不为空,则将链表尾指向null. 删除连表尾(删除最少使用的缓存对象)
        if (last != null) {
            if (last.prev != null){
            	last.prev.next = null;
            }
            else{
            	first = null;
            }
            last = last.prev;
        }
    }  

    /**
     * 移动到链表头,表示这个节点是最新使用过的
     */
    private void moveToHead(Entry node) {
        if (node == first)
            return;
        if (node.prev != null)
            node.prev.next = node.next;
        if (node.next != null)
            node.next.prev = node.prev;
        if (last == node)
            last = node.prev;
        if (first != null) {
            node.next = first;
            first.prev = node;
        }
        first = node;
        node.prev = null;
        if (last == null){
        	last = first;
        }  

    }
    /*
     * 清空缓存
     */
    public void clear() {
        first = null;
        last = null;
        currentSize = 0;
    }  

}

7、生产者与消费者

public class ConsumerProducerByWaitNotify {  

	public Integer monitor = new Integer(1);

    public static void main(String[] args) {
    	ConsumerProducerByWaitNotify instance = new ConsumerProducerByWaitNotify();
    	instance.bootstrap();
    } 

    public void bootstrap(){
    	  Godown godown = new Godown(30); // 必须操作同一个库的实例,否则不存在多线程的问题  

          Consumer c1 = new Consumer(20, godown);
          Consumer c2 = new Consumer(20, godown);
          Consumer c3 = new Consumer(30, godown);

          Producer p1 = new Producer(10, godown);
          Producer p2 = new Producer(10, godown);
          Producer p3 = new Producer(10, godown);
          Producer p4 = new Producer(10, godown);
          Producer p5 = new Producer(10, godown);
          Producer p6 = new Producer(10, godown);
          Producer p7 = new Producer(10, godown);  

          c1.start();
          c2.start();
          c3.start();
          p1.start();
          p2.start();
          p3.start();
          p4.start();
          p5.start();
          p6.start();
          p7.start();
    }

    // 仓库
    class Godown {
        public static final int max_size = 100; // 最大库存量
        public int curnum; // 当前库存量
        Godown(int curnum) {
            this.curnum = curnum;
        }  

		// 生产指定数量的产品
		public void produce(int neednum) {
			synchronized (monitor) {
				// 测试是否需要生产
				while (neednum + curnum > max_size) {
					System.out.println("要生产的产品数量" + neednum + "超过剩余库存量" + (max_size - curnum) + ",暂时不能执行生产任务!");
					try {
						// 当前的生产线程等待,并让出锁(注意,只有获取到锁,才有让锁的一说)
						// 如果调用某个对象的wait()方法,当前线程必须拥有这个对象的monitor(即锁),
						// 因此调用wait()方法必须在同步块或者同步方法中进行(synchronized块或者synchronized方法)
						monitor.wait();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				// 满足生产条件,则进行生产,这里简单的更改当前库存量
				curnum += neednum;
				System.out.println("已经生产了" + neednum + "个产品,现仓储量为" + curnum);
				// 唤醒在此对象监视器上等待的所有线程
				// 调用某个对象的notify()方法,当前线程也必须拥有这个对象的monitor,
				// 因此调用notify()方法必须在同步块或者同步方法中进行(synchronized块或者synchronized方法)。
				monitor.notifyAll();
			}
		}

		// 消费指定数量的产品
		public void consume(int neednum) {
			synchronized (monitor) {
				// 测试是否可消费
				while (curnum < neednum) {
					try {
						// 当前的消费线程等待,并让出锁
						monitor.wait();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				// 满足消费条件,则进行消费,这里简单的更改当前库存量
				curnum -= neednum;
				System.out.println("已经消费了" + neednum + "个产品,现仓储量为" + curnum);
				// 唤醒在此对象监视器上等待的所有线程
				monitor.notifyAll();
			}
		}
    }  

    // 生产者
    class Producer extends Thread {
        private int neednum; // 生产产品的数量
        private Godown godown; // 仓库
        Producer(int neednum, Godown godown) {
            this.neednum = neednum;
            this.godown = godown;
        }
        @Override
    	public void run() {
            // 生产指定数量的产品
            godown.produce(neednum);
        }
    }  

    // 消费者
    class Consumer extends Thread {
        private int neednum;  // 生产产品的数量
        private Godown godown; // 仓库  

        Consumer(int neednum, Godown godown) {
            this.neednum = neednum;
            this.godown = godown;
        }
        @Override
    	public void run() {
            // 消费指定数量的产品
            godown.consume(neednum);
        }
    }  

}

还可以使用阻塞队列、Semaphore等手段来实现。  

  

时间: 2024-10-10 21:44:08

Java面试13|算法的相关文章

java面试 - 经典算法题

题目一: public class testClockwiseOutput { //顺时针打印一个矩阵 @Test public void test(){ int[][] num = new int[100][100]; int n = 4; int count =1; for(int i=0;i<n;i++){ for(int j =0;j<n;j++){ num[i][j]=count++; } } output(num,0,n-1); } public void output(int[]

[转载]java面试中经常会被问到的一些算法的问题

Java面试中经常会被问到的一些算法的问题,而大部分算法的理论及思想,我们曾经都能倒背如流,并且也能用开发语言来实现过, 可是很多由于可能在项目开发中应用的比较少,久而久之就很容易被忘记了,在此我分享一下在面试中经常被问到的一些基本的算法,也当做一次知识的巩固. 排序算法的一些特点: * 排序算法的分类如下:* 1.插入排序(直接插入排序.折半插入排序.希尔排序):* 2.交换排序(冒泡泡排序.快速排序):* 3.选择排序(直接选择排序.堆排序):* 4.归并排序:* 5.基数排序.* * 关于

转:最近5年133个Java面试问题列表

最近5年133个Java面试问题列表 Java 面试随着时间的改变而改变.在过去的日子里,当你知道 String 和 StringBuilder 的区别就能让你直接进入第二轮面试,但是现在问题变得越来越高级,面试官问的问题也更深入. 在我初入职场的时候,类似于 Vector 与 Array 的区别.HashMap 与 Hashtable 的区别是最流行的问题,只需要记住它们,就能在面试中获得更好的机会,但这种情形已经不复存在.如今,你将会被问到许多 Java 程序员都没有看过的领域,如 NIO,

java 面试 -- 4

Java面试知识点总结 本篇文章会对面试中常遇到的Java技术点进行全面深入的总结,帮助我们在面试中更加得心应手,不参加面试的同学也能够借此机会梳理一下自己的知识体系,进行查漏补缺(阅读本文需要有一定的Java基础:若您初涉Java,可以通过这些问题建立起对Java初步的印象,待有了一定基础后再后过头来看收获会更大).本文的问题列表来自于http://www.nowcoder.com/discuss/3043,在此感谢原作者的无私分享:) 1. Java中的原始数据类型都有哪些,它们的大小及对应

Java 面试-- 1

JAVA面试精选[Java基础第一部分] 这个系列面试题主要目的是帮助你拿轻松到offer,同时还能开个好价钱.只要能够搞明白这个系列的绝大多数题目,在面试过程中,你就能轻轻松松的把面试官给忽悠了.对于那些正打算找工作JAVA软件开发工作的童鞋们来说,当你看到这份题目的时候,你应该感动很幸运,因为,只要你把题目中的内容都搞懂了,在笔试的时候就可以游刃有余,通过面试只有半步之遥了,笔试只能反映你的JAVA技能.不管你是面试各个级别的JAVA工程师.架构师.还是项目经理,这个系列文章都是你最宝贵的资

java面试大汇总

java面试笔试题大汇总     第一,谈谈final, finally, finalize的区别. 最常被问到. 第二,Anonymous Inner Class (匿名内部类) 是否可以extends(继承)其它类,是否可以implements(实现)interface(接口)? 第三,Static Nested Class 和 Inner Class的不同,说得越多越好(面试题有的很笼统). 第四,&和&&的区别. 这个问得很少. 第五,HashMap和Hashtable的区

java面试笔试题大汇总

java面试笔试题大汇总(一)JAVA相关基础知识 1.面向对象的特征有哪些方面 1.抽象:抽象就是忽略一个主题中与当前目标无关的那些方面,以便更充分地注意与当前目标有关的方面.抽象并不打算了解全部问题,而只是选择其中的一部分,暂时不用部分细节.抽象包括两个方面,一是过程抽象,二是数据抽象. 2.继承:继承是一种联结类的层次模型,并且允许和鼓励类的重用,它提供了一种明确表述共性的方法.对象的一个新类可以从现有的类中派生,这个过程称为类继承.新类继承了原始类的特性,新类称为原始类的派生类(子类),

Java面试宝典2017

JAVA面试.笔试题(2017版)                 欲想成功,必须用功!   目录 一.                  HTML&CSS部分.......................................................................................... 9 1.HTML中定义表格的宽度用80px和80%的区别是什么?...................................... 9 2.CSS样

java面试之Hashmap

在java面试中hashMap应该说一个必考的题目,而且HashMap 和 HashSet 是 Java Collection Framework 的两个重要成员,其中 HashMap 是 Map 接口的常用实现类,HashSet 是 Set 接口的常用实现类.虽然 HashMap 和 HashSet 实现的接口规范不同,但它们底层的 Hash 存储机制完全一样,甚至 HashSet 本身就采用 HashMap 来实现的. 通过 HashMap.HashSet 的源代码分析其 Hash 存储机制