【Day3】每天三算法
题目
WC134 设计 LRU
题目
设计LRU(最近最少使用)缓存结构,该结构在构造时确定大小,假设大小为 k ,并有如下两个功能
1、 set(key, value):将记录(key, value)插入该结构
2、get(key):返回key对应的value值
提示:
1.某个key的set或get操作一旦发生,认为这个key的记录成了最常使用的,然后都会刷新缓存。
2.当缓存的大小超过k时,移除最不经常使用的记录。
3.输入一个二维数组与k,二维数组每一维有2个或者3个数字,第1个数字为opt,第2,3个数字为key,value
若opt=1,接下来两个整数key, value,表示set(key, value)
若opt=2,接下来一个整数key,表示get(key),若key未出现过或已被移除,则返回-1
对于每个opt=2,输出一个答案
4.为了方便区分缓存里key与value,下面说明的缓存里key用""号包裹
要求:set和get操作复杂度均为O(1)
思考
LRU 中文全称是最近最少用算法
即在队列满的时候呀,将最远一次使用的空间剔除,放入新的空间
LRU 的实现是有固定套路的,我们从数据结构选型和实现来讲讲
- 数据结构选型:
我们需要 Hash 表存储数据,以达到 O(1)
的访问时间复杂度
为了是我们的数据有序,还必须借助链表,为了方便增删,需要使用双向链表
为了让 Map 的 val 值形成双向链表,我们还要自己封装 val 值
class Node {
public int key;
public int val;
public Node next,pre;
public Node(int key, int val) {
this.key = key;
this.val = val;
}
public Node() {
}
}
总结一下,设计 LRU 需要的数据结构为:哈希表和双向链表(自定义类不是数据结构)
- 实现方式:
双向链表头为最近访问,尾为最远访问
每当有元素被 set 或者 get 时,都要将该元素置于双向链表头
具体的方式,还是请看代码
题解
public class WC134_LRU {
public static void main(String[] args) {
WC134_LRU lru = new WC134_LRU();
lru.LRU(null,3);
lru.set(1,1);
lru.set(2,2);
lru.set(3,3);
System.out.println(lru);
lru.get(2);
System.out.println(lru);
lru.set(4,4);
System.out.println(lru);
}
private Map<Integer, Node> map = new HashMap<>();
private Node head = new Node(-1, -1);
private Node tail = new Node(-1, -1);
private int k;
public int[] LRU (int[][] operators, int k) {
this.k=k;
head.next=tail;
tail.pre=head;
ArrayList<Integer> res = new ArrayList<>();
// 1 为 set 2 为 get
for (int[] operator : operators) {
if (operator[0]==1) {
set(operator[1],operator[2]);
} else {
res.add(get(operator[1]));
}
}
int[] arr = new int[res.size()];
for (int i = 0; i < arr.length; i++) {
arr[i]=res.get(i);
}
return arr;
}
public void set(int key,int val) {
// 当前 key 已存在
if (get(key)!=-1) {
// 更新 value 值
map.get(key).val=val;
} else {
// 超出空间,移除最不常用
if (k==map.size()) {
removeLastUse();
}
Node node = new Node(key, val);
map.put(key,node);
makeRecent(node);
}
}
public int get(int key) {
Node node = map.get(key);
if (node!=null) {
node.pre.next=node.next;
node.next.pre=node.pre;
makeRecent(node);
return node.val;
}
return -1;
}
// 去除最近不用的元素
private void removeLastUse() {
int rk = tail.pre.key;
tail.pre.pre.next=tail;
tail.pre=tail.pre.pre;
map.remove(rk);
}
// 设置为最近访问
private void makeRecent(Node node) {
node.pre=head;
node.next=head.next;
head.next=node;
node.next.pre=node;
}
class Node {
public int key;
public int val;
public Node next,pre;
public Node(int key, int val) {
this.key = key;
this.val = val;
}
public Node() {
}
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
Node tmpHead=head.next;
while (tmpHead!=tail) {
sb.append(tmpHead.val)
.append("->");
tmpHead=tmpHead.next;
}
return sb.toString();
}
}
小结
以后碰到手撕 LRU 的问题,要注意一下几个点:
- 数据结构选型为 : 哈希表,双向链表
- val 值要自定义,以满足构建双向链表的条件
WC133 子数组的最大累加和问题
题目
给定一个长度为 n 的数组 arr ,返回其中任意子数组的最大累加和
题目保证没有全为负数的数据
思考
我们可以使用暴力算法,两个 for 循环搞定
但是更好的办法是使用动态规划:
dp[i]表示的是包含当前位置的情况下,累加可以获得的最大值
状态转换方程 : dp[i]=max(dp[i],dp[i-1]+dp[i])
题解
public int maxsumofSubarray (int[] arr) {
int max=arr[0];
for (int i = 1; i < arr.length; i++) {
arr[i]=Math.max(arr[i],arr[i]+arr[i-1]);
max=Math.max(max,arr[i]);
}
return max;
}
WC135 两个链表生成相加链表
题目
假设链表中每一个节点的值都在 0 - 9 之间,那么链表整体就可以代表一个整数。
给定两个这种链表,请生成代表两个整数相加值的结果链表。
例如:链表 1 为 9->3->7,链表 2 为 6->3,最后生成新的结果链表为 1->0->0->0。
思考
步骤如下:
1、两个链表反转
2、遍历相加,如果结果为两位数,保留十位(额外设置一个变量保存)
3、不断进行相加,同时要加上前一位相加后多出的位数
4、当有一方结束的时候,另一方继续相加
5、当所有链表都结束后,如果溢出保留为不为0,再创建一个节点进行尾插
6、链表反转
题解
public class WC135_addInList {
public static void main(String[] args) {
WC135_addInList wc135_addInList = new WC135_addInList();
ListNode n1 = new ListNode(1);
ListNode n2 = new ListNode(2);
ListNode n3 = new ListNode(3);
n1.next=n2;
n2.next=n3;
ListNode n4 = new ListNode(4);
ListNode n5 = new ListNode(5);
n4.next=n5;
ListNode head = wc135_addInList.addInList(n1, n4);
while (head!=null) {
System.out.print(head.val+" ");
head=head.next;
}
}
public ListNode addInList (ListNode head1, ListNode head2) {
head1 = reverse(head1);
head2 = reverse(head2);
// 保存溢出位
int cache = 0;
ListNode head = new ListNode();
ListNode tail = head;
while (head1!=null && head2!=null) {
int sum = head1.val + head2.val + cache;
cache=sum/10;
ListNode node = new ListNode();
node.val=sum%10;
// 尾插
tail.next=node;
tail=tail.next;
head1=head1.next;
head2=head2.next;
}
while (head1!=null) {
int sum = head1.val + cache;
cache=sum/10;
ListNode node = new ListNode();
node.val=sum%10;
// 尾插
tail.next=node;
tail=tail.next;
head1=head1.next;
}
while (head2!=null) {
int sum = head2.val + cache;
cache=sum/10;
ListNode node = new ListNode();
node.val=sum%10;
// 尾插
tail.next=node;
tail=tail.next;
head2=head2.next;
}
if (cache!=0) {
ListNode node = new ListNode();
node.val=cache;
// 尾插
tail.next=node;
tail=tail.next;
}
ListNode tmp = head.next;
head.next=null;
return reverse(tmp);
}
public ListNode reverse(ListNode head) {
ListNode pre=null,curr=head;
while (curr!=null) {
ListNode tmp = curr.next;
curr.next=pre;
pre=curr;
curr=tmp;
}
return pre;
}
}