剑指 Offer 05. 替换空格

image.png

思路一:辅助空间

  1. class Solution {
  2. public:
  3. string replaceSpace(string s) {
  4. string res;
  5. for(const auto& ch : s) {
  6. if (ch == ' ') {
  7. res += "%20";
  8. } else {
  9. res.push_back(ch);
  10. }
  11. }
  12. return res;
  13. }
  14. };

结果不算的话空间复杂度O(1)
时间复杂度O(N)

思路二:原地操作

  1. class Solution {
  2. public:
  3. string replaceSpace(string s) {
  4. int cnt = 0;// 统计空格的个数
  5. int old = s.size();// 保留原字符串的长度
  6. for (auto c : s)
  7. if (c == ' ')
  8. cnt++;
  9. s.resize(s.size() + 2 * cnt);
  10. for (int i = s.size() - 1, j = old - 1; j < i; i--, j--) {
  11. if (s[j] == ' ') {
  12. s[i--] = '0';
  13. s[i--] = '2';
  14. s[i] = '%';
  15. } else {
  16. s[i] = s[j];
  17. }
  18. }
  19. return s;
  20. }
  21. };

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

image.png

直接计算

  1. class Solution {
  2. public:
  3. vector<int> printNumbers(int n) {
  4. vector<int> res;
  5. int i = 1;
  6. while (n--) {
  7. i *= 10;
  8. }
  9. for (int j = 1; j < i; j++) {
  10. res.push_back(j);
  11. }
  12. return res;
  13. }
  14. };

这种思路比较简单,但是如果给的n比较大会出现int溢出问题,因此可以看情况考虑下面的解法

考虑大数问题💦

  1. 表示大数的变量类型:
  • 无论是 short / int / long … 任意变量类型,数字的取值范围都是有限的。因此,大数的表示应用字符串 String 类型。
  1. 生成数字的字符串集:
  • 使用 int 类型时,每轮可通过 +1+1 生成下个数字,而此方法无法应用至 String 类型。并且, String 类型的数字的进位操作效率较低,例如 “9999” 至 “10000” 需要从个位到千位循环判断,进位 4 次。

  • 观察可知,生成的列表实际上是 nn 位 00 - 99 的 全排列 ,因此可避开进位操作,通过递归生成数字的 String 列表。

  1. 递归生成全排列:
  • 基于分治算法的思想,先固定高位,向低位递归,当个位已被固定时,添加数字的字符串。例如当 n = 2n=2 时(数字范围 1 - 991−99 ),固定十位为 00 - 99 ,按顺序依次开启递归,固定个位 00 - 99 ,终止递归并添加数字字符串。

image.png

剑指 Offer 20. 表示数值的字符串

image.png
image.png

模拟

  1. class Solution {
  2. public boolean isNumber(String s) {
  3. if(s == null || s.length() == 0) return false; // s为空对象或 s长度为0(空字符串)时, 不能表示数值
  4. boolean isNum = false, isDot = false, ise_or_E = false; // 标记是否遇到数位、小数点、‘e’或'E'
  5. char[] str = s.trim().toCharArray(); // 删除字符串头尾的空格,转为字符数组,方便遍历判断每个字符
  6. for(int i=0; i<str.length; i++) {
  7. if(str[i] >= '0' && str[i] <= '9') isNum = true; // 判断当前字符是否为 0~9 的数位
  8. else if(str[i] == '.') { // 遇到小数点
  9. if(isDot || ise_or_E) return false; // 小数点之前可以没有整数,但是不能重复出现小数点、或出现‘e’、'E'
  10. isDot = true; // 标记已经遇到小数点
  11. }
  12. else if(str[i] == 'e' || str[i] == 'E') { // 遇到‘e’或'E'
  13. if(!isNum || ise_or_E) return false; // ‘e’或'E'前面必须有整数,且前面不能重复出现‘e’或'E'
  14. ise_or_E = true; // 标记已经遇到‘e’或'E'
  15. isNum = false; // 重置isNum,因为‘e’或'E'之后也必须接上整数,防止出现 123e或者123e+的非法情况
  16. }
  17. else if(str[i] == '-' ||str[i] == '+') {
  18. if(i!=0 && str[i-1] != 'e' && str[i-1] != 'E') return false; // 正负号只可能出现在第一个位置,或者出现在‘e’或'E'的后面一个位置
  19. }
  20. else return false; // 其它情况均为不合法字符
  21. }
  22. return isNum;
  23. }
  24. }
  1. class Solution {
  2. public:
  3. bool isNumber(string s) {
  4. int len = s.size();
  5. if (len == 0) return false;
  6. bool isNum = false, isDot = false, isE = false;
  7. int i = 0;
  8. // 找到第一个不为空格的字符
  9. while (i < len && s[i] == ' ') i++;
  10. int first = i; // 记录第一个非空格
  11. for (; i < len; i++) {
  12. if (s[i] >= '0' && s[i] <= '9') {
  13. isNum = true; // 标记遇到数字
  14. } else if (s[i] == '.') {
  15. if (isDot || isE) return false; // 小数点不能重复,e后面不能有小数
  16. isDot = true;
  17. } else if (s[i] == 'e' || s[i] == 'E'){
  18. if (!isNum || isE ) return false; // e前面要有数字,e不能重复
  19. isE = true;
  20. isNum = false; // 重置,然后判断e后面是否有整数
  21. } else if (s[i] == '+' || s[i] == '-') {
  22. if (i != first && s[i - 1] != 'e' && s[i - 1] != 'E') return false; // + - 只能出现在第一个字符或者e的后面
  23. } else if (s[i] == ' ') {
  24. // 处理字符串尾部的空格
  25. while (i < len && s[i] == ' ') i++;
  26. // 如果尾部之前出现其他非空格,返回false
  27. if (i == len) return isNum;
  28. else return false;
  29. } else {
  30. return false; // 其他情况都不是数字
  31. }
  32. }
  33. return isNum;
  34. }
  35. };

有限状态机💦

剑指 Offer 45. 把数组排成最小的数

image.png

class Solution {
public:
    string minNumber(vector<int>& nums) {
        vector<string> strs;
        for (auto num : nums) {
            strs.push_back(to_string(num));
        }
        sort(strs.begin(), strs.end(), [](string &a, string &b) {
            return a + b < b + a; 
        });
        string s;
        for (auto str : strs) {
            s += str;
        }
        return s;
    }
};

(滑动窗口、双指针)剑指 Offer 48. 最长不含重复字符的子字符串

image.png

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        if (s.size() <= 1) return s.size();
        unordered_map<char, int> um;
        int res = 0;
        int i = 0, j = 0;
        int len = 0;
        while (i < s.size()) {
            while(um[s[i]] > 0) {
                um[s[j++]]--;
            }
            res = max(res, i - j + 1);
            um[s[i++]]++;
        }
        return res;
    }
};

剑指 Offer 50. 第一个只出现一次的字符

image.png

用哈希表记录出现次数,然后遍历哈希表,将第一个次数为1的返回

class Solution {
public:
    char firstUniqChar(string s) {
        unordered_map<int, int> frequency;
        for (char ch: s) {
            ++frequency[ch];
        }
        for (int i = 0; i < s.size(); ++i) {
            if (frequency[s[i]] == 1) {
                return s[i];
            }
        }
        return ' ';
    }
};

时间复杂度O(N),空间复杂度O(N)

剑指 Offer 58 - I. 翻转单词顺序

image.png
151题笔记
https://www.yuque.com/u22270676/qvvmek/nn95ro#WZfIE
解题思路参考151题的笔记

用栈

class Solution {
public:
    string reverseWords(string s) {
        stack<string> st;
        for (int i = 0; i < s.size(); i++) {
            if (s[i] == ' ') continue;
            string temp;
            while (i < s.size() && s[i] != ' ') 
                temp.push_back(s[i++]);
            st.push(temp); // 保存单词
        }
        string res;
        while (!st.empty()) {
            res += st.top();
            st.pop();
            res.push_back(' ');
        }
        // 删除最后一个空格
        res.pop_back();
        return res;
    }
};
  • 时间复杂度O(N)
  • 空间复杂度O(N)

反向遍历,直接将单词添加到要返回的字符串结尾

class Solution {
public:
    string reverseWords(string s) {
       string res;
       for (int i = s.size() - 1; i >= 0; i--) {
           if (s[i] == ' ') continue;
           int end = i, begin = i;//记录单词结束的位置和开始的位置
           while (i >= 0 && s[i] != ' ') {
               begin--;
               i--;
           }
           i++;
           for(int j = begin + 1; j <= end; j++) {
               res.push_back(s[j]);
           }
           res += ' ';
       }

       res.pop_back();
       return res;
    }
};

原地操作

class Solution {
public:
    // 移除多余的空格
    void removeSpace(string &s) {
        // 移除前面的空格
        int slow = 0, fast = 0;
        while (fast < s.size() && s[fast] == ' ') fast++;
        // 移动中间的空格
        while (fast < s.size()) {
            if (fast - 1 > 0 && s[fast] == s[fast - 1] && s[fast] == ' ') {
                fast++;
            } else {
                s[slow++] = s[fast++];
            }
        }
        // 移除尾部的空格
        if (slow - 1 > 0 && s[slow - 1] == ' ') {//最后一个字符为空格
            s.resize(slow - 1);
        } else {//最后一个字符不是空格
            s.resize(slow);
        }
    }

    //翻转字符串中的单词,左闭右闭
    void reverse(string &s, int begin, int end) {
        for (int i = begin, j = end; i < j; i++, j--) {
            swap(s[i], s[j]);
        }
    }

    string reverseWords(string s) {
        removeSpace(s);
        reverse(s, 0, s.size() - 1);
        for (int i = 0; i < s.size(); i++) {
                int begin = i, end = i;
                while (end < s.size() && s[end] != ' ') {
                    end++;
                    i++;//for循环上i再加一,正好将空格跳过
                }
                reverse(s, begin, end - 1);
        }
        return s;
    }

};

剑指 Offer 58 - II. 左旋转字符串

image.png

思路一:将字符串左移位n次

class Solution {
public:
    string reverseLeftWords(string s, int n) {
        for (int i = 0; i < n; i++) {
            char tmp = s[0];
            for (int j = 1; j < s.size(); j++) {
                s[j - 1] = s[j];
            }
            s[s.size() - 1] = tmp;
        }
        return s;
    }
};

复杂度分析:

  • 时间复杂度为O(n^2)
  • 空间复杂度为O(1)

    思路二:空间换时间

    class Solution {
    public:
      string reverseLeftWords(string s, int n) {
          string res;
          for (int i = n; i < s.size(); i++)
              res.push_back(s[i]);
          for (int i = 0; i < n; i++)
              res.push_back(s[i]);
          return res;
      }
    };
    

    复杂度分析:

  • 时间复杂度为O(n)

  • 空间复杂度为O(n)

思路三:全局反转+局部反转

参考反转字符串里的单词的解法,可以先将字符串整体反转,然后再将0 ~ s.size() - - 1s.zie() - ~ s.size() - 1分别反转就可以实现原地左旋
例如:
字符串 s = “abcdefg”, k = 2

  • 全局反转后得到”gfedcba”
  • 局部反转后得到”cdefgab” ```cpp class Solution { public: string reverseLeftWords(string s, int n) {
      int len = s.size();
      reverse(s.begin(), s.end());//左闭右开
      reverse(s.begin(), s.begin() + len - n);
      reverse(s.begin() + len - n, s.end());
      return s;
    
    } };


当然,先局部反转,再全局反转,也是一样的<br />s = "abcdefg", k = 2

- 局部反转:"bagfedc"
- 全局反转:"cdefgab"
```cpp
class Solution {
public:
    string reverseLeftWords(string s, int n) {
        reverse(s.begin(), s.begin() + n);//左闭右开
        reverse(s.begin() + n, s.end());
        reverse(s.begin(), s.end());
        return s;
    }
};

剑指 Offer 67. 把字符串转换成整数

image.png
image.png

class Solution {
public:
    int myAtoi(string s) {
        if (s.size() == 0) return 0;

        int i = 0, n = s.size();
        while (i < n && s[i] == ' ') ++i;

        int flag = 1;
        long long num = 0;
        if (i < n) {
            if (s[i] == '+') {
                ++i;
            } else if (s[i] == '-') {
                flag = -1;
                ++i;
            } 
            if (s[i] >= '0' && s[i] <= '9') {  
                while (i < n && s[i] >= '0' && s[i] <= '9') {
                    num *= 10;
                    num += (s[i] - '0');
                    if (num > INT32_MAX || num < INT32_MIN)
                        return flag == -1? INT32_MIN : INT32_MAX;
                    ++i;
                }
            }
        }
        return flag * num;
    }
};