面试常考的coding

牛客网输入输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
//输入描述:

//输入包括2行:

//第一行为整数n(1 <= n <= 50),即抹除一个数之后剩下的数字个数

//第二行为n个整数num[i] (1 <= num[i] <= 1000000000)
import java.util.*;
public class Next {
    public static void main(String[] args) {
        Scanner sc=new Scanner(System.in);
        int N=sc.nextInt();
        int[] num = new int[N];
        for (int i = 0; i < N; i++) {
            num[i] = sc.nextInt();
        }      
    }
}
import java.util.*;
public class Main{
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        String str = sc.nextLine();
        char[] arr = str.toCharArray();
   }
}
//输入包括n+1行:

//第一行为单词个数n(1 ≤ n ≤ 50)

//接下来的n行,每行一个单词word[i],长度length(1 ≤ length ≤ 50)。由小写字母构成
public class Demo1 {
    private int N;
    private String[] arr;
    private int count;
    public static void main(String[] args) {
      
        Scanner sc = new Scanner(System.in);
        N = sc.nextInt();
       arr = new String[demo.N];
        for (int i = 0; i < demo.N; i++) {
            String str = sc.next();          
        }      
        sc.close();
    }

树的前序遍历 -

1
2
3
4
5
6
7
8
9
10
11
12
13
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList();
if(root==null) return res;
Stack<TreeNode> s = new Stack();
s.push(root);
while(!s.isEmpty()){
root=s.pop();
res.add(root.val);
if(root.right!=null) s.push(root.right);
if(root.left!=null) s.push(root.left);
}
return res;
}

树的中序遍历 -

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList();
if(root==null) return res;
Stack<TreeNode> s = new Stack();
while(!s.isEmpty()||root!=null){
if(root!=null){
while(root!=null){
s.push(root);
root=root.left;
}
}else{
root = s.pop();
res.add(root.val);
root=root.right;
}
}
return res;
}

树的后序遍历 -

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList();
if(root==null) return res;
Stack<TreeNode> s1 = new Stack();
Stack<TreeNode> s2 = new Stack();
s1.push(root);
while(!s1.isEmpty()){
TreeNode tmp = s1.pop();
s2.push(tmp);
if(tmp.left!=null) s1.push(tmp.left);
if(tmp.right!=null) s1.push(tmp.right);
}
while(!s2.isEmpty()) res.add(s2.pop().val);
return res;
}

设计模式

工厂模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public interface FoodFactroy{
Food makeFood(String name);
}
public class ChineseFoodFactory implements FoodFactory{
public Food makeFood(String name){
if(name.equals("A")){
return new ChineseFoodA();
}else if (name.equals("B")) {
return new ChineseFoodB();
} else {
return null;
}
}
}
public class AmericanFoodFactory implements FoodFactory {
@Override
public Food makeFood(String name) {
if (name.equals("A")) {
return new AmericanFoodA();
} else if (name.equals("B")) {
return new AmericanFoodB();
} else {
return null;
}
}
}

public class APP {
public static void main(String[] args) {
// 先选择一个具体的工厂
FoodFactory factory = new ChineseFoodFactory();
// 由第一步的工厂产生具体的对象,不同的工厂造出不一样的对象
Food food = factory.makeFood("A");
}
}

单例模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Singleton{
private static volatile Singleton instance=null;
private Singleton(){}
public Singleton(){
if(instance==null){
synchronized(Singleton.class){
if(instance==null){
instance = new Singleton();
}
}
}
return instance;
}
}

第一个 if 语句用来避免 uniqueInstance 已经被实例化之后的加锁操作,而第二个 if 语句进行了加锁,所以只能有一个线程进入,就不会出现 uniqueInstance == null 时两个线程同时进行实例化操作。

的, uniqueInstance = new Singleton(); 这段代码其实是分为三步执行:

  1. 为 uniqueInstance 分配内存空间
  2. 初始化 uniqueInstance
  3. 将 uniqueInstance 指向分配的内存地址

使用 volatile 可以禁止 JVM 的指令重排,保证在多线程环境下也能正常运行。

算法

死锁

1
2
3
4
5
6
7
8
9
public class DeadLock {
static final Object lock1=new Object();
static final Object lock2=new Object();

public static void main(String[] args) {
new Thread(new A()).start();
new Thread(new B()).start();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class A implements Runnable {
@Override
public void run() {
while(true){
synchronized(DeadLock.lock1){
System.out.println("A get lock1");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (DeadLock.lock2){
System.out.println("A get lock2");
}
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class B implements Runnable {
public void run() {
while(true){
synchronized(DeadLock.lock2){
System.out.println("B get lock2");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (DeadLock.lock1){
System.out.println("B get lock1");
}
}
}
}
}

一.什么是死锁?

   死锁是由于两个或以上的线程互相持有对方需要的资源,导致这些线程处于等待状态,无法执行。

二.产生死锁的四个必要条件

   1.互斥性:线程对资源的占有是排他性的,一个资源只能被一个线程占有,直到释放。

   2.请求和保持条件:一个线程对请求被占有资源发生阻塞时,对已经获得的资源不释放。

   3.不剥夺:一个线程在释放资源之前,其他的线程无法剥夺占用。

   4.循环等待:发生死锁时,线程进入死循环,永久阻塞。

三.避免死锁的方法

1.破坏“请求和保持”条件

    一次性申请所有需要用到的资源,不要一次一次来申请,当申请的资源有一些没空,那就让线程等待。不过这个方法比较浪费资源,进程可能经常处于饥饿状态。还有一种方法是,要求进程在申请资源前,要释放自己拥有的资源。

2.破坏“不可抢占”条件

    允许进程进行抢占,方法一:如果去抢资源,被拒绝,就释放自己的资源。方法二:操作系统允许抢,只要你优先级大,可以抢到。

3.破坏“循环等待”条件

    将系统中的所有资源统一编号,进程可在任何时刻提出资源申请,但所有申请必须按照资源的编号顺序(升序)提出

LRU

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
class LRUCache {
int cap,count;
HashMap<Integer,Node> map=new HashMap();
Node head,tail;
class Node{
Node pre,next;
int val,key;
public Node(int key,int value){
this.key=key;
this.val=value;
}
public Node(){
this(0,0);
}
}
public LRUCache(int capacity) {
cap=capacity;
count=0;
head=new Node();
tail=new Node();
head.next=tail;
tail.pre=head;
}

public int get(int key) {
Node n = map.get(key);
if(n==null) return -1;
remove(n);
add(n);
return n.val;
}

public void put(int key, int value) {
Node n = map.get(key);
if(n==null){
n = new Node(key,value);
map.put(key,n);
add(n);
count++;
}else{
n.val=value;
remove(n);
add(n);
}if(count>cap){
Node del=tail.pre;
remove(del);
map.remove(del.key);
count--;
}
}

public void add(Node n){
Node after=head.next;
head.next=n;
n.pre=head;
n.next=after;
after.pre=n;
}
public void remove(Node n){
Node before=n.pre,after=n.next;
before.next=after;
after.pre=before;
}
}

生产者消费者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class ClothesFactory{
private final int size=10;
private LinkedList<Object> list = new LinkedList();
public void produce(){
synchronized(list){
while(list.size()>=size){
try{
list.wait();
}catch(InterruptedException e){
e.printStackTrace();
}
}
list.addFirst(new Object());
list.notifyAll();
}
}
public void comsume(){
synchronized(list){
while(list.size()<=0){
try{
list.wait();
}catch(InterruptedException e){
e.printStackTrace();
}
}
list.removeLast();
list.notifyAll();
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Producer implements Runnable{
private ClothesFactory clothesFactory;
public Producer(ClothesFactory clothesFactory){
this.clothesFactory=clothesFactory;
}
public void run(){
while(true){
try{
clothesFactory.produce();
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Comsumer implements Runnable{
private ClothesFactory clothesFactory;
public Comsumer(ClothesFactory clothesFactory){
this.clothesFactory=clothesFactory;
}
public void run(){
while(true){
try{
clothesFactory.comsume();
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
}

排序

分析时间复杂度

快排 √

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public void sort(int a[]){
int lo=0,hi=a.length-1;
if(lo<hi){
int mid = qs(a,lo,hi);
sort(a,lo,mid-1);
sort(a,mid+1,hi);
}
}
public void qs(int[] a,int lo,int hi){
int i=lo,j=hi,x=a[lo];
if(i<j){
while(i<j&&a[j]>=x)j--;
if(i<j) a[i]=a[j];
while(i<j&&a[i]<x)i++;
if(i<j) a[j]=a[i];
}
a[i]=x;
return i;
}

1、最优情况

在最优情况下,Partition每次都划分得很均匀,如果排序n个关键字,其递归树的深度就为 [log2n]+1( [x] 表示不大于 x 的最大整数),即仅需递归 log2n 次,需要时间为T(n)的话,第一次Partiation应该是需要对整个数组扫描一遍,做n次比较。然后,获得的枢轴将数组一分为二,那么各自还需要T(n/2)的时间(注意是最好情况,所以平分两半)。于是不断地划分下去,就有了下面的不等式推断:

img

2、最糟糕情况

然后再来看最糟糕情况下的快排,当待排序的序列为正序或逆序排列时,且每次划分只得到一个比上一次划分少一个记录的子序列,注意另一个为空。如果递归树画出来,它就是一棵斜树。此时需要执行n‐1次递归调用,且第i次划分需要经过n‐i次关键字的比较才能找到第i个记录,也就是枢轴的位置,因此比较次数为

快速选择 √

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public void select(int a[],int k){
int lo=0,hi=a.length-1;
k--;
if(lo<hi){
int mid = qs(a,lo,hi);
if(mid>k){
hi=mid-1;
}else if(mid==k){
break;
}else{
lo=mid+1;
}
}
}
public void qs(int[] a,int lo,int hi){
int i=lo,j=hi,x=a[lo];
if(i<j){
while(i<j&&a[j]>=x)j--;
if(i<j) a[i]=a[j];
while(i<j&&a[i]<x)i++;
if(i<j) a[j]=a[i];
}
a[i]=x;
return i;
}

堆排

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public void heap(int[] a){
int len = a.length;
build(a,len);
for(int i=len-1;i>0;i--){
int tmp=a[0];
a[0]=a[i];
a[i]=tmp;
len--;
sink(a,0,len);
}
}
public void build(int[] a,int len){
for(int i=len/2;i>=0;i--){
sink(a,i,len);
}
}
public void sink(int[] a,int i,int len){
int left=i*2+1;
int right=2*i+2;
int present=i;
if(left<len&&a[left]>a[present]) present=left;
if(right<len&&a[right]>a[present]) present=right;
if(present!=i){
int tmp=a[i];
a[i]=a[present];
a[present]=tmp;
sink(a,present,len);
}
}

在构建堆的过程中,因为我们是完全二叉树从最下层最右边的非终端结点开始构建,将它与其孩子进行比较和若有必要的互换,对于每个非终端结点来说,其实最多进行两次比较和互换操作,因此整个构建堆的时间复杂度为O(n)。

在正式排序时,第i次取堆顶记录重建堆需要用O(logi)的时间(完全二叉树的某个结点到根结点的距离为.log2i.+1),并且需要取n-1次堆顶记录,因此,重建堆的时间复杂度为O(nlogn)。

归并排序 √

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public void merge(int a[]){
return sort(a,0,a.length-1,new int[a.length]);
}
public void sort(int[] a,int lo,int hi,int[] tmp){
int lo=0,hi=a.length-1;
if(lo<hi){
int mid = lo+(hi-lo)/2;
sort(a,lo,mid,tmp);
sort(a,mid+1,hi,tmp);
mergeArray(a,lo,mid,hi,tmp);
}
}
public void mergeArray(int[] a,int lo,int mid,int hi,int[] tmp){
int i=lo,m=mid,j=mid+1,n=hi,k=0;
while(i<=m&&j<=n){
if(a[i]<=a[j]){
tmp[k++]=a[i++];
}else{
tmp[k++]=a[j++];
}
}
while(i<=m) tmp[k++]=a[i++];
while(j<=n) tmp[k++]=a[j++];
for(int l=0;l<=k;l++){
a[l+lo]=tmp[l];
}
}

公式:T[n] = 2T[n/2] + O(n);