【Leetcode Sheet】Weekly Practice 8

Leetcode Test

2560 打家劫舍Ⅳ(9.19)

沿街有一排连续的房屋。每间房屋内都藏有一定的现金。现在有一位小偷计划从这些房屋中窃取现金。

由于相邻的房屋装有相互连通的防盗系统,所以小偷 不会窃取相邻的房屋

小偷的 窃取能力 定义为他在窃取过程中能从单间房屋中窃取的 最大金额

给你一个整数数组 nums 表示每间房屋存放的现金金额。形式上,从左起第 i 间房屋中放有 nums[i] 美元。

另给你一个整数 k ,表示窃贼将会窃取的 最少 房屋数。小偷总能窃取至少 k 间房屋。

返回小偷的 最小 窃取能力。

提示:

  • 1 <= nums.length <= 105
  • 1 <= nums[i] <= 109
  • 1 <= k <= (nums.length + 1)/2

【动态规划 + 二分】

int minCapability(int* nums, int numsSize, int k){
    int lower=nums[0],upper=nums[0];
    for(int i=0;i<numsSize;i++){
        lower=fmin(nums[i],lower);
        upper=fmax(nums[i],upper);
    }
    //取得nums里面的最大值和最小值作为二分的边界

    while(lower<=upper){
        int mid=(lower+upper)/2;
        int cnt=0;  //计算有多少个符合条件的房子
        bool visit=0;   //用于判定上一间房是否被访问过
        for(int i=0;i<numsSize;i++){
            //当前房子小于mid,且邻居没访问
            if(nums[i]<=mid && visit==0){
                cnt++;
                visit=1;
            }
            //重置邻居
            else{
                visit=0;
            }
        }
        if(cnt>=k){
            upper=mid-1;
        }
        else{
            lower=mid+1;
        }
    }
    return lower;
}

LCP 06 拿硬币(9.20)

桌上有 n 堆力扣币,每堆的数量保存在数组 coins 中。我们每次可以选择任意一堆,拿走其中的一枚或者两枚,求拿完所有力扣币的最少次数。

限制:

  • 1 <= n <= 4
  • 1 <= coins[i] <= 10

【贪心】每次都先拿2个币,分奇偶讨论

int minCount(int* coins, int coinsSize){
    //n stacks, choose 1 stack and take 1 / 2 coins
    int cnt=0;
    for(int i=0;i<coinsSize;i++){
        if(coins[i]%2==0){
            cnt+=(coins[i]/2);
        }
        else{
            cnt+=(coins[i]/2)+1;
        }
    }
    return cnt;
}

2603 收集树中金币(9.21)

给你一个 n 个节点的无向无根树,节点编号从 0n - 1 。给你整数 n 和一个长度为 n - 1 的二维整数数组 edges ,其中 edges[i] = [ai, bi] 表示树中节点 aibi 之间有一条边。再给你一个长度为 n 的数组 coins ,其中 coins[i] 可能为 0 也可能为 11 表示节点 i 处有一个金币。

一开始,你需要选择树中任意一个节点出发。你可以执行下述操作任意次:

  • 收集距离当前节点距离为 2 以内的所有金币,或者
  • 移动到树中一个相邻节点。

你需要收集树中所有的金币,并且回到出发节点,请你返回最少经过的边数。

如果你多次经过一条边,每一次经过都会给答案加一。

提示:

  • n == coins.length
  • 1 <= n <= 3 * 104
  • 0 <= coins[i] <= 1
  • edges.length == n - 1
  • edges[i].length == 2
  • 0 <= ai, bi < n
  • ai != bi
  • edges 表示一棵合法的树。
class Solution {
public:
    int collectTheCoins(vector<int> &coins, vector<vector<int>> &edges) {
        int n = coins.size();
        vector<vector<int>> g(n);
        vector<int> deg(n);
        for (auto &e: edges) {
            int x = e[0], y = e[1];
            g[x].push_back(y);
            g[y].push_back(x); // 建图
            deg[x]++;
            deg[y]++; // 统计每个节点的度数(邻居个数)
        }

        int left_edges = n - 1; // 剩余边数
        // 拓扑排序,去掉没有金币的子树
        vector<int> q;
        for (int i = 0; i < n; i++)
            if (deg[i] == 1 && coins[i] == 0) // 没有金币的叶子
                q.push_back(i);
        while (!q.empty()) {
            left_edges--; // 删除节点 x(到其父节点的边)
            int x = q.back(); q.pop_back();
            for (int y: g[x])
                if (--deg[y] == 1 && coins[y] == 0) // 没有金币的叶子
                    q.push_back(y);
        }

        // 再次拓扑排序
        for (int i = 0; i < n; i++)
            if (deg[i] == 1 && coins[i]) // 有金币的叶子(判断 coins[i] 是避免把没有金币的叶子也算进来)
                q.push_back(i);
        left_edges -= q.size(); // 删除所有叶子(到其父节点的边)
        for (int x: q) // 遍历所有叶子
            for (int y: g[x])
                if (--deg[y] == 1) // y 现在是叶子了
                    left_edges--; // 删除 y(到其父节点的边)
        return max(left_edges * 2, 0);
    }
};

2591 将钱分给最多的儿童(9.22)

给你一个整数 money ,表示你总共有的钱数(单位为美元)和另一个整数 children ,表示你要将钱分配给多少个儿童。

你需要按照如下规则分配:

  • 所有的钱都必须被分配。
  • 每个儿童至少获得 1 美元。
  • 没有人获得 4 美元。

请你按照上述规则分配金钱,并返回 最多 有多少个儿童获得 恰好 8 美元。如果没有任何分配方案,返回 -1

提示:

  • 1 <= money <= 200
  • 2 <= children <= 30

【贪心】

int distMoney(int money, int children){
    //钱不够分
    if(money<children) return -1;

    //每个人先分1元
    money-=children;
    //给尽可能多的人7元
    int cnt=fmin(money/7,children);
    //剩余的钱和孩子
    money-=cnt*7;
    children-=cnt;

    //如果孩子都有8元,且还剩钱,则把剩下的钱都给一个孩子
    //如果孩子还剩1个,且钱剩3元,则这个孩子会是4元,得找一个8元孩子换钱
    if((children==0 && money>0)||(children==1 && money==3)){
        cnt--;
    }
    return cnt;
}

1993 树上的操作(9.23)

给你一棵 n 个节点的树,编号从 0n - 1 ,以父节点数组 parent 的形式给出,其中 parent[i] 是第 i 个节点的父节点。树的根节点为 0 号节点,所以 parent[0] = -1 ,因为它没有父节点。你想要设计一个数据结构实现树里面对节点的加锁,解锁和升级操作。

数据结构需要支持如下函数:

  • **Lock:**指定用户给指定节点 上锁 ,上锁后其他用户将无法给同一节点上锁。只有当节点处于未上锁的状态下,才能进行上锁操作。
  • **Unlock:**指定用户给指定节点 解锁 ,只有当指定节点当前正被指定用户锁住时,才能执行该解锁操作。
  • **Upgrade:**指定用户给指定节点 上锁 ,并且将该节点的所有子孙节点 解锁 。只有如下 3 个条件 全部 满足时才能执行升级操作:
    • 指定节点当前状态为未上锁。
    • 指定节点至少有一个上锁状态的子孙节点(可以是 任意 用户上锁的)。
    • 指定节点没有任何上锁的祖先节点。

请你实现 LockingTree 类:

  • LockingTree(int[] parent) 用父节点数组初始化数据结构。
  • lock(int num, int user) 如果 id 为 user 的用户可以给节点 num 上锁,那么返回 true ,否则返回 false 。如果可以执行此操作,节点 num 会被 id 为 user 的用户 上锁
  • unlock(int num, int user) 如果 id 为 user 的用户可以给节点 num 解锁,那么返回 true ,否则返回 false 。如果可以执行此操作,节点 num 变为 未上锁 状态。
  • upgrade(int num, int user) 如果 id 为 user 的用户可以给节点 num 升级,那么返回 true ,否则返回 false 。如果可以执行此操作,节点 num 会被 升级

提示:

  • n == parent.length
  • 2 <= n <= 2000
  • 对于 i != 0 ,满足 0 <= parent[i] <= n - 1
  • parent[0] == -1
  • 0 <= num <= n - 1
  • 1 <= user <= 104
  • parent 表示一棵合法的树。
  • lockunlockupgrade 的调用 总共 不超过 2000 次。

【DFS】

typedef struct {
    int nodeSize;
    int *parent;
    int *lockNodeUser;
    struct ListNode **children;
} LockingTree;

struct ListNode *createListNode(int val){
    struct ListNode *obj=(struct ListNode*)malloc(sizeof(struct ListNode));
    obj->val=val;
    obj->next=NULL;
    return obj;
}

LockingTree* lockingTreeCreate(int* parent, int parentSize) {
    LockingTree *obj=(LockingTree*)malloc(sizeof(LockingTree));
    obj->nodeSize=parentSize;
    obj->parent=(int*)malloc(sizeof(int)*parentSize);
    obj->lockNodeUser=(int*)malloc(sizeof(int)*parentSize);
    obj->children=(struct ListNode**)malloc(sizeof(struct ListNode*)*parentSize);
    memcpy(obj->parent,parent,sizeof(int)*parentSize);

    for(int i=0;i<parentSize;i++){
        obj->lockNodeUser[i]=-1;
        obj->children[i]=NULL;
    }

    for(int i=0;i<parentSize;i++){
        int p=parent[i];
        if(p!=-1){
            struct ListNode *node=createListNode(i);
            node->next=obj->children[p];
            obj->children[p]=node;
        }
    }
    return obj;
}

//可以用一个数组变量lockNodeUser记录给各个节点上锁的用户
bool lockingTreeLock(LockingTree* obj, int num, int user) {
    if(obj->lockNodeUser[num]==-1){
        obj->lockNodeUser[num]=user;
        return 1;
    }
    return 0;
}

//通过比较变量lockNodeUser[num]和user是否先等来判断当前节点是否可以解锁,通过赋值来解锁。
bool lockingTreeUnlock(LockingTree* obj, int num, int user) {
    if(obj->lockNodeUser[num]==user){
        obj->lockNodeUser[num]=-1;
        return 1;
    }
    return 0;
}

bool hasLockedAncestor(LockingTree *obj,int num){
    num=obj->parent[num];
    while(num!=-1){
        if(obj->lockNodeUser[num]!=-1){
            return 1;
        }
        num=obj->parent[num];
    }
    return 0;
}

bool checkAndUnlockDescendant(LockingTree *obj,int num){
    bool res=obj->lockNodeUser[num]!=-1;
    obj->lockNodeUser[num]=-1;
    for(struct ListNode *node=obj->children[num];node;node=node->next){
        res |=checkAndUnlockDescendant(obj,node->val);
    }
    return res;
}

bool lockingTreeUpgrade(LockingTree* obj, int num, int user) {
    bool res=obj->lockNodeUser[num]==-1 \ 
                && !hasLockedAncestor(obj, num) \
                && checkAndUnlockDescendant(obj, num);
    if (res) {
        obj->lockNodeUser[num] = user;
    }
    return res;
}

void freeList(struct ListNode *list) {
    while (list) {
        struct ListNode *cur = list;
        list = list->next;
        free(cur);
    }
}

void lockingTreeFree(LockingTree* obj) {
    free(obj->parent);
    free(obj->lockNodeUser);
    for (int i = 0; i < obj->nodeSize; i++) {
        freeList(obj->children[i]);
    }
    free(obj);
}

/**
 * Your LockingTree struct will be instantiated and called as such:
 * LockingTree* obj = lockingTreeCreate(parent, parentSize);
 * bool param_1 = lockingTreeLock(obj, num, user);
 
 * bool param_2 = lockingTreeUnlock(obj, num, user);
 
 * bool param_3 = lockingTreeUpgrade(obj, num, user);
 
 * lockingTreeFree(obj);
*/

146 LRU缓存(9.24)

请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。

实现 LRUCache 类:

  • LRUCache(int capacity)正整数 作为容量 capacity 初始化 LRU 缓存
  • int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1
  • void put(int key, int value) 如果关键字 key 已经存在,则变更其数据值 value ;如果不存在,则向缓存中插入该组 key-value 。如果插入操作导致关键字数量超过 capacity ,则应该 逐出 最久未使用的关键字。

函数 getput 必须以 O(1) 的平均时间复杂度运行。

提示:

  • 1 <= capacity <= 3000
  • 0 <= key <= 10000
  • 0 <= value <= 105
  • 最多调用 2 * 105getput

【灵神题解】146. LRU 缓存 - 力扣(LeetCode)

class Node{
public:
    int key,value;
    Node *prev,*next;
    Node(int k=0,int v=0) : key(k), value(v) {}
};

class LRUCache {
    int capacity;
    Node *dummy;
    unordered_map<int,Node*> key_to_node;

    void remove(Node *x){
        x->prev->next=x->next;
        x->next->prev=x->prev;
    }

    void push_front(Node *x){
        x->prev=dummy;
        x->next=dummy->next;
        x->prev->next=x;
        x->next->prev=x;
    }

    Node *get_node(int key){
        auto it=key_to_node.find(key);
        if(it==key_to_node.end()){
            //no book 
            return nullptr;
        }
        auto node=it->second;
        //book exists
        remove(node);
        push_front(node);
        return node;
    }

public:
    LRUCache(int capacity) {
        this->capacity=capacity;
        this->dummy=new Node;
        dummy->prev=dummy;
        dummy->next=dummy;
    }
    
    int get(int key) {
        auto node=get_node(key);
        return node ? node->value : -1;
    }
    
    void put(int key, int value) {
        auto node=get_node(key);
        if(node){
            //book exists
            node->value=value;
            return;
        }
        key_to_node[key]=node=new Node(key,value);
        //new book
        push_front(node);
        if(key_to_node.size() > capacity){
            //to many books
            auto back_node=dummy->prev;
            key_to_node.erase(back_node->key);
            remove(back_node);
            delete back_node;
        }
    }
};

/**
 * Your LRUCache object will be instantiated and called as such:
 * LRUCache* obj = new LRUCache(capacity);
 * int param_1 = obj->get(key);
 * obj->put(key,value);
 */

460 LFU缓存(9.25)

请你为 最不经常使用(LFU)缓存算法设计并实现数据结构。

实现 LFUCache 类:

  • LFUCache(int capacity) - 用数据结构的容量 capacity 初始化对象
  • int get(int key) - 如果键 key 存在于缓存中,则获取键的值,否则返回 -1
  • void put(int key, int value) - 如果键 key 已存在,则变更其值;如果键不存在,请插入键值对。当缓存达到其容量 capacity 时,则应该在插入新项之前,移除最不经常使用的项。在此问题中,当存在平局(即两个或更多个键具有相同使用频率)时,应该去除 最近最久未使用 的键。

为了确定最不常使用的键,可以为缓存中的每个键维护一个 使用计数器 。使用计数最小的键是最久未使用的键。

当一个键首次插入到缓存中时,它的使用计数器被设置为 1 (由于 put 操作)。对缓存中的键执行 getput 操作,使用计数器的值将会递增。

函数 getput 必须以 O(1) 的平均时间复杂度运行。

提示:

  • 1 <= capacity <= 104
  • 0 <= key <= 105
  • 0 <= value <= 109
  • 最多调用 2 * 105getput 方法

【灵神题解】460. LFU 缓存 - 力扣(LeetCode)

class Node {
public:
    int key, value, freq = 1; // 新书只读了一次
    Node *prev, *next;

    Node(int k = 0, int v = 0) : key(k), value(v) {}
};

class LFUCache {
private:
    int min_freq;
    int capacity;
    unordered_map<int, Node*> key_to_node;
    unordered_map<int, Node*> freq_to_dummy;

    Node *get_node(int key) {
        auto it = key_to_node.find(key);
        if (it == key_to_node.end()) { // 没有这本书
            return nullptr;
        }
        auto node = it->second; // 有这本书
        remove(node); // 把这本书抽出来
        auto dummy = freq_to_dummy[node->freq];
        if (dummy->prev == dummy) { // 抽出来后,这摞书是空的
            freq_to_dummy.erase(node->freq); // 移除空链表
            delete dummy; // 释放内存
            if (min_freq == node->freq) {
                min_freq++;
            }
        }
        push_front(++node->freq, node); // 放在右边这摞书的最上面
        return node;
    }

    // 创建一个新的双向链表
    Node *new_list() {
        auto dummy = new Node(); // 哨兵节点
        dummy->prev = dummy;
        dummy->next = dummy;
        return dummy;
    }

    // 在链表头添加一个节点(把一本书放在最上面)
    void push_front(int freq, Node *x) {
        auto it = freq_to_dummy.find(freq);
        if (it == freq_to_dummy.end()) { // 这摞书是空的
            it = freq_to_dummy.emplace(freq, new_list()).first;
        }
        auto dummy = it->second;
        x->prev = dummy;
        x->next = dummy->next;
        x->prev->next = x;
        x->next->prev = x;
    }

    // 删除一个节点(抽出一本书)
    void remove(Node *x) {
        x->prev->next = x->next;
        x->next->prev = x->prev;
    }

public:
    LFUCache(int capacity) : capacity(capacity) {}

    int get(int key) {
        auto node = get_node(key);
        return node ? node->value : -1;
    }

    void put(int key, int value) {
        auto node = get_node(key);
        if (node) { // 有这本书
            node->value = value; // 更新 value
            return;
        }
        if (key_to_node.size() == capacity) { // 书太多了
            auto dummy = freq_to_dummy[min_freq];
            auto back_node = dummy->prev; // 最左边那摞书的最下面的书
            key_to_node.erase(back_node->key);
            remove(back_node); // 移除
            delete back_node; // 释放内存
            if (dummy->prev == dummy) { // 这摞书是空的
                freq_to_dummy.erase(min_freq); // 移除空链表
                delete dummy; // 释放内存
            }
        }
        key_to_node[key] = node = new Node(key, value); // 新书
        push_front(1, node); // 放在「看过 1 次」的最上面
        min_freq = 1;
    }
};

/**
 * Your LFUCache object will be instantiated and called as such:
 * LFUCache* obj = new LFUCache(capacity);
 * int param_1 = obj->get(key);
 * obj->put(key,value);
 */

你可能感兴趣的:(随想录,leetcode,linux,java)