王道论坛机试指南学习笔记(一)经典入门

1. 排序

2.1 快速排序

- 思想

  • 逐个元素读入
  • 为每个元素找到它应该在的位置
  • 将该位置之后的元素逐个向后移动

- 复杂度

  • n(logn)

- 代码

Scanner sc = new Scanner(System.in);
while (sc.hasNextLine()){
    int num = sc.nextInt();
    int[] elements = new int[num+1];
    int temp, index = 0;
    for (int i = 0; i < num; i++) {
        temp = sc.nextInt();
        if (i == 0) elements[i] = temp;
        else {
            //calculate index
            for (int j = 0; j < i; j++){
                index = i-1;
                while (index > -1 && temp < elements[index]){ index -= 1; }
            }
            //move elements
            for (int y = i+1; y > index+1; y--){
                elements[y] = elements[y-1];
            }
            elements[index+1] = temp;
        }
    }
    //print elements
    for (int i = 0; i < num; i++) {
        System.out.print(elements[i] + " ");
    }
    System.out.print('\n');
}

2. 日期问题

2.1 数据结构

import java.util.Scanner;

public class MyDate {
    private int day;
    private int month;
    private int year;

    public int getDay() { return day; }
    public int getMonth() { return month; }
    public int getYear() { return year; }
    
    public MyDate(){
        this.day = 1;
        this.month = 1;
        this.year = 0;
    }

    public MyDate(int y, int m, int d){
        this.year = y;
        this.month = m;
        this.day = d;
    }
    
    private static int[][] dayOfMonth = {{0,0},{31,31},{28,29},{31,31},{30,30},{31,31},{30,30},{31,31},{31,31},{30,30},{31,31},{30,30},{31,31}};
    
    private static int[][][] days = new int[3001][13][32];

    //取绝对值
    public static int ABS(int x){ return (x>0)?x:-x;}
}

2.2 判断闰年

private static int ISLEAP(int year){
    return (year%400==0)||(year%100!=0 && year%4==0)?1:0;
}

2.3 获取下一天

private void nextDay(){
        this.day++;
        if(this.day>dayOfMonth[this.month][ISLEAP(this.year)]){
            this.day = 1;
            this.month++;
            if(this.month >12){
                this.month = 1;
                this.year++;
            }
        }
    }

2.4 计算间隔日期

public static void initDays(){
    MyDate d = new MyDate();
    int count = 0;
    while (d.year<3001){
        days[d.year][d.month][d.day] = count;
        count++;
        d.nextDay();
    }
}
public void calDays(){
    MyDate.initDays();
    Scanner sc = new Scanner(System.in);
    while (sc.hasNextLine()){
        MyDate[] myDates = new MyDate[2];
        int year = 0, month = 1, day = 1;
        for (int i = 0; i < 2; i++) {
            String str = sc.nextLine();
            year = Integer.parseInt(str.substring(0,4));
            month = Integer.parseInt(str.substring(4,6));
            day = Integer.parseInt(str.substring(6,8));
            myDates[i] = new MyDate(year, month, day);
        }
        int d1 = myDates[0].getdays(), d2 = myDates[1].getdays();
        int result = MyDate.ABS(d2-d1);
        System.out.println(result);
    }
}

3. HashMap

3.1 内部结构

  • 使用数组+链表结合的方式进行存储
  • 元素存储到数组的规则:
    • hash(key)%len
  • 内部有静态类 Entry
    • 属性:key, value, next
    • 数组就是 Entry[],长度必须为2的次方

![image-20190306082033206](/Users/mustafa/Library/Application Support/typora-user-images/image-20190306082033206.png)

3.2 存取实现

  • 每个key的hash是一个固定的int值

    • 根据键的hash值找到其在Entry[]数组中的下标,进行存取
    • 因此,下标相同的key,hashcode不一定相同
    int hash = key.hashCode(); 
    int index = hash % Entry[].length;
    Entry[index] = value; //存
    return Entry[index]; //取
    
  • 当key的哈希值对容量的余数相同时,A和B在Entry[]中有相同的index

    B.next = A;
    Entry[index] = B;
    
  • 插入key相同的对象时,新数据会覆盖旧数据

  • 当键值对链表的size>threshold时,哈希表会动态扩容

    void addEntry(int hash, K key, V value, int bucketIndex) {
        Entry<K,V> e = table[bucketIndex];
        table[bucketIndex] = new Entry<K,V>(hash, key, value, e); //参数e是Entry.next
        //如果size超过threshold,则扩充table大小,再散列
        if (size++ >= threshold)
            resize(2 * table.length);
    }
    void resize(int newCapacity) {  
        Entry[] newTable = new Entry[newCapacity];  
        transfer(newTable);  
        table = newTable;  
        threshold = (int)(newCapacity * loadFactor);  
    } 
    //将原来的数据转移到新数组中
    void transfer(Entry[] newTable) {  
        Entry[] src = table;  
        int newCapacity = newTable.length;  
        for (int j = 0; j < src.length; j++) {  
            Entry<K,V> e = src[j];  
            if (e != null) {  
                src[j] = null;  
                do {  
                    Entry<K,V> next = e.next;  
                    int i = indexFor(e.hash, newCapacity);  
                    e.next = newTable[i];  
                    newTable[i] = e;  
                    e = next;  
                } while (e != null);  
            }  
        }  
    }
    
  • get()时,先定位key在Entry[]中的下标,再遍历此处链表进行查找

  • null key总是存放在Entry[]的第一个元素

3.3 基本用法

//声明
HashMap<Integer, String> map = new HashMap<>();
//插入数据
map.put(1,"a");
//查询是否含有 key/value
boolean result = map.containsKey(1);
boolean r = map.containsValue("a");
//查询哈希表大小
int size = map.size();
//获取某个key对应的value
String str = map.get(1);
//获取哈希表中的 value 数组
List<String> s = (List<String>) map.values();

3.4 遍历

HashMap<Integer, Integer> resultMap = new HashMap<>();
Iterator i = resultMap.entrySet().iterator();
while (i.hasNext()){
    Map.Entry entry = (Map.Entry)i.next();
    int key = (int)entry.getKey();
    int val = (int)entry.getValue();
}

3.5 专用名词

编号 名称 解释
1 hashCode 根据某种映射关系(哈希函数)将key映射为一个int型的哈希值
2 Entry[] 存储内部静态对象的数组,具有一个初始容量
3 bucket 在Entry[]数组中,每个下标对应一个桶,用来存储哈希值相同的对象
4 equals 比较函数,在指定桶中寻找key符合目标值的对象
5 capacity 容量,哈希表数组的长度
6 loadFactor 负载因子,小于1的浮点数(默认0.75),当哈希表在其容量自动增加之前可以达到的一种尺度
7 threshold capacity*loadFactor,最多容纳的Entry数,元素数大于它时需要扩容
8 rehash 再哈希,当哈希表中的条目数>容量*负载因子(填满75%的bucket),将数组容量翻倍,对每个条目重新进行哈希、计算桶号进行分配

3.6 面试问题

  • HashMap的工作原理?
    • 基于hashing原理,我们通过put()和get()方法储存和获取对象。
    • 将键值对传递给put()方法时,它调用键对象的hashCode()方法来计算hashcode,让后找到bucket位置来储存值对象。当获取对象时,通过键对象的equals()方法找到正确的键值对,然后返回值对象。
    • HashMap使用LinkedList来解决碰撞问题,当发生碰撞了,对象将会储存在LinkedList的下一个节点中。
    • HashMap在每个LinkedList节点中储存键值对对象。
  • 为什么String, Interger这样的wrapper类适合作为键?
    • 因为String是不可变的,也是final的,而且已经重写了equals()和hashCode()方法了。
    • 不可变性是必要的,因为为了要计算hashCode(),就要防止键值改变,如果键值在放入时和获取时返回不同的hashcode的话,那么就不能从HashMap中找到你想要的对象。
    • 不可变性还有其他的优点如线程安全。
  • 我们可以使用自定义的对象作为键吗?
    • 可以使用任何对象作为键,只要它遵守了equals()和hashCode()方法的定义规则,并且当对象插入到Map中之后将不会再改变了。
    • 如果这个自定义对象时不可变的,那么它已经满足了作为键的条件,因为当它创建之后就已经不能改变了。

4. 查找

4.1 二分法

- 思想

  • 递归调用函数
  • 每次递归判断中间下标元素是否是目标元素
  • 如不匹配,根据大小关系向左、右递归

- 复杂度

  • logn

- 代码

public void biSearch(){
        int num = 5, target = 8, left = 0, right = 0, mid = 0, index = 0;
        right = num-1;
        mid = (left+right)/2;
        int[] arr = {1,2,4,6,8};
        while (right>=left){
            if(target==arr[mid]){
                System.out.println("Done, result is "+mid);
                break;
            }
            else if(target<arr[mid]){
                right = mid-1;
            }
            else {
                left = mid+1;
            }
            mid = (left+right)/2;
        }
        System.out.println("Not done, result is -1");
    }

猜你喜欢

转载自blog.csdn.net/qq_42290765/article/details/89845665