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

61 阅读3分钟

图片.png

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

剑指 Offer 57. 和为s的两个数字

思路

(双指针) O(n)

具体过程如下:

1、初始化两个指针 i , j 分别指向数组nums 的左右两端。

2、循环搜索,当双指针相遇时跳出:

  • 计算s = nums[i] + nums[j]
  • 如果s < target,则指针i 向右移动;
  • 如果s > target,则指针j 向左移动;
  • 如果s == target,则返回{nums[i],nums[j]}

3、最后返回空数组,代表没有答案。

时间复杂度分析: O(n)。

c++代码

 class Solution {
 public:
     vector<int> twoSum(vector<int>& nums, int target) {
         int i = 0, j = nums.size() - 1;
         while(i < j){
             if(nums[i] + nums[j] < target) i++;
             else if(nums[i] + nums[j] > target) j--;
             else return {nums[i],nums[j]};
         }
         return {};
     }
 };

(哈希) O(n)

具体过程如下:

  • 1、定义一个hash表,用来记录nums数组中每个数出现的次数

  • 2、遍历整个nums数组,对于当前遍历的数字nums[i],我们先在hash表中查找target - nums[i]是否存在。

    • 如果存在:直接返回 {target - nums[i], nums[i]}
    • 否则,将nums[i]加入hash表中 。

时间复杂度分析: O(n)。

c++代码

 class Solution {
 public:
     vector<int> twoSum(vector<int>& nums, int target) {
         vector<int> res;
         unordered_set<int> hash;
         for(int x : nums){
             if(hash.count(target - x)){
                 res = vector<int>{target - x, x};
                 return res;
             }
             hash.insert(x);
         }
         return res;
     }
 };
 ​

剑指 Offer 57 - II. 和为s的连续正数序列

思路

(双指针) O(n)

我们定义两个指针ij指针,将区间[i,j]看成滑动窗口,那么两个指针就分别表示滑动窗口的开始位置和结束位置,同时我们再维护一个sum变量用来存贮区间[j,i]连续序列的的和。通过不断调整两个指针来使得滑动窗口维护的区间和sum等于target,并记录答案。

过程如下:

  • 1、我们定义两个指针ij,初始化i = 1, j = 1,让两个指针都指向连续正数序列的开头,i指针用于扩展窗口,j指针用于收缩窗口。
  • 2、枚举整个正数序列,当sum < target时,我们可以不断增加j使得滑动窗口向右扩展,同时sum += j,即j++, sum += j
  • 3、当sum == target并且滑动窗口的长度大于1时,将滑动窗口中的数记录到res中。
  • 4、我们记录完一次合法的方案以后,就可以向右收缩滑动窗口,进行下一次合法方案的查找,即sum -= i, i++

整个序列只需要枚举到target/2,即target的一半。

时间复杂度分析: O(n)。

c++代码

 class Solution {
 public:
     vector<vector<int>> findContinuousSequence(int target) {
         vector<vector<int>> res;
         int sum = 1;
         for(int i = 1, j = 1; i <= target / 2; i++){
             while(sum < target)  j++, sum += j;
             if(sum == target && j - i + 1 > 1){
                 vector<int> path;
                 for(int k = i; k <= j; k++) path.push_back(k);
                 res.push_back(path);
             }
             sum -= i;
         }
         return res;
     }
 };

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

思路

对于样例 "the sky is blue"分两步操作:

  • 1、将字符串中的每个单词逆序,样例输入变为: "eht yks si eulb"
  • 2、将整个字符串逆序,样例输入变为:"blue is sky the"

图示样例过程:

1、将ij指针指向字符串的开头,并让j指针跳过字符串s单词的前导空格,指向单词的首非空字符。

图片.png

2、再让ij指针指向同一个位置,并让j指针跳过若干个非空字符指向单词后的第一个空格。

图片.png

3、将ij指针之间的单词翻转。

图片.png

4、我们将翻转后的单词重新赋值给字符串s的前若干个字符(长度为单词的大小),重复上述过程。

5、最后将整个字符串翻转。

细节:

  • 赋值之后要给每个单词后补个空格作为单词之间的分隔符。

  • 最后要将字符串s之后多余的空格擦除。

    image-20210628151606051

图片.png

  • 具体实现细节看代码。

时间复杂度分析: 整个字符串总共扫描两遍,所以时间复杂度是 O(n)。且每次翻转一个字符串时,可以用两个指针分别从两端往中间扫描,每次交换两个指针对应的字符,所以额外空间的复杂度是 O(1)。

c++代码

 class Solution {
 public:
     string reverseWords(string s) {
         int k = 0;
         for(int i = 0; i < s.size(); i++)
         {
             int j = i;
             while(j < s.size() && s[j] == ' ') j++; //j指针跳过单词的前导空格
             if(j == s.size()) break; //这里break是为了保证最后不会s[k++] = ' ',避免行首多加一个空格
             i = j;
             while(j < s.size() && s[j] != ' ') j++;
             reverse(s.begin() + i, s.begin() + j);
             if(k)  s[k++] = ' '; //补个空格
             while( i < j) s[k++] = s[i++];
         }
         s.erase(s.begin() + k,s.end());//擦除多余的空格
         reverse(s.begin(),s.end());
         return s;
     }
 };