《剑指offer》题解

文章目录

说明

0-还未做,先略过

1-待尝试其它方法

剑指 Offer 03. 数组中重复的数字

剑指 Offer 03. 数组中重复的数字

【数组,元素&索引的对应关系】

题干:

给定一个长度为n的数组nums,里面的所有数字都在 [ 0 , n − 1 ] [0,n-1] [0,n1]内,找出数组中任意一个重复数字并返回。

数据范围:2<=n<=1e5

函数签名:

int findRepeatNumber(vector<int>& nums);

解:

  • 方法1:哈希
    • 时间复杂度 O ( n ) O(n) O(n),空间复杂度 O ( 1 ) O(1) O(1)
  • 方法2:元素与索引一一对应
    • 对于这种数组元素在 [ 0 , n − 1 ] [0,n-1] [0,n1]范围的问题,可以将值为i的元素调整到第i个位置上进行求解;在调整过程中,如果第i个位置上已经有一个值为i的元素,就可以知道i值重复了。
    • 原地修改,时间复杂度 O ( n ) O(n) O(n),空间复杂度 O ( 1 ) O(1) O(1)

for循环版:

int findRepeatNumber(vector<int>& nums) {
   
        for(int i = 0; i< nums.size(); i++){
   
            while(nums[i]!= i){
   
                if(nums[nums[i]] == nums[i]){
   
                    return nums[i];
                }
                swap(nums[i], nums[nums[i]]);
            }
        }
        return -1;
    }

while循环版:

int findRepeatNumber(vector<int>& nums) {
   
        int i = 0;
        while(i < nums.size()){
   
            if(nums[i] == i){
   
                ++i;
                continue;
            }
            if(nums[nums[i]] == nums[i]) return nums[i];
            swap(nums[i], nums[nums[i]]);
        }
        return -1;
    }

剑指 Offer 04. 二维数组中的查找

剑指 Offer 04. 二维数组中的查找

【二分&抽象BST】

题干:给定一个m*n的二维数组,每行按从左到右非递减,每列从上到下非递减的顺序排序。给定一个整数target,判断数组中是否含有该整数。

数据范围:0<=m,n<=1e3

函数签名:

bool findNumberIn2DArray(vector<vector<int>>& matrix, int target)

解:

参考

  • 方法1:二分
    • 遍历每行,对每列二分,判断能否找到target
    • 时间复杂度 O ( m l o g n ) O(mlogn) O(mlogn)(若遍历列二分行则 O ( n l o g m ) O(nlogm) O(nlogm)),空间复杂度 O ( 1 ) O(1) O(1)
bool findNumberIn2DArray(vector<vector<int>>& matrix, int target) {
   
        if(!matrix.size() || !matrix[0].size()) return false;
        int m = matrix.size(), n = matrix[0].size();
        for(int i = 0; i < m; i++){
   
            int left = 0, right = n;
            int index = 0;
            while(left < right){
   
                int mid = left + ((right - left) >> 1);
                if(matrix[i][mid]== target) return true;
                else if(matrix[i][mid] > target) right = mid;
                else left = mid+1;
                
            }    
        }
        return false;
    }
  • 方法2:抽象BST
    • 该题的matrix从左到右递增,从上到下递增,所以左上角是最小元素,右下角是最大元素。若想高效在matrix中搜索一个元素,肯定要从某个角开始(如左上角),每次只能向右或向下移动,不要走回头路。
      • 但左上角无论向右还是向下,元素值都会增加;右下角开始同理
      • 所以从右上角或左下角开始就可以了。如规定从右上角开始,每次只能向左或向下移动,这样就能根据当前位置的元素和target的大小关系判断如何移动,不断接近从而找到target的位置
    • 其实这就是一棵以右上角为根节点的BST
      • 若target<当前节点,搜索当前节点的左子树,即col–;
      • 若target>当前节点,搜索当前节点的右子树,即row++。
    • 时间复杂度 O ( m + n ) O(m+n) O(m+n),空间复杂度 O ( 1 ) O(1) O(1)
bool findNumberIn2DArray(vector<vector<int>>& matrix, int target) {
   
        if(!matrix.size() || !matrix[0].size()) return false;
        int m = matrix.size(), n = matrix[0].size();
        int i = 0, j = n-1;
        while(i < m && j >= 0){
   
            if(target == matrix[i][j]) return true;
            else if(target < matrix[i][j]) j--;
            else i++;
        }
        return false;
    }

剑指 Offer 05. 替换空格

剑指 Offer 05. 替换空格

【双指针】

题干:把字符串s的每个空格替换成"%20"

数据范围:0<=s.length()<=1e5

函数签名:

string replaceSpace(string s);

解:

  • 方法1:另开空间
    • 另开一个string变量,遍历s,遇到空格时往里添加"%20",遇到非空格时添加s[i]。
    • 时间复杂度 O ( n ) O(n) O(n),空间复杂度 O ( 1 ) O(1) O(1)
    string replaceSpace(string s) {
   
        string res;
        int j = 0;
        for(char c: s){
   
            if(c == ' ') res+="%20";
            else res += c;
        }
        return res;
    }
  • 方法2:双指针,原地修改
    • 每替换一个空格,字符串长度增加2,则可以先统计s中空格的总数,就可计算出替换后的字符串的总长度(s.length()+2
    • 从字符串的末尾开始复制和替换,实现原地修改。
      • 双指针i,j,一个指向原字符串末尾,一个指向替换后的字符串末尾,根据s[i]是否等于空格进行复制或替换操作
      • 若空格都替换完了,则i==j,可退出循环
    • 时间复杂度 O ( n ) O(n) O(n),空间复杂度 O ( 1 ) O(1) O(1)
    string replaceSpace(string s) {
   
        int cnt = 0;
        for(char c: s){
   
            if(c == ' ') ++cnt;
        }
        int len = s.size();
        s.resize(len + 2*cnt);
        int i = len-1, j = s.size()-1;
        while(i != j){
   
            if(s[i] != ' '){
   
                s[j] = s[i];
                i--, j--;
            }
            else{
   
                s[j--] = '0';
                s[j--] = '2';
                s[j--] = '%';
                i--;
            }
        }
        return s;
    }

剑指 Offer 06. 从尾到头打印链表

剑指 Offer 06. 从尾到头打印链表

【递归,二叉树后序遍历】

题干:输入一个链表的头节点,从尾到头反过来返回每个节点的值。

数据范围:0<=链表长度<=1e5

函数签名:

vector<int> reversePrint(ListNode* head)

解:

  • 方法:递归
    • 类似二叉树的后序遍历。
    • 对于每个节点,先访问(打印)它的子节点的值,再打印该节点的值,所以是在后序位置写打印语句。
    • 时间复杂度 O ( n ) O(n) O(n),空间复杂度 O ( n ) O(n) O(n)
    vector<int> reversePrint(ListNode* head) {
   
        vector<int> res;
        printLink(head, res);
        return res;
    }
    void printLink(ListNode* head, vector<int>& res){
   
        if(!head) return;
        printLink(head->next, res);
        res.push_back(head->val);
    }

剑指 Offer 07. 重建二叉树

剑指 Offer 07. 重建二叉树

【递归】

题干:通过二叉树的前序序列和中序序列重建二叉树

数据范围:0<=节点个数<=5e3

函数签名:

TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder)

解:

  • 方法:递归,前序位置建根节点。见之前的文章。
    • 时间复杂度 O ( n ) O(n) O(n),空间复杂度 O ( n ) O(n) O(n)
      • 时间:初始化hashMap占用 O ( n ) O(n) O(n);递归建立n个节点,每层递归中的节点建立、搜索操作占用 O ( n ) O(n) O(n)。因此使用 O ( n ) O(n) O(n)时间。
      • 空间:hashMap使用 O ( n ) O(n) O(n)额外空间;最差情况下(二叉树为链表时),递归深度 O ( n ) O(n) O(n),占用 O ( n ) O(n) O(n)的栈帧空间。因此总共使用 O ( n ) O(n) O(n)空间
  • 注意条件:不含重复元素。
	unordered_map<int, int> mp;
    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
   
        for(int i = 0; i < inorder.size(); i++) mp[inorder[i]] = i;
        int n = preorder.size();
        return build(preorder, 0, n-1, inorder, 0, n-1);
    }
    TreeNode* build(vector<int>& preorder, int preL, int preR, vector<int>& inorder, int inL, int inR){
   
        if(preL > preR) return nullptr;
        int rootVal = preorder[preL];
        TreeNode* root = new TreeNode(rootVal);
        int inRootIndex = mp[rootVal];
        int leftSize = inRootIndex - inL;
        root->left = build(preorder, preL+1, preL+1 + leftSize-1, inorder, inL, inRootIndex-1);
        root->right = build(preorder, preL+ leftSize + 1, preR, inorder, inRootIndex+1, inR);
        return root;
    }

剑指 Offer 09. 用两个栈实现队列

剑指 Offer 09. 用两个栈实现队列

【模拟】

题干:用两个栈实现队列的push和pop操作(pop不成功返回-1)

数据范围:1<=val<=1e4,最多进行1e4次操作

函数签名:

class CQueue {
   
public:
    CQueue() {
   }
    
    void appendTail(int value) {
   
    }
    int deleteHead() {
   
    }
};

解:

  • 方法:
    • “in 栈用来处理入栈(push)操作,out 栈用来处理出栈(pop)操作。一个元素进入 in 栈之后,出栈的顺序被反转。当元素要出栈时,需要先进入 out 栈,此时元素出栈顺序再一次被反转,因此出栈顺序就和最开始入栈顺序是相同的,先进入的元素先退出,这就是队列的顺序”(来源)。
class CQueue {
   
private:
    stack<int> st1, st2;
public:
    CQueue() {
   }
    
    void appendTail(int value) {
   
        st1.push(value);
    }
    int deleteHead() {
   
        if(!st2.empty()){
   
            int val = st2.top();
            st2.pop();
            return val;
        }
        while(!st1.empty()){
   
            int val = st1.top();
            st1.pop();
            st2.push(val);
        }
        if(st2.empty()) return -1;
        int val = st2.top();
        st2.pop();
        return val;
    }
};

剑指 Offer 10- I. 斐波那契数列【1】

剑指 Offer 10- I. 斐波那契数列

【递归,递推,矩阵快速幂(待整理)】

题干:求斐波那契数列(f[0]=0,f[1]=1, f[n]=f[n-1]+f[n-2]),并对答案取模(1e9+7)

数据范围:0<=n<=100

函数签名:

int fib(int n);

解:

  • 方法1:递归+记忆化搜索
    const int mod = 1e9+7;
    static const int N = 100;
    int f[N+2];
    int fib(int n){
        if(n == 0) return 0;
        if(n == 1) return 1;
        if(f[n]) return f[n];
        f[n] = (fib(n-1) + fib(n-2)) % mod;
        return f[n];
    }
  • 方法2:递推
    const int mod = 1e9+7;
    static const int N = 100;
    int f[N+2];
    int fib(int n) {
   
        int f[n+2];
        f[0] = 0;
        f[1] = 1;
        for(int i = 2; i <= n; i++){
   
            f[i] = (f[i-1] + f[i-2]) % mod;
        }
        return f[n];
    }

剑指 Offer 10- II. 青蛙跳台阶问题【1】

剑指 Offer 10- II. 青蛙跳台阶问题

【递归,递推,矩阵快速幂(待整理)】

题干:青蛙往上跳台阶,一次可以跳1级也可以跳2级,求青蛙跳上一个n级的台阶共有多少种跳法,答案对1e9+7取模。

数据范围:0<=n<=100

函数签名:

int numWays(int n);

解:

  • 方法1:递归+记忆化搜索
    const int mod = 1e9+7;
    static const int N = 100;
    int dp[N+5];
    int numWays(int n) {
   
        if(n == 0) return 1;
        if(n == 1) return 1;
        if(dp[n]) return dp[n];
        dp[n] = (numWays(n-1) + numWays(n-2)) % mod;
        return dp[n];
    }
  • 方法2:递推
    const int mod = 1e9+7;
    static const int N = 100;
    int dp[N+5];
    int numWays(int n){
   
        dp[0]=1;
        dp[1]=1;
        for(int i = 2; i<= n; i++){
   
            dp[i] = (dp[i-1] + dp[i-2]) % mod;
        }
        return dp[n];
    }
  • 优化:空间压缩
    const int mod = 1e9+7;
    int numWays(int n){
   
        int dp_0 = 1, dp_1 = 1;
        for(int i = 2; i<= n; i++){
   
            int dp_2 = (dp_0 + dp_1) % mod;
            dp_0 = dp_1;
            dp_1 = dp_2;
        }
        return dp_1;
    }

剑指 Offer 11. 旋转数组的最小数字

剑指 Offer 11. 旋转数组的最小数字

【二分】

题干:给定一个不降序数组numbers(里面可以有相同元素),现将numbers k k k位置( k k k未知)旋转(即将从 k k k开始到末尾的部分移到最前面),要求在numbers中寻找到最小的元素并返回元素值。

数据范围:1<=numbers.length<=5e3, -5e3<=numbers[i]<=5e3

函数签名:

int minArray(vector<int>& numbers);

解:

  • 方法:二分,见之前的文章。
    int minArray(vector<int>& numbers) {
   
        int left = 0, right = numbers.size()-1;
        if(left == right) return numbers[0]; // boundary
        while(numbers[left] == numbers[right] && (left != right)) left++;
        int target = numbers[right];
        right++;
        int ans=0;
        while(left < right){
   
            int mid = left + ((right - left)>>1);
            if(numbers[mid]<=target){
   
                ans = numbers[mid];
                right = mid;
            }
            else left = mid + 1;
        }
        return ans;
    }

剑指 Offer 12. 矩阵中的路径

剑指 Offer 12. 矩阵中的路径

【DFS回溯】

题干:给定一个二维字符网格board和一个字符串word,判断word是否在board中存在,存在返回true,不存在返回false。

数据范围:1<=m,n<=6, 1<=word.length()<=15

函数签名:

bool exist(vector<vector<char>>& board, string word);

解:

  • 方法:DFS递归+回溯
    • 使用vis数组标记,防止重复搜索
    bool exist(vector<vector<char>>& board, string word) {
   
        m = board.size(), n = board[0].size();
        for(int i = 0; i < m;i ++){
   
            for(int j = 0; j < n; j++){
   
                if(board[i][j] == word[0]){
   
                    if(dfs(board, word, i, j, 0)) return true;
                }
            }
        }
        return false;
    }
private:
    bool vis[10][10];
    int m, n;
    int dx[4] = {
   -1, 1, 0, 0};
    int dy[4] = {
   0, 0, -1, 1}; 
    bool dfs(vector<vector<char>>& board, string word, int x, int y, int pos){
   
        if(pos == word.size()) return true;
        if(x<0 || x >= m || y<0 || y >= n){
   
            return false;
        }
        if(vis[x][y] || word[pos] != board[x][y]) return false;
        vis[x][y] = 1;
        for(int i = 0; i<= 3; i++){
   
            int newX = x + dx[i];
            int newY = y + dy[i];
            if(dfs(board, word, newX, newY, pos+1)){
   
                return true;
            }
        }
        vis[x][y] = 0;
        return false;
    }

剑指 Offer 14- I. 剪绳子

剑指 Offer 14- I. 剪绳子

【DP,贪心+快速幂】

题干:给定一根长度为n的绳子,把它剪成整数长度的m段(m必须大于1),问这m的乘积最大可能是多少?

数据范围:2<=n<=58

函数签名:

int cuttingRope(int n);

解:

  • 方法:dp
    • 设长度为n的绳子cut的最大乘积为dp(n),
  • 方法1:递归+记忆化搜索
    int cuttingRope(int n){
   
        return dp(n);
    }
private:
    int memo[60];
    int dp(int n){
   
        if(n==0 || n==1) return 0;
        if(memo[n]) return memo[n];
        for(int i = 1; i < n; i++){
   
            memo[n] = max(memo[n], max(i*(n-i), i*dp(n-i)));
        }
        return memo[n];
    }
  • 方法2:递推
    int cuttingRope(int n){
   
        vector<int> dp(n+2);
        for(int i = 2; i <= n; i++){
   
            for(int j = 1; j < i; j++){
   
                dp[i] = max(dp[i], max(j*(i-j), j*dp[i-j]));
            }
        }
        return dp[n];
    }

剑指 Offer 14- II. 剪绳子 II

剑指 Offer 14- II. 剪绳子 II

【贪心+快速幂】

解:

  • 方法:贪心
    • 要取模,DP要比较最大值,所以不行
    • 一个整数n切分(至少切分成两段),使得乘积最大:尽可能多的3,不行就换2
      • 数学证明略
    const int p = 1e9+7;
    long long qmod(long long a, long long b){
   
        long long res = 1;
        while(b){
   
            if(b&1) res = res * a %p;
            a = a * a %p;
            b>>=1;
        }
        return res;
    }
    int cuttingRope(int n) {
   
        if(n == 2) return 1;
        if(n == 3) return 2;
        int cnt = n/3;
        int mod = n%3;
        int ans = 1;
        if(!mod){
   
            ans = qmod(3ll, cnt) %p;
        }
        else if(mod == 1){
   
            ans = qmod(3ll, cnt-1) * 4ll % p;
        }
        else{
   
            ans = qmod(3ll, cnt) * 2ll % p;
        }
        return ans;
    }

剑指 Offer 15. 二进制中1的个数

剑指 Offer 15. 二进制中1的个数

【位运算】

解:

  • 方法1:快速幂的思路
    int hammingWeight(uint32_t n) {
   
        int cnt = 0;
        while(n){
   
            if(n&1) ++cnt;
            n>>=1;
        }
        return cnt;
    }
  • 方法2:n&(n-1):把最后一个1变为0
    int hammingWeight(uint32_t n) {
   
        int cnt = 0;
        while(n){
   
            ++cnt;
            n = n & (n-1);
        }
        return cnt;
    }

剑指 Offer 16. 数值的整数次方

剑指 Offer 16. 数值的整数次方

【快速幂,浮点数】

解:

  • 方法:快速幂
    • 注意对浮点数的处理(指数为负数时)
    double myPow(double x, int n){
   
        if(!x) return 0;
        long long b = n;
        if(n < 0){
   
            x = 1.0/x;
            b = -b;
        }

        double res = 1.0;
        while(b){
   
            if(b & 1) res = res * x;
            x = x * x;
            b >>=1;
        }
        return res;
    }

剑指 Offer 17. 打印从1到最大的n位数

剑指 Offer 17. 打印从1到最大的n位数

【dfs,全排列,元素无重可复选】

解:

  • 方法:递归,DFS全排列

    • 大数,利用string转int。

      • int转string:to_string

        • string s = to_string(x);
          
      • string转int:atoi+c_str

      • 大数,利用string。转int需要去除前导0,所以用atoi+s.c_str()转为int

        • 区别:

          • 使用atoi,不全为字符串转为0
          • 使用stoi,不全为字符串会报错
        • int x = atoi(s.c_str());
          //先将string转为const char*,再转为int
          
    bool flag = 0;
    vector<int> ans;
    vector<int> printNumbers(int n) {
   
        string s = "0123456789";
        string path = "";
        dfs(s, path, n);
        return ans;
    }
    void dfs(string &s, string &path, int res){
   
        if(res == 0){
   
            if(!flag){
    //去掉0
                flag = 1;
                return;
            }
            ans.push_back(atoi(path.c_str()));
            return;
        }
        for(int i = 0; i< s.length(); i++){
   
            path += s[i]; //path.push_back(s[i]);
            dfs(s, path, res-1);
            path.pop_back();
        }
    }

剑指 Offer 18. 删除链表的节点

剑指 Offer 18. 删除链表的节点

【链表删除】

解:

判断的是cur->next是否为要删除的节点,注意判空

    ListNode* deleteNode(ListNode* head, int val) {
   
        ListNode* dummy = new ListNode(-1);
        dummy->next = head;
        ListNode* cur = dummy;
        while(cur && cur->next){
    //while(cur->next)
            if(cur->next->val == val){
   
                cur->next = cur->next->next;
                break;
            }
            cu
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值