打家劫舍系列(动态规划、二分查找、贪心)

力扣热题 100:动态规划专题经典题解析 在力扣(LeetCode)平台上,动态规划相关的题目是算法面试和练习中的重要部分。今天,我们就来详细解析动态规划专题中的几道经典题目,帮助大家更好地理解解题思路和技巧。我们可以将问题转化为背包问题,判断是否存在一个子集的和等于总和的一半。我们可以使用动态规划来记录字符串的每个位置是否可以被拆分。我们可以使用动态规划来记录每个房子能抢劫到的最大金额。我们可以使用动态规划来记录每个数的最少完全平方数数量。我们可以使用动态规划来记录到达每个台阶的方法数。我们可以使用动态规划来记录每个金额的最少硬币数。 阅读详情

总结

  1. 打家劫舍 Ⅰ不能偷相邻房间
  2. 打家劫舍 Ⅱ 不能偷相邻房间,且房间首尾相连
  3. 打家劫舍 Ⅲ 房间以二叉树形式连接
  4. 打家劫舍 Ⅳ 小偷最少偷 k 间房屋,返回小偷的最小窃取能力,也就是偷取的最大金额 最小(最小化最大值)

打家劫舍 Ⅰ

你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。

给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。

设 dp[i] 为 i 号房间能够偷窃的最高金额。

dp[0] = nums[0];, dp[1] = max(nums[0], nums[1]);

对于 i 号房间,有两种选择:偷 或者 不偷

  • 如果偷,则 dp[i] = dp[i-2] + nums[i];
  • 如果不偷,则 dp[i] = dp[i - 1];
  • 返回两者的最大值。

完整代码:

class Solution {
public:
    int rob(vector<int> &nums)
    {
        int n = nums.size();
        if (n == 1)
            return nums[0];
        if (n == 2) 
            return max(nums[0], nums[1]);
 
        vector<int> dp(n, 0);
        dp[0] = nums[0];
        dp[1] = max(nums[0], nums[1]);
        
        for (int i = 2; i < n; ++i)
        {
            dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]);
        }

        //printNums(dp);
        return dp[n - 1];
    
    }
};

打家劫舍 Ⅱ

你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都 围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警 。

给定一个代表每个房屋存放金额的非负整数数组,计算你 在不触动警报装置的情况下 ,今晚能够偷窃到的最高金额。

与 打家劫舍 Ⅰ不同之处在于,房屋现在是首尾相连的,因此,偷了第 1 间房屋,就不能偷最后一间房屋,偷了最后一间房屋,就不能偷第一间房屋。

那么第 1 间房屋就有两个状态:偷 或者 不偷,这两种状态会影响到后续递推公式/状态转移方程的推导,所以我们要用两个 dp 数组来存储这两种不同的状态( 打家劫舍 Ⅰ中不存在这样的影响)。

  • dp[i][0]:表示第一个房间被偷,以此为基础进行推导得到的 i 号房间能够偷窃的最高金额。
  • dp[i][1]:表示第一个房间不被偷,以此为基础进行推导得到的 i 号房间能够偷窃的最高金额。

状态转移公式:

  • dp[i][0] = max(dp[i - 2][0] + nums[i], dp[i - 1][0]);
  • dp[i][1] = max(dp[i - 2][1] + nums[i], dp[i - 1][1]);

由于如果偷了第 1 间房屋,就不能偷最后一间房屋了,所以dp[n-1][0] 是没有意义的,第 n-1 也就是最后一间房间的最大金额一定是等于倒数第二间的最大金额,即dp[n-2][0],最后只需要比较dp[n-2][0] 和 dp[n-1][1]的较大值。

完整代码:

class Solution {
public:
    int rob(vector<int> &nums)
    {
        int n = nums.size();
        if (n == 1)
            return nums[0];
        if (n == 2) 
            return max(nums[0], nums[1]);
        
        vector<vector<int>> dp(n, vector<int>(2, 0));
        dp[0][0] = nums[0];
        dp[1][0] = nums[0];

        dp[0][1] = 0;
        dp[1][1] = nums[1];
        
        for (int i = 2; i < n; ++i)
        {
            dp[i][0] = max(dp[i - 2][0] + nums[i], dp[i - 1][0]);
            dp[i][1] = max(dp[i - 2][1] + nums[i], dp[i - 1][1]);
        }

        //printNums(dp);
        return max(dp[n-2][0], dp[n-1][1]);
    
    }
};

另一种版本:

class Solution {
public:
    int robRange(vector<int>& nums, int start, int end) {
        int n = end - start + 1;
        if (n == 1)
            return nums[start];
        if (n == 2) 
            return max(nums[start], nums[end]);
 
        vector<int> dp(n, 0);
        dp[0] = nums[start];
        dp[1] = max(nums[start], nums[start + 1]);
        
        for (int i = 2; i < n; ++i)
        {
            dp[i] = max(dp[i - 2] + nums[i + start], dp[i - 1]);
        }

        return dp[n - 1];
    }

    int rob(vector<int> &nums)
    {
        int n = nums.size();
        if (n == 1)
            return nums[0];
        if (n == 2) 
            return max(nums[0], nums[1]);
        
        int res1 = robRange(nums, 0, n - 2);
        int res2 = robRange(nums, 1, n - 1);

        return max(res1, res2);
    
    }
};

打家劫舍 Ⅲ

小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为 root 。

除了 root 之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果 两个直接相连的房子在同一天晚上被打劫 ,房屋将自动报警。

给定二叉树的 root 。返回 在不触动警报的情况下 ,小偷能够盗取的最高金额 。

好了,现在房子的连接方式变成了二叉树形结构。

这道题是我第一次遇到树形结构的动态规划类题目。可以详细记录一下解题过程

错误解法

这里记录一下我第一次想到的错误解法:思路是通过层序遍历得到每一层的节点之和形成一个数组,然后套用 打家劫舍 Ⅰ的解法计算最终结果:

#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
#include <queue>

using namespace std;

// Definition for a binary tree node.
struct TreeNode {
    int val;
    TreeNode *left;
    TreeNode *right;
    TreeNode() : val(0), left(nullptr), right(nullptr) {}
    TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
    TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 };

class Solution
{
public:
    void printNums(vector<int> &nums)
    {
        for (int i = 0; i < nums.size(); ++i)
        {
            cout << nums[i] << " ";
        }
        cout << endl;
    }

    // 从数组构建二叉树
    TreeNode* buildTree(const vector<int>& nums) {
        if (nums.empty()) return nullptr;

        vector<TreeNode*> nodes;
        for (int val : nums) {
            if (val == -1) {
                nodes.push_back(nullptr); // 用 -1 表示空节点
            } else {
                nodes.push_back(new TreeNode(val));
            }
        }

        int pos = 1;
        for (int i = 0; i < nodes.size() && pos < nodes.size(); ++i) {
            if (nodes[i]) {
                nodes[i]->left = nodes[pos++];
                if (pos < nodes.size()) {
                    nodes[i]->right = nodes[pos++];
                }
            }
        }

        return nodes[0];
    }

    vector<int> bfs(TreeNode* cur) {
        
        vector<int> result;
        queue<TreeNode*> myQueue;
        myQueue.push(cur);

        while (!myQueue.empty()) {
            int n = myQueue.size();
            int sum = 0;
            while (n--) {
                TreeNode* current = myQueue.front();
                myQueue.pop();
                
                sum += current->val;

                if (current->left){
                    myQueue.push(current->left);
                }
                if (current->right) {
                    myQueue.push(current->right);
                }
            }
            result.push_back(sum);   
        }
        printNums(result);
        return result;
    }

    int rob(TreeNode* root) {
        vector<int> nums = bfs(root);
        int n = nums.size();
        if (n == 1)
            return nums[0];
        if (n == 2) 
            return max(nums[0], nums[1]);
 
        vector<int> dp(n, 0);
        dp[0] = nums[0];
        dp[1] = max(nums[0], nums[1]);
        
        for (int i = 2; i < n; ++i)
        {
            dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]);
        }

        //printNums(dp);
        return dp[n - 1]; 

    }
};

int main()
{
    Solution s;
    vector<int> nums = {2,1,3,-1,4};
    TreeNode* root = s.buildTree(nums);
    vector<int> values = s.bfs(root);
    int result = s.rob(root);
    cout << result << endl;
    return 0;
}

为什么这样的解法不对呢,举个例子来看:

        2
       / \
      1   3
       \   
        4

对于这棵树,层序遍历得到的数组是 [2. 4. 4],dp 数组是 [2, 4, 6],最终结果是 6,然而正确结果应该是 7,偷窃第二层的 3 以及第三层的 4,他们并不是同一层的两个节点。

打家劫舍 III 的约束是不能同时偷取直接相连的父子节点,而不是简单的相邻层次的节点。后面会进一步解释我的解法错误原因。

暴力解

类似 打家劫舍 Ⅱ 的思想,对于根节点,有两种情况,偷 或者 不偷

  • 如果偷根节点,那么接下来只能继续偷根节点的孙子节点了:
int value1 = root->val 
        + rob(root->left ? root->left->left : nullptr) 
        + rob(root->left ? root->left->right : nullptr) 
        + rob(root->right ? root->right->left : nullptr) 
        + rob(root->right ? root->right->right : nullptr);
  • 如果不偷根节点,那么接下来可以偷根节点的儿子节点。
int value2 = rob(root->left) + rob(root->right);
class Solution {
public:
    int rob(TreeNode* root) {

        if (!root) return 0;

        int value1 = root->val 
            + rob(root->left ? root->left->left : nullptr) 
            + rob(root->left ? root->left->right : nullptr) 
            + rob(root->right ? root->right->left : nullptr) 
            + rob(root->right ? root->right->right : nullptr);
        int value2 = rob(root->left) + rob(root->right);

        return max(value1, value2);
    }
};

结果是超时了。

记忆化搜索

在第一种暴力递归的方法中,假设节点结构如下:

第一次我们计算了 A, C, D, G 的价值之和,以及 B, F, E, H 的价值之和,然后当 B, F 为父节点时,我们又需要重新计算 B, F, E ,H 的价值和 C, D, G 的价值,这里面是有重复计算的,通过记忆化搜索可以解决暴力递归中出现的重复子问题。

class Solution {
public:
    unordered_map<TreeNode*, int> mp;

    int rob(TreeNode* root) {

        if (!root) return 0;
        if (!root->left && !root->right) return root->val;
        if (mp[root]) return mp[root];

        int value1 = root->val;
        if (root->left)
            value1 += rob(root->left->left) + rob(root->left->right);
        if (root->right)
            value1 += rob(root->right->left) + rob(root->right->right);

        int value2 = rob(root->left) + rob(root->right);

        mp[root] = max(value1, value2);
        return mp[root];
    }
};

通过一个 unorder_map 来记录以每个节点为根节点,计算得到的最终结果,来避免重复计算的子问题,此时二叉树的每个节点只需要遍历一次,时间复杂度降低为O(n),自然是不会超时了。

这里我感觉其实已经有了一点动态规划的味道在里面了,接下来继续看树形动态规划的具体解法。

树形动态规划

在 打家劫舍 Ⅱ 我们提到,第 1 间房屋有两个状态:偷 或者 不偷,它的选择会影响到后续的状态,对于这题也是如此,对于当前节点 TreeNode* current,有 偷 / 不偷 两种状态,用一个大小为 2 的数组记录这两种状态的结果 value1, value2:

  • 偷当前节点,左右孩子都不能偷:int value1 = current->val + 左孩子不偷返回的最大值 + 右孩子不偷返回的最大值。
  • 不偷当前节点,左右孩子既可以偷,也可以不偷:int value2 = max(左孩子偷返回的最大值, 左孩子不偷返回的最大值) + max(右孩子偷返回的最大值, 右孩子不偷返回的最大值) 。
  • return {value1, value2};

以上就是单层递归的核心逻辑。

class Solution {
public:
    vector<int> robTree(TreeNode* root) {
        // 该节点为空, 偷与不偷都返回0
        if (!root) return {0, 0};

        // 计算该节点的左、右孩子的两种状态返回的结果
        vector<int> left = robTree(root->left);
        vector<int> right = robTree(root->right);

        // 0表示偷,1表示不偷        
        // 偷该节点,左右孩子都不能偷
        int value0 = root->val + left[1] + right[1];
        
        // 不偷该节点,左右孩子既可以偷,也可以不偷
        int value1 = max(left[0], left[1]) + max(right[0], right[1]);

        return {value0, value1};
    }

    int rob(TreeNode* root) {
        if (!root) return 0;
        if (!root->left && !root->right) return root->val;

        vector<int> result = robTree(root);

        return max(result[0], result[1]);
    }
};

这里有很重要的一点需要理解,如果当前节点我们不偷的话,对于他的左右孩子(如果存在),我们既可以选择偷,也可以选择不偷,我们要选择其中的更大值,而不是一定要偷它的左右孩子。

这也就是为什么,我写的层序遍历解法会错误,因为层序遍历的逻辑是,当前节点不偷,则它的左右孩子就一定要偷。

打家劫舍 Ⅳ

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

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

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

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

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

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

根据题意,小偷最少偷 k 间房屋,返回小偷的最小窃取能力,也就是偷取的最大金额 最小,很容易就想到这是求一个 最小化最大值 的问题,我们在 2439. 最小化数组中的最大值 讲过,这类问题,可以想到的方法有两种:前缀和 以及 二分查找。

但是这道题多了一个条件,小偷至少要偷 k 间房屋。

我们同样用二分查找的来思考,设 mid 表示小偷的窃取能力,也就是窃取单间房屋的最大金额

  • 如果此时能偷的房间数目 ≥ k,则对于(mid, nums.max()],能偷的房间数一定 ≥k;
  • 如果此时能偷的房间数目 < k,则对于 [nums.min(), mid),能偷的房间数一定 <k;

这就是使用二分查找所需要的单调性

设计一个函数 robNum(),返回 小偷的窃取能力为 maxMoney 时,能偷取的最大房间数 roomNum:

  • 如果 roomNum >=k,则寻找是否有更小的窃取能力;
  • 如果 roomNum < k,则寻找是否有更大的窃取能力;

完整代码:

class Solution {
public:
    int rob(vector<int>& nums, int maxNum) {
        int n = nums.size();
         
        // dp[i]: 偷取房屋的最大金额不超过maxNum, 最多能偷dp[i]间房屋
        vector<int> dp(n, 0);

        dp[0] = nums[0] > maxNum ? 0 : 1;
        if (n == 1) return dp[0];
        
        if (nums[1] > maxNum) {
            dp[1] = dp[0];
        } else {
            dp[1] = 1;
        }

        for (int i = 2; i < n; ++i) {
            if (nums[i] > maxNum) {
                dp[i] = dp[i-1];
            } else {
                dp[i] = max(dp[i-1], dp[i-2] + 1);
            }
        }
        return dp[n-1];
    }

    int minCapability(vector<int>& nums, int k) {
    
        int left = *min_element(nums.begin(), nums.end());
        int right = *max_element(nums.begin(), nums.end());

        while (left <= right) {
            int mid = left + (right - left) / 2;    // 代表小偷的窃取能力
            int temp = rob(nums, mid);  // 返回对应窃取能力下,能偷的房间数目

            if (temp >= k) {
                right = mid - 1;
            } else {
                left = mid + 1;
            }
        } 
        return left;
    }
};

空间复杂度:O(n)

还可以使用滚动变量来优化rob 函数的空间复杂度,将 dp 数组优化为常数空间:

class Solution {
public:
    int rob(vector<int>& nums, int maxNum) {
        int n = nums.size();
         
        // 滚动数组
        // dp[0] 第i-2间房屋
        // dp[1] 第i-1间房屋
        // dp[2] 第i间房屋
        vector<int> dp(3, 0);

        dp[0] = nums[0] > maxNum ? 0 : 1;
        if (n == 1) return dp[0];
        
        dp[1] = nums[1] > maxNum ? dp[0] : 1;

        for (int i = 2; i < n; ++i) {
            if (nums[i] > maxNum) {
                dp[2] = dp[1];
            } else {
                dp[2] = max(dp[1], dp[0] + 1);
            }

            dp[0] = dp[1];
            dp[1] = dp[2];
        }
        return dp[1];
    }

    int minCapability(vector<int>& nums, int k) {
    
        int left = *min_element(nums.begin(), nums.end());
        int right = *max_element(nums.begin(), nums.end());

        while (left <= right) {
            int mid = left + (right - left) / 2;    // 代表小偷的窃取能力
            int temp = rob(nums, mid);  // 返回对应窃取能力下,能偷的房间数目

            if (temp >= k) {
                right = mid - 1;
            } else {
                left = mid + 1;
            }
        } 
        return left;
    }
};

空间复杂度:O(1)

进一步还可以使用二分查找 + 贪心算法来计算:

class Solution {
public:
    int rob(vector<int>& nums, int maxNum) {
        int n = nums.size();
        int count = 0;
        for (int i = 0; i < n;) {
            if (nums[i] <= maxNum) {
                count++;
                i += 2; // 跳过相邻的下一个房屋
            } else {
                i++;
            }
        }
        return count;
    }

    int minCapability(vector<int>& nums, int k) {
    
        int left = *min_element(nums.begin(), nums.end());
        int right = *max_element(nums.begin(), nums.end());

        while (left <= right) {
            int mid = left + (right - left) / 2;    // 代表小偷的窃取能力
            int temp = rob(nums, mid);  // 返回对应窃取能力下,能偷的房间数目

            if (temp >= k) {
                right = mid - 1;
            } else {
                left = mid + 1;
            }
        } 
        return left;
    }
};

在rob 函数中:从左到右遍历 nums ,只要当前房子可以偷,就立刻偷。

必然成立。那么对于 i 号房间,如果 nums[i] <= maxMoney:

  • 如果偷当前房间,则 dp[i] = dp[i-2] + 1;
  • 如果不偷当前房间,则 dp[i] = dp[i-1];

根据公式 (4),偷当前房间得到的 dp[i](房间数目) 是一定大于不偷房间的,所以小偷面对一间他可以偷的房间,他的最优决策就是偷!

算法复杂度分析:

  • 时间复杂度:

        贪心算法的单次遍历为 O(n)。

        二分查找的次数为 O(log M),其中 M 为数组中的最大值。

        总的时间复杂度为 O(n log M)。

  • 空间复杂度:

        只使用了常数级别的额外空间,空间复杂度为 O(1)。

算法(Java)——动态规划 动态规划是经典算法的一种。在算法中动态规划算法的重要性不容置疑,本博客主要是记载自己在刷题和学习过程中对动态规划的一个理解和总结。 动态规划 定义 动态规划算法是通过拆分问题,定义问题状态和状态之间的关系,使得问题能够以递推(或者说分治)的方式去解决。 动态规划算法的基本思想与分治法类似,也是将待求解的问题分解为若干个子问题(阶段),按顺序求解子阶段,前一子问题的解,为后一子问题的求解提供了有用的信息。在求解任一子问题时,列出各种可能的局部解,通过决策保留那些有可能达到最优的局部解,丢弃其他局部解。依次解决 阅读详情

相关推荐

动态规划之背包问题——完全背包

文章目录一、完全背包问题二、完全背包遍历顺序三、leetcode例题讲解完全背包问题518. 零钱兑换 II377. 组合总和 Ⅳ322. 零钱兑换279. 完全平方数139. 单词拆分四、完全背包问题总结1. 动规五步分析法2. 背包递推公式3. 遍历顺序 前面整理了01背包,在leetcode题库中主要就是01背包和完全背包问题,所以在这里整理一下完全背包的知识点。 一、完全背包问题 有N件物品和一个最多能背重量为W的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品都

小朱小朱绝不认输的博客 8750

动态规划之打家劫舍系列

打家劫舍问题

1432

Isaacgym使用操作指南

Isaac Gym 是一个强大的仿真工具,特别适合那些需要进行大规模并行仿真和训练的机器人和强化学习任务。通过 GPU 加速、深度学习集成和丰富的物理仿真能力,Isaac Gym 能够显著提高仿真和训练效率,是机器人学和 AI 研究中的一大利器。

Tony Wey的博客 1万+

动态规划:打家劫舍系列

针对首尾房间是否被偷,划分为以下几种情况,每种情况均可以使用"打家劫舍1"的代码求解。:环形问题不利于思考,可以将环形展开为线性结构,单独考虑首尾元素是否选取,分情况讨论。:每间房内都藏有一定的现金(非负整数数组),问一夜之内能够偷窃到的最高金额?:每间房内都藏有一定的现金(非负整数数组),问一夜之内能够偷窃到的最高金额?:利用"状态压缩"进行优化 →。

大胡子的博客 1808

基于python的奥运会历史数据分析【120年】

数据分析实战案例

xff123456_的博客 3332

PWM调节DCDC参数计算原理

SOC芯片的核电压、GPU电压、NPU电压、GPU电压等,都会根据性能和实际应用场景来进行电压和频率的调整。 即动态电压频率调整DVFS(Dynamic Voltage and Frequency scaling),优化性能和功耗。

电子绿洲的博客 3145

动态规划----解决“打家劫舍”问题

动态规划----解决“打家劫舍”问题

qq_63691275的博客 2044

STM32G030C8T6 ADC+DMA实战:同时采集外部电压和芯片温度的完整代码流程

本文详细介绍了STM32G030C8T6芯片通过ADC+DMA实现外部电压和芯片内部温度采集的完整流程。从硬件设计到CubeMX配置,再到驱动模块化实现与系统优化,提供了高精度多通道采集系统的实战指南,特别适合嵌入式开发者在工业控制和环境监测项目中应用。

weixin_42653691的博客 460

力扣题目-----打家劫舍三道

例题一: 你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。 给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。 这道题直接简化为:给你一组数字,让你选择若干个数字,前提条件是你选的数字不能是相邻的。 思路:采用动态规划来做,对于第 k (k>2) 间房屋,有两个选项: 偷窃第 k 间房屋,那么就不能偷窃第

weixin_43743711的博客 714

抖音短视频数据抓取实战系列(一)——模拟器性能对比与优化配置

本文是抖音短视频数据抓取实战系列的开篇,重点探讨了模拟器选型与优化配置。通过对比雷电、夜神、逍遥三大主流模拟器在性能、抖音兼容性及抓包友好度上的表现,文章指出雷电模拟器因其稳定的桥接网络模式和良好的兼容性,是数据抓取的优选。并提供了雷电模拟器从安装、网络桥接到系统优化的详细配置指南,旨在帮助读者构建稳定高效的抓取环境。

weixin_29230901的博客 234

rk3588 LightSensor适配从驱动到应用层

本文详细介绍了在RK3588平台上实现光感驱动的开发流程。主要包括:1) 在设备树中配置I2C5接口挂载光感设备;2) 编写内核驱动代码实现光感数据采集;3) HAL层通过ioctl与驱动交互;4) 在BoardConfig.mk中启用相关宏定义;5) Framework层通过AutomaticBrightnessController实现自动亮度调节算法;6) 应用层可通过监听系统设置获取光感变化并调整屏幕亮度。整个流程涵盖了从底层驱动到上层应用的完整实现方案,重点解决了光感设备注册、数据采集传输和自动亮度

tzj1058756809的博客 447

动态规划-打家劫舍类问题

你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。之后,删除 2 获得 2 个点数。总共获得 6 个点数。偷窃到的最高金额 = 1 + 3 = 4。给定一个代表每个房屋存放金额的非负整数数组,计算你。删除 4 获得 4 个点数,因此 3 也被删除。返回你能通过这些操作获得的最大点数。,一夜之内能够偷窃到的最高金额。,你可以对它进行一些操作。

m0_47411815的博客 665

【LaTex】3.4 立体几何

本文介绍了使用LaTeX的tikz包绘制立体几何图形的两种方法。首先展示了基础立方体的绘制代码,通过定义8个顶点坐标并连接各边完成绘制。接着讲解了scope功能在3D绘图中的应用,包括标准平面(xy、xz、yz)的绘制方法,以及任意平面的定义技巧。重点说明了如何通过定义平面原点、x和y方向单位向量来创建旋转后的绘图平面,并以45°旋转的yz平面为例演示了圆的绘制。文章提供了完整的代码示例和对应的效果图示,展示了tikz在3D绘图中的灵活性和强大功能。

你的指尖有改变世界的力量 2446

破解打家劫舍:动态规划与二分查找的高效算法

本文介绍了四种不同条件下的“打家劫舍”问题解决方案,包括一维和二维动态规划、环形房屋处理、二叉树结构房屋以及最小窃取能力的二分查找方法。

数据与算法架构提升之路专栏 1188

使用STM32H7点亮RGB屏幕

希望本文能对你有所帮助!RGB屏幕是一种具有红、绿、蓝三种颜色通道的显示设备,通过控制每个通道的亮度,可以显示出丰富多彩的图像和动画。要使用STM32H7控制RGB屏幕,我们需要连接适当的引脚,并编写适当的代码来控制每个颜色通道的亮度。在主循环中,我们先点亮红色通道,然后延迟一段时间,接着点亮绿色通道,再延迟一段时间,最后点亮蓝色通道,并再次延迟。需要注意的是,示例代码中的延迟循环使用了一个简单的计数器实现,但实际应用中可能需要更精确的延迟方式,例如使用定时器中断或操作系统提供的延迟函数。

QoyOle的博客 919

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

代码随想录算法训练营第四十一天|198.打家劫舍,213.打家劫舍II,337.打家劫舍III

代码随想录算法训练营第一天|数组理论基础,704. 二分查找,27. 移除元素 代码随想录算法训练营第二天|977.有序数组的平方 ,209.长度最小的子数组 ,59.螺旋矩阵II 代码随想录算法训练营第三天|链表理论基础,203.移除链表元素,707.设计链表,206.反转链表 代码随想录算法训练营第四天|24. 两两交换链表中的节点,19.删除链表的倒数第N个节点,面试题 02.07. 链表相交,142.环形链表II,总结 代码随想录算法训练营第五天|哈希表理论基础,242.有效的字母异位词,349.

weixin_47748259的博客 1103

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

【编程之路】面试必刷TOP101:动态规划(78-82,Python实现)

面试必刷TOP101:动态规划(78-82,Python实现)

Code · Cloud · Think · Repeat 778

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

RU DOS版、EFI版32bit/64bit BIOS常用工具

DOS版、EFI版的RU工具,可用来查看IO、MEM、PCI、SMBIOS等资源。亲测在EFI SHELL下可用。

【面试必刷101】动态规划1

【面试必刷101】系列blog目的在于总结面试必刷101中有意思、可能在面试中会被考到的习题。总结通用性的解题方法,针对特殊的习题总结思路。既是写给自己复习使用,也希望与大家交流。

weixin_41399650的博客 709

【动态规划基础专题刷题】每道题均有完整详细题目描述、多组示例(附解释)、清晰解题思路、带注释Java代码、复杂度分析

本文介绍了三个动态规划基础问题:爬楼梯(LeetCode 70)、斐波那契数(LeetCode 509)和打家劫舍(LeetCode 198)。每个问题均包含详细题目描述、示例、解题思路和Java代码实现。爬楼梯问题通过斐波那契数列模型解决,斐波那契数问题展示了递归、基础DP和空间优化三种解法,打家劫舍问题则通过状态转移避免相邻选择。代码均提供注释和复杂度分析,基础DP版本空间复杂度为O(n),优化后可达O(1)。这些题目是动态规划的经典入门案例,适合理解DP的核心思想。

独立思考,明辨是非 1475

动态规划之背包问题——01背包

文章目录一、01背包问题二、二维dp数组解决01背包问题1. 确定dp数组以及下标的含义2. 确定递推公式3. dp数组初始化4. 确定遍历顺序5. 举例推导dp数组三、一维dp数组解决01背包问题1. 确定dp数组以及下标的含义2. 一维dp数组的递推公式3. 一维dp数组如何初始化4. 一维dp数组遍历顺序5.举例推导dp数组四、leetcode例题讲解01背包问题416. 分割等和子集1049. 最后一块石头的重量 II494. 目标和474. 一和零 背包问题中我们常见的就是01背包和完全背包。在l

小朱小朱绝不认输的博客 1万+

算法系列

leetcode题解4-寻找两个正序数组的中位数 leetcode题解20-有效的括号 leetcode题解14-最长公共前缀 leetcode题解3-无重复字符的最长子串 leetcode题解70-爬楼梯 leetcode题解8-盛最多水的容器 leetcode题解75-颜色分类 leetcode题解50-Pow(x,n) leetcode题解26-删除数组的重复项 leetcode题解167-两数之和 II - 输入有序数组 leetcode题解54-螺旋矩阵 leetcode题解53-最大子序和 le

Java硬件工程师的博客 1460

动态规划攻克数组区间问题:从打家劫舍到区间调度

动态规划是解决最优化问题的核心算法,其精髓在于状态定义与转移方程的设计。在数组区间类问题中,如打家劫舍、最长递增子序列等,动态规划通过将问题分解为重叠子问题,利用记忆化或递推高效求解。掌握区间DP的通用框架,能显著提升解决复杂场景的能力,例如资源调度、路径规划等。本文深入剖析线性、环形及树形结构下的DP思维跃迁,结合打家劫舍、区间调度等经典案例,助你彻底理解状态设计的本质。

weixin_33871366的博客 317

动态规划系列-从LeetCode 300 最长递增子序列开始

给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。 子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。 动态规划: dp[i]: nums数组中前i个元素的最长上升子数组的长度 对于0 <= j < i ...

kittimzhe的博客 171

算法分析之贪心算法

文章目录一、贪心算法基础知识1. 基本概念2. 基本思路3. 存在问题4. 适用问题5. 贪心选择性质与最优子结构性质6. 实现框架二、贪心算法与动态规划1. 举例分析1)0-1背包问题2)背包问题2. 贪心套路三、leetcode例题讲解贪心算法问题1. 贪心算法常规问题(1)常规问题455. 分发饼干1005. K 次取反后最大化的数组和860. 柠檬水找零2. 贪心算法进阶问题(1)序列问题376. 摆动序列738. 单调递增的数字(2)股票问题122. 买卖股票的最佳时机 II714. 买卖股票的最

小朱小朱绝不认输的博客 2860

Leetcode编程实践 - 动态规划(300/674/198/213/72/5/516)

当看到题目没有什么好想法,且题目涉及最大/最小问题时,可以考虑DP(小Tip: 为了方便初始化,有时DP多设+1长度) 文章目录一. 动态规划1.1 动态规划的步骤:1.2 举个栗子 - 最长上升子序列A)题目描述B)套思路C)代码二. DP的应用1. 最长连续递增序列A)题目描述B)思路C)代码2. 打家劫舍A)题目描述B)思路C)代码3. 打家劫舍IIA)题目描述B)思路C)代码4. 编辑距离A)题目描述B)思路C)代码5. 最长回文子串A)题目描述B)思路C)代码三. 参考资料 一. 动态规划 1.

libh的博客 265

leetcode_动态规划

目录1. 斐波那契数列70. 爬楼梯198. 打家劫舍213. 打家劫舍 II信件错排母牛生产2. 矩阵路径64. 最小路径和62. 不同路径3. 数组区间303. 区域和检索 - 数组不可变413. 等差数列划分343. 整数拆分*279. 完全平方数91. 解码方法5. 最长递增子序列300. 最长上升子序列* (中等)646. 最长数对链* 1. 斐波那契数列 70. 爬楼梯 假设你正在爬...

powerful_ren的博客 915
上一篇: 2439. 最小化数组中的最大值 (前缀和 / 二分查找)
下一篇: 【HOT100】技巧篇(136. 只出现一次的数字/169. 多数元素/75. 颜色分类/31. 下一个排列/287. 寻找重复数)
红茶川
博客等级 码龄8年 943粉丝 55原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

红茶川

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值