找工作准备刷题Day11 回溯算法+动态规划 (卡尔41期训练营 7.25)

第一题:Leetcode39. 组合总和

题目描述

题解

class Solution {
public:
    vector<vector<int>> ans;
    vector<int> path;
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        backTracking(candidates, target, 0, 0);
        return ans;
    }

    void backTracking(vector<int>& candidates, int target, int sum,
                      int startIdx) {
        if (sum > target)
            return;
        if (sum == target) {
            ans.push_back(path);
            return;
        }

        for (int i = startIdx; i < candidates.size(); i++) {
            path.push_back(candidates[i]);
            backTracking(candidates, target, sum + candidates[i], i);
            path.pop_back();
        }
    }
};
  1. 由于可以使用重复元素,在向深处遍历时,从当前 i 开始;
  2. 利用函数参数的传值特性,不用对sum进行加减操作。
  3. sum大于target时,可以进行剪枝。

第二题:Leetcode40. 组合总和 II 

题目描述

题解

此题与第一题不同在于:不可以重复选取元素,就算candidates里头有两个1,也只能使用一次。

class Solution {
public:
    vector<vector<int>> ans;
    vector<int> path;
    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        sort(candidates.begin(), candidates.end());
        backTracking(candidates, target, 0, 0);
        return ans;
    }

    void backTracking(vector<int>& candidates, int target, int sum,
                      int startIdx) {
        if (sum > target)
            return;

        if (sum == target) {
            ans.push_back(path);
            return;
        }

        for (int i = startIdx; i < candidates.size(); i++) {
            if (i > startIdx && candidates[i] == candidates[i - 1])
                continue;
            path.push_back(candidates[i]);
            backTracking(candidates, target, sum + candidates[i], i + 1);
            path.pop_back();
        }
    }
};

这里为了避免使用同一个元素,首先对candidates进行排序,紧接着才回溯遍历时,使用

if (i > startIdx && candidates[i] == candidates[i - 1])
                continue;

跳过重复元素,这个技巧在Leetcode15.三数之和 & Leetcode18.四数之和 见过。
 

第三题:Leetcode131. 分割回文串

题目描述

题解1——回溯+每次判断是否回文

class Solution {
public:
    vector<vector<string>> ans;
    vector<string> path;
    vector<vector<string>> partition(string s) {
        backTracking(s, 0);
        return ans;
    }

    void backTracking(string s, int startIdx) {
        if (startIdx >= s.size()) {
            ans.push_back(path);
            return;
        }

        for (int i = startIdx; i < s.size(); i++) {
            if (isHuiwen(s, startIdx, i)) {
                path.push_back(s.substr(startIdx, i - startIdx + 1));
                backTracking(s, i + 1);
                path.pop_back();
            } else
                continue;
        }
    }

    // [start,end]
    bool isHuiwen(const string s, int start, int end) {
        while (start <= end) {
            if (s[start++] != s[end--])
                return false;
        }

        return true;
    }
};

对于一个字符串是否回文,使用双指针进行遍历即可

bool isHuiwen(const string s, int start, int end) {
    while (start <= end) {
        if (s[start++] != s[end--])
            return false;
    }

    return true;
}

小技巧

  1. string s.substr(Idx,length)会返回字符串s从Idx为下标开始、长度为length的子串(length省略,则到末尾)。
  2. 由于每次都计算,其中会有重复。
  3. 这个算法的复杂度为O(n*2^n),空间复杂度为(n^2)。

题解2——回溯+预先动态规划计算是否回文

  1. 预先动态规划计算是否回文:二维数组isHuiWen[i,j]取值表示[i,j]是否为回文字符串,isHuiWin[i][j] 由 s[i] == s[j]  和 isHuiWen[i+1][j-1] 决定,因此可以选用动态规划提前计算好回文串。
  2.  i 依赖于 i+1 ,所以i从大到小遍历;j 依赖于 j-1,所以 j 从小到大遍历;
  3.  i <= j;
  4. 当 i 等于 j 时,是回文串;
class Solution {
public:
    vector<vector<string>> ans;
    vector<string> path;
    vector<vector<bool>> vecIsHuiwen;
    vector<vector<string>> partition(string s) {
        computeHuiwen(s);
        backTracking(s, 0);
        return ans;
    }

    void backTracking(string s, int startIdx) {
        if (startIdx >= s.size()) {
            ans.push_back(path);
            return;
        }

        for (int i = startIdx; i < s.size(); i++) {
            if (vecIsHuiwen[startIdx][i]) {
                // if (isHuiwen(s, startIdx, i)) {
                
                path.push_back(s.substr(startIdx, i - startIdx + 1));
                backTracking(s, i + 1);
                path.pop_back();
            } else
                continue;
        }
    }

    // [start,end]
    bool isHuiwen(const string s, int start, int end) {
        while (start <= end) {
            if (s[start++] != s[end--])
                return false;
        }

        return true;
    }

    void computeHuiwen(const string s) {
        vecIsHuiwen.resize(s.size(), vector<bool>(s.size(), false));
        // vecIsHuiwen[i][j]代表从i到j是否为回文,[i,j]
        // vecIsHuiwen[i][i] = true
        // vecIsHuiwen[i][j] = s[i]==s[j]&&
        // vecIsHuiwen[i+1][j-1],因此,i需要从大到小,j从小到大。
        for (int i = s.size() - 1; i >= 0; i--) {
            for (int j = i; j <= s.size(); j++) {
                if (i == j)
                    vecIsHuiwen[i][j] = true;
                else if (i == j - 1)
                    vecIsHuiwen[i][j] = (s[i] == s[j]);
                else
                    vecIsHuiwen[i][j] =
                        ((s[i] == s[j]) && vecIsHuiwen[i + 1][j - 1]);
            }
        }
    }
};

第四题:Leetcode 62.不同路径(一维数组写法)

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

        return paths[n - 1];
    }
};

第五题:Leetcode 63.不同路径 II(一维数组写法)

class Solution {
public:
    int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {
        const int m = obstacleGrid.size();
        const int n = obstacleGrid[0].size();
        vector<int> paths(n, 0);
        for (int j = 0; j < n; j++) {
            if (obstacleGrid[0][j] == 1)
                break;
            else
                paths[j] = 1;
        }

        for (int i = 1; i <= m - 1; i++) {
            for (int j = 0; j <= n - 1; j++) {
                if (obstacleGrid[i][j] == 1)
                    paths[j] = 0;
                else if (j != 0) {
                    paths[j] += paths[j - 1];
                }
            }
        }
        return paths[n - 1];
    }
};

要点

  1. j 需要从0开始,大于0 时使用递归,等于0时根据是否是障碍物来取值。

第六题:Leetcode96. 不同的二叉搜索树

第一次做不懂什么叫二叉搜索树。

二叉搜索树定义:

  1. 根节点大于左子树所有节点,小于右子树所有节点;
  2. 左右子树均为二叉搜索树。

题解

class Solution {
public:
    int numTrees(int n) {
        vector<int> dp(n + 1, 0);
        dp[0] = 1;
        // dp[i] 代表i个连续数字组成的二叉搜索树种数:例如dp[5]可以代表从1~5 也可以代表从5~9 的二叉搜索树种类之和
        // dp[i] 递推可以这样认为:对于任意 j从[1,i],以 j 为根节点
        //                       左子树为有 j-1个节点(均小于j)
        //                       右子树有i-j个节点(均大于j)的搜索树  种类之和
        // 即dp[i] 等于 以1为根节点 + 以2为根节点 + 。。。 + 以i为根节点
        // 递推公式为 for j=1 up to i: dp[i]+= dp[j-1]*dp[i-j]  左子树是[1,j-1]共j-1个节点,右子树是[j+1,i]共i-j个节点
        // 此时,dp[0]没有意义,但是将其初始化为1才能符合条件
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= i; j++)
                dp[i] += dp[j - 1] * dp[i - j];
        }

        return dp[n];
    }
};

卡尔对于动态规划的解题思路

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值