剑指offer刷题笔记--Num11-20

​​​​​​​

目录

1-旋转数组的最小数字(11)

2--矩阵中的路径(12)

3--机器人的运动范围(13)

4--剪绳子(14-I)

5--剪绳子(14-II)

6--二进制中1的个数(15)

7--数值的整数次方(16)

8--打印从1到最大的n位数(17)

9--删除链表中的节点(18)

10--正则式匹配(19)

11--表示数值的字符串(20)


1-旋转数组的最小数字(11)

 主要思路:

        一次旋转将最后一个元素移动最前面,由于数组最开始是升序的,因此数组的大部分元素都应该保持升序的状态(n1<n2<...<n3>n4<n5<...<n6);

        最后面开始遍历,只要遇到一个元素小于其前一个元素(numbers[i] < numbers[i - 1]),则numbers[i],就是最小的元素,因为前面的元素都是旋转移动过去的。

// 利用遍历
class Solution {
public:
    int minArray(vector<int>& numbers) {
        int size = numbers.size();
        for(int i = size - 1; i > 0; i--){
            if(numbers[i] < numbers[i - 1]){
                return numbers[i];
            }
        }
        return numbers[0];
    }
};
// 利用二分法
class Solution {
public:
    int minArray(vector<int>& numbers) {
        int low = 0;
        int high = numbers.size() - 1;
        while (low < high) {
            int pivot = low + (high - low) / 2;
            if (numbers[pivot] < numbers[high]) {
                high = pivot;
            }
            else if (numbers[pivot] > numbers[high]) {
                low = pivot + 1;
            }
            else {
                high -= 1;
            }
        }
        return numbers[low];
    }
};

2--矩阵中的路径(12)

视频讲解参考:剑指 Offer 12. 矩阵中的路径

主要思路:

        遍历 board 中的每一个位置,判断当前位置的元素是否与word[k]相同,并递归判断其上左下右四个元素是否与word[k+1]相同;如果word所有字符都匹配,则返回true,否则返回false并遍历board的下一个元素;

        为了防止重复访问元素,在判断一个位置上左下右四个元素时,将当前元素用特殊字符mask掉(当其被重复访问时,一定是返回false),避免其被重复访问;

代码:

#include <vector>
#include <string>
#include <iostream>

class Solution {
public:
    bool exist(std::vector<std::vector<char>>& board, std::string word) {
        for(int i = 0; i < board.size(); i++){
            for(int j = 0; j < board[0].size(); j++){
                if(dfs(board, word, i, j, 0)){ // 搜索每一个字符
                    return true;
                }
            }
        }
        return false;
    }
    bool dfs(std::vector<std::vector<char>>& board, std::string word, int i, int j, int k){
        // 判断是否越界、当前board[i][j]是否等于word[k]
        if(i >= board.size() || i < 0 || j >= board[0].size() || j < 0 || board[i][j] != word[k]){
            return false;
        }
        if (k == (word.length() - 1)){ // word的所有字符全部匹配成功
            return true;
        } 
        // 通过上面的if循环,可知当前board[i][j] == word[k]
        // 将当前 board[i][j] mask掉,防止被重复访问
        board[i][j] = '#';
        // 重复调用dfs,判断board[i][j]上左下右四个字符是否与word[k+1]相同
        bool result = dfs(board, word, i-1, j, k+1) || dfs(board, word, i, j-1, k+1)
        || dfs(board, word, i+1, j, k+1) || dfs(board, word, i, j+1, k+1);

        // 返回前,恢复mask掉的board[i][j]
        board[i][j] = word[k];
        return result;
    }
};

int main(int argc, char* argv[]){
    std::vector<std::vector<char>> board;
    std::vector<char> b0 = {'A', 'B', 'C', 'E'};
    std::vector<char> b1 = {'S', 'F', 'C', 'S'};
    std::vector<char> b2 = {'A', 'D', 'E', 'E'};
    board.push_back(b0);
    board.push_back(b1);
    board.push_back(b2);
    std::string word = "ABCCED";
    Solution s1;
    bool status = s1.exist(board, word);
    std::cout << status << std::endl;
    return 0;
}

3--机器人的运动范围(13)

 主要思路:

        与《矩阵中的路径》类似,可利用深度优先递归判断每一个方格是否可跳,可跳条件是数位之和不大于 k;

        机器人的运动范围必须从(0, 0)开始,当访问某个可行方格后,可利用一个二维数组记录其已被访问,最后统计二维数组即可确定可访问的方格数;

        同时一个方格上、下、左、右移动可优化为只向右和向下移动,因为任何一个可跳方格都可以由上一个可行方格通过向右和向下移动得到,节省了递归四个方向的消耗(具体可看官方解释);

#include <vector>
#include <iostream>

class Solution {
public:
    int movingCount(int m, int n, int k) {
        for(int j = 0; j < n; j++){
            tmp.push_back(0);
        }
        for(int i = 0; i < m; i++){
            v.push_back(tmp);
        }
        DFS(0, 0, m, n, k);
        for(int i = 0; i < m; i++){
            for(int j = 0; j < n; j++){
                if(v[i][j] == 1){
                    num++;
                }
            }
        }
        return num;
    }

    void DFS(int i, int j, int m, int n, int k){
        // 越界、已访问
        if(i < 0 || i >= m || j < 0 || j >= n || v[i][j] == 1){
            return;
        }
        bool status = Cal_ij(i, j, k);
        if (status) v[i][j] = 1;
        else return; // 不能进入该点,也就不能从该点移动到其它四个点,直接返回
        // DFS(i-1, j, m, n, k);
        // DFS(i, j-1, m, n, k);
        DFS(i+1, j, m, n, k); // 向右移动
        DFS(i, j+1, m, n, k); // 向下移动
    }

    bool Cal_ij(int i, int j, int k){
        int sum = 0;
        sum += i % 10;
        i = i / 10;
        sum += i;
        
        sum += j % 10;
        j = j / 10;
        sum += j;

        if(sum > k) return false;
        else return true;
    }
private:
    std::vector<std::vector<int>> v; 
    std::vector<int> tmp;
    int num = 0;
};

int main(int argc, char *argv[]){
    int m = 16;
    int n = 8;
    int k = 4;
    Solution s1;
    int num = s1.movingCount(m, n, k);
    std::cout << "num is: " << num << std::endl;
}

4--剪绳子(14-I)

主要思路:

        每段绳子的长度 k 必须是 2 或 3,因为当 k>= 4时,可以进一步裁剪,例如长度 5 可以拆分为 2*3 大于 5;

        对于一段长度为 n 的绳子,将其最大乘积定义为 dp[n],当裁剪出一段长度为 2 或 3 的绳子后,有两种情况:继续裁剪,则 dp[n] = 2 * dp[n - 2] 或 3 * dp[n - 3];不裁剪,则 dp[n] = 2 * (n - 2) 或 3 * (n - 3);

        基于动态规划,则状态转移方程为:dp[n] = max(max(2 * dp[n-2], 2 * (n -2)), max(3*dp[n - 3], 3 * (n - 3)));边界条件dp[1] = 0, dp[2] = 1;

#include <vector>
#include <iostream>

class Solution {
public:
    int cuttingRope(int n) {
        for(int i = 0; i <= n; i++){
            dp.push_back(0);
        }
        if(n <= 3){
            dp[n] = n - 1;
            return dp[n];
        }
        dp[2] = 1;
        for(int i = 3; i <= n; i++){
            dp[i] = std::max(std::max(2*dp[i - 2], 2*(i - 2)), std::max(3*dp[i - 3], 3*(i - 3)));                
        }
        return dp[n];
    }
private:
    std::vector<int> dp;
};

int main(int argc, char *argv[]){

    int n = 10;
    Solution s1;
    int sum = s1.cuttingRope(n);
    std::cout << sum << std::endl;
}

5--剪绳子(14-II)

主要思路:

        在考察最大乘积的同时,考察了大数取余法;

        最大乘积,应尽可能将绳子切成长度为 3 的小段,具体分析参考视频讲解:剪绳子(14-I)

        本题重点考察大数取余法:

#include <iostream>

class Solution {
public:
    int cuttingRope(int n) {
        if(n <= 3){
            return n - 1;
        }
        int res = n / 3;
        int mod = n % 3;
        int p = 1000000007;
        if(mod == 0){
            return pow(3, res, p);
        }
        else if(mod == 1){ // 将 1 和其中一段 3 合并构成长度为4
            return pow(3, res-1, p) * 4 % p;
        }
        else{ // mod == 2
            return pow(3, res, p) * 2 % p;
        }
    }
    // 计算 a^n % p 
    long long int pow(int a, int n, int p = 1000000007){
        long long int res = 1;
        for(int i = 1; i <= n ; i++){
            res = (res * a) % p;
        }
        return res;
    }
};

int main(int argc, char *argv[]){
    int n = 127;
    Solution s1;
    int sum = s1.cuttingRope(n);
    std::cout << sum << std::endl;
    return 0;
}

6--二进制中1的个数(15)

主要思路:

        检查第 i 位是否是 1 时,只需将其与 2^i 按位相与,结果为 1 表明该位为 1,否则该位为 0;

#include <iostream>
class Solution {
public:
    int hammingWeight(uint32_t n) {
        int ret = 0;
        for (int i = 0; i < 32; i++) {
            if (n & (1 << i)) { // 1 << i == 2 ^ i
                ret++;
            }
        }
        return ret;
    }
};

int main(int argc, char *argv[]){
    int n = 11;
    Solution s1;
    int sum = s1.hammingWeight(n);
    std::cout << sum << std::endl;
    return 0;
}

7--数值的整数次方(16)

主要思路:

        ① 最容易想到的方法是遍历 n 次,每次累乘 x,时间复杂度为O(n),但在本题中会超时;

        ② 优化方法:利用递归,将x^n拆分为x^(2(n/2)),类似于快速幂,但需要区分n是奇数还是偶数,如果n是奇数,将一个 x 提出来使得 n-1 变成偶数;

class Solution {
public:
    double myPow(double x, int n) {
        long long N = n;
        if(N < 0){
            x = 1 / x;
            N = -N;
        }
        return quickMul(x, N);
    }

    double quickMul(double x, long long n){
        // 零次幂,返回1
        if(n == 0){
            return 1;
        }
        // 奇数,返回x*x^(n-1)
        else if(n % 2 == 1){
            double pre_x = x; 
            x = x * x;
            n = n / 2;
            x = pre_x * quickMul(x, n);
            return x;
        }
        // 偶数
        else{
            x = x * x;
            n = n / 2;
            return quickMul(x, n);
        }
    }
};

8--打印从1到最大的n位数(17)

 主要思路:

        本题实际考察大数打印,可用 string 表示每一个数,基于全排列的思想利用DFS生成每一个位数;

#include <vector>
#include <iostream>
#include <cmath>

class Solution {
public:
    std::vector<int> printNumbers(int n) {
        for(int len = 1; len <= n; len++){
            dfs(0, len); // 长度为len, 确定第0位数字
        }
        for(int i = 0; i < std::pow(10, n) - 1; i++){ // 通过本题,需要将string重新转换为int
            res.push_back(std::stoi(string_res[i]));
        }
        return res;
    }
    
    void dfs(int i, int len){ // 数字长度为len,正在确定第i位数字
        if(i == len){ // 确定完所有数字后,记录该数字
            string_res.push_back(s);
            return;
        }
        int start = i==0? 1:0; // i==0时,表明确定第0位数字,则开始不能为0,只能从1开始
        for(int j = start; j < 10; j++){ // 选择第j位的字符作为本位数字
            s.push_back(choice[j]); // 确定当前第i位数字后,确定第i+1位的数字
            dfs(i+1, len);
            s.pop_back(); // 删除本位数字,选择其他数字,可以理解为回溯
        }
    }

private:
    std::string s;
    std::vector<std::string> string_res;
    std::vector<char> choice = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
    std::vector<int> res;
};

int main(int argc, char *argv[]){
    int n = 3;
    Solution s1;
    std::vector<int> res = s1.printNumbers(2);
    for(auto item : res){
        std::cout << item << " ";
    }
    return 0;
}

9--删除链表中的节点(18)

10--正则式匹配(19)

11--表示数值的字符串(20)

猜你喜欢

转载自blog.csdn.net/weixin_43863869/article/details/130927465
今日推荐