直接刷题链接直达
- LRU Cache
- 头尾两个伪节点(避免判断) + 双向链表
- 146. LRU 缓存
- 买卖股票的最佳时机系列
- 实现一个HashMap
- 要求:1. 定义内部存储结构; 2.实现 insert(key, value) 和 remove(key);3.不能使用任何Java集合框架。
- 706. 设计哈希映射
- 求给定区间内子区间的最大值(区间内最小值*区间元素相加)
- 要求时间复杂度O(n)
- 原题
- 斐波那契数列的尾递归实现
- 面试官原意是斐波那契的递归实现如何优化
- 尾递归就是把当前的运算结果(或路径)放在参数里传给下层函数
- 递归与尾递归总结
- 1到10000有多少个数字7
- 答案 :4000
- 腾讯面试题-0到9999这1万个数中有多少个数字7
- 给定精度(如小数点后10位),写一个函数求根号2的具体值
- 给定一个乱序数组[0,100],替换其中一个数,找出这个被替换的数
- 类似 142. 环形链表 II 思路,重复数字会形成环,双指针寻找
- 287. 寻找重复数
- 缺失的第一个正数
- 顺时针打印矩阵(星环)
- 实现一个int转中文表示的函数,同时设计测试用例
- 定界数据范围 + 梳理演变规则(以“万”为节,化繁为简,以及“零”如何处理)
- 数字(int型范围内正整数)和中文的相互转换
- 100盏灯问题
- 给定一个数组,一个闭区间[n,m],给出该数组中所有最大值在闭区间中的连续子数组数目
- 要求时间复杂度O(n)
- 如 [2,1,4,3],[2,3],有[2],[2,1],[3],返回3
- 字符串相乘
- 用Java实现一个String转Map(Map<String,Object>)的函数,不能使用String的split和第三方库
- 整数中1出现的次数(从1到n中1出现的次数)
- 单词接龙
- 广度优先 --> 队列 + 暴力搜索(遍历过的单词删除),视频讲解见 花花酱 LeetCode 127. Word Ladder - 刷题找工作 EP71
- 127. 单词接龙
- 分数到小数
- 将除法过程代码化
- 166. 分数到小数
- 只出现一次的数字
- 分糖果
- 从左至右扫一遍,再从右至左扫一遍,取两遍中的最大值
- 135. 分发糖果
- 实现一个命令行逆波兰计算器
- Largest M-aligned Subset
LRU Cache
请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。
实现 LRUCache 类:
LRUCache(int capacity)
以 正整数 作为容量 capacity
初始化 LRU 缓存
int get(int key)
如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。
void put(int key, int value)
如果关键字 key 已经存在,则变更其数据值 value ;如果不存在,则向缓存中插入该组 key-value 。如果插入操作导致关键字数量超过 capacity ,则应该 逐出 最久未使用的关键字。
函数 get 和 put 必须以 O(1) 的平均时间复杂度运行。
class LRUCache {
private final int capacity;
// 双向链表 put 操作 会把数据放表尾-》最近最久未使用在表头(remove去掉表头)
private final LinkedHashMap<Integer, Integer> data;
public LRUCache(int capacity) {
this.capacity = capacity;
this.data = new LinkedHashMap<>(capacity);
}
public int get(int key) {
// 这个key存在 =》 删除,放表尾
if (data.containsKey(key)) {
Integer value = data.remove(key);
data.put(key, value);
return value;
}
return -1;
}
public void put(int key, int value) {
if (data.containsKey(key)) {
data.remove(key);
data.put(key, value);
return;
}
if (data.size() == capacity) {
// 删除表头元素
data.remove(data.keySet().iterator().next());
}
data.put(key, value);
}
}
补充:
class LRUCache {
private static class Node {
int key, value;
Node prev, next;
Node(int k, int v) {
key = k;
value = v;
}
}
private final int capacity;
private final Node dummy = new Node(0, 0); // 哨兵节点
private final Map<Integer, Node> keyToNode = new HashMap<>();
public LRUCache(int capacity) {
this.capacity = capacity;
dummy.prev = dummy;
dummy.next = dummy;
}
public int get(int key) {
Node node = getNode(key); // getNode 会把对应节点移到链表头部
return node != null ? node.value : -1;
}
public void put(int key, int value) {
Node node = getNode(key);
if (node != null) { // 有这本书
node.value = value; // 更新 value
return;
}
node = new Node(key, value); // 新书
keyToNode.put(key, node);
pushFront(node); // 放在最上面
if (keyToNode.size() > capacity) { // 书太多了
Node backNode = dummy.prev;
keyToNode.remove(backNode.key);
remove(backNode); // 去掉最后一本书
}
}
// 获取 key 对应的节点,同时把该节点移到链表头部
private Node getNode(int key) {
if (!keyToNode.containsKey(key)) { // 没有这本书
return null;
}
Node node = keyToNode.get(key); // 有这本书
remove(node); // 把这本书抽出来
pushFront(node); // 放在最上面
return node;
}
// 删除一个节点(抽出一本书)
private void remove(Node x) {
x.prev.next = x.next;
x.next.prev = x.prev;
}
// 在链表头添加一个节点(把一本书放在最上面)
private void pushFront(Node x) {
x.prev = dummy;
x.next = dummy.next;
x.prev.next = x;
x.next.prev = x;
}
}
买卖股票的最佳时机系列
给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。
你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。
返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。
class Solution {
public int maxProfit(int[] prices) {
int mp = Integer.MAX_VALUE;
int max = Integer.MIN_VALUE;
for (int p:prices) {
if (p < mp) {
mp = p;
}
max = Math.max(max, p - mp);
}
return max;
}
}
实现一个HashMap
public class MyHashMap {
int capacity = 16;
class Node {
int key;
int value;
Node next;
public Node() {
this.next = null;
}
public Node(int key, int value) {
this.key = key;
this.value = value;
this.next = null;
}
}
Node[] bucket;
public MyHashMap() {
this.bucket = new Node[capacity];
}
public void put(int key, int value) {
int i = key % capacity;
if (bucket[i] == null) {
bucket[i] = new Node(key, value);
return;
}
Node temp = bucket[i];
if (temp.key == key) {
temp.value = value;
return;
}
while (temp.next != null) {
if (temp.next.key == key) {
temp.next.value = value; // fix here
return;
}
temp = temp.next;
}
temp.next = new Node(key, value); // insert at end
}
public int get(int key) {
int i = key % capacity;
Node temp = bucket[i];
while (temp != null) {
if (temp.key == key) {
return temp.value;
}
temp = temp.next;
}
return -1; // not found
}
public void remove(int key) {
int i = key % capacity;
Node temp = bucket[i];
if (temp == null) return;
if (temp.key == key) {
bucket[i] = temp.next;
return;
}
while (temp.next != null) {
if (temp.next.key == key) {
temp.next = temp.next.next;
return;
}
temp = temp.next;
}
}
}
环形链表
给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。
不允许修改 链表。
思路:
- 快慢指针 判断是否有环。
- 有环,快指针=第一个节点,然后两个指针向下遍历,相遇的点就是开始入环的第一个节点。
public class Solution {
public ListNode detectCycle(ListNode head) {
if (head == null || head.next == null) return null;
ListNode fast = head;
ListNode slow = head;
// 快慢指针:判断是否有环
while (fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
if (fast == slow) {
break;
}
}
// 没有环
if (fast == null || fast.next == null) {
return null;
}
// 有环:重新让 fast 从头走,和 slow 一起一步一步走,相遇点就是环的入口
fast = head;
while (fast != slow) {
fast = fast.next;
slow = slow.next;
}
return slow;
}
}
寻找重复数
给定一个包含 n + 1 个整数的数组 nums ,其数字都在 [1, n] 范围内(包括 1 和 n),可知至少存在一个重复的整数。
假设 nums 只有 一个重复的整数 ,返回 这个重复的数 。
你设计的解决方案必须 不修改 数组 nums 且只用常量级 O(1) 的额外空间。
将数组视为链表,nums[i] 表示“指针”,指向 nums[nums[i]]。
💡 思路:
把 nums 看成链表,数组下标是节点编号,值是指针指向的下一个节点;
因为有重复值,所以一定有“环”;
问题转化为:找这个链表的“环入口”,这就是“重复的数”。
public class Solution {
public int findDuplicate(int[] nums) {
// 初始化快慢指针
int slow = nums[0];
int fast = nums[0];
// 第一步:快慢指针找相遇点(在环中)
do {
slow = nums[slow];
fast = nums[nums[fast]];
} while (slow != fast);
// 第二步:找环的入口(即重复的数字)
fast = nums[0];
while (fast != slow) {
fast = nums[fast];
slow = nums[slow];
}
return slow; // or fast
}
}
缺失的第一个正数
给你一个未排序的整数数组 nums ,请你找出其中没有出现的最小的正整数。
请你实现时间复杂度为 O(n) 并且只使用常数级别额外空间的解决方案。
思想:把每个数字放到它“该去的位置”,然后再检查哪个位置缺少那个数字。
class Solution {
public int firstMissingPositive(int[] nums) {
// 把每个数字放到它“该去的位置”,然后再检查哪个位置缺少那个数字。
int i = 0;
while (i < nums.length) {
if (nums[i] != i+1 && nums[i] > 0 && nums[i] <= nums.length && nums[nums[i] - 1] != nums[i]) {
int temp = nums[nums[i]-1];
nums[nums[i]-1] = nums[i];
nums[i] = temp;
}else {
i++;
}
}
for (int k = 0; k < nums.length; k++) {
if (nums[k] != k + 1) {
return k+1;
}
}
return nums.length + 1;
}
}
螺旋矩阵
给你一个 m 行 n 列的矩阵 matrix ,请按照 顺时针螺旋顺序 ,返回矩阵中的所有元素。
class Solution {
public List<Integer> spiralOrder(int[][] matrix) {
List<Integer> ans = new ArrayList<>();
int m = matrix.length;
int n = matrix[0].length;
// 边界
int l = 0, r = n - 1, t = 0, b = m - 1;
while (l <= r && t <= m) {
// 左 到 右
for (int i = l; i <= r; i++) {
ans.add(matrix[t][i]);
}
// 边界判断
if (++t > b) break;
// 上 到 下
for (int i = t; i <= b; i++) {
ans.add(matrix[i][r]);
}
if (--r < l) break;
// 右 到 左
for (int i = r; i >= l; i--) {
ans.add(matrix[b][i]);
}
if (--b < t) break;
// 从 下 到 上
for (int i = b; i >= t;i--) {
ans.add(matrix[i][l]);
}
if (++l > r) break;
}
return ans;
}
}
字符串相乘
给定两个以字符串形式表示的非负整数 num1
和 num2
,返回 num1
和 num2
的乘积,它们的乘积也表示为字符串形式。
注意:不能使用任何内置的 BigInteger
库或直接将输入转换为整数。
class Solution {
public String multiply(String num1, String num2) {
if ("0".equals(num1) || "0".equals(num2)) return "0";
int m = num1.length();
int n = num2.length();
int[] res = new int[m + n]; // 最多 m+n 位
for (int i = m - 1; i >= 0; i--) {
int a = num1.charAt(i) - '0';
for (int j = n-1; j >= 0; j--) {
int b = num2.charAt(j) - '0';
int sum = res[i+j+1] + a*b;
res[i+j+1] = sum % 10;
res[i+j] += sum/10; // +=
}
}
// 转换成字符串,去掉前导0
StringBuilder sb = new StringBuilder();
for (int num : res) {
if (sb.length() == 0 && num == 0) continue;
sb.append(num);
}
return sb.toString();
}
}
分发糖果
n 个孩子站成一排。给你一个整数数组 ratings 表示每个孩子的评分。
你需要按照以下要求,给这些孩子分发糖果:
- 每个孩子至少分配到 1 个糖果。
- 相邻两个孩子评分更高的孩子会获得更多的糖果。
请你给每个孩子分发糖果,计算并返回需要准备的 最少糖果数目 。
class Solution {
public int candy(int[] ratings) {
int n = ratings.length;
int[] candys = new int[n];
// 每个孩子至少分配到 1 个糖果
Arrays.fill(candys, 1);
// 从 左 到 右 遍历:相邻两个孩子评分更高的孩子会获得更多的糖果
for (int i = 1; i < n; i++) {
if (ratings[i] > ratings[i-1]) {
candys[i] = candys[i-1]+1;
}
}
// 从 右 到 左 遍历:相邻两个孩子评分更高的孩子会获得更多的糖果
for (int i = n-2; i >= 0; i--) {
if (ratings[i] > ratings[i+1]) {
candys[i] = Math.max(candys[i], candys[i+1]+1);
}
}
int sum = 0;
for (int i = 0; i < n; i++) {
sum += candys[i];
}
return sum;
}
}