大数相加和验证回文字符和LRU缓存机制
前言
昨天刷题刷的有一些些累,所以周末决定休息一下,总结一下之前做过的一些题目,顺便复习一下知识点。
今天这篇主要总结一下做大数相加这道题时候出现的错误以及复习一下之前遇到过的一些比较有意思的题目。
大数相加
题目非常简单,就是给你两个非常大的数字,让你返回这两个数字相加之后的和。
一般来说这种题目我们都是从两个字符串的最后一位开始加起,判断一下是否需要进位,需要的话就把结果减去10,然后把进位加到前面两位的相加和中。
说的挺简单,那我这次错哪了呢?
前面其实没什么问题,主要问题出现在两个地方。
字符数组的返回值
char[] res = {'1','2','3'};
return res.toString();
猜猜上面这段代码返回的是什么?是不是"123"
?
好吧,居然返回的是数组的地址。看来数组的toString()
函数不能乱用,没有覆盖的话会返回数组的地址,这和数组是什么类型的没有关系。
多余的字符串怎么处理
大数问题在处理两个字符串不一样长的时候,我们需要把剩下的字符串直接加到结果数组上去。那我是怎么写的呢?
if(i>=0){
int sum = chars1[i--]-'0'+flag;
if(sum >= 10){
flag = 1;
sum -= 10;
}else{
flag = 0;
}
res[index--] = (char)(sum + '0');
}
if(j>=0){
int sum = chars2[j--]-'0'+flag;
if(sum >= 10){
flag = 1;
sum -= 10;
}else{
flag = 0;
}
res[index--] = (char)(sum + '0');
}
如果字符串长度还大于0的话就执行,没毛病。于是我就真的写了if
,这样只会判断一次呀,万一有好几位,你就加一次就算了嘛?
说起来这个错误我好像在上次写归并的时候就错了一次了,没想到这次又错了。
惭愧惭愧。
正确代码
class Solution {
public String addStrings(String num1, String num2) {
if(num1 == null && num2 == null)
return "0";
if(num1 == null)
return num2;
if(num2 == null)
return num1;
char[] chars1 = num1.toCharArray();
char[] chars2 = num2.toCharArray();
char[] res = new char[Math.max(chars1.length,chars2.length)];
//进位标志
int flag = 0;
//当前相加的位
int i=chars1.length-1,j=chars2.length-1;
//当前结果的位
int index = res.length - 1;
while(i>=0&&j>=0){
int sum = chars1[i--]-'0'+chars2[j--]-'0'+flag;
if(sum >= 10){
flag = 1;
sum -= 10;
}else{
flag = 0;
}
res[index--] = (char)(sum + '0');
}
while(i>=0){
int sum = chars1[i--]-'0'+flag;
if(sum >= 10){
flag = 1;
sum -= 10;
}else{
flag = 0;
}
res[index--] = (char)(sum + '0');
}
while(j>=0){
int sum = chars2[j--]-'0'+flag;
if(sum >= 10){
flag = 1;
sum -= 10;
}else{
flag = 0;
}
res[index--] = (char)(sum + '0');
}
if(flag == 0)
return String.valueOf(res);
return "1"+String.valueOf(res);
}
}
验证回文字符串(其二)
给出一个非空字符串,最多删除一个字符,是否能成为一个回文字符串。
看到这道题,我的第一反应是暴力解决,先看这个字符是不是回文字符串,如果是的话就直接返回,否则依次删除每个字符,看是否是回文字符串。
虽然我没有尝试,但是直觉告诉我这么写一定会超时。
那么简单一点的方法是怎么做的呢?
其实也不复杂,还是使用双指针的方式(不要乱用i++),如果左右指针的字符相同,那么缩进。
如果不同,那么判断删除左指针的字符后是否能构成回文字符串,再判断右指针删除之后能否构成回文字符串。
具体代码如下:
class Solution {
public boolean validPalindrome(String s) {
if(s == null || s.length() <= 1)
return true;
int left = 0;
int right = s.length()-1;
while(left < right){
if(s.charAt(left) == s.charAt(right)){
left++;
right--;
}else{
boolean leftFlag = true,rightFlag = true;
int tmpLeft = left+1;
int tmpRight = right;
while(tmpLeft < tmpRight){
if(s.charAt(tmpLeft) == s.charAt(tmpRight)){
tmpLeft++;
tmpRight--;
}else{
leftFlag = false;
break;
}
}
tmpLeft = left;
tmpRight = right-1;
while(tmpLeft < tmpRight){
if(s.charAt(tmpLeft) == s.charAt(tmpRight)){
tmpLeft++;
tmpRight--;
}else{
rightFlag = false;
break;
}
}
return leftFlag || rightFlag;
}
}
return true;
}
}
LRU缓存机制
题目大意:实现一个LRU缓存机制,使得每次进入元素的时候保存其值,如果内存不够则淘汰一个最近最不经常使用的元素。
题目还是比较经典的,我在别人的面经中也看到过这个问题。LRU在缓存淘汰中都是一个比较重要的算法,Redis、操作系统中的cache、内存都用到了这个经典的算法。
但是大多数教科书上都只是对这个算法的机制做了简单说明(因为机制真的很简单),具体怎么实现的却没有提及。其实这个实现方式还是有一点巧妙的。
具体来说就是使用两个数据结构——哈希表和双向链表。
每当有元素进入的时候新建一个链表节点,并且把这个节点加入链表头(链表尾也行,但是要规定最后进入的元素在一侧),然后再把这个节点保存到哈希表中,这样当访问的时候能够迅速取出这个节点而不用浪费时间查询。
当访问元素的时候先从哈希表中将节点取出,然后将这个节点移动到链表头。
当空间不够需要进行淘汰的时候,淘汰链表的尾结点(说明最久没有使用),然后将哈希表中的对应键值对移除。
说起来还是比较简单的,那就来实现以下吧
class LRUCache {
class LRUNode{
int value;
//保存键值,省的删除的时候找不对应的map
int key;
LRUNode pre;
LRUNode next;
public LRUNode(int key,int value){
this.key = key;
this.value = value;
}
}
int capacity = 0;
int size = 0;
HashMap<Integer,LRUNode> map;
LRUNode headNode;
LRUNode tailNode;
public LRUCache(int capacity) {
this.capacity = capacity;
map = new HashMap<>();
headNode = new LRUNode(0,0);
tailNode = new LRUNode(0,0);
tailNode.pre = headNode;
headNode.next = tailNode;
}
public int get(int key) {
if(map.containsKey(key)){
LRUNode node = map.get(key);
removeToHead(node);
return node.value;
}else{
return -1;
}
}
public void put(int key, int value) {
if(map.containsKey(key)){
LRUNode node = map.get(key);
//这里主要put的时候如果已经有节点了,记得要更新节点的值
node.value = value;
removeToHead(node);
}else{
LRUNode node = new LRUNode(key,value);
map.put(key,node);
node.next = headNode.next;
LRUNode next = headNode.next;
next.pre = node;
node.pre = headNode;
headNode.next = node;
size++;
if(size > capacity){
LRUNode deletNode = tailNode.pre;
//删除节点的时候记得要把map里面的记录也移除
map.remove(deletNode.key);
tailNode.pre = deletNode.pre;
tailNode.pre.next = tailNode;
deletNode = null;
size--;
}
}
}
private void removeToHead(LRUNode node){
//移除当前节点
LRUNode next = node.next;
node.pre.next = next;
next.pre = node.pre;
//把当前节点加入链表头
node.next = headNode.next;
headNode.next.pre = node;
node.pre = headNode;
headNode.next = node;
}
}
最后再总结一下注意的小细节:
- put的时候记得更新节点的值
- 删除的时候记得移除map中的记录
后记
今天做了两道新题,复习了一道旧的题目。
晚点的时候再做一下今天的SQL题目,先久违的看一会书吧。