博客记录-day155-力扣(背包)

135 阅读6分钟

一、力扣

1、寻找旋转排序数组中的最小值

153. 寻找旋转排序数组中的最小值

image.png

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、搜索旋转排序数组

33. 搜索旋转排序数组

image.png

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、每日温度

739. 每日温度

image.png

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 缓存

460. LFU 缓存

image.png

要点是创建不同层级的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、课程表

207. 课程表

image.png

三色标记法

image.png

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

1049. 最后一块石头的重量 II

image.png

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背包-目标和

494. 目标和

image.png

image.png

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-求组合个数

518. 零钱兑换 II

image.png

如果求组合数就是外层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、完全背包-组合总和 Ⅳ-求排列数

377. 组合总和 Ⅳ

image.png

如果求排列数就是外层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];
    }
}

10、背包问题总结

image.png