leetcode刷题:Week1

博主分享了一周刷题经历,包括动态规划的HouseRobber系列(I、II、III),不同路径问题(UniquePathI、II、III),以及DFS在深度遍历中的应用,如最接近目标甜点成本、岛屿问题等。最后提到将继续深入理解和总结这些知识点。
摘要由CSDN通过智能技术生成

0 废话

这周集中刷了点题,准备每周总结一下,先占个坑。周末主要任务是

  • 写完上周作业
  • 搞明白没懂的知识
  • 周六晚参加一下双周赛
  • 周日上午参加这周的周赛
  • 概率论感觉没有复习的那么透,做点题?
  • 总结这周题型和知识点

那么周日晚上见啦 !


哎…本来打算九点就开始写这篇的,结果知乎又给我推送了一个保研的帖子,就又把心仪的学校成功录取的学长学姐们的保研帖子看了一遍。呜呜呜太难过了,感觉更没戏了,哎,而且真的还差好多知识,我准备要继续加油了! 最后能去哪个学校也就随缘吧55555

1 动态规划

1.1 打家劫舍系列 House Robber I II III

1.1.1 House Robber I

题目:有一排房子,不能连续偷,求最大盗窃金额
思路:第 i 个房子的最大盗窃金额取决于前一个房子偷了没偷,如果前一个没偷,今天可以偷,反之则不行。两种情况:

  • 前一个没偷:那就是现在第i个可以偷,也就是前前 i - 2 的最大盗窃金额 + 第 i 个房子的。
  • 前一个偷了:就是现在这个不可以偷,也即是前一个 i - 1 房子的最大盗窃金额。
    转移方程dp[i] = max(dp[i -2] + dp[i], dp[i - 1]);
    初始条件dp[0] = nums[0]; dp[1] = max(nums[0], nums[1]);
    代码
class Solution {
public:
    int rob(vector<int>& nums) {
        int n = nums.size();
        if(n == 0)
            return 0;
        else
        {
            vector<int> dp(n);
            dp[0] = nums[0];
            if(n == 1)
                return dp[0];
            else if(n == 2)
            {
                return max(nums[0], nums[1]);
            }
            else
            {
                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]);
                return dp[n -1];
            }
            
        }
        return 0;
    }
};

先到这,我回宿舍洗澡,十点半双周赛 !
———————————————————————————————————
back

1.1. 2 House Robber II

题目 :在 I 的基础上,将房子变成了环状,也就是开头结尾不能一起偷。
思路 :在 I 分为两种情况,取两种情况的max

  • 不偷开头的房子 :那么结尾的房子就可以偷,偷取范围是[1, n - 1]

  • dp1[1] = nums[1]; dp1[2] = max(nums[1], nums[2]);

  • for(int i = 3; i < n; i++) dp1[i] = max(dp1[i - 2] + nums[i], dp1[i -1];

  • 不偷结尾的房子 :那么开头的房子可以偷,偷取范围是[0, n- 2]

  • dp2[0] = nums[0]; dp2[1] = max(nums[0], nums[1]);

  • for(int i = 2; i < n - 1; i++) dp2[i] = max(dp2[i - 2] + nums[i], dp2[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> dp1(n);
        vector<int> dp2(n);
        dp1[1] = nums[1];
        dp1[2] = max(nums[1], nums[2]);
        for(int i = 3; i < n; i++)
            dp1[i] = max(dp1[i - 2] + nums[i], dp1[i - 1]);
        dp2[0] = nums[0];
        dp2[1] = max(nums[0], nums[1]);
        for(int i = 2; i < n - 1; i++)
            dp2[i] = max(dp2[i - 2] + nums[i], dp2[i - 1]);
        //cout << dp1[n - 1] << " " << dp2[n - 2];
        return max(dp1[n - 1], dp2[n - 2]);
    }
};

1.1.3 House Robber III

题目:现在房子变成了二叉树(真厉害),直接相连的还是不能偷。

思路
一开始的错误思路:对二叉树进行层序遍历,求出每层的和,再按照 I 的dp进行求解。但是在[2,1,3,null,4]这个错误case,错误思路求解结果是6 = 2 + 4,但结果是7 = 4 + 3。好吧,这个偷了右子树,是可以偷左子树的孩子的,这两个不是直接相连的。然后就不会了,看了题解。
正确思路:对二叉树进行dfs,对于当前节点总是有两种情况:

  • 一种是偷当前节点,那就不可以偷他的左子树和右子树,但是可以偷他左子树的孩子和右子树的孩子。
  • 另一种则是不偷当前节点,那就可以偷他的左子树和右子树,但是不能偷左子树的孩子和右子树的孩子。
    统计这两种情况下的max,作为该节点所能偷窃的最大金额。子节点陆续将最大金额向上汇报,知道在根节点处返回。
    优化:记忆化搜索,我们知道计算当前节点的时候,总会计算到子节点,孙子节点。我们可以把已经计算的值用hashmap保存一下,当我们计算儿子节点时,就可以直接使用下面的孙子节点值。
    代码
class Solution {
public:
    unordered_map<TreeNode*, int> hashMap;
    int rob(TreeNode* root) {
        if(root == nullptr) return 0;
        if(root->left == nullptr && root->right == nullptr) return root->val;
        if(hashMap[root])   return hashMap[root];
        int robVal = root->val;
        if(root->left)
            robVal += rob(root->left->left) + rob(root->left->right);
        if(root->right)
            robVal += rob(root->right->left) + rob(root->right->right);
        int noRobVal = rob(root->left) + rob(root->right);
        hashMap[root] = max(robVal, noRobVal);
        return max(robVal, noRobVal);
    }
};

1.2 不同路径 I II III

1.2.1 Unique Path I

题目:一个人站在左上角,想要去右下角,问有多少条不同路径?
思路:当前位置的路径种类来自上方位置路径种类和左侧路径的种类。
初始化条件边界都是1,为了方便全都初始化为1
转移方程dp[i][j] = dp[i - 1][j] + dp[i][j -1];
代码:

class Solution {
public:
    int uniquePaths(int m, int n) {
        vector<vector<int>> dp(m, vector<int>(n, 1));
        for(int i = 1; i < m; i++)
            for(int j = 1; j < n; j++)
                dp[i][j] = dp[i][j - 1] + dp[i - 1][j];
        return dp[m - 1][n - 1];
    }
};

排列组合:由于向下走向右走几步都是确定的,直接列公式就行
@powcai

1.2.2 Unique Path II

题目:在 I 的基础上加入了障碍
思路:若当前位置加入障碍,那么当前位置的路径种类就变成了0,转移方程啥的都一样。但是,还有一点不一样,如果障碍在边界上,需要预先处理一下,障碍前的不变,还是1,障碍之后的边界线上都为0。这道题还有一个坑是障碍放在出发点,也是无语了。。。
代码

class Solution {
public:
    int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {
        
        int m = obstacleGrid.size();
        int n = obstacleGrid[0].size();
        if(obstacleGrid[0][0] == 1  || obstacleGrid[m - 1][n - 1] == 1)
            return 0;
            //处理边界上的障碍
        for(int j = 0; j < n; j++)
        {
            if(obstacleGrid[0][j] == 0)
                obstacleGrid[0][j] = 1;
            else
            {
                while(j < n)
                {
                    obstacleGrid[0][j] = 0;
                    j++;
                }
                break;
            }
        }
        //开始dp
        for(int i = 1; i < m; i++)
        {
            if(obstacleGrid[i][0]== 0)
                obstacleGrid[i][0] = 1;
            else
            {
                while(i < m)
                {
                    obstacleGrid[i][0] = 0;
                    i++;
                }
                break;
            }
        }
        for(int i = 1; i < m; i++)
        {
            for(int j = 1; j < n; j++)
            {
                if(obstacleGrid[i][j])
                    obstacleGrid[i][j] = 0;
                else 
                    obstacleGrid[i][j] = obstacleGrid[i - 1][j] + obstacleGrid[i][j - 1];
            }
        }
        
        return obstacleGrid[m - 1][n - 1];
    }
};

1.2.3 Unique Path III(还没做留坑)

2 DFS 深度遍历搜索

原来上课的时候就只以为DFS是用来遍历树的,直到做了1774,才深深的体会到DFS,本来自己想暴力for循环的,发现真的不行,才慢慢了解到DFS遍历网格。那第一道题就写1774吧 !

2.1 最接近目标的甜点成本

题目: 两个数组,一个基料成本,必须选一种;一个辅料成本,随便选几种[0,…],但是每种最多两份。还有一个target,求最接近target的成本
思路:首先遍历基料成本数组,将target = target - baseCosts[i],然后就是选辅料了。辅料成本和toppingCosts[0] * k0 + toppingCosts[1] * k1 + .. + toppingCosts[n] * kn,其中ki可以取0, 1, 2。这个可以通过dfs实现,每层增加一种辅料可选项,具体如下图:
在这里插入图片描述
代码

class Solution {
public:
    void dfs(int level, int tmp, const vector<int> &a, int &ans , int target)
    {
        if(level == a.size())
        {
            if(abs(tmp - target) < abs(ans - target) || (abs(tmp - target) == abs(ans - target) && tmp < ans))
                ans = tmp;
            return ;
        }
        for(int i = 0; i < 3; i++)
            dfs(level + 1, tmp + i * a[level], a, ans, target);
    }
    int closestCost(vector<int>& baseCosts, vector<int>& toppingCosts, int target) 
    {
        int ans = INT_MAX;
        for(int i = 0; i < baseCosts.size(); i++)
            dfs(0, baseCosts[i], toppingCosts, ans, target);
        return ans;
    }
};

2.2 岛屿问题

岛屿系列属于dfs遍历网格类型,这个系列也很有意思。感觉网格和图很像,但是网格有边界,需要处理一下。

2.2.1 岛屿数量

题目: 二维网格grid,1表示水,0表示陆地。岛屿是指被水包围的陆地,网格只能通过水平或者垂直方向连接。求岛屿数量
思路:遍历二维网格,如果是陆地就dfs遍历该陆地连接的所有陆地,标记为2(防止外面遍历时重复),有多少次dfs就有多少个岛屿。
代码

class Solution {
public:
    void dfs(vector<vector<char>>& grid, int i, int j)
    {
        if(i < 0 || i >= grid.size() || j < 0 || j >= grid[0].size() || grid[i][j] != '1')
            return;
        grid[i][j] = '2';
        dfs(grid, i, j + 1);
        dfs(grid, i, j - 1);
        dfs(grid, i - 1, j);
        dfs(grid, i + 1, j);
    }
    int numIslands(vector<vector<char>>& grid) {
        int ans = 0;
        for(int i = 0; i < grid.size(); i++)
        {
            for(int j = 0; j < grid[0].size(); j++)
            {
                if(grid[i][j] == '1')
                {
                    dfs(grid, i, j);
                    ans++;
                }
            }
        }
        return ans;
    }
};

dfs能做那么层序遍历也可以做的,方法就是queue进队出队,主要思路还是一样的。
代码

class Solution {
public:
    
    int numIslands(vector<vector<char>>& grid) {
        int ans = 0;
        queue<pair<int, int>> q;
        for(int i = 0; i < grid.size(); i++)
        {
            for(int j = 0; j < grid[0].size(); j++)
            {
                if(grid[i][j] == '1')
                {
                    q.push({i, j});
                    grid[i][j] = '2';
                    ans++;
                    while(!q.empty())
                    {
                        pair<int, int> t = q.front();
                        int m = t.first;
                        int n = t.second;
                        q.pop();
                        if(m >= -1 && m < grid.size() - 1 && grid[m + 1][n] == '1')
                        {
                            q.push(pair{m + 1, n});
                            grid[m + 1][n] = '2';
                        }
                        if(m >= 1 && m < grid.size() + 1 && grid[m - 1][n] == '1')
                        {
                            q.push(pair{m - 1, n});
                            grid[m - 1][n] = '2';
                        }
                        if(n >= -1 && n < grid[0].size() - 1 && grid[m][n + 1] == '1')
                        {
                            q.push(pair{m, n + 1});
                            grid[m][n + 1] = '2';
                        }
                        if(n >= 1 && n < grid[0].size() + 1 && grid[m][n - 1] == '1')
                        {
                            q.push(pair{m, n - 1});
                            grid[m][n - 1] = '2';
                        }
                    }
                }
            }
        }
        return ans;
    }
};

2.2.2 岛屿周长

题目:题目背景和2.2.1相同,不过这道题只有一个岛屿,求岛屿外面的周长。
在这里插入图片描述
思路:我发现如果某块陆地位于边界上,就可以加一条边,如果左边有水,可以加一条边,同理右边、上边、下边有水的画都可以加一条边。
代码

class Solution {
public:
    int islandPerimeter(vector<vector<int>>& grid) {
        int ans = 0;
        int m = grid.size();
        int n = grid[0].size();
        for(int i = 0; i < m; i++)
        {
            for(int j = 0; j < n; j++)
            {
                if(grid[i][j] == 1)
                {
                    if(i == 0) //左边界
                        ans++;
                    if(i == m - 1) //右边界
                        ans++;
                    if(i - 1 >= 0 && grid[i - 1][j] == 0) // 左有水
                        ans++;
                    if(i + 1 < m && grid[i + 1][j] == 0) // 右有水
                        ans++;

                    if(j == 0)
                        ans++;
                    if(j == n - 1)
                        ans++;
                    if(j - 1 >= 0 && grid[i][j - 1] == 0)
                        ans++;
                    if(j + 1 < n && grid[i][j + 1] == 0)
                        ans++;
                }
            }
        }
        return ans;
    }
};

下面这个@笨猪爆破组的思路也不错
笨猪爆破组

2.2.3 岛屿的最大面积

题目:背景同上,求最大的岛屿面积
思路:有了前面的基础,就很简单了。还是dfs遍历,每次遍历求一个面积,最后求最大面积
代码:

class Solution {
public:
    void dfs(vector<vector<int>>& grid, int& tmp, int i, int j)
    {
        if(i < 0 || i >= grid.size() || j < 0 || j >= grid[0].size() || grid[i][j] != 1)
            return;
        grid[i][j] = 2;
        tmp++;
        dfs(grid, tmp, i, j + 1);
        dfs(grid, tmp, i, j - 1);
        dfs(grid, tmp, i - 1, j);
        dfs(grid, tmp, i + 1, j);
    }
    int maxAreaOfIsland(vector<vector<int>>& grid) {
        int ans = 0;
        int m = grid.size();
        int n = grid[0].size();
        for(int i = 0; i < m; i++)
        {
            for(int j = 0; j < n; j++)
            {
                if(grid[i][j] == 1)
                {
                    int tmp = 0;
                    dfs(grid, tmp, i, j);
                    if(tmp > ans)
                        ans = tmp;
                }
            }
        }
        return ans;
    }
};

2.3.4 被围绕的区域

题目:将被X围绕的区域O用X填充。这道题有问题的地方在于下面的解释,边界上不会被围绕,我一开始没理解这个啥意思,看了评论区才发现是说与边界上的O连通的区域都不需要用X覆盖。
思路: 先对边界上的O进行dfs,标记为P。然后剩下的网格中的O就是需要用X修改的了,然后再将P改回O即可。
在这里插入图片描述
代码:

class Solution {
public:
    void dfs(vector<vector<char>>& board,int i, int j)
    {
        int m = board.size();
        int n = board[0].size();
        if(i < 0 || i >= board.size() || j < 0 || j >= board[0].size() || board[i][j] != 'O')
            return ;
        board[i][j] = 'P';
        dfs(board, i, j - 1);
        dfs(board, i, j + 1);
        dfs(board, i - 1, j);
        dfs(board, i + 1, j);

    }

    void solve(vector<vector<char>>& board) 
    {
        int m = board.size();
        int n = board[0].size();
        for(int i = 0; i < n; i++)
        {
            if(board[0][i] == 'O')
                dfs(board, 0, i);
            if(board[m - 1][i] == 'O')
                dfs(board, m - 1, i);
        }
        for(int i = 0; i < m; i++)
        {
            if(board[i][0] == 'O')
                dfs(board, i, 0);
            if(board[i][n - 1] == 'O')
                dfs(board, i, n - 1);
        }
        for(int i = 0; i < m; i++)
            for(int j = 0; j < n; j++)
            {
                if(board[i][j] == 'O')
                    board[i][j] = 'X';
                if(board[i][j] == 'P')
                    board[i][j] = 'O';
            }             
    }
};

不行了,我好累,晚上再写点吧,争取把他写完。我看了一下还有好多要写的啊 ! 😭

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值