前言
昨天参加了阿里云的笔试,笔试是单独给我发了一个链接,不像是统一的笔试,两个小时做两个题也是我没有想到的,最后还是没有做完。所以就想着来整理以下这两个题目,下面就开始了。
LRU缓存
题目
- 第一题:
- 实现一个 LRU 缓存,支持指定缓存大小,支持 get 和 put 操作。使用频率越低的数据放在越后面,若缓存满了,删除最后面的数据。(语言不限)
- 输入:操作序列,包括 get 和 put 操作。
- 输出:根据 get 操作返回对应的值,put 操作无输出。
- 要求:时间复杂度为 O(1)。需要考虑编程习惯,包括但不限于可读性、可维护性、命名规范、代码风格等。
分析
- 这个题是力扣第147题,如果我们要实现时间复杂度为O(1)的要求,而且要满足使用频率越低的数据放在最后,也就是说当我们put一个数据之后,首先需要查询这个数据是否存在,如果存在还需要把这个数据放到最前面,其次是get方法,也需要我们查找到一个数据,再返回。
- 我们能够想到的查询方法基本上不会有O(1)的存在,所以这里我们需要借助多个数据结构,比如用一个HashMap记录每一个节点的id和节点,用一个双向链表记录节点之间的相对位置。每次get节点,就用这个id来获取节点信息,可以直接在哈希映射(Hashmap)里面查找,这个复杂度为O(1),找到之后获取对应的节点,然后输出节点的value。
- 在实现get功能的时候,我们也要知道怎么定义一个节点,也就是说节点类里面有什么信息,就像定义二叉树的节点一样,双向链表的节点如何定义呢?那就看以下代码:
class DlinkedNode{
int key;
int value;
DlinkedNode prev;
DlinkedNode next;
DlinkedNode(){}
DlinkedNode(int key_,int value_){
key = key_;
value = value_;
}
}
由此能够构建一个双向链表。
代码
public class LRUCache {
// 正是因为链表节点有向前和向后的指针,所以当我们在HashMap中找到他的时候,我们同样能够在双向链表中找到他的位置
class DLinkedNode {
int key;
int value;
DLinkedNode prev;
DLinkedNode next;
public DLinkedNode() {}
public DLinkedNode(int _key, int _value) {key = _key; value = _value;}
}
// 用HashMap来存储节点和id, 一个LRUCache就包括以下内容,首先有一个HashMap
// 其次就是双向链表所需要的头指针与尾指针,再就是Cache的容量和目前已有的数据个数
private Map<Integer, DLinkedNode> cache = new HashMap<Integer, DLinkedNode>();
private int size;
private int capacity;
private DLinkedNode head, tail;
//初始化的时候就得将各个变量都初始化,而且形成双向链表
public LRUCache(int capacity) {
this.size = 0;
this.capacity = capacity;
// 使用伪头部和伪尾部节点
head = new DLinkedNode();
tail = new DLinkedNode();
head.next = tail;
tail.prev = head;
}
public int get(int key) {
DLinkedNode node = cache.get(key);
if (node == null) {
return -1;
}
// 如果 key 存在,先通过哈希表定位,再移到头部
moveToHead(node);
return node.value;
}
public void put(int key, int value) {
DLinkedNode node = cache.get(key);
if (node == null) {
// 如果 key 不存在,创建一个新的节点
DLinkedNode newNode = new DLinkedNode(key, value);
// 添加进哈希表
cache.put(key, newNode);
// 添加至双向链表的头部
addToHead(newNode);
++size;
if (size > capacity) {
// 如果超出容量,删除双向链表的尾部节点
DLinkedNode tail = removeTail();
// 删除哈希表中对应的项
cache.remove(tail.key);
--size;
}
}
else {
// 如果 key 存在,先通过哈希表定位,再修改 value,并移到头部
node.value = value;
moveToHead(node);
}
}
private void addToHead(DLinkedNode node) {
node.prev = head;
node.next = head.next;
head.next.prev = node;
head.next = node;
}
private void removeNode(DLinkedNode node) {
node.prev.next = node.next;
node.next.prev = node.prev;
}
// 想要把一个节点移到头部,就得先删除这个节点,再加入这个节点
private void moveToHead(DLinkedNode node) {
removeNode(node);
addToHead(node);
}
private DLinkedNode removeTail() {
DLinkedNode res = tail.prev;
removeNode(res);
return res;
}
}
符号排序
题目
-
第二题:
// 给出一个长度为N的数组,每个元素可能是数字或者字母(数字、字母不混合),
// 请用自己最熟悉、且性能较高的方式进行排序,输出两个数组。
// 要求:
// 1、数字数组从小到大排序,字母数组从A到Z排序(字母不区分大小写)
// 2、如果是多个字母,从首字母开始按顺序逐个从A到Z排序。 -
示例1:
// 输入:
// {“1”, “3”, “2”, “1”,”C”,”A”};
// 输出:
// {“1”,”1”,”2”,“3”};
// {“A”,“C”}; -
示例2:
// 输入:
// {“11”,“333”,“BC”,“1”,“BA”,“A”};
// 输出:
// {“1”,“111”,“333”};
// {“A”, “BA”,“BC”};
分析
我在这个题目卡住的地方就是排序算法。所以我重点记录我的排序算法,大致思路也就是把数字和字符串分组,分组之后各自排序,然后输出,题目挺简单的,就是排序算法自己是真的不熟,也没有写好。这里有两种路径,一是自己写排序算法,二是重写Arrays.sort()的排序规则。
自己写排序算法
快速排序
public class QuickSort {
public static void main(String[] args) {
int[] nums = {3,4,5,2,6,7,8,2,1};
quickSort(nums,0,nums.length-1);
for(int num:nums){
System.out.print(num+" ");
}
}
public static void quickSort(int[] nums,int L,int R){
/*
主要思想:分治法
1. 随机选取一个pivot值
2. 将所有小于pivot的值放在它的左边
3. 将所有大于pivot的值放在它的右边
4. 对所得到的左右两个子序列分别进行上面三部操作
*/
/*
具体怎么放:
1. 习惯取第一个值为pivot,最左边为left,最右边为right
2. 从right开始往左遍历,直到碰到比pivot小的数,将这个数放在left位置
3. 从left开始往右遍历,直到碰到比pivot大的数,将这个数放在right位置
4. 重复2、3,直到left==right,将pivot放到这个位置
*/
// 这里必须是大于等于,不能是等于
if(L>=R) return;
int left = L,right = R;
int pivot = nums[left];
while(left<right){
// 这里的判断必须加上
while(nums[right]>=pivot && left<right) right--;
if(nums[right]<pivot) nums[left] = nums[right];
while (nums[left]<=pivot && left<right) left++;
if(nums[left]>pivot) nums[right] = nums[left];
// 这里必须是大于等于,不能是等于
if(left>=right) nums[left] = pivot;
}
quickSort(nums,L,left-1);
quickSort(nums,left+1,R);
}
}
冒泡排序
public class 冒泡排序 {
public static void main(String[] args) {
int[] nums = {3,4,5,2,6,7,8,8,8,2,1};
sort(nums);
for(int num:nums){
System.out.print(num+" ");
}
}
public static void sort(int[] nums){
/*
主要思想:
外层进行N-1此迭代,每次迭代都能确定一个最大值的位置(如果是升序排序)
内层如何迭代:从第一个位置到第N-i个位置,i为已经确定位置的个数,每次比较当前元素和下一个元素,让大的放后面
*/
for(int i = 0;i<nums.length-1;i++){
for(int j = 0;j<nums.length-i-1;j++){
if(nums[j]>nums[j+1]){
int temp = nums[j];
nums[j] = nums[j+1];
nums[j+1] = temp;
}
}
}
}
}
如何修改Arrays.sort()的排序方式
import java.util.Arrays;
import java.util.Comparator;
public class Arrays的排序 {
public static void main(String[] args) {
int[][] nums = {{1,5},{2,3},{3,4}};
Arrays.sort(nums,new Comparator<int[]>(){
@Override
public int compare(int[] a,int[] b){
return a[1]-b[1];
}
});
String[] nums1 = {"sbc","csd","aaa","aaabc","csw"};
// 这里实现了一个字符串的比较算法
Arrays.sort(nums1,new Comparator<String>(){
@Override
public int compare(String str1,String str2){
int s1 = 0,s2 = 0;
while(s1<str1.length() && s2<str2.length()){
if(str1.charAt(s1)==str2.charAt(s2)){
s1++;
s2++;
}else {
return str1.charAt(s1)-str2.charAt(s2);
}
}
// 按照第一个数减去第二个数的规律,就是升序
return ((s1>=str1.length())?0:str1.charAt(s1)) - ((s2>=str2.length())?0:str2.charAt(s2));
}
});
}
}