• Leetcode刷题笔记--Hot71--80


    目录

    1--会议室II(253)

    2--完全平方数(279)

    3--移动零(283)

    4--寻找重复数(287)

    5--二叉树的序列化与反序列化(297)

    6--最长递增子序列(300)

    7--删除无效的括号(301)

    8--买卖股票的最佳时机含冷冻期(309)

    9--戳气球(312)

    10--零钱兑换(322)


    1--会议室II(253)

    2--完全平方数(279)

    主要思路:

            完全背包问题,每一个平方数可以选取多次。

            本题的物品组合与顺序无关,对应于组合问题,因此先遍历物品,再遍历背包。

            定义dp[j]表示背包容量为j时,装满背包所需完全平方数的最少数量。

    1. #include
    2. #include
    3. class Solution {
    4. public:
    5. int numSquares(int n) {
    6. std::vector<int>dp(n+1, n); // 求最少数量,因此初始化不应该为0
    7. dp[0] = 0;
    8. for(int i = 1; i*i <= n; i++){ // 遍历物品
    9. for(int j = i*i; j <= n; j++){ // 遍历背包
    10. dp[j] = std::min(dp[j], dp[j - i*i] + 1); // dp[j]表示不选取物品i*i
    11. }
    12. }
    13. return dp[n];
    14. }
    15. };
    16. int main(int argc, char* argv[]){
    17. // n = 12
    18. int test = 12;
    19. Solution S1;
    20. int res = S1.numSquares(test);
    21. std::cout << res << std::endl;
    22. return 0;
    23. }

    3--移动零(283)

    主要思路:

            本题只是要保持非零元素的相对顺序,没要求保持零元素的相对顺序(卡了很久,没看清楚题意);

            用双指针算法即可,左指针和右指针初始化为0,左指针指向已处理元素序列的尾部(左指针左边全是非零值),右指针指向待处理元素序列的头部;当右指针遇到非零值,交换左右指针的值即可;

    1. #include
    2. #include
    3. class Solution {
    4. public:
    5. void moveZeroes(std::vector<int>& nums) {
    6. int left = 0, right = 0;
    7. while(right < nums.size()){
    8. if(nums[right] != 0){
    9. std::swap(nums[left], nums[right]);
    10. left++;
    11. }
    12. right++;
    13. }
    14. }
    15. };
    16. int main(int argc, char* argv[]){
    17. // nums = [0, 1, 0, 3, 12]
    18. Solution S1;
    19. std::vector<int> test = {0, 1, 0, 3, 12};
    20. S1.moveZeroes(test);
    21. for(int num : test) std::cout << num << " ";
    22. std::cout << std::endl;
    23. return 0;
    24. }

    4--寻找重复数(287)

    主要思路:

            对于数组nums,定义cnt[i]表示nums数组中小于等于i的数的数量,假设重复的数是target,则对于[1, target-1]里所有的数满足cnt[i] <= i;对于[target, n]里的所有数满足cnt[i] > i;

            不断二分,直到找到target即可。

    1. #include
    2. #include
    3. class Solution {
    4. public:
    5. int findDuplicate(std::vector<int>& nums) {
    6. int left = 1, right = nums.size() - 1;
    7. int ans;
    8. while(left <= right){
    9. int mid = left + (right - left) / 2;
    10. int count = 0;
    11. for(int num : nums){
    12. if(num <= mid) count++; // 统计小于mid的元素个数
    13. }
    14. if(count <= mid){ // 表面mid在目标元素左侧
    15. left = mid + 1;
    16. }
    17. else{ // mid可能是目标元素,也可能在目标元素右侧
    18. right = mid - 1;
    19. ans = mid; // 记录目标元素
    20. }
    21. }
    22. return ans;
    23. }
    24. };
    25. int main(int argc, char* argv[]){
    26. // nums = [1, 3, 4, 2, 2] 001 011 100 010 010
    27. std::vector<int> test = {1, 3, 4, 2, 2};
    28. Solution S1;
    29. int res = S1.findDuplicate(test);
    30. std::cout << res << std::endl;
    31. return 0;
    32. }

    5--二叉树的序列化与反序列化(297)

    主要思路:

            基于层次遍历序列化和反序列化;

    1. #include
    2. #include
    3. #include
    4. #include
    5. struct TreeNode {
    6. int val;
    7. TreeNode *left;
    8. TreeNode *right;
    9. TreeNode(int x) : val(x), left(NULL), right(NULL) {}
    10. };
    11. class Codec {
    12. public:
    13. std::string serialize(TreeNode* root) {
    14. // 层次遍历序列化
    15. std::queue q;
    16. std::string res;
    17. q.push(root);
    18. while(!q.empty()){
    19. TreeNode* cur = q.front();
    20. q.pop();
    21. if(cur != nullptr){
    22. res.append(std::to_string(cur->val) + ',');
    23. q.push(cur->left);
    24. q.push(cur->right);
    25. }
    26. else{
    27. res.append("#,"); // 使用"#"表示nullptr
    28. }
    29. }
    30. return res;
    31. }
    32. TreeNode* deserialize(std::string data){
    33. if(data[0] == '#') return nullptr;
    34. std::vector vec;
    35. std::string tmp = "";
    36. for(int i = 0; i < data.length(); i++){
    37. if(data[i] != ','){
    38. tmp += data[i];
    39. }
    40. else{
    41. vec.push_back(tmp);
    42. tmp = "";
    43. }
    44. }
    45. // 层次遍历反序列化
    46. std::queue q;
    47. TreeNode *root = new TreeNode(std::stoi(vec[0])); // i = 0 作为根节点
    48. q.push(root);
    49. int i = 1; // 从1开始
    50. while(!q.empty()){
    51. TreeNode* cur = q.front();
    52. q.pop();
    53. if(vec[i] != "#"){ // 重构左孩子
    54. TreeNode* left = new TreeNode(std::stoi(vec[i]));
    55. cur->left = left;
    56. q.push(left);
    57. }
    58. i++;
    59. if(vec[i] != "#"){ // 重构右孩子
    60. TreeNode* right = new TreeNode(std::stoi(vec[i]));
    61. cur->right = right;
    62. q.push(right);
    63. }
    64. i++;
    65. }
    66. return root;
    67. }
    68. };
    69. int main(int argc, char* argv[]){
    70. // root = [1, 2, 3, null, null, 4, 5]
    71. TreeNode *Node1 = new TreeNode(1);
    72. TreeNode *Node2 = new TreeNode(2);
    73. TreeNode *Node3 = new TreeNode(3);
    74. TreeNode *Node4 = new TreeNode(4);
    75. TreeNode *Node5 = new TreeNode(5);
    76. Node1->left = Node2;
    77. Node1->right = Node3;
    78. Node3->left = Node4;
    79. Node3->right = Node5;
    80. Codec S1;
    81. std::string test1 = S1.serialize(Node1);
    82. std::cout << test1 << std::endl;
    83. TreeNode* res = S1.deserialize(test1);
    84. std::queue q;
    85. q.push(res);
    86. while(!q.empty()){
    87. TreeNode* cur = q.front();
    88. q.pop();
    89. if(cur != nullptr){
    90. std::cout << cur->val << " ";
    91. q.push(cur->left);
    92. q.push(cur->right);
    93. }
    94. else{ // nullptr
    95. std::cout << "null" << " ";
    96. }
    97. }
    98. std::cout << std::endl;
    99. return 0;
    100. }

    6--最长递增子序列(300)

    主要思路:

            dp[i]表示以nums[i]结尾的子序列的最大长度;

            状态转移方程:

            dp[i] = std::max(dp[i], dp[j] + 1) if nums[i] > nums[j] && i > j;

    1. #include
    2. #include
    3. class Solution {
    4. public:
    5. int lengthOfLIS(std::vector<int>& nums) {
    6. int res = 1;
    7. std::vector<int> dp(nums.size(), 1); // dp[i]表示以第i个字符结尾的子序列的最长长度
    8. for(int i = 1; i < nums.size(); i++){
    9. for(int j = 0; j <= i; j++){
    10. if(nums[i] > nums[j]){
    11. dp[i] = std::max(dp[i], dp[j]+1);
    12. }
    13. }
    14. res = std::max(res, dp[i]);
    15. }
    16. return res;
    17. }
    18. };
    19. int main(int argc, char* argv[]){
    20. // nums = [10, 9, 2, 5, 3, 7, 101, 18]
    21. std::vector<int> test = {10, 9, 2, 5, 3, 7, 101, 18};
    22. Solution S1;
    23. int res = S1.lengthOfLIS(test);
    24. std::cout << res << std::endl;
    25. return 0;
    26. }

    7--删除无效的括号(301)

    主要思路:

            基于回溯法,递归删除每一个字符,判断最后的字符串是否合法。

    1. #include
    2. #include
    3. #include
    4. class Solution {
    5. public:
    6. std::vector removeInvalidParentheses(std::string s) {
    7. // 首先计算最少需要删除的左括号和右括号个数
    8. int lmove = 0, rmove = 0;
    9. for(int i = 0; i < s.length(); i++){
    10. if(s[i] == '(') lmove++;
    11. else if(s[i] == ')'){
    12. if(lmove > 0) lmove--;
    13. else rmove++;
    14. }
    15. }
    16. std::vector res; // 返回结果
    17. dfs(s, 0, lmove, rmove, res);
    18. return res;
    19. }
    20. // 递归回溯
    21. void dfs(std::string &s, int start, int lmove, int rmove, std::vector& res){
    22. if(lmove == 0 && rmove == 0){ // 收获结果
    23. if(isValid(s)){
    24. res.push_back(s);
    25. }
    26. return;
    27. }
    28. for(int i = start; i < s.length(); i++){
    29. if(i != start && s[i] == s[i-1]) continue; // 去重
    30. // 还需要删除的括号个数小于能删除的字符个数
    31. if(lmove + rmove > s.length() - i) return; // 提前剪枝
    32. // 删除当前字符
    33. if(s[i] == '(' && lmove > 0){ // 当前字符是左括号
    34. s.erase(i, 1);
    35. dfs(s, i, lmove-1, rmove, res); // 删除后,下一次开始还是第cur个字符
    36. s.insert(s.begin() + i, '('); // 回溯恢复
    37. }
    38. else if(s[i] == ')' && rmove > 0){ // 当前字符是右括号
    39. s.erase(i, 1);
    40. dfs(s, i, lmove, rmove-1, res);
    41. s.insert(s.begin() + i, ')'); // 回溯恢复
    42. }
    43. }
    44. }
    45. // 判断字符串是否有效
    46. bool isValid(std::string str){
    47. int left = 0;
    48. for(int i = 0; i < str.length(); i++){
    49. if(str[i] == '(') left++;
    50. else if(str[i] == ')') left--;
    51. if(left < 0){ // 当某次 left < 0 时,表明当前右括号的个数多于左括号,即 ()())( 的情况
    52. return false;
    53. }
    54. }
    55. if(left == 0) return true;
    56. else return false;
    57. }
    58. };
    59. int main(int argc, char* argv[]){
    60. // s = "()())()"
    61. std::string test = "()())()";
    62. Solution S1;
    63. std::vector res = S1.removeInvalidParentheses(test);
    64. for(std::string str : res) std::cout << str << " ";
    65. std::cout << std::endl;
    66. return 0;
    67. }

    8--买卖股票的最佳时机含冷冻期(309)

    主要思路:

            对于每一天,都有三种状态,处于买入状态,处于卖出状态,处于冷冻期状态。

            定义dp[i][0]表示第 i 天处于买入状态,dp[i][1]处于卖出状态,dp[i][2]处于冷冻期状态。

            初始化dp[0][0] = -prices[0],dp[0][1] = 0, dp[0][2] = 0;

            状态转移方程:

            dp[i][0] = max(dp[i-1][2] - prices[i], dp[i-1][0]); // 前一天可以是冷冻期,也可以是处于买入状态;

            dp[i][1] = max(dp[i-1][1], dp[i-1][0] + prices[i], dp[i-1][2]); // 前一天可以是卖出状态,也可以是买入状态和冷冻期状态。

            dp[i][2] = dp[i-1][1]; // 前一天只能是卖出状态。 

    1. #include
    2. #include
    3. class Solution {
    4. public:
    5. int maxProfit(std::vector<int>& prices) {
    6. // dp[i][0]买入、dp[i][1]卖出、dp[i][2]冷冻期三种状态
    7. std::vectorint>> dp(prices.size(), std::vector<int>(3, 0));
    8. // 初始化
    9. dp[0][0] = -prices[0];
    10. dp[0][1] = 0;
    11. dp[0][2] = 0;
    12. // 状态转移
    13. for(int i = 1; i < prices.size(); i++){
    14. dp[i][0] = std::max(dp[i-1][2] - prices[i], dp[i-1][0]); // dp[i-1][0]表示第i-1天就是买入的状态
    15. dp[i][1] = std::max(dp[i-1][1], std::max(dp[i-1][0] + prices[i], dp[i-1][2]));
    16. dp[i][2] = dp[i-1][1];
    17. }
    18. return std::max(dp[prices.size() - 1][1], dp[prices.size() - 1][2]);
    19. }
    20. };
    21. int main(int argc, char* argv[]){
    22. // prices = [1, 2, 3, 0, 2]
    23. std::vector<int> test = {1, 2, 3, 0, 2};
    24. Solution S1;
    25. int res = S1.maxProfit(test);
    26. std::cout << res << std::endl;
    27. return 0;
    28. }

    9--戳气球(312)

    主要思路:

            基于动态规划,dp[i][j] 表示戳爆在区间 [i, j] 内所有气球能获得的最大硬币数;

            状态转移方程:dp[i][j] = std::max(dp[i][j], dp[i][k-1] + dp[k+1][j] + a[i-1]*a[k]*a[j+1]);

            k 表示在区间[i, j]中最后戳爆的气球。

    1. #include
    2. #include
    3. #include
    4. class Solution {
    5. public:
    6. int maxCoins(std::vector<int>& nums){
    7. int n = nums.size();
    8. std::vector<int> a(n+2); // 在两个边界填充1
    9. for(int i = 1; i <= n; i++){
    10. a[i] = nums[i - 1];
    11. }
    12. a[0] = a[n + 1] = 1;
    13. // dp[i][j] 表示戳爆在区间[i, j]内所有气球能获得的最大硬币数
    14. std::vectorint>> dp(nums.size() + 2, std::vector<int>(nums.size() + 2, 0));
    15. for(int i = 1; i <= n; i++){ // 初始化区间长度为1时的最大硬币数
    16. dp[i][i] = a[i-1] * a[i] * a[i+1];
    17. }
    18. for(int len = 2; len <= n; len++){ // 枚举区间长度
    19. for(int i = 1; i + len - 1 <= n; i++){ // 枚举起点
    20. int j = i + len - 1; // 区间终点
    21. for(int k = i; k <= j; k++){ // 枚举最后一个戳爆的气球
    22. dp[i][j] = std::max(dp[i][j], dp[i][k-1] + dp[k+1][j] + a[i-1]*a[k]*a[j+1]);
    23. }
    24. }
    25. }
    26. return dp[1][n];
    27. }
    28. };
    29. int main(int argc, char* argv[]){
    30. // nums = [3, 1, 5, 8]
    31. Solution S1;
    32. std::vector<int> test = {3, 1, 5, 8};
    33. int res = S1.maxCoins(test);
    34. std::cout << res << std::endl;
    35. return 0;
    36. }

    10--零钱兑换(322)

    主要思路:

            经典完全背包问题;本题的结果与顺序无关,属于组合问题,因此先遍历物品,再遍历背包。

    1. #include
    2. #include
    3. #include
    4. class Solution {
    5. public:
    6. int coinChange(std::vector<int>& coins, int amount) {
    7. std::vector<int> dp(amount+1, INT_MAX);
    8. dp[0] = 0;
    9. for(int i = 0; i < coins.size(); i++){ // 遍历物品
    10. for(int j = coins[i] ; j <= amount; j++){ // 遍历背包
    11. if(dp[j - coins[i]] != INT_MAX){ // 确保dp[j-coins[i]]恰好可以由硬币构成
    12. dp[j] = std::min(dp[j], dp[j - coins[i]] + 1);
    13. }
    14. }
    15. }
    16. if(dp[amount] == INT_MAX) return -1;
    17. return dp[amount];
    18. }
    19. };
    20. int main(int argc, char* argv[]){
    21. // coins = [1, 2, 5], amount = 11
    22. std::vector<int> test = {1, 2, 5};
    23. int target = 11;
    24. Solution S1;
    25. int res = S1.coinChange(test, target);
    26. std::cout << res << std::endl;
    27. return 0;
    28. }

  • 相关阅读:
    springcloudalibaba架构(30):Dubbo的使用入门
    Qt窗体边框阴影的绘制
    计算机视觉+人工智能面试笔试总结——卷积网络压缩面试题
    velero 迁移k8s集群资源
    Java类加载机制
    世界杯海信再出圈,三星:“谈不上愉悦”
    10.DesignForSymbols\ExportPadstack
    (离散数学)命题公式的等价
    【JVM技术专题】网络问题分析和故障排查规划指南「实战篇」
    windows服务器热备、负载均衡配置
  • 原文地址:https://blog.csdn.net/weixin_43863869/article/details/133972402