力扣每日一题(2023年7月)

力扣每日一题(2023年7月)

2023年7月——每日一题

1、7月11日 1911. 最大子序列交替和

思路:动态规划

动态规划分析步骤:确定dp数组下标及含义,确定dp数组的递推公式,dp数组初始化,确定遍历顺序

  1. dp数组下标及含义:和122. 买卖股票的最佳时机 II类似,设dp[i][0]为从nums[0]nums[1],…,nums[i]中选择一个子序列,并且该子序列最后一个元素的下标为偶数的最大交替和;设dp[i][1]为从nums[0]nums[1],…,nums[i]中选择一个子序列,并且该子序列最后一个元素的下标为奇数的最大交替和。

  2. 递推公式

    • 对于dp[i][0]可以由两个状态推导:

      • 不选择nums[i],则dp[i][0]=dp[i-1][0]
      • 选择nums[i],则dp[i][0]=dp[i-1][1]+nums[i]
    • 对于dp[i][1]可以由两个状态推导:

      • 不选择nums[i],则dp[i][1]=dp[i-1][1]
      • 选择nums[i],则dp[i][1]=dp[i-1][0]-nums[i]
  3. 初始化dp[i][0]=nums[0]dp[i][1]=0

  4. 遍历顺序:因为dp[i]dp[i-1]确定,所以由前到后遍历

最大的交替和的子序列最后一个元素一定是偶数,所以最终返回dp[nums.size() - 1][0]

C++代码

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

2、7月12日 2544. 交替数字和

思路

由题意可知n的最高位符号为正,后面位按负正负正…的顺序,而我们通常通过倒序获得一个整数的各个位的值,因此如果知道最后一位的符号即可按倒序求解交替数字的和。如果n由奇数位组成则最后一位符号为正,由偶数位组成则最后一位符号为负,因此首先求n有多少位(可以先将n转为string,进而获取位数的奇偶性)。

C++代码

class Solution {
public:
    int alternateDigitSum(int n) {
        int res = 0;
        int flag = to_string(n).size() % 2 == 1 ? 1 : -1;
        while (n > 0) {
            int num = n % 10;
            n /= 10;
            res += flag * num;
            flag = -flag;
        }
        return res;
    }
};

3、7月13日 931. 下降路径最小和

思路:动态规划

  1. dp数组下标及含义dp[i][j]表示从第0行到第i行,第j列和最小的下降路径长度,最后返回最后一行的和最小下降路径长度的最小值即可

  2. 递推公式dp[i][j]=nums[i][j]+min(dp[i-1][j-1],dp[i-1][j],dp[i-1][j+1]),第一列和最后一列有边界情况,需要特殊处理。

  3. 初始化dp[0][j]=nums[0][j]

  4. 遍历顺序:由左向右,由上到下

C++代码

class Solution {
public:
    int minFallingPathSum(vector<vector<int>>& matrix) {
        int n = matrix.size();
        vector<vector<int>> dp(n, vector<int>(n, 0));
        copy(matrix[0].begin(), matrix[0].end(), dp[0].begin());  // copy函数
        for (int row = 1; row < n; row++) {
            for (int col = 0; col < n; col++) {
                dp[row][col] = dp[row - 1][col];
                if (col > 0 ) dp[row][col] = min(dp[row][col], dp[row - 1][col - 1]);
                if (col < n - 1) dp[row][col] = min(dp[row][col], dp[row - 1][col + 1]);
                dp[row][col] += matrix[row][col];
            }
        }
        return *min_element(dp[n - 1].begin(), dp[n - 1].end());  // vector特殊函数
    }
};

4、7月14日 979. 在二叉树中分配硬币

思路:二叉树的深度优先搜索(dfs)

  • dfs(a)表示以a为根结点的子树满足每个结点只有一个金币时,a结点的父结点需要从a拿走的金币数量
  • 设当前结点为nodeval(node)为该结点的金币数目,设node的左孩子为left,右孩子为right
    • 若要使左子树每个结点的金币数目为1,此时node需要从left拿走的金币数目为dfs(left)nodeleft之间需要移动|dfs(left)|次金币;
    • 若要使右子树每个结点的金币数目为1,此时node需要从right拿走的金币数目为dfs(right)noderight之间需要移动|dfs(right)|次金币。
    • 此时node结点的金币数目为|dfs(left)|+|dfs(right)|+val(node),由于node需要保留一个金币,所以node的根结点需要拿走它的金币数量为|dfs(left)|+|dfs(right)|+val(node)-1
  • 因此,使用后序遍历,每次遍历node时,返回其父节点需要从node拿走的金币数目,并统计当前结点与其子结点之间移动金币的数目,通过递归遍历即可求得所有结点与其父结点之间移动金币的次数统计之和即为总的金币移动次数

C++代码

/**
 * 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:
    int res = 0;
    int dfs(TreeNode* root) {
        if (root == NULL) return 0;
        int left = dfs(root->left);
        int right = dfs(root->right);
        res += abs(left) + abs(right);
        return left + right + root->val - 1;
    }
    int distributeCoins(TreeNode* root) {
        dfs(root);
        return res;
    }
};

5、7月15日 18. 四数之和

思路:双指针法

15. 三数之和类似,注意剪枝去重操作,不再详细介绍(可参考代码随想录解析)。

C++代码

class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
        sort(nums.begin(), nums.end());
        vector<vector<int>> res;
        for (int k = 0; k < nums.size(); k++) {
            if (k > 0 && nums[k] == nums[k - 1]) continue;
            for (int i = k + 1; i < nums.size(); i++) {
                if (i > k + 1 && nums[i] == nums[i - 1]) continue;
                int left = i + 1;
                int right = nums.size() - 1;
                while (left < right) {
                    if ((long)nums[k] + nums[i] + nums[left] + nums[right] > target) right--;
                    else if ((long)nums[k] + nums[i] + nums[left] + nums[right] < target) left++;
                    else {
                        res.push_back({nums[k], nums[i], nums[left], nums[right]});
                        while (left < right && nums[left] == nums[left + 1]) left++;
                        while (left < right && nums[right] == nums[right - 1]) right--;
                        left++;
                        right--; 
                    }
                    
                }
            }
        }
        return res;
    }
};

6、7月17日 415. 字符串相加

思路:双指针+直接模拟

定义两个指针ij,分别指向num1num2的末尾,同时定义add变量维护是否出现进位,从末尾到开头逐位相加即可(如果指针出现了负值,则补零即可)。

C++代码

class Solution {
public:
    string addStrings(string num1, string num2) {
        int i = num1.size() - 1, j = num2.size() - 1, add = 0;
        string res = "";
        while (i >= 0 || j >= 0 || add != 0) {
            int x = i >= 0 ? num1[i] - '0' : 0;
            int y = j >= 0 ? num2[j] - '0' : 0;
            int result = x + y + add;
            res.push_back(result % 10 + '0');
            add = result / 10;
            i--;
            j--;
        }
        reverse(res.begin(), res.end());
        return res;
    }
};

7、7月19日 874. 模拟行走机器人

思路:直接模拟+哈希表

直接模拟即可,详情见代码(注意unordered_setset以及unordered_mapmap不能有重复的key值)

C++代码

class Solution {
public:
    int robotSim(vector<int>& commands, vector<vector<int>>& obstacles) {
        unordered_set<int> uset;
        for (int i = 0; i < obstacles.size(); i++) uset.insert(obstacles[i][0] * 60001 + obstacles[i][1]);
        int x = 0;
        int y = 0;
        int head = 4;  // 东西南北1234
        int res = 0;
        for (int i = 0; i < commands.size(); i++) {
            if ((head == 4 && commands[i] == -1) || (head == 3 && commands[i] == -2)) head = 1;
            else if ((head == 4 && commands[i] == -2) || (head == 3 && commands[i] == -1)) head = 2;
            else if ((head == 1 && commands[i] == -1) || (head == 2 && commands[i] == -2)) head = 3;
            else if ((head == 1 && commands[i] == -2) || (head == 2 && commands[i] == -1)) head = 4;
            else if (commands[i] >= 1 && commands[i] <= 9) {
                if (head == 1) {
                    int temp = x + commands[i];
                    for (int j = x + 1; j <= temp; j++) {
                        if (uset.find(j * 60001 + y) != uset.end()) {
                            x = j - 1;
                            break;
                        } else x = j;
                    }
                }
                if (head == 2) {
                    int temp = x - commands[i];
                    for (int j = x - 1; j >= temp; j--) {
                        if (uset.find(j * 60001 + y) != uset.end()) {
                            x = j + 1;
                            break;
                        } else x = j;
                    }
                }
                if (head == 3) {
                    int temp = y - commands[i];
                    for (int j = y - 1; j >= temp; j--) {
                        if (uset.find(x * 60001 + j) != uset.end()) {
                            y = j + 1;
                            break;
                        } else y = j;
                    }
                }
                
                if (head == 4) {
                    int temp = y + commands[i];
                    for (int j = y + 1; j <= temp; j++) {
                        if (uset.find(x * 60001 + j) != uset.end()) {
                            y = j - 1;
                            break;
                        } else y = j;
                    }
                    
                }
                res = max(res, x * x + y * y);
            }
        }
        return res;
    }
};

8、7月20日 918. 环形子数组的最大和

思路:贪心

本题为53. 最大子数组和的进阶版,最大和的数组存在两种情况:

  • case1:nums[i]~nums[j]
  • case2:nums[0]~nums[i]和nums[j]~nums[nums.size() - 1],可以转换为求nums[i]~nums[j]最小子数组和。

针对case1,通过i遍历nums(可视为数组开头),count1记录数组和(可以视为末尾),当count1<=0时,抛弃前面元素,count1置为0,从i + 1开始重新计算。

针对case2,先寻找最子数组和:通过i遍历nums(可视为数组开头),count2记录数组和(可以视为末尾),当count2>=0时,抛弃前面元素,count2置为0,从i + 1开始重新计算。之后用sum-最小字数即为最大子数组和。

取case1和case2的较大值为最终结果。得注意的一点,当case1得到的最大子数组和为负数时(即数组中所有元素都为负数),不能用case2的结果(case2的结果为0,此时最小子数组为整个数组,最大子数组就没有元素了,显然不合理)。

C++代码

class Solution {
public:
    int maxSubarraySumCircular(vector<int>& nums) {
        int sum = 0;
        for (auto n : nums) sum += n;
        int res1 = INT_MIN;
        int count1 = 0;
        int res2 = INT_MAX;
        int count2 = 0;
        for (int i = 0; i < nums.size(); i++) {
            count1 += nums[i];
            if (res1 < count1) res1 = count1;
            if (count1 <= 0) count1 = 0;

            count2 += nums[i];
            if (res2 > count2) res2 = count2;
            if (count2 >= 0) count2 = 0;
        }
        if (res1 < 0) return res1;
        else return max(res1, sum - res2);
    }
};

9、7月22日 860. 柠檬水找零

思路:直接模拟+贪心

存储5美元和10美元的数量。遇到5美元就收下;遇到10美元就判断是否有5美元,有的话就收下10美元并找给5美元,否则不能找零return false;遇到20美元就先判断是否有10美元和5美元,再判断是否有3张5美元(上述找零涉及贪心,即尽可能保留5美元),否则不能找零return false。

C++代码

class Solution {
public:
    bool lemonadeChange(vector<int>& bills) {
        int five = 0, ten = 0;
        for (auto b : bills) {
            if (b == 5) five++;
            if (b == 10) {
                if (five > 0) {
                    ten++;
                    five--;
                } else return false;
            }
            if (b == 20) {
                if (five > 0 && ten > 0) {
                    five--;
                    ten--;
                } else if (five >= 3) five -= 3;
                else return false;
            } 
        }
        return true;

    }
};

10、7月23日 42. 接雨水

思路:单调栈

在这里插入图片描述

计算方法:按行来计算雨水体积,使用单调栈,从栈顶到栈底元素应该是从小到大,因为如果将要添加的元素大于栈顶元素,则出现凹槽:栈顶元素是凹槽的底部柱子,栈顶的第二个元素是凹槽左侧柱子,要添加的元素是凹槽右侧柱子。由于通过宽*高来计算雨水的体积,既需要知道元素值,又需要知道元素的下标,因此单调栈中存储元素下标,元素值则通过下标来获得。

单调栈逻辑

  • 当前遍历的元素小于栈顶元素:压入新元素的下标
  • 当前遍历的元素等于栈顶元素:弹出栈顶的下标,压入新元素的下标
  • 当前遍历的元素大于栈顶元素:计算栈顶元素雨水体积,弹出栈顶的下标,并压入新元素的下标

C++代码

class Solution {
public:
    int trap(vector<int>& height) {
        stack<int> st;
        int res = 0;
        st.push(0);
        for (int i = 1; i < height.size(); i++) {
            if (height[i] < height[st.top()]) st.push(i);
            else if (height[i] == height[st.top()]) {
                st.pop();
                st.push(i);
            } else {
                while (!st.empty() && height[i] > height[st.top()]) {
                    int mid = st.top();
                    st.pop();
                    if (!st.empty()) {
                        int h = min(height[st.top()], height[i]) - height[mid];
                        int w = i - st.top() - 1;
                        res += h * w;
                    } 
                }
                st.push(i);
            }
        }
        return res;
    }
};

11、7月24日 771. 宝石与石头

思路:哈希表

jewelsvector转为unordered_set哈希表,因为哈希表的查找的时间复杂度很低,只有O(1)。之后再遍历stones,判断每个元素是否出现在了jewels

C++代码

class Solution {
public:
    int numJewelsInStones(string jewels, string stones) {
        unordered_set<char> uset(jewels.begin(), jewels.end());
        int res = 0;
        for (int i = 0; i < stones.size(); i++) {
            if (uset.find(stones[i]) !=  uset.end()) res++;
        }
        return res;
    }
};

12、7月25日 2208. 将数组和减半的最少操作次数

思路:优先级队列+贪心

每次减去最大元素值的一半,可以得到最小的操作数。

nums可以保存为大顶堆的优先级队列priority_queueless表示大顶堆,greater表示小顶堆,缺省为大顶堆)。在循环函数中,每次通过大顶堆最大元素(top())的一半计算减去数的和,并将堆顶元素弹出,压入原堆顶元素值的一半,当减去数的和大于等于原nums数组元素和的一半时,停止循环。

C++代码

 class Solution {
public:
    int halveArray(vector<int>& nums) {
        int res = 0;
        priority_queue<double> pq(nums.begin(), nums.end());
        double sumDiff = 0;
        double sumAll = accumulate(nums.begin(), nums.end(), 0.0);  // 注意是0.0,不能是0,因为如果是0的话,nums先按int相加,再转变为double,可能会超出int范围
        while (sumDiff < sumAll / 2) {
            double temp = pq.top() / 2;
            pq.pop();
            pq.push(temp);
            sumDiff += temp;
            res++;
        }
        return res;
    }
};

13、7月27日 2500. 删除每行中的最大值

思路:直接模拟

将题意转化为:对数组的每一行排序,排完序后选择每一列的最大值,再求和。

C++代码

class Solution {
public:
    int deleteGreatestValue(vector<vector<int>>& grid) {
        int res = 0;
        for (int i = 0; i < grid.size(); i++) {
            sort(grid[i].begin(), grid[i].end());
        }
        for (int j = 0; j < grid[0].size(); j++) {
            int temp = INT_MIN;
            for (int i = 0; i < grid.size(); i++) {
                temp = max(temp, grid[i][j]);
            }
            res += temp;
        }
        return res;
    }
};

14、7月29日 141. 环形链表

思路:双指针

定义快慢指针,最初快慢指针都在链表头,之后慢指针一次走一步,快指针一次走两步。不断前进,如果慢指针等于了快指针,则有环,否则无环(如果想要找到环形链表的入口,则再定义两个指针,一个指针指向链表头,另一个指针指向快慢指针的交点处,这两个指针每次都走一步,直至相遇,相遇点即为环形链表的入口,详见142. 环形链表 II

C++代码

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    bool hasCycle(ListNode *head) {
        ListNode* fast = head;
        ListNode* slow = head;
        while (fast != NULL && fast->next != NULL) {
            slow = slow->next;
            fast = fast->next->next;
            if (slow == fast) return true;
        }
        return false;

    }
};

15、7月30日 142. 环形链表 II

思路:双指针同7月29日每日一题

C++代码

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        ListNode* slowIndex = head;
        ListNode* fastIndex = head;
        while (fastIndex != NULL && fastIndex->next != NULL) {
            slowIndex = slowIndex->next;
            fastIndex = fastIndex->next->next;
            if (slowIndex == fastIndex) {
                ListNode* tmp1 = head;
                ListNode* tmp2 = fastIndex;
                while (tmp1 != tmp2) {
                    tmp1 = tmp1->next;
                    tmp2 = tmp2->next;
                }
                return tmp1;
            }
        }
        return nullptr;
    }
};

16、7月31日

思路:线性表+双指针

先将链表用线性表存储,之后定义两个指针,一个头指针,一个尾指针(详见代码)

C++代码

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    void reorderList(ListNode* head) {
        vector<ListNode*> vec;
        ListNode* cur = head;
        while (cur != NULL) {
            vec.emplace_back(cur);
            cur = cur->next;
        }
        int i = 0, j = vec.size() - 1;
        while (i < j) {
            vec[i]->next = vec[j];
            i++;
            vec[j]->next = vec[i];
            j--;
        }
        vec[i]->next = nullptr;
    }
};
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值