剑指offer 打卡计划| 每日进步一点点 | 第七天

145 阅读3分钟

图片.png 一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第9天,点击查看活动详情

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

思路 (模拟,字符串处理) O(n)

遍历整个字符串,将所有的不合法情况都去除掉,剩下的就是合法情况。

具体过程如下:

1、首先去除行首和行尾空格,如果去除完之后,整个字符串为空,则返回false

2、行首如果有一个正负号,直接忽略。

3、如果字符串为空或只有一个'.',则不是一个合法方案。 4、遍历整个字符串s,对于当前字符s[i]

  • s[i]为数字,则不做任何处理。

  • s[i] == '.'.的个数加1。如果此时'.''e'后面出现或者'.'的个数多于1个,则返回false。【1e2.11e2.1.1

  • s[i] == 'e' || s[i] == 'E'e的个数加1

    • 如果此时'e'的后面为空或者'e'多于1个或者'e'的前面为空或者为'.e',则返回false。【12e12e3ee1212.e3.e
    • 'e'后面紧跟着正负号,但正负号后面为空,则返回false。【1e2+
  • s[i]为其他字符,返回false

5、排除了各种非法情况,此时s则为合法方案,我们返回true

c++代码

 class Solution {
 public:
     bool isNumber(string s) {
         int i = 0;
         while (i < s.size() && s[i] == ' ') i ++ ; //删除行首空格
         int j = s.size() - 1;
         while (j >= 0 && s[j] == ' ') j -- ; //删除行末空格
         if (i > j) return false;
         s = s.substr(i, j - i + 1);
 ​
         if (s[0] == '-' || s[0] == '+') s = s.substr(1); //忽略行首正负号
         if (s.empty() || s[0] == '.'&& s.size() == 1) return false;//如果字符串为空或只有一个'.',则不是一个合法方案
 ​
         int dot = 0, e = 0;
         for (int i = 0; i < s.size(); i ++ )
         {
             if (s[i] >= '0' && s[i] <= '9');//遇到数字不做任何处理
             else if (s[i] == '.')
             {
                 dot ++ ; //'.'的个数加1
                 if (e || dot > 1) return false;//'.'在'e'后面出现 , '.'的个数多于1个;
             }
             else if (s[i] == 'e' || s[i] == 'E')
             {
                 e ++ ; //'e'的个数加1  
                 //'e'的后面为空或者'e'多于1个或者'e'的前面为空或者为  
                 if (i + 1 == s.size() || !i || e > 1 || i == 1 && s[0] == '.') return false;
                 if (s[i + 1] == '+' || s[i + 1] == '-')//'e'后面紧跟着正负号,但正负号后面为空
                 {
                     if (i + 2 == s.size()) return false;
                     i ++ ;  //跳过正负号
                 }
             }
             else return false; //其他字符
         }
         return true;
     }
 ​
 };

剑指 Offer 21. 调整数组顺序使奇数位于偶数前面

思路

(双指针扫描) O(n)

用两个指针分别从首尾开始,往中间扫描。扫描时保证第一个指针前面的数都是奇数,第二个指针后面的数都是偶数。

每次迭代时需要进行的操作:

  1. 第一个指针一直往后走,直到遇到第一个偶数为止;
  2. 第二个指针一直往前走,直到遇到第一个奇数为止;
  3. 交换两个指针指向的位置上的数,再进入下一层迭代,直到两个指针相遇为止;

图片.png image-20211124190746173

时间复杂度分析: 当两个指针相遇时,走过的总路程长度是 n,所以时间复杂度是 O(n)。

c++代码

 class Solution {
 public:
     vector<int> exchange(vector<int>& nums) {
         int i = 0, j = nums.size() - 1;
         while(i < j){
             while(i < j && nums[i] % 2 == 1) i++;
             while(i < j && nums[j] % 2 == 0) j--;
             swap(nums[i], nums[j]);
         }
         return nums;
     }
 };

剑指 Offer 22. 链表中倒数第k个节点

思路

(链表) O(n)

由于单链表不能索引到前驱节点,所以只能从前往后遍历。

我们一共遍历两次:

  1. 第一次遍历得到链表总长度 n;

  2. 链表的倒数第 k 个节点,相当于正数第n−k+1 个节点。所以第二次遍历到第 n−k+1 个节点,就是我们要找的答案。

图片.png

注意:

当 k>n 时要返回nullptr。

时间复杂度分析: 链表总共遍历两次,所以时间复杂度是 O(n)。

c++代码

 /**
  * Definition for singly-linked list.
  * struct ListNode {
  *     int val;
  *     ListNode *next;
  *     ListNode(int x) : val(x), next(NULL) {}
  * };
  */
 class Solution {
 public:
     ListNode* getKthFromEnd(ListNode* head, int k) {
        int n = 0;
        for(auto p = head; p ; p = p->next) n++;
        if(k > n) return NULL;
        auto p = head;
        for(int i = 0; i < n - k; i++) p = p->next;
        return p;
     }
 };