总结个人力扣刷题记录:
第一题:
STL容器的综合运用题https://leetcode.cn/problems/cong-shang-dao-xia-da-yin-er-cha-shu-iii-lcof/submissions/代码:
class Solution
{
public:
vector<vector<int>> levelOrder(TreeNode* root)
{
vector<vector<int>>res_vec;//存放结果的容器
if (!root)
{
return res_vec;
}
queue< TreeNode*>T_que; //利用队列的特行存放树的结点
T_que.push(root);
bool Inleft = true; //确定一个需要遍历左树的条件
while (!T_que.empty()) //只要容器内有元素那就一直循环
{
int len = T_que.size(); //获得队列的元素个数
deque<int>D_que;//利用双端数组的特效来遍历左右树
for (int i = 0; i < len; ++i)
{
TreeNode* temp = T_que.front(); //获取队头元素后出队
T_que.pop();
if (Inleft)//首先根据题目要求判断左右树
{
D_que.push_back(temp->val); //如果在左树的话那么就头插入
}
else
{
D_que.push_front(temp->val); //反之
}
if (temp->left) T_que.push(temp->left);
if (temp->right) T_que.push(temp->right);
}
res_vec.emplace_back(vector<int>{D_que.begin(), D_que.end()});
Inleft = !Inleft;
}
return res_vec;
}
};
执行结果:
此题主要考虑去使用三个STL容器解决,可以加深对STL的理解,相信做完这道题,对STL的掌握度会小有提升
第二题:
礼物的最大价值https://leetcode.cn/problems/li-wu-de-zui-da-jie-zhi-lcof/
这道题我觉得非常考验逻辑能力
题解:
class Solution {
public:
int maxValue(vector<vector<int>>& grid)
{
int hang = grid.size();
int lie = grid[0].size();
vector<vector<int>>dp(hang, vector<int>(lie, 0));
dp[0][0] = grid[0][0];
for (int j = 1; j < lie; ++j) dp[0][j] = dp[0][j - 1] + grid[0][j];
for (int i = 1; i < hang; ++i) dp[i][0] = dp[i - 1][0] + grid[i][0];
for (int z = 1; z < hang; ++z)
{
for (int x = 1; x < lie; ++x)
{
dp[z][x] = max(dp[z][x - 1], dp[z - 1][x]) + grid[z][x];
}
}
return dp[hang - 1][lie - 1];
}
};
执行结果:
第三题:
42 连续子数组的最大和https://leetcode.cn/problems/lian-xu-zi-shu-zu-de-zui-da-he-lcof/
代码如下:
class Solution {
public:
int maxSubArray(vector<int>& nums) {
vector<int>dp(nums.size());
if (nums.size() == 0) { return 0 ;}
dp[0] = nums[0]; // 基本条件
for(int i = 1; i < nums.size() ; i++)
{
dp[i] = max(nums[i],dp[i - 1] + nums[i]);
}
int maxRes = INT_MIN;
for(int i = 0 ; i < dp.size() ; i++)
{
maxRes = max(dp[i],maxRes);
}
return maxRes;
}
};
此题采取动归解法,至于如何理解动归思路建议先从斐波那契函数https://leetcode.cn/problems/fei-bo-na-qi-shu-lie-lcof/
尽管此题非规范的动归题,但是理解动归正题框架也是不错的。
第四题:
编辑距离https://leetcode.cn/problems/edit-distance/
代码如下:
class Solution {
public:
int minDistance(string word1, string word2) {
vector<vector<int>> dp(word1.size() + 1, vector<int>(word2.size() + 1, 0));
for (int i = 0; i <= word1.size(); i++) dp[i][0] = i;
for (int j = 0; j <= word2.size(); j++) dp[0][j] = j;
for (int i = 1; i <= word1.size(); i++) {
for (int j = 1; j <= word2.size(); j++) {
if (word1[i - 1] == word2[j - 1]) {
dp[i][j] = dp[i - 1][j - 1];
}
else {
dp[i][j] = min({dp[i - 1][j - 1], dp[i - 1][j], dp[i][j - 1]}) + 1;
}
}
}
return dp[word1.size()][word2.size()];
}
};
虽然此题为hard题,显而易见的此题确实令人望而生畏无从下手,但是相反的此题也是极具意义的一道题,虽然要明确一点的是dp数组的含义,此处我将dp[i][j]表示为以下标 i - 1为结尾的字符串s1,以j - 1为结尾的字符串s2的最近编辑距离为dp[i][j]
此处为什么是i - 1和j - 1?因为字符串会存在空字符串的情况,那么此处我们可以用 - 1 来表示空字符串,但是显而易见的,dp数组的下标不可能为 - 1 所以我们选择前移一位,下标设置为 0 ,那么在遍历的时候 i j 自然而然的从 1 开始遍历,代表的字符串相应位置同样的需要 - 1 而同样的确认好编辑操作之后,情况无非就不操作、增、删、替换,那么如何确定此处需要做什么操作呢?选择将全部情况举例而出选择最小的编辑距离,所以dp数组的推到公式便确定成功。而边界条件很简单,哪个是空字符串,便将另一个的距离填充进dp数组即可。至于递推顺序,由四个操作可以得出依赖的递推方向。此题虽是hard,但是解法却极其的优美,对于动归系列解法很有学习意义。
第五题:
300 最长递增子序列https://leetcode.cn/problems/longest-increasing-subsequence/代码如下:
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
vector<int>dp(nums.size(),1);
if(nums.size() == 0 ) {return -1;}
for(int i = 0 ; i < nums.size() ; i++)
{
for(int j = 0 ; j < i ; j++)
{
if(nums[i] > nums[j])
{
dp[i] = max(dp[i],dp[j] + 1);
}
}
}
int res = INT_MIN;
for(int i = 0 ; i < dp.size() ; i++)
{
res = max(res,dp[i]);
}
return res;
}
};
此题的思路是将dp[i]作为以nums[i]为结尾时能够得到的最长递增子序列,那么无非就是两种情况,要么自己作为序列的头,要么加入前序列的尾。所以递归公式也很容易得出
第六题:
174 地下城游戏https://leetcode.cn/problems/dungeon-game/直接放代码:
class Solution
{
public:
int calculateMinimumHP(vector<vector<int>> &dungeon)
{
int m = dungeon.size();
int n = dungeon[0].size();
vector<vector<int>> dp(m + 1, vector<int>(n + 1));
dp[m - 1][n - 1] = dungeon[m - 1][n - 1] <0 ? -dungeon[m -1][n -1] + 1 : 1;
for(int i = m ; i>=0 ; i--)
{
for(int j = n ; j>=0 ; j--)
{
if (i == m || j == n)
{
dp[i][j] = INT_MAX;
continue;
}
if (i == m - 1 && j == n - 1) {
continue;
}
int res = min(dp[i][j+1],dp[i+1][j]) - dungeon[i][j];
dp[i][j] = res <= 0 ? 1 : res;
}
}
return dp[0][0];
}
};
首先这道hard题的难度并不高,主要在于弄清动归的思路,毕竟能用动归解答的题目,思路才是最重要的。
首先思路是将dp数组定义为走到某个部分需要多少生命值,但是这样定义的话会造成一个问题,那就是无法做出正确的决策,来计算出下一位dp数组的值,给的信息量不足,无法做出正确的状态转移。那么说明正向思考(正向遍历)的思路是错误的(个人理解)。那么应该尝试反向遍历的思路。试图以dp【i + 1】【j】 和 dp【i】【j + 1】来推到出dp【i】【j】 那么如何定义dp数组?可以尝试定义为到达终点所需要的最少生命值。打个比方,如果你走到dp【0】【1】 = 5,dp【1】【0】 = 4,那么肯定是由dp【0】【0】走向dp【1】【0】,因为4 小于 5,根据题意明白一点,走到grid【1】【0】至少的时候dp【1】【0】为4,那么至少你在dp【0】【0】的时候需要4 - 1 = 3 的初始血量。所以尝试推导:
int res = min(dp[i + 1][j],dp[i][j + 1]) - grid[i][j];
dp[i][j] = res <= 0 ? 1 : res;
// 此处为什么是 <= 0,因为这就代表着你所在方格的值大于你走向下一方格可能会扣除的血量,所以你在当前方格只需要最少的1点生命值就足够了
本题明确推到一下思路,也是不难得出结果的
第七题:
514 自由之路https://leetcode.cn/problems/freedom-trail/solution/上代码:
class Solution {
public:
int findRotateSteps(string ring, string key) {
int m = ring.size();
int n = key.size();
vector<int> pos_r[26];
for(int i=0; i<m; i++){
pos_r[ring[i] - 'a'].push_back(i);
}
vector<vector<int>> dp(m+1,vector<int>(n+1,1000));
for(int i=0; i<m; i++){
dp[i][n] = 0;
}
for(int j=n-1; j>=0; j--){
for(int i=0; i<m; i++){
int res = INT_MAX;
for(int k : pos_r[key[j]-'a']){
int delta = abs(k-i);
delta = min(delta, m - delta);
res = min(res, dp[k][j+1] + delta + 1);
}
dp[i][j] = res;
}
}
return dp[0][0];
}
};
解释:
首先最需要明白的一点事,我们怎么去认定转的方向?如果单纯的判断转到所要求的key[i]字符的最少次数的话,能否得出我们想要的结构?肯定是不行的。因为题目所给的ring字符串可能包含两个key[i],而如果仅仅只是判断到哪个ring[j]的距离最短,那么如果较远的ring[j]的下一位就是key[i + 1],而较近的ring[j]到达key[i + 1]可能需要更长的一段距离,那这种情况怎么做好判断呢?所以此题更应该以穷举所有结果来考虑!将所有的结果列出,从此再寻找最少的距离。那么就需要对每一种结果都做判断。
段分析:
vector<int> pos_r[26];
for(int i=0; i<m; i++)
{
pos_r[ring[i] - 'a'].push_back(i);
}
// 此段的作用:将ring[i]位置的字母索引存储起来,比如ring为"aaa",那么pos_r[0] = {0,1,2}
// 先将ring中的索引数组存储起来,后续方便做处理
vector<vector<int>> dp(m+1,vector<int>(n+1,1000));
for(int i=0; i<m; i++){
dp[i][n] = 0;
}
// 这段就是属于对dp数组的边界条件做处理了。因为此处的n为key.size() dp数组的边界不可能达到 n
for(int k : pos_r[key[j]-'a']){
int delta = abs(k-i); // 寻找指针偏移距离
delta = min(delta, m - delta); // 寻找逆时针 顺时针的最少距离
res = min(res, dp[k][j+1] + delta + 1); //此处为什么是dp[k][j+1]呢?
}
// j + 1 的目的是 : 指针已经指向了k位置,那么接下来我们需要寻找的key字符就是key[j+1]。此处跟dp数组的定义有关,dp的定义应该是从ring[i]字符到key[j]字符的最短操作次数
主要在求最值问题,而求最值问题基本就是动归思路,可以考虑空间优化