剑指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;
}

Python版本:

from typing import List

class Solution:
    def hasPath(self , matrix: List[List[str]], word: str) -> bool:
        m = len(matrix)
        n = len(matrix[0])
        vis = [[False for i in range (n)] for j in range(m)] # 初始化访问矩阵
        for i in range(m):
            for j in range(n):
                if self.dfs(matrix, word, i, j, 0, vis): 
                    return True
        return False
    
    def dfs(self, matrix: List[List[str]], word: str, i: int, j: int, cur: int, vis: List[List[bool]]) -> bool:
        if cur == len(word): 
            return True
        if i < 0 or i >= len(matrix) or j < 0 or j >= len(matrix[0]) or (matrix[i][j] != word[cur]) or (vis[i][j] == True): 
            return False

        vis[i][j] = True
        res = self.dfs(matrix, word, i-1, j, cur+1, vis) or self.dfs(matrix, word, i+1, j, cur+1, vis) or self.dfs(matrix, word, i, j-1, cur+1, vis) or self.dfs(matrix, word, i, j+1, cur+1, vis)
        vis[i][j] = False
        return res        
    
if __name__ == "__main__":
    matrix = [['a','b','c','e'],['s','f','c','s'],['a','d','e','e']]
    word = "see"
    S1 = Solution()
    res = S1.hasPath(matrix, word)
    print(res)
    print("All done!")
    

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;
}

python版本:

class Solution:
    def NumberOf1(self , n: int) -> int:
        res = 0
        for i in range(32):
            if(n & 1 << i):
                res += 1
        return res       
    
if __name__ == "__main__":
    S1 = Solution()
    n = 10 
    res = S1.NumberOf1(n)
    print(res)

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)

主要思路:遍历链表,判断节点的值是否等于输入的 val,当匹配成功后,将前一个结点指向后一个结点;(题目说明了可以不释放删除的结点)

#include <iostream>

struct ListNode {
    int val;
    ListNode *next;
    ListNode(int x) : val(x), next(NULL) {}
};

class Solution {
public:
    ListNode* deleteNode(ListNode* head, int val) {
        if (head->val == val){
            return head->next;
        }
        ListNode* cur = head;
        ListNode* pre = cur;
        while(cur != NULL){
            if (cur->val == val){
                pre->next = cur->next;
                break;
            }
            pre = cur;
            cur = cur->next;
        }
        return head;
    }

    void PrintList(ListNode* head){
        ListNode* cur = head;
        while(cur != NULL){
            std::cout << cur->val << " ";
            cur = cur->next;
        }
    }
};

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

    ListNode *L1 = new ListNode(4);
    ListNode *L2 = new ListNode(1);
    ListNode *L3 = new ListNode(5);
    ListNode *L4 = new ListNode(9);
    L1->next = L2;
    L2->next = L3;
    L3->next = L4;
    int val = 5;

    Solution s1;
    ListNode *L = s1.deleteNode(L1, val);
    s1.PrintList(L);
    return 0;
}

10--正则式匹配(19)

主要思路:

        基于动态规划,具体视频讲解参考:正则表达式匹配

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


class Solution {
public:
    bool isMatch(std::string s, std::string p) {
        int n = s.length();
        int m = p.length();
        s.insert(s.begin(), '0'); // 从索引1开始比较
        p.insert(p.begin(), '0');

        std::vector<std::vector<bool>> f(n+1, std::vector<bool>(m+1, false));
        // f[i][j] 表示s串前i个字符与p串前j个字符是否能够匹配


        f[0][0] = true; // 插入的索引0字符为true
        // 初始化f[0][j]的情况
        for(int j = 1; j <=m; j++){
            if(p[j] == '*') f[0][j] = f[0][j-1];
            else if(j + 1 > m || p[j+1] != '*') break;
            else f[0][j] = true; // a*可以认为是空串
        }

        for(int i = 1; i <= n; i++){
            for(int j = 1; j <= m; j++){
                if(p[j] == '*'){ 
                    f[i][j] = f[i][j-1];
                    continue;
                }
                
                if(j+1 <= m && p[j+1] == '*'){ // p[j] != '*'
                    // p[j]*为空串
                    // p[j]*为p[j]
                    // p[j]*为若干个p[j]
                    f[i][j] = f[i][j-1] ||
                            (f[i-1][j-1] && (p[j] == '.' || s[i] == p[j])) ||
                            (f[i-1][j] && (p[j] == '.' || s[i] == p[j]));
                }
                else{ // p[j] p[j+1]均不是*的情况
                    f[i][j] = f[i-1][j-1] && (p[j] == '.' || s[i] == p[j]);
                }
            }
        }
        return f[n][m];
    }
};

int main(int argc, char *argv[]){
    std::string s = "aa";
    std::string p = "a*";
    Solution S1;
    bool res = S1.isMatch(s, p);
    if(res) std::cout << "true" << std::endl;
    else std::cout << "false" << std::endl;

    return 0;
}

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

主要思路:用状态表示数值的所有格式或状态转移,根据状态转移来判断是否属于数值;

#include <iostream>
#include <string>

class Solution {
private:
    // 整数的格式可以用[+|-]B表示, 其中 B 为无符号整数
    bool scanInteger(const std::string s, int& index){

        if(s[index] == '+' || s[index] == '-')
            ++index;

        return scanUnsignedInteger(s, index);
    }
    
    bool scanUnsignedInteger(const std::string s, int& index){

        int befor = index;
        while(index != s.size() && s[index] >= '0' && s[index] <= '9')
            index ++;

        return index > befor;
    }
public:
    // 数字的格式可以用 A[.[B]], [e|EC] or .B[e|EC] 表示,
    // 其中 A 和 C 都是整数(可以有正负号,也可以没有),而 B 是一个无符号整数
    bool isNumber(std::string s) {

        if(s.size() == 0) // 字符串为空串,直接返回 false
            return false;
        int index = 0; // 当前遍历的字符

        while(s[index] == ' ') // 去除字符串开始的空格
            ++index;

        bool numeric = scanInteger(s, index); // 判断开头是否是整数

        // 如果出现'.',接下来是数字的小数部分
        if(s[index] == '.'){
            ++index;
            // 下面一行代码用||的原因:
            // 1. 小数可以没有整数部分,例如.123等于0.123;
            // 2. 小数点后面可以没有数字,例如233.等于233.0;
            // 3. 当然小数点前面和后面可以有数字,例如233.666
            numeric = scanUnsignedInteger(s, index) || numeric;
        }

        // 如果出现'e'或者'E',接下来是数字的指数部分
        if(s[index] == 'e' || s[index] == 'E'){
            ++index;
            // 下面一行代码用&&的原因:
            // 1. 当e或E前面没有数字时,整个字符串不能表示数字,例如.e1、e1;
            // 2. 当e或E后面没有整数时,整个字符串不能表示数字,例如12e、12e+5.4
            numeric = numeric && scanInteger(s ,index);
        }

        // 去除字符串末尾的空格
        while(s[index] == ' ')
            ++index;

        return numeric && index == s.size();
    }
};

int main(int argc, char *argv[]){
    std::string s = "3.1416";
    Solution s1;
    bool res = s1.isNumber(s);
    if (res){
        std::cout << "true" << std::endl;
    }
    else{
        std::cout << "false" << std::endl;
    }
    return 0;
}

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值