一、力扣
1、寻找旋转排序数组中的最小值
class Solution {
public int findMin(int[] nums) {
int n=nums.length;
int left=0,right=n;
while(left<right){
int mid=(left+right)/2;
if(nums[mid]>nums[n-1]){
left=mid+1;
}else{
right=mid;
}
}
return nums[right];
}
}
2、搜索旋转排序数组
class Solution {
public int search(int[] nums, int target) {
int min=find(nums);
int n=nums.length;
if(target<=nums[n-1]){
return search(nums,min,n-1,target);
}else{
return search(nums,0,min-1,target);
}
}
public int find(int[] nums){
int n=nums.length;
int left=0,right=n;
while(left<right){
int mid=(left+right)/2;
if(nums[mid]>nums[n-1]){
left=mid+1;
}else{
right=mid;
}
}
return right;
}
public int search(int[] nums,int left,int right,int target){
while(left<=right){
int mid=(left+right)/2;
if(nums[mid]<target){
left=mid+1;
}else if(nums[mid]>target){
right=mid-1;
}else{
return mid;
}
}
return -1;
}
}
3、每日温度
class Solution {
public int[] dailyTemperatures(int[] temperatures) {
ArrayDeque<Integer> queue=new ArrayDeque<>();
int[] res=new int[temperatures.length];
int n=temperatures.length;
for(int i=n-1;i>=0;i--){
while(!queue.isEmpty()&&temperatures[queue.peek()]<=temperatures[i]){
queue.pop();
}
if(!queue.isEmpty()) res[i]=queue.peek()-i;
queue.push(i);
}
return res;
}
}
4、LFU 缓存
要点是创建不同层级的dummy,并存放在map中。同时维持minfreq,每次删除时使用这个找到最不经常使用的。记得要删除空的freq,和key。
class LFUCache {
// 定义双向链表的节点
private static class Node {
int key, value, freq = 1; // 新节点初始化频率为1,表示被访问了一次
Node prev, next; // 前驱和后继指针
// 节点构造函数
Node(int key, int value) {
this.key = key; // 存储键
this.value = value; // 存储值
}
}
int capacity; // 缓存的容量
Map<Integer, Node> keyToNode = new HashMap<>(); // 存储键到节点的映射
Map<Integer, Node> freqToDummy = new HashMap<>(); // 存储频率到虚拟节点的映射
int minFreq; // 当前最小频率
// 构造函数,初始化缓存容量
public LFUCache(int capacity) {
this.capacity = capacity; // 设置容量
}
// 获取值的方法
public int get(int key) {
Node node = getNode(key); // 尝试获取节点
return node != null ? node.value : -1; // 如果节点存在,返回值;否则返回-1
}
// 添加或更新值的方法
public void put(int key, int value) {
Node node = getNode(key); // 尝试获取节点
if (node != null) { // 如果节点存在
node.value = value; // 更新节点的值
return; // 退出
}
// 如果缓存已满
if (keyToNode.size() == capacity) {
Node dummy = freqToDummy.get(minFreq); // 获取当前最小频率的虚拟节点
Node backNode = dummy.prev; // 获取最左边书堆中最底部的书
keyToNode.remove(backNode.key); // 从缓存中移除
remove(backNode); // 从链表中移除节点
if (dummy.prev == dummy) { // 如果该堆现在是空的
freqToDummy.remove(minFreq); // 移除空的链表
}
}
node = new Node(key, value); // 创建新节点
keyToNode.put(key, node); // 加入缓存
pushFront(1, node); // 将节点放到“访问1次”的链表最前面
minFreq = 1; // 更新最小频率值
}
// 获取节点的方法(并更新频率)
private Node getNode(int key) {
if (!keyToNode.containsKey(key)) { // 如果缓存中没有该书
return null; // 返回null
}
Node node = keyToNode.get(key); // 获取节点
remove(node); // 从链表中移除该节点
Node dummy = freqToDummy.get(node.freq); // 获取当前频率的虚拟节点
if (dummy.prev == dummy) { // 如果该频率的链表为空
freqToDummy.remove(node.freq); // 移除空链表
if (minFreq == node.freq) { // 如果是最小频率的节点
minFreq++; // 更新最小频率
}
}
pushFront(++node.freq, node); // 更新频率并放到新频率的链表最前面
return node; // 返回节点
}
// 创建一个新的双向链表
private Node newList() {
Node dummy = new Node(0, 0); // 创建哨兵节点
dummy.prev = dummy; // 前后指向自己,形成空链表
dummy.next = dummy;
return dummy; // 返回哨兵节点
}
// 在链表头添加一个节点(把一本书放在最上面)
private void pushFront(int freq, Node x) {
Node dummy = freqToDummy.computeIfAbsent(freq, k -> newList()); // 获取频率链表的哨兵节点
x.prev = dummy; // 设置前驱为哨兵
x.next = dummy.next; // 设置后继为链表的第一个节点
x.prev.next = x; // 将新节点添加到链表中
x.next.prev = x; // 更新后继节点的前驱
}
// 删除一个节点(从链表中移除一本书)
private void remove(Node x) {
x.prev.next = x.next; // 更新前驱的后继指针
x.next.prev = x.prev; // 更新后继的前驱指针
}
}
5、课程表
三色标记法
class Solution {
public boolean canFinish(int numCourses, int[][] prerequisites) {
List<Integer>[] path=new List[numCourses];
Arrays.setAll(path,e->new ArrayList<>());
for(var e:prerequisites){
path[e[1]].add(e[0]);
}
int[] color=new int[numCourses];
for(int i=0;i<numCourses;i++){
if(color[i]==0&&dfs(path,i,color)){
return false;
}
}
return true;
}
public boolean dfs(List<Integer>[] path,int x,int[] color){
color[x]=1;
for(int nex:path[x]){
if(color[nex]==1||color[nex]==0&&dfs(path,nex,color)){
return true;
}
}
color[x]=2;
return false;
}
}
6、01背包-最后一块石头的重量 II
class Solution {
public int lastStoneWeightII(int[] stones) {
int sum=0;
for(var e:stones){
sum+=e;
}
int target=sum/2;
int n=stones.length;
boolean[][] dp=new boolean[n+1][target+1];
int max=0;
dp[0][0]=true;
for(int i=0;i<n;i++){
for(int j=0;j<=target;j++){
if(j<stones[i]){
dp[i+1][j]=dp[i][j];
}else{
dp[i+1][j]=dp[i][j]||dp[i][j-stones[i]];
}
if(dp[i+1][j]){
max=Math.max(max,j);
}
}
}
return sum-max*2;
}
}
7、01背包-目标和
class Solution {
public int findTargetSumWays(int[] nums, int target) {
// 计算数组元素总和
int s = 0;
for (int x : nums) {
s += x;
}
// 转换问题:将正负号问题转化为子集和问题
// 新目标为找到和为 (s - |target|)/2 的子集,因为 sumPos - sumNeg = target 且 sumPos + sumNeg = s
s -= Math.abs(target);
// 若新目标为负数或无法整除,无解
if (s < 0 || s % 2 == 1) {
return 0;
}
// m 表示需要找到的子集和(等价于原问题中的 sumPos)
int m = s / 2;
int n = nums.length;
// 动态规划数组:f[i][c] 表示前i个元素中,和为c的子集的方案数
int[][] f = new int[n + 1][m + 1];
f[0][0] = 1; // 初始条件:前0个元素和为0的方案数为1
// 遍历每个元素
for (int i = 0; i < n; i++) {
// 遍历所有可能的子集和(0到m)
for (int c = 0; c <= m; c++) {
if (c < nums[i]) {
// 当前元素大于目标值,无法选择,继承前i-1个元素的方案数
f[i + 1][c] = f[i][c];
} else {
// 状态转移:不选当前元素 + 选当前元素的方案数之和
f[i + 1][c] = f[i][c] + f[i][c - nums[i]];
}
}
}
// 返回前n个元素中和为m的方案数,即原问题的解
return f[n][m];
}
}
8、完全背包-零钱兑换 II-求组合个数
如果求组合数就是外层for循环遍历物品,内层for遍历背包。
class Solution {
public int change(int amount, int[] coins) {
int n=coins.length;
int[][] dp=new int[n+1][amount+1];
dp[0][0]=1;
for(int i=0;i<n;i++){
for(int j=0;j<=amount;j++){
dp[i+1][j]=dp[i][j];
if(j>=coins[i]){
dp[i+1][j]+=dp[i+1][j-coins[i]];
}
}
}
return dp[n][amount];
}
}
9、完全背包-组合总和 Ⅳ-求排列数
如果求排列数就是外层for遍历背包,内层for循环遍历物品。
class Solution {
public int combinationSum4(int[] nums, int target) {
int n = nums.length;
// dp[i]: 凑成目标正整数为i的排列个数为dp[i]
int[] dp = new int[target + 1];
dp[0] = 1;
// 遍历所有可能的目标值
for (int j = 0; j <= target; j++) {
// 尝试每个候选数字
for (int i = 0; i < n; i++) {
// 当当前目标值j >= 候选数字时,才能进行组合
if (j >= nums[i]) {
// 状态转移方程:当前组合数 += 剩余值(j-nums[i])的组合数
// 注意:此处使用两层循环的顺序会导致计算的是排列数(不同顺序算不同组合)
dp[j] += dp[j - nums[i]];
}
}
}
return dp[target];
}
}