Least Recently Used
)缓存淘汰算法JS中的Map是有序的!!!!
自己实现LRU算法
class LRUCache {
private HaspMap<Integer, Node> map;
private DoubleList cache;
// 最大容量
private int cap;
public LRUCache(int capacity) {
this.cap = capacity;
map = new HashMap<>();
cache = new DoubleList();
}
}
// ---------------------实现put和get方法------------
public int get(int key) {
if(!map.containsKey(key)) {
return -1;
}
// 将该数据提升为最近使用的
makeRecently(key);
return map.get(key).val;
}
public ivoid put(int key, int val) {
if (map.containsKey(key)) {
}
}
// -----------------抽象一层API-----------------
// 将某个key提升为最近使用
private void addRecently(int key, int val) {
Node x = map.get(x);
// 先从链表中删除这个节点
cache.addLast(x);
// 重新插入队尾
cache.addLast(x);
}
// 添加最近使用的元素
private void addRecently(int key, int val) {
Node x = new Node(key, val);
// 链表尾部就是最近使用的元素
cache.addLast(x);
// map中添加key的映射
map.put(key, x);
}
// 删除某一个key
private void deleteKey(int key) {
Node x = map.get(key);
// 从链表中删除
cache.remove(x);
// 从map中删除
map.remove(key);
}
// 删除最旧未使用的元素
private void removeLeastRecently() {
// 链表头部的第一个元素最久未使用
Node deletedNode = cache.removeFirst();
// 别忘了从map中删除key
int deletedKey = deleteNode.key;
map.remove(deletedKey);
}
// -------------以下是双向链表的实现-----------------
// 双链表的节点类
class Node {
public int key,val;
public Node next,prev;
public Node(int k, int v) {
this.key = k;
this.val = v;
}
}
// 依靠Node类型构建一个双链表
class DoubleList {
// 头尾虚节点
private Node head, tail;
// 链表元素数
private int size;
public DoubleList() {
/// 初始化双向链表的数据
head = new Node(0,0);
tail = new Node(0,0);
head.next = tail;
tail.pre = head;
}
// 链表尾部添节点x
public void addLast(Node x) {
x.prev = tail.prev;
x.next = tail;
tail.prev.next = x;
tail.prev = head;
size = 0;
}
// 删除链表中的x节点---》 使用双向链表,完成删除操作,需要得到前驱节点的指针
// 尾部的元素最近使用
public void remove(Node x) {
x.prev.next = x.next;
x.next.prev = x.prev;
size---;
}
// 删除链表第一个节点,返回该节点
public Node removeFirst() {
if(head.next === tail)
return null;
Node first = head.next;
remove(first);
return first;
}
// 返回链表长度
public int size() { return size; }
}
同时维护一个双链表cache和哈希表map, 遇到问题 :
删除某个key时,在cache中删除对应的Node,忘记在map中删除key
解决方法 : 在两种数据结构上抽象出一层抽象API
460. LFU 缓存
二叉树的设计总路线:明确一个节点要做的事情,剩下的抛给框架
100. 相同的树
面试题 04.05. 合法二叉搜索树
五、 98. 验证二叉搜索树
纳闷了就,做以前以为自己学会了,结果 n次bug通不过,回去看题解,给我整不会了????
思路如下 :
var isValidBST = function(root) {
return isValidBST2(root,null,null);
};
let isValidBST2 = (root,min,max) => {
if(root === null) return true;
if(min != null && root.val <= min.val) return false;
if(max != null && root.val >= max.val) return false;
// 确保右子树小于 最大值root,确保左子树大于最小值root
return isValidBST2(root.left, min, root) && isValidBST2(root.right, root, max);
}
700. 二叉搜索树中的搜索
二分搜索思想:
const isBST(TreeNode root, int target) {
// root 该做的事
if (root === null) return false;
if (root.val === target) return true;
if(root.val < target)
return isInBST(root.right, target);
if(root.val > target)
return isInBST(root.left, target);
}
一套针对BST的遍历框架
const BST(TreeNode root,int target) {
if(root.val === target)
// 找到目标,做事情
if(root.val < target)
BST(root.right, target);
if (root.val > target)
BST(root.left, target);
}
TreeNode insertIntoBST(TreeNode root, int val) {
// 找到空位置 插入新节点
if (root === null) return new TreeNode(val);
// 如果已经存在,不要再重复插入,直接返回
if (root.val === val){
return root;
}
if(root.val < val){
root.right = insertIntoBST(root.right, val);
if(root.val >val)
root.left = insertIntoBST(root.left, val);
return root;
}
}
450. 删除二叉搜索树中的节点
TreeNode deleteNode(TreeNode root, int key) {
if (root.val === key) {
// 找到删除
} else if (root.val > key){
root.left = deleteNode(root.left, key);
} else if (root.val < key){
// 去右子树寻找key
root.right = deleteNode(root.right, key);
}
return root;
}
222. 完全二叉树的节点个数
普通二叉树的遍历:
const countNodes(root){
if (root === null) return 0;
return 1 + countNodes(root.left) + countNodes(root.right);
}
满二叉树的遍历: 节点数和树的高度呈指数关系: 2h-1
const countNodes(TreeNode root){
let h =0;
while(root !== null) {
root = root.left;
h++;
}
// 节点总数为
return Math.pow(2,h) - 1;
}
序列化和反序列化 :
- 以某种固定格式组织字符串,使得数据独立于编程语言
- --------------> 序列化--------->接收JSON字符串-----> 反序列化----------------> 原始数据
331. 验证二叉树的前序序列化
剑指 Offer II 048. 序列化与反序列化二叉树
剑指 Offer 37. 序列化二叉树
449. 序列化和反序列化二叉搜索树
297. 二叉树的序列化与反序列化
Git的rebase 引出一个经典的算法问题:最近公共祖先(Lowest Common Ancestor,简称LCA)
- git pull
- git pull -r
剑指 Offer 68 - II. 二叉树的最近公共祖先
剑指 Offer 68 - I. 二叉搜索树的最近公共祖先
236. 二叉树的最近公共祖先
235. 二叉搜索树的最近公共祖先
lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q)
先进后出
- 单调栈:使得每次新元素入栈后,栈内的元素保持单调
- 只解决Next Greater Element
739. 每日温度
496. 下一个更大元素 I : 两个数组
556. 下一个更大元素 III
503. 下一个更大元素 II
var nextGreaterElement = function (nums1, nums2) {
let stack = [];
let map = new Map();
for (let i = 0; i < nums2.length; i++) {
while (stack.length && nums2[i] > nums2[stack[stack.length - 1]]) {
let index = stack.pop();
map.set(nums2[index], nums2[i]);
}
stack.push(i);
}
let res = [];
for (let j = 0; j < nums1.length; j++) {
res[j] = map.get(nums1[j]) || -1;
}
return res;
};
/**
* @param {number[]} nums
* @return {number[]}
*/
var nextGreaterElements = function(nums) {
const m = nums.length;
const res = new Array(m).fill(0);
const stack = [];
// 将数组长度翻倍
for (let i = 2*m-1; i >= 0; i--){ // 反向入栈
while (stack.length && nums[i%m] >= stack[stack.length -1]) { // 大于栈顶元素
stack.pop();
}
// 利用 % 求模 防止索引越界
res[i%m] = stack.length ? stack[stack.length -1] : -1;
stack.push(nums[i%m]);
}
return res;
};
/**
* @param {number} n
* @return {number}
*/
// function nextGreaterElement(n) {
// let res = 0
// let q = []
// let str = Array.from(String(n))//字符串数组
// for (let i = str.length - 1; i >= 0; i--) {
// if (q.length === 0 || str[i] >= q[q.length - 1]) q.push(str[i]) ;
// else {
// let count = 0
// // 出栈,记录出栈的位数
// while (q.length !== 0 && str[i] < q[q.length - 1]) {
// q.pop()
// count++
// }
// [str[i], str[i + count]] = [str[i + count], str[i]] // swap元素
// res = parseInt(
// str.slice(0, i + 1).join('') +
// str.slice(i + 1).reverse().join('')
// ) // 反转右边
// return res >= 2 ** 31 - 1 ? -1 : res
// }
// }
// return -1
// }
/**
* @param {number} n
* @return {number}
*/
// var nextGreaterElement = function(n) {
// let s = ('' + n).split('');
// let i = s.length - 2;
// while (s[i] >= s[i + 1]) i--; // 从右开始,找第一个严格降序的数字
// if (i < 0) return -1; // 不存在,返回-1
// let j = s.length - 1;
// while (s[j] <= s[i]) j--; // 从右开始,找到第一个比上一步找到数字大的
// [ s[i], s[j] ] = [ s[j], s[i] ]; // 换位
// let res = parseInt(s.slice(0, i + 1).join('') + s.slice(i + 1).reverse().join('')); // 反转右边
// return res >= 2 ** 31 - 1 ? -1 : res;
// };
/**
* @param {number} n
* @return {number}
*/
var nextGreaterElement = function(n) {
// 把 n 字符串化
const nStr = n + ''
const len = nStr.length
// 我们从后往前遍历,找到第一个比他后面的最大数字小的一个数字,这个位置的数字就是我们要更换的数字
let max = nStr[len - 1]
// 已经遍历过的数字组成的数字,当我们已经更换的数字更换了之后,这个数组就保存更换数字之后的所有数字,我们取得这个数组能组成的数字的最小值拼接在后面即可
const after = [max]
// 需要更换的数字的位置
let before = -1
for (let i = len - 2; i >= 0; i--) {
if (nStr[i] < max) {
before = i
break;
} else {
max = nStr[i]
after.unshift(nStr[i])
}
}
// 当没有需要更换的数字,表示重排找不到比 n 最大的数字了,返回 -1
if (before < 0) return -1
// 保存需要更换的数字
let num = nStr[before]
// after数组从小到大排序,到时候直接拼接就是能组合的最小数字
after.sort((a, b) => a - b)
// 遍历after数组,找到第一个比 num 大的数,来和 num 做交换
for (let i = 0, len = after.length; i < len; i++) {
if (after[i] > num) {
const temp = after[i]
after[i] = num
num = temp
break
}
}
// 拼接数字
// 需要更换的数字之前 + 更换的后数字 + 更换的数字位置之后的所有数字能组成的最小数字
const ret = +(nStr.slice(0, before) + num + after.join(''))
return ret <= (2 ** 31 - 1) ? ret : -1
};
int[] maxSlidingWindow(int[] nums, int k) {
MonotonicQueue window = new MonotonicQueue();
List<Integer> res = new ArrayList<>();
for(int i = 0; i<nums.length; i++){
if(i <k-1){
// 先把 窗口的前 k-1 填满
window.push(nums[i]);
} else {
// 窗口开始向前滑动
// 移入新元素
window.push(nums[i]);
// 将当前窗口中的最大元素计入结果
res.add(window.max());
// 移出最后的元素
window.pop(nums[i- k + 1]);
}
}
// 将List 类型转换为int[]数组作为返回值
int[] arr = new int[res.size()];
for (int i = 0; i< res.size(); i++){
arr[i] = res.get(i);
}
return arr;
}
队列中只有一个,队头持续是最大的
若队列不为空,且当前元素大于等于队尾所存下标的元素,则弹出队尾
入队当前元素下标
判断当前最大值(即队首元素)是否在窗口中,如不在便将其出队
当达到窗口大小时 开始 向结果中添加数据
var maxSlidingWindow = function (nums, k) {
// 队列数组(存放的是元素下标,为了取值方便)
const q = [];
// 结果数组
const ans = [];
for (let i = 0; i < nums.length; i++) {
// 若队列不为空,且当前元素大于等于队尾所存下标的元素,则弹出队尾
while (q.length && nums[i] >= nums[q[q.length - 1]]) {
q.pop();
}
// 入队当前元素下标
q.push(i);
// 判断当前最大值(即队首元素)是否在窗口中,若不在便将其出队
while (q[0] <= i - k) {
q.shift();
}
// 当达到窗口大小时便开始向结果中添加数据
if (i >= k - 1) ans.push(nums[q[0]]);
}
return ans;
};
。
看看这里的动画,会好很多 : 232. 用栈实现队列:【两个栈来模拟队列】详解 - 用栈实现队列 - 力扣(LeetCode) (leetcode-cn.com)
var MyQueue = function() {
this.inStack = [];
this.outStack = [];
};
MyQueue.prototype.push = function(x) {
this.inStack.push(x);
};
MyQueue.prototype.pop = function() {
if (!this.outStack.length) {
this.in2out();
}
return this.outStack.pop();
};
MyQueue.prototype.peek = function() {
if (!this.outStack.length) {
this.in2out();
}
return this.outStack[this.outStack.length - 1];
};
MyQueue.prototype.empty = function() {
return this.outStack.length === 0 && this.inStack.length === 0;
};
MyQueue.prototype.in2out = function() {
while (this.inStack.length) {
this.outStack.push(this.inStack.pop());
}
};
234. 回文链表
剑指 Offer II 027. 回文链表
剑指 Offer II 018. 有效的回文
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} head
* @return {boolean}
*/
var isPalindrome = function(head) {
let left = 0;
const res = [];
let sign = true;
while(head !== null){
res.push(head.val);
head = head.next;
}
// console.log(res)
let right = res.length-1;
while(left < right) {
if(res[left]!== res[right]) {
sign = false
}
left++;
right--;
}
return sign
};
/**
* @param {string} s
* @return {boolean}
*/
var isPalindrome = function(s) {
let str = s.toLowerCase()
let len = str.length
let arr = []
for(let i = 0;i < len;i++){
if(str.charCodeAt(i) >= 97 && str.charCodeAt(i) <= 122) arr.push(str[i])
if(str.charCodeAt(i) >= 48 && str.charCodeAt(i) <= 57) arr.push(str[i])
}
if(arr === []) return true
str = arr.join('')
str2 = arr.reverse().join('')
if(str === str2) return true
return false
};