文章目录
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");
}