【数据结构与算法】程序员MS金典 8.1-8.14

本文介绍了八个不同类型的编程问题解决方案,包括动态规划(DP)在迷路机器人路径寻找、深度优先搜索(DFS)在路径探索、二分查找在魔术索引定位、以及递归乘法和汉诺塔问题。通过实例展示了如何运用这些算法技巧来解决实际编程挑战。
摘要由CSDN通过智能技术生成

08.01. 三步问题

class Solution {
public:
    int waysToStep(int n) {
        vector<long> memo(n+1,-1);//当使用[]索引时,应该设置初始大小
        if(n == 0){return 0;}
        if(n == 1){return 1;}
        if(n == 2){return 2;}
        if(n == 3){return 4;}
        memo[0] = 0;
        memo[1] = 1;
        memo[2] = 2;
        memo[3] = 4;
        for(int i=4;i<=n;++i){
            memo[i] = (memo[i-1] + memo[i-2] + memo[i-3]) % 1000000007;
        }
        return (int)memo[n];
    }
};

08.02. 迷路的机器人
DP

class Solution {
public:
    vector<vector<int>> pathWithObstacles(vector<vector<int>>& obstacleGrid) {
        if(obstacleGrid.empty() || obstacleGrid[0].empty()){
            return {};
        }
        int m = obstacleGrid.size(), n = obstacleGrid[0].size(), i, j, k;
        if(obstacleGrid[0][0] == 1 || obstacleGrid[m-1][n-1] == 1){
            return {};
        }
        vector<vector<bool>> dp(m, vector<bool>(n,false));//dp[i][j] 表示机器人能否到达该处
        // 初始化第一列
        for(i=0; i<m; ++i){
            if(obstacleGrid[i][0] == 1){
                break;
            }else{
                dp[i][0] = true;
            }
        }
        // 初始化第一行
        for(j=0; j<n; ++j){
            if(obstacleGrid[0][j] == 1){
                break;
            }else{
                dp[0][j] = true;
            }
        }

        // 初始化整个dp表的剩余部分
        for(i=1; i<m; ++i){
            for(j=1; j<n; ++j){
                if(obstacleGrid[i][j] == 0){//不是障碍物
                    dp[i][j] = (dp[i-1][j] || dp[i][j-1]);//从左边或上边到达都行
                }
            }
        }

        if(dp[m-1][n-1] == false){
            return {};
        }

        vector<vector<int>> res(m+n-1);//走格子,总步数是确定的m+n-1
        k = m+n-2, i = m-1, j = n-1;
        while(i != 0 || j != 0){
            res[k--] = {i,j};
            if(i - 1 >= 0 && dp[i-1][j]){
                --i;
            }else if(j - 1 >= 0 && dp[i][j-1]){
                --j;
            }
        }
        res[0] = {0,0};
        return res;
    }
};

也是DP:

class Solution {
public:
    vector<vector<int>> pathWithObstacles(vector<vector<int>>& obstacleGrid) {
        vector<vector<int>> res;

        // 判断地图尺寸,如果行或列为0,则不可能到达
        int r = obstacleGrid.size();
        if(r == 0){
            return res;
        }
        int c = obstacleGrid[0].size();
        if(c == 0){
            return res;
        }

        // 起点或终点是障碍,不可能到达
        if(obstacleGrid[0][0] == 1 || obstacleGrid[r-1][c-1] == 1){
            return res;
        }

        // 定义一个二维数组,用于记录连通性:从起点(0,0)开始到当前点的连通性
        int dp[r][c];
        dp[0][0] = 1;

        // 初始化首列
        for(int i=1; i<r; ++i){
            if(obstacleGrid[i][0] == 1){
                dp[i][0] = 0;
            }else{
                dp[i][0] = dp[i-1][0];//下面格子的值
            }
        }
        // 初始化首行
        for(int i=1; i<c;++i){
            if(obstacleGrid[0][i] == 1){
                dp[0][i] = 0;
            }else{
                dp[0][i] = dp[0][i-1];//左边格子的值
            }
        }

        // 求路径
        for(int i=1; i<r; ++i){
            for(int j=1; j<c; ++j){
                if(obstacleGrid[i][j] == 1){
                    dp[i][j] = 0;
                }else{
                    dp[i][j] = max(dp[i-1][j] , dp[i][j-1]); // max(上边的值,左边的值)
                }
            }
        }

        // 到达终点的路径,连通性为0,直接返回
        if(dp[r-1][c-1] == 0){
            return res;
        }

        // 从终点反推回起点
        int row = r-1, col = c-1;
        while(row != 0 || col != 0){
            res.push_back({row , col});

            int up = 0;
            if(row > 0){
                up = dp[row-1][col];
            }

            int left = 0;
            if(col > 0){
                left = dp[row][col-1];
            }

            if(up >= left){
                --row;
            }else{
                --col;
            }
        }
        res.push_back({0,0});

        // 注意路径反转
        reverse(res.begin(), res.end());
        return res;
    }
};

DFS:

class Solution {
    vector<vector<int>> path;
    vector<vector<int>> ans;
    int m, n;
    bool found = false;
    vector<vector<int>> dir = {{1,0},{0,1}};
public:
    vector<vector<int>> pathWithObstacles(vector<vector<int>>& obstacleGrid) {
        if(obstacleGrid.empty() || obstacleGrid[0].empty()){
            return {};
        }
        m = obstacleGrid.size(), n = obstacleGrid[0].size();
        if(obstacleGrid[0][0] == 1 || obstacleGrid[m-1][n-1] == 1){
            return {};
        }
        vector<vector<bool>> visited(m, vector<bool>(n, false));
        dfs(obstacleGrid, 0, 0, visited);
        return ans;
    }

    void dfs(vector<vector<int>>& obstacleGrid, int i, int j, vector<vector<bool>>& visited){
        if(found){
            return;
        }
        if(i == m-1 && j == n-1){
            path.push_back({i,j});
            ans = path;
            found = true;
            return;
        }
        visited[i][j] = true;
        path.push_back({i,j});
        int x, y;
        for(int k=0; k<2; ++k){
            x = i + dir[k][0];
            y = j + dir[k][1];
            if(x>=0 && x<m && y>=0 && y<n && obstacleGrid[x][y]==0 && !visited[x][y]){
                dfs(obstacleGrid, x, y, visited);
            }
        }
        // visited[i][j] = false;//不注释会超时
        path.pop_back();
    }

};

08.03. 魔术索引
找到nums[i]==i 的值i

class Solution {
public:
    int findMagicIndex(vector<int>& nums) {
        int res = -1;
        binary_search(nums, 0, nums.size()-1, res);
        return res;
    }
    void binary_search(vector<int>& nums, int left, int right, int& res){
        //这里进行了剪枝?
        if(res != -1 && res < left){
            return;
        }

        if(left <= right){
            int mid = left + (right-left)/2;
            //如果找到目标的索引,需要尝试能否找到更小的索引值
            if(nums[mid] == mid){
                if(res == -1){
                    res = mid;
                }else{
                    res = (mid < res ? mid : res);
                }
                binary_search(nums, left, mid-1, res);
            }else{
                // 如果不符合要求,需要在mid两边继续查找
                binary_search(nums, left, mid-1, res);
                binary_search(nums, mid+1, right, res);
            }
        }
    }
};

08.04. 幂集
编写一种方法,返回某集合的所有子集。集合中不包含重复的元素

先在大容器里面添加一个空容器,然后在nums里面取一个元素加入到当前大容器里面的所有子容器中并将新得到的每个容器添加到大容器中,直到nums的数据取完为止
【123】

先在大容器里面存一个空容器[].
第一轮:取1
大容器:[] [1];
第二轮:取2
大容器:[] [1] [2] [1,2];
第三轮:取3
大容器:[] [1] [2] [1,2] [3] [1,3] [2,3] [1,2,3] ;
class Solution {
public:
    
    vector<vector<int>> subsets(vector<int>& nums) {
        if(nums.empty()){
            return {nums};
        }
        vector<vector<int>> res;//用于存储最终要返回的结果
        res.push_back({});//首先有个[]作为子集
        
        for(auto n : nums){
            vector<vector<int>> children = res;
            for(auto r : children){
                vector<int> child = r;
                child.push_back(n);
                res.push_back(child);
            }
        }
        return res;
    }
};
for(auto n : nums){
    vector<vector<int>> children = res;
    for(auto r : children){
        vector<int> child = r;
        child.push_back(n);
        res.push_back(child);
    }
}

这一段可以用下一段替代,下面的方法更快:
for(int i=0; i<nums.size(); ++i){
   vector<vector<int>> children = res;
    for(int j=0; j<children.size(); ++j){
        vector<int> child = res[j];
        child.push_back(nums[i]);
        res.push_back(child);
    }
}

利用位运算做,思路清奇:

nums = [1, 2, 3],长度为3,取看成是1,不取看成是0.
那么就有:
[0, 0, 0] -> 0
[0, 0, 1] -> 1
[0, 1, 0] -> 2
[0, 1, 1] -> 3
[1, 0, 0] -> 4
[1, 0, 1] -> 5
[1, 1, 0] -> 6
[1, 1, 1] -> 7

把这些01看成是二进制,代表0~78个数字。幂集大小是2^nums.length。
class Solution {
public:
    
    vector<vector<int>> subsets(vector<int>& nums) {
        if(nums.empty()){
            return {nums};
        }
        vector<vector<int>> res;//用于存储最终要返回的结果
        
        int total = 1 << nums.size(); //2的nums.size()次方
        for(int i=0; i<total; ++i){
            vector<int> vec;
            int num = i, idx = 0;
            while(num){
                if(num & 1){
                    vec.push_back(nums[idx]);
                }
                num >>= 1;
                ++idx;
            }
            res.push_back(vec);
        }
        return res;
    }
};

08.05. 递归乘法

class Solution {
public:
    int multiply(int A, int B) {
        if(A == 0 || B == 0){
            return 0;
        }
        return A + multiply(A, B-1);
    }
};

08.06. 汉诺塔问题

class Solution {
public:
    void hanota(vector<int>& A, vector<int>& B, vector<int>& C) {
        int n = A.size();//盘子的总数
        move(A, B, C, n);
    }
    //move函数,第一个数组指当前从谁移盘子,第二个数组表示借助谁,第三个数组表示把盘子移到谁那去
    void move(vector<int>& A, vector<int>& B, vector<int>& C, int n){
        if(n == 1){ //如果只有1个盘子
            C.push_back(A.back());
            A.pop_back();
            return;
        }
        //有2个或多于2个盘子
        move(A, C, B, n-1);//借助C盘,将n-1个盘子从A转移到B
        C.push_back(A.back());//A上只有一个最大的盘子,把它转移到C上
        A.pop_back();//pop之后,A上是空的了
        move(B, A, C, n-1);//借助A盘,将n-1个盘子从B转移到C
    }
};

08.07. 无重复字符串的排列组合

方法1:玩转字符串

将每个字母插入到前面字符串所有可能的情况:
例如:"qwe"
第一轮:q
第二轮:将w插入q,有两种情况:wq, qw
第三轮:将e插入wq, qw中去 即:ewq,weq,wqe;eqw,qew,qwe六种情况
class Solution {
public:
    vector<string> permutation(string S) {
        int len = S.length();
        vector<string> res,result;
        if(len <=0 )
            return res;
        string tmp = S.substr(0,1);
        res.push_back(tmp);
        for(int i=1;i<len;i++){ //对于后面的每个字符,都要尝试插入res中字符串的合适位置
            int count = res.size();
            for(int j=0;j<count;j++){//遍历res中每个字符串
                string origin = res[j];
                int length = origin.length();
                for(int k=0;k<length;k++){ // 在每个字符串里,找到合适的位置,就是从头找到尾
                    string t=origin.substr(0,k)+S[i]+origin.substr(k);
                    res.push_back(t);
                }
                res.push_back(origin+S[i]);// substr(length-1)只能得到最后一个值,而到不了尾后位置,所以最后添上这句
            }
            res.erase(res.begin(),res.begin()+count); // 将前面一轮用过的扔掉 “q”
        }
        return res;
    }
};

方法2:递归:
实在理解不了递归

class Solution {
public:
    vector<string> permutation(string S) {
         vector<string> ret;
         dfs(ret, 0, S);
         return ret;
    }

    void dfs(vector<string>& ret, int index, string s) {
        if (index >= s.size()) {
            ret.push_back(s);
            return;
        }
        for (int i = index; i < s.size(); ++i) {
            swap(s[i], s[index]);
            dfs(ret, index + 1, s);
            swap(s[i], s[index]);
        }
    }
};

方法3:全排列:太秀了

class Solution {
public:
    vector<string> permutation(string S) {
        sort(S.begin(), S.end());
        vector<string> ans;
        ans.emplace_back(S); // 这句必须有
        while (next_permutation(S.begin(), S.end()))
        {
            ans.emplace_back(S);
        }
        return ans;
    }
};

08.08. 有重复字符串的排列组合
回溯回溯回溯:

class Solution {
public:
    vector<string> permutation(string s) {
        vector<string> ans;
        sort(s.begin(), s.end());//先排列,使得重复字符相邻,便于后面去重
        dfs(s, 0, ans);
        return ans;
    }
    void dfs(string& s, int idx, vector<string>& ans) {
        if (idx == s.size()) ans.push_back(s);
        for (int i = idx; i < s.size(); ++i) {
            if (i > idx && (s[i] == s[i - 1] ||s[i] == s[idx])){//因为首次交换时i == begin,S[i]与自身交换,因此考虑两种重复情况的去除:1.S[i] == S[i-1];2.S[i] == S[begin]
				continue;
			}
            swap(s[i], s[idx]);
            dfs(s, idx + 1, ans);
            swap(s[idx], s[i]);
        }
    }
};

08.09. 括号
参考
简单dfs:

class Solution {
    int rows, cols;
public:
    vector<vector<int>> floodFill(vector<vector<int>>& image, int sr, int sc, int newColor) {
        rows = image.size();
        cols = image[0].size();

        dfs(image, row, col, image[sr][sc], newColor);
        return image;
    }

    void dfs(vector<vector<int>>& image, int row, int col, int oldColor, int newColor){
        if(row<0 || row>=rows || col<0 || col>=cols || image[row][col]!=oldColor || image[row][col]==newColor){
            return;
        }

        // 染色
        image[row][col] = newColor;

        dfs(image, row-1, col, oldColor, newColor);
        dfs(image, row, col-1, oldColor, newColor);
        dfs(image, row, col+1, oldColor, newColor);
        dfs(image, row+1, col, oldColor, newColor);
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值