1.滑动窗口
给你一个字符串 S、一个字符串 T,请在字符串 S 里面找出:包含 T 所有字符的最小子串
示例:
输入: S = "ADOBECODEBANC", T = "ABC"
输出: "BANC"
public void gethuaDong() {
String s = "ADOBECODEBANC";
String t = "ABC";
Map<Character, Integer> needMap = new HashMap<>();
Map<Character, Integer> windowMap = new HashMap<>();
//初始needMap 符合条件为[left, right)里面的数据即是要的数据
for (int i = 0; i < t.length(); i++) {
Integer num = needMap.getOrDefault(t.charAt(i), 0);
needMap.put(t.charAt(i), ++num);
}
int left = 0;
int right = 0;
int startLen = 0;//最短字符串的起始位置
int minLen = 0;//最短字符串的长度
int validNum = 0;//目前已经符合need中的元素个数
//移动right,并更新windowMap数据
while (right < s.length()) {
Character srcStr = s.charAt(right);
Integer targetNum = needMap.get(srcStr);
//如果是符合needMap的才统计入windowMap窗口
if (targetNum != null) {
Integer srcNum = windowMap.getOrDefault(srcStr, 0);
windowMap.put(srcStr, ++srcNum);
if (srcNum.equals(targetNum)) {
validNum++;
}
}
//设置好windowMap就移动right
right++;
//移动left,并更新数据(windowMap,最小字符串的位置)
Boolean tempFlag = false;
while (validNum == needMap.size()) {
Character srcStr2 = s.charAt(left);
Integer targetNum2 = needMap.get(srcStr2);
if (targetNum2 != null) {
Integer srcNum2 = windowMap.get(srcStr2);
//如果srcNum2是0则不正常,要异常处理
srcNum2 -= 1;
windowMap.put(srcStr2, srcNum2);
if (srcNum2 < targetNum2) {
validNum--;
}
}
tempFlag = true;
left++;
}
//更新最短数据
if (tempFlag) {
//因为是算长度,所以加一
int len = right - left + 1;
if (len > 0 && (len < minLen || minLen == 0)) {
minLen = len;
startLen = left - 1;
}
}
}
System.out.println(s.substring(startLen, startLen + minLen));
}
2.LRU算法
运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制。它应该支持以下操作: 获取数据 get 和 写入数据 put 。 获取数据 get(key) - 如果密钥 (key) 存在于缓存中,则获取密钥的值(总是正数),否则返回 -1。 写入数据 put(key, value) - 如果密钥已经存在,则变更其数据值;如果密钥不存在,则插入该组「密钥/数据值」。当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间
方法一:LinkedList+Map 实现
class LRUCache {
Map<Integer, Integer> lruMap =null;
LinkedList<Integer> deque = null;
int capacity ;
public LRUCache(int capacity) {
lruMap = new HashMap<>(capacity+1);
deque = new LinkedList<>();
this.capacity = capacity;
}
public int get(int key) {
Integer integer = lruMap.get(key);
if (integer == null) {
return -1;
} else {
deque.removeFirstOccurrence(key);
deque.addFirst(key);
return integer;
}
}
public void put(int key, int value) {
Integer integer = lruMap.put(key, value);
if (integer != null) {
deque.removeFirstOccurrence(key);
}
deque.addFirst(key);
//因为有可能put了两次1,实际size并没有增加,所以放后面判断
if (this.lruMap.size() > this.capacity) {
this.lruMap.remove(deque.removeLast());
}
}
}
方法二:自实现双向链表+Map 实现
这个方法查询性能比方法一好,因为不需要链表再去找某个节点然后删除
public class LRUCache {
private int size;
private HashMap<Integer, Node> map;
private Node head;
private Node tail;
LRUCache(int size) {
this.size = size;
map = new HashMap<>();
}
/**
* 添加元素
* 1.元素存在,将元素移动到队尾
* 2.不存在,判断链表是否满。
* 如果满,则删除队首元素,放入队尾元素,删除更新哈希表
* 如果不满,放入队尾元素,更新哈希表
*/
public void put(int key, int value) {
Node node = map.get(key);
if (node != null) {
//更新值
node.v = value;
moveNodeToTail(node);
} else {
Node newNode = new Node(key, value);
//链表满,需要删除首节点
if (map.size() == size) {
Node delHead = removeHead();
map.remove(delHead.k);
}
addLast(newNode);
map.put(key, newNode);
}
}
public int get(int key) {
Node node = map.get(key);
if (node != null) {
moveNodeToTail(node);
return node.v;
}
return -1;
}
public void addLast(Node newNode) {
if (newNode == null) {
return;
}
if (head == null) {
head = newNode;
tail = newNode;
} else {
//连接新节点
tail.next = newNode;
newNode.pre = tail;
//更新尾节点指针为新节点
tail = newNode;
}
}
public void moveNodeToTail(Node node) {
if (tail == node) {
return;
}
if (head == node) {
head = node.next;
head.pre = null;
} else {
//调整双向链表指针
node.pre.next = node.next;
node.next.pre = node.pre;
}
node.pre = tail;
node.next = null;
tail.next = node;
tail = node;
}
public Node removeHead() {
if (head == null) {
return null;
}
Node res = head;
if (head == tail) {
head = null;
tail = null;
} else {
head = res.next;
head.pre = null;
res.next = null;
}
return res;
}
class Node {
int k;
int v;
Node pre;
Node next;
Node(int k, int v) {
this.k = k;
this.v = v;
}
}
}
方法三 用LinkedHashMap
public class LRULinkedHashMap<K, V> extends LinkedHashMap<K, V> {
private int capacity;
LRULinkedHashMap(int capacity) {
// 初始大小,0.75是装载因子,true是表示按照访问时间排序
super((int) Math.ceil(cacheSize / 0.75) + 1, 0.75f, true);
//传入指定的缓存最大容量
this.capacity = capacity;
}
/**
* 实现LRU的关键方法,如果map里面的元素个数大于了缓存最大容量,则删除链表的顶端元素
removeEldestEntry默认是返回false,不删除元素的
*/
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
return size() > capacity;
}
}
3.二分结合抽屉
给定一个包含 n + 1 个整数的数组 nums,其数字都在 1 到 n 之间(包括 1 和 n),可知至少存在一个重复的整数。假设只有一个重复的整数,找出这个重复的数。
示例:
输入:(2 -> 4 -> 3) + (5 -> 6 -> 4)
输出:7 -> 0 -> 8
原因:342 + 465 = 807
解法1:
public static int demo() {
int[] nums = new int[]{1, 6, 6, 4, 3, 5, 2};
//数值的范围是只能是 [left, right]
int left = 1;
int right = nums.length - 1;
while (left < right) {
int countNum = 0;
//>>>是为了防止负数的出现
int halfNum = (left + right) >>> 1;
for (int i : nums) {
if (i <= halfNum) {
countNum++;
}
}
//半分查找,这里已经有天然的自然排序就是: 抽屉[left, right]
if (countNum <= halfNum) {
left = halfNum + 1;
} else {
right = halfNum;
}
}
return left;
}