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

image.png
这道题不能把它当简单题来看待

思路一:排序

原地排序,然后遍历,判断前后两个元素是否相等即可
时间复杂度O(nlogn)、较慢

  1. class Solution {
  2. public:
  3. int findRepeatNumber(vector<int>& nums) {
  4. sort(nums.begin(), nums.end());
  5. for (int i = 1; i < nums.size(); i++) {
  6. if (nums[i] == nums[i - 1])
  7. return nums[i];
  8. }
  9. return -1;
  10. }
  11. };

思路二:哈希表

使用哈希表或者集合来存储元素,当访问的元素在哈希表中出现两次,则返回该元素
时间复杂度O(n) 空间复杂度O(n),属于是时间换空间

集合:

  1. class Solution {
  2. public:
  3. int findRepeatNumber(vector<int>& nums) {
  4. unordered_set<int> us;
  5. for (auto num : nums) {
  6. if (us.count(num)) {
  7. return num;
  8. } else {
  9. us.insert(num);
  10. }
  11. }
  12. return -1; // 如果一定有重复数字的话,程序不会执行到这里
  13. }
  14. };

哈希表:

  1. class Solution {
  2. public:
  3. int findRepeatNumber(vector<int>& nums) {
  4. unordered_map<int, int> um;
  5. for (auto num : nums) {
  6. if (um.count(num)) {
  7. return num;
  8. } else {
  9. um[num]++;
  10. }
  11. }
  12. return -1; // 如果一定有重复数字的话,程序不会执行到这里
  13. }
  14. };

思路三:原地处理O(N)

修改数组

数组长度为n,并且所有数字的范围都在 0 ~ n-1 之间,假设数组完全没有重复,那么数组排序后,值为 i 的元素一定在数组中的下标一定是 i
所以我们可以遍历数组,假设nums[i] = m:

  • 如果 i == m 那么说明 m 处于排序后正确的位置,不作处理
  • 如果 i != m,将 m 与 索引为 m 处的元素进行比较:
    • 如果 nums[m] == m,说明m重复,返回m
    • 如果 nums[m] != m,则交换nums[m]与nums[i],将m置于排序后正确的位置

循环直到找到重复元素位置

  1. class Solution {
  2. public:
  3. int findRepeatNumber(vector<int>& nums) {
  4. // O(n) O(1)
  5. for (int i = 0; i < nums.size(); i++) {
  6. while (nums[i] != i) {
  7. int index = nums[i];
  8. if (nums[index] == index)
  9. return index;
  10. // swap
  11. nums[i] = nums[index];
  12. nums[index] = index;
  13. }
  14. }
  15. return nums[0];
  16. }
  17. };

虽然两层循环,但是每个数字最多两次交换即可达到正确位置,时间复杂度为O(n),空间复杂度为O(1),但会修改原数组

二分查找,不修改原数组

将题目修改为:
image.png
数组中共有 n+1 个数字,数字的范围为 1~n 那么必然会有数字重复,在不修改原数组的情况下,找出重复数字,可以使用二分查找
以题目中的例子分析:
数组长度为8,那么数字的范围为1~7,
用中间的数字4将1~7分为两段,1~4 和 5~7,然后统计1~4这四个数字在数组中出现的次数,出现了5次,那么1~4中定有重复数字;
用2将1~4分为两段,1~2和3~4,3、4在数组中共出现3次,那么 3、4中有重复数字
用将3、4分为两部分,3出现两次,返回3
用此方法无法得到所有的重复数字

  1. // 统计范围内数字出现的次数
  2. int countRange(vector<int>& v, int begin, int end) {
  3. int count = 0;
  4. for (const auto & num : v ) {
  5. if (num >= begin && num <= end)
  6. count++;
  7. }
  8. return count;
  9. }
  10. int findDuplication(vector<int>& v) {
  11. int n = v.size();
  12. if (n == 0)
  13. return -1;
  14. int start = 1, end = n - 1; // 数组中数字的范围为 1 ~ n-1
  15. while (start <= end) {
  16. int mid = start + ((end - start) >> 1);
  17. // 统计左半部分
  18. int count = countRange(v, start, mid);
  19. if (start == end) {
  20. if (count > 1)
  21. return start;
  22. else
  23. break;
  24. }
  25. if (count > mid - start + 1) // 数字左边有重复
  26. end = mid;
  27. else // 数字右边有重复
  28. start = mid + 1;
  29. }
  30. return -1;
  31. }

按照二分查找的思路,countRange会被调用logn次,每次需要O(n)的时间,因此时间复杂度为O(nlogn),空间复杂度为O(1)

总结

这道题思路很多,面试时要仔细审题,并认真考虑面试官需求

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

image.png

思路一:暴力查找

时间复杂度O(N^2)

思路二:二分查找

每一行进行二分查找,时间复杂度O(NlogN)

思路三:从左上角往下搜索

  1. class Solution {
  2. public:
  3. bool findNumberIn2DArray(vector<vector<int>>& matrix, int target) {
  4. if (matrix.size() == 0) return false;
  5. int n = matrix.size(), m = matrix[0].size();
  6. int i = 0, j = m - 1;
  7. while ( i < n && j >= 0) {
  8. if (matrix[i][j] == target) {
  9. return true;
  10. } else if (matrix[i][j] < target) {
  11. i++;
  12. } else if (matrix[i][j] > target) {
  13. j--;
  14. }
  15. }
  16. return false;
  17. }
  18. };

时间复杂度O(M + N)

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

image.png

思路一:暴力查找

  1. class Solution {
  2. public:
  3. int minArray(vector<int>& numbers) {
  4. int minN = INT32_MAX;
  5. for (const auto& num : numbers) {
  6. minN = min(minN, num);
  7. }
  8. return minN;
  9. }
  10. };

时间复杂度为O(N)

思路二:排序返回第一个元素

  1. class Solution {
  2. public:
  3. int minArray(vector<int>& numbers) {
  4. sort(numbers.begin(), numbers.end());
  5. return numbers[0];
  6. }
  7. };

时间复杂度为排序的复杂度,快排的话是O(NlogN)

思路三:min_element

class Solution {
public:
    int minArray(vector<int>& numbers) {
        return *min_element(numbers.begin(), numbers.end());
    }
};

思路四:二分查找💦

使用二分法将线性复杂度降为对数复杂度
二分的目的是缩小区间

设左端点为left,右端点为right,中间值为mid (左开右闭)
有序数组经过旋转后,分为两个有序数组,并且左边这个值稍大些,大概是这样的
数组 - 图5
有序数组经过旋转后,最小值的左边元素一定大于等于其右边的元素

  • 假如nums[mid] > nums[right],最小值一定出现在mid的左边,忽略左边部分
  • 假如nums[mid] < nums[right],最小值一定出现在mid的右边,忽略右边部分
  • 假如nums[mid] = nums[right],由于有重复元素的存在,不确定最小值在哪边,为了使最小值依然在区间内,收缩右端点。

当left = right时,返回nums[left]就是最小值
时间复杂度为O(logN)

class Solution {
public:
    int minArray(vector<int>& numbers) {
        int left = 0, right = numbers.size() - 1;
        while (left < right) {
            int mid = left + ((right - left) >> 1);
            if (numbers[mid] > numbers[right]) {
                left = mid + 1;
            } else if (numbers[mid] < numbers[right]) {
                right = mid;
            } else {
                right--;
            }
        }
        return numbers[left];
    }
};

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

image.png

这道题考察快速排序的划分操作

单向扫描法

class Solution {
public:
    vector<int> exchange(vector<int>& nums) {
        // 快速排序的划分 partition 单向扫描法 从右向左扫描
        int i = 0, j = nums.size() - 1;
        while (i < j) {
            if (nums[j] % 2 == 0) { // 偶数不用管
                j--;
            } else { // 奇数和i指向的元素交换
                int temp = nums[i];
                nums[i] = nums[j];
                nums[j] = temp;
                i++;
            }
        }
        return nums;
    }
};

双向扫描法

class Solution {
public:
    vector<int> exchange(vector<int>& nums) {
        // 快速排序的划分 partition 双向扫描法
        int i = 0, j = nums.size() - 1;
        while (i < j) {
            while (i < j && nums[i] % 2 != 0) i++; // 找到左数第一个偶数
            while (i < j && nums[j] % 2 == 0) j--; // 找到右数第一个奇数
            // 交换
            if (i < j) {
                int temp = nums[i];
                nums[i] = nums[j];
                nums[j] = temp;
            }
            // 这两行加不加都无所谓
            // i++;
            // j--;
        }
        return nums;
    }
};

剑指 Offer 29. 顺时针打印矩阵

image.png
这种比较抽象的问题,可以画图分析,注意循环边界
数组 - 图8

class Solution {
public:
    vector<int> spiralOrder(vector<vector<int>>& matrix) {
        if (matrix.size() == 0 || matrix[0].size() == 0) return {};
        // 记录左上角和右下角的坐标
        int m = matrix.size(), n = matrix[0].size();
        int x1 = 0, y1 = 0; // 左上角
        int x2 = m - 1, y2 = n - 1; // 右下角

        vector<int> res;
        while (x1 <= x2 && y1 <= y2) {
            // 打印第一行
            for (int i = y1; i <= y2; i++) {
                res.push_back(matrix[x1][i]);
            }
            // 打印右边一列
            for (int i = x1 + 1; i <= x2; i++) {
                res.push_back(matrix[i][y2]);
            }
            // 如果最内圈是一行或者一列就不用执行下面的了
            if (x1 < x2 && y1 < y2) {
                // 打印下面一行
                for (int i = y2 - 1; i >= y1; i--) {
                    res.push_back(matrix[x2][i]);
                }
                // 打印左面一列
                for (int i = x2 - 1; i > x1; i--) {
                    res.push_back(matrix[i][y1]);
                }
            }
            x1++;
            y1++;
            x2--;
            y2--;
        }
        return res;
    }
};
class Solution {
public:
    vector<int> spiralOrder(vector<vector<int>>& matrix) {
        if (matrix.size() == 0 || matrix[0].size() == 0) {
            return {};
        }

        int rows = matrix.size(), columns = matrix[0].size();
        vector<int> order;
        int left = 0, right = columns - 1, top = 0, bottom = rows - 1;
        while (left <= right && top <= bottom) {
            for (int column = left; column <= right; column++) {
                order.push_back(matrix[top][column]);
            }
            for (int row = top + 1; row <= bottom; row++) {
                order.push_back(matrix[row][right]);
            }
            if (left < right && top < bottom) {
                for (int column = right - 1; column > left; column--) {
                    order.push_back(matrix[bottom][column]);
                }
                for (int row = bottom; row > top; row--) {
                    order.push_back(matrix[row][left]);
                }
            }
            left++;
            right--;
            top++;
            bottom--;
        }
        return order;
    }
};

剑指 Offer 39. 数组中出现次数超过一半的数字

image.png

排序

先排序,再取中间元素,时间复杂度O(nlogn)

class Solution {
public:
    int majorityElement(vector<int>& nums) {
        sort(nums.begin(), nums.end());
        return nums[nums.size() / 2];
    }
};

哈希表

class Solution {
public:
    int majorityElement(vector<int>& nums) {
        int n = nums.size();
        unordered_map<int, int> m;
        for (auto num: nums) {
            m[num]++;
            if (m[num] > n / 2)
                return num;
        }
        return -1;
    }
};

时间复杂度O(n)
空间复杂度O(n)

Partition

目标还是寻找排序后位于中间位置的元素,这里简记为中间元素,这里可以使用快速排序的划分思想来处理。
随机选择一个数字,将比它大的放在右边,比它小的放在左边,如果调整后,该数字位于 n / 2的位置,那么该数字就是中间元素,如果位置大于n/2,说明中间元素位于左边,否则位于右边

class Solution {
public:
    int paritition(vector<int>& nums, int left, int right) { // 左闭右开
        int index = left;
        int i = left, j = right - 1;
        int pivot = nums[index];
        while (i < j) {
            while (i < j && nums[j] >= pivot)
                j--;
            if (i < j)
                nums[i++] = nums[j];
            while (i < j && nums[i] < pivot)
                i++;
            if (i < j)
                nums[j--] = nums[i];
        }
        nums[i] = pivot;
        return i;
    }
    int majorityElement(vector<int>& nums) {
        int left = 0, right = nums.size();
        // int mid = left + ((right - left) >> 1);
        int mid = right >> 1;
        int index = paritition(nums, left, right);
        while (index != mid) {
            if (index > mid) {
                right = index - 1;
                index = paritition(nums, left, right);
            } else {
                left = index + 1;
                index = paritition(nums, left, right);
            }
        }
        return nums[index];
    }
};

两两相消,剩下的就是最多的, 摩尔投票法

class Solution {
public:
    int majorityElement(vector<int>& nums) {
        int count = 0, maj;
        for (auto num : nums) {
            if (count == 0) {
                maj = num;
                count++;
            } else {
                if (num == maj)
                    count++;
                else
                    count--;
            }
        }
        return maj;
    }
};

时间复杂度O(n)
空间复杂度O(1)

剑指 Offer 40. 最小的k个数

image.png

排序

class Solution {
public:
    vector<int> getLeastNumbers(vector<int>& arr, int k) {
        sort(arr.begin(), arr.end());
        vector<int> res;
        for (int i = 0; i < k; i++) {
            res.push_back(arr[i]);
        }
        return res;
    }
};

基于partition

与剑指offer39题思路一致,可以基于nums[k]来调整,将比第k个数字大的都置于右边,小的置于左边,然后返回前k个数字

class Solution {
public:
    vector<int> getLeastNumbers(vector<int>& arr, int k) {
        if (k >= arr.size()) return arr;
        return quickSort(arr, k, 0, arr.size() - 1);
    }
private:
    vector<int> quickSort(vector<int>& arr, int k, int l, int r) {
        int i = l, j = r;
        while (i < j) {
            while (i < j && arr[j] >= arr[l]) j--;
            while (i < j && arr[i] <= arr[l]) i++;
            swap(arr[i], arr[j]);
        }
        swap(arr[i], arr[l]);
        if (i > k) return quickSort(arr, k, l, i - 1);
        if (i < k) return quickSort(arr, k, i + 1, r);
        vector<int> res;
        res.assign(arr.begin(), arr.begin() + k);
        return res;
    }
};

image.png

适合处理海量数据

(堆、优先队列)剑指 Offer 41. 数据流中的中位数

image.png
如果使用数组作为数据结构,那么不断进行插入操作比较耗时
可以使用大顶堆和小顶堆来保存元素,大顶堆保存较小的一半,小顶堆保存较大的一半,可以方便进行插入操作,并且取中位数只需要从堆顶拿就好
这里,元素总数为奇数时,让大顶堆多放一个,取中位数直接取大顶堆堆顶
总数为偶数时,取中位数取二个堆顶元素取平均值

进行插入操作时,

  • 如果两个堆元素数相同,可以先将元素插入小顶堆,然后将小顶堆堆头插入大顶堆,这样可以保证小顶堆始终是较大的一半,大顶堆始终是较小的一半
  • 如果两个堆元素数不同,大顶堆多一个,插入后两堆元素持平,可以先将元素插入大顶堆,然后将大顶堆堆头插入小顶堆,这样可以保证小顶堆始终是较大的一半,大顶堆始终是较小的一半 ```cpp class MedianFinder { public: /* initialize your data structure here. / MedianFinder() {

    } void addNum(int num) {

      if (pq1.size() == pq2.size()) {
          pq2.push(num);
          pq1.push(pq2.top());
          pq2.pop();
      } else {
          pq1.push(num);
          pq2.push(pq1.top());
          pq1.pop();
      }
    

    }

    double findMedian() {

      return pq1.size() != pq2.size() ? pq1.top() : (pq1.top() + pq2.top()) / 2.0;
    

    } priority_queue pq1; // 大顶堆,保存小的一半 priority_queue, greater> pq2; // 小顶堆,保存大的一半 };

/**

  • Your MedianFinder object will be instantiated and called as such:
  • MedianFinder* obj = new MedianFinder();
  • obj->addNum(num);
  • double param_2 = obj->findMedian(); */
    addnum的另一种写法
    ```cpp
     void addNum(int num) {
         pq1.push(num);
         pq2.push(pq1.top());
         pq1.pop();
         if (pq1.size() < pq2.size()) {
             pq1.push(pq2.top());
             pq2.pop();
         }
     }
    
    image.png

(哈希表)面试题50. 第一个只出现一次的字符

image.png

class Solution {
public:
    char firstUniqChar(string s) {
        if(s.size() == 0) return ' ';
        int hash[26] = {0};
        for (auto ch : s) {
            hash[ch - 'a']++;
        }
        for (auto ch : s) {
            if (hash[ch - 'a'] == 1)
                return ch;
        }
        return ' ';
    }
};

(分治)剑指 Offer 51. 数组中的逆序对

image.png

归并排序

class Solution {
public:
    int mergeSort(vector<int>& nums, vector<int>& tmp, int l, int r) {
        if (l >= r) {
            return 0;
        }

        int mid = (l + r) / 2;
        int inv_count = mergeSort(nums, tmp, l, mid) + mergeSort(nums, tmp, mid + 1, r);
        int i = l, j = mid + 1, pos = l;
        while (i <= mid && j <= r) {
            if (nums[i] <= nums[j]) {
                tmp[pos] = nums[i];
                ++i;
                inv_count += (j - (mid + 1));
            }
            else {
                tmp[pos] = nums[j];
                ++j;
            }
            ++pos;
        }
        for (int k = i; k <= mid; ++k) {
            tmp[pos++] = nums[k];
            inv_count += (j - (mid + 1));
        }
        for (int k = j; k <= r; ++k) {
            tmp[pos++] = nums[k];
        }
        copy(tmp.begin() + l, tmp.begin() + r + 1, nums.begin() + l);
        return inv_count;
    }

    int reversePairs(vector<int>& nums) {
        int n = nums.size();
        vector<int> tmp(n);
        return mergeSort(nums, tmp, 0, n - 1);
    }
};
class Solution {
public:
    int reversePairs(vector<int>& nums) {
        vector<int> tmp(nums.size());
        return merge(0, nums.size() - 1, nums, tmp);

    }
    int merge(int left, int right, vector<int>& nums, vector<int>& tmp) {
        if (left >= right)
            return 0;
        int mid = left + ((right - left) >> 1);
        // 递归
        int res = merge(left, mid, nums, tmp) + merge(mid + 1, right, nums, tmp);
        int i = left, j = mid + 1;
        for (int k = left; k <= right; ++k) {
            tmp[k] = nums[k];
        }
        for (int k = left; k <= right; ++k) {
            if (i == mid + 1) { // 左数组使用完毕
                nums[k] = tmp[j++];
            } else if (j == right + 1 || tmp[i] <= tmp[j]) { // 右数组使用完毕或者左元素小于右元素
                nums[k] = tmp[i++];
            } else { // 当归并操作时,如果左元素大于右元素,需要统计逆序对个数
                nums[k] = tmp[j++];
                res += mid - i + 1; // 如果当前左边元素大于右边,那么左边剩余元素也会比右边的大
                // 因此逆序数对个数增加左边剩余元素个,也就是mid - i + 1
            }
        }
        return res;
    }
};

image.png

剑指 Offer 53 - I. 在排序数组中查找数字 I

image.png

思路一:暴力查找

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int count = 0;
        for (const auto & num : nums) {
            if (num == target)
                ++count;
        }
        return count;
    }
};

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

思路二:二分查找

既然给定的数组是有序的,那么可以使用二分查找来加快查找速度
用二分查找找到的target不一定是第一个,因此需要向左向右继续查找。

先找到target再向两边搜索

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int n = nums.size();
        if (n == 0) 
            return 0;  
        int left = 0, right = n;
        while (left < right) { // 左闭右开
            int mid = left + ((right - left) >> 1);
            if (nums[mid] > target) {
                right = mid; // 右边是开区间
            } else if (nums[mid] < target) {
                left = mid + 1;
            } else { // 找到了target
                int count = 1;
                int l = mid - 1, r = mid + 1; // 向两边查找
                while (l >= 0 && nums[l--] == target) count++;
                while (r < n && nums[r++] == target) count++;
                return count;
            }
        }
        return 0;
    }
};

查找target的左右边界

class Solution {
public:
    int search(vector<int>& nums, int target) {
        if(nums.empty()) return 0;

        int l = 0, r = nums.size() - 1; //二分范围
        // 寻找下界
        while(l < r)                   
        {
            int mid = (l + r) / 2;
            if (nums[mid] >= target) r = mid; // 大于或者等于都要继续向左
            else l = mid + 1;
        }
        if (nums[r] != target) return 0;  //查找失败
        // 查找下界
        int L = r;
        l = 0, r = nums.size() - 1;     //二分范围
        while(l < r)                   //查找元素的结束位置
        {
            int mid = (l + r + 1) / 2;
            if(nums[mid] <= target ) l = mid;
            else r = mid - 1;
        }
        return r - L + 1;
    }
};

STL:

class Solution {
public:
    int search(vector<int>& nums, int target) {
        auto l = lower_bound(nums.begin(), nums.end(), target);
        auto r = upper_bound(nums.begin(), nums.end(), target); 
        return r - l;
    }
};

剑指 Offer 53 - II. 0~n-1中缺失的数字

image.png

思路一:理论减去实际

求 0 ~ n-1的和,实际的和会比理论和小一个缺少的数字

class Solution {
public:
    int missingNumber(vector<int>& nums) {
        int n = nums.size();
        int ans = n * (n + 1) / 2;
        for (const auto & num : nums) {
            ans -= num;
        }
        return ans;
    }
};

时间复杂度为O(N)

思路二:二分查找

数组是有序的,可以使用二分查找
假如某个元素的比它的索引要小了(不可能比索引大),说明在这个元素的前面缺少数字
如果该元素和索引一样大,说明在这个元素的后面缺少数字
我们只需用二分查找来找到第一个比索引小的元素即可,它的下标就是缺失的数字

class Solution {
public:
    int missingNumber(vector<int>& nums) {
        int left = 0, right = nums.size();
        while (left < right) { // 左闭右开
            int mid = left + ((right - left) >> 1);
            if (nums[mid] == mid) { // 索引与数字相等,那么左边所有元素索引都与索引相等,找右边
                left = mid + 1;
            } else {
                right = mid;
            }
        }
        return left; // 最后左右指针相遇的位置,就是第一个下标大于值的元素
    }
};

时间复杂度为O(logN)

(双指针)剑指 Offer 57. 和为s的两个数字

image.png

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

(滑动窗口)剑指 Offer 57 - II. 和为s的连续正数序列

image.png

class Solution {
public:
    vector<vector<int>> findContinuousSequence(int target) {
        if (target <= 2)
            return {};
        int left = 1, right = 2;
        vector<vector<int>> res;
        list<int> window;
        window.push_back(1);
        window.push_back(2);
        int windowSum = 3;
        int mid = (target + 1) / 2;
        while (right <= mid) {
            if (windowSum == target)
                res.push_back(vector<int>(window.begin(), window.end()));
            while (windowSum > target && left < mid) {
                window.pop_front();
                windowSum -= left++;
                if (windowSum == target)
                res.push_back(vector<int>(window.begin(), window.end()));
            }
            windowSum += ++right;
            window.push_back(right);

        }
        return res;
    }
};

用链表保存窗口元素,再拷贝开销比较大,不如直接构建

class Solution {
public:
    vector<vector<int>> findContinuousSequence(int target) {
        if (target <= 2)
            return {};
        int left = 1, right = 2;
        vector<vector<int>> res;
        vector<int> window;
        int windowSum = 3;
        int mid = (target + 1) / 2;
        while (right <= mid) {
            while (windowSum > target && left < mid) {
                windowSum -= left++;
            }
            if (windowSum == target) {
                window.clear();
                for (int i = left; i <= right; ++i) {
                    window.push_back(i);
                }
                res.push_back(window);
            }
            windowSum += ++right;         
        }
        return res;
    }
};

剑指 Offer 59 - I. 滑动窗口的最大值

image.png

优先队列

class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        if (nums.size() == 0) return {};
        priority_queue<pair<int, int>> pq;
        for (int i = 0; i < k; ++i) {
            pq.emplace(nums[i], i);
        }
        vector<int> res;
        res.push_back(pq.top().first);
        for (int i = k; i < nums.size(); ++i) {
            pq.emplace(nums[i], i);
            while (pq.top().second <= i - k) {
                pq.pop();
            }
            res.push_back(pq.top().first);
        }
        return res;
    }
};

剑指 Offer 59 - II. 队列的最大值

image.png
维护一个单调双端队列,来记录队列的最大值
这个双端队列由队尾到队头为递增
当push操作时,如果一个元素比双端队列队尾的某些元素大的话,这些元素对于队列的最大值就没有影响了,可以将其移除
数组 - 图23
pop_front操作时,如果删除的元素正好是双端队列的队头,那么需要将其从队头删除,否则不做操作

class MaxQueue {
public:
    MaxQueue() {}

    int max_value() {
        if (dq.empty()) return -1;
        return dq.front();
    }

    void push_back(int value) {
        while (!dq.empty() && dq.back() < value) dq.pop_back();
        dq.push_back(value);
        q.push(value);
    }

    int pop_front() {
        if (q.empty()) return -1;
        int ans = q.front();
        if (ans == dq.front()) dq.pop_front();
        q.pop();
        return ans;
    }
    queue<int> q;
    deque<int> dq;
};

剑指 Offer 61. 扑克牌中的顺子

image.png
先对排排序,然后统计大小王的数量,然后统计凑成顺子需要补多少张排,如果大小王能够补上,那么返回true

class Solution {
public:
    bool isStraight(vector<int>& nums) {
        sort(nums.begin(), nums.end());
        // 统计0的个数
        int nZero = 0;
        for (int i = 0; i < nums.size() && nums[i] == 0; ++i) {
            ++nZero;
        }
        int small = nZero, big = small + 1;
        int gap = 0;
        while (big < nums.size()) {
            if (nums[small] == nums[big]) // 有对子,不是顺子
                return false;
            gap += nums[big] - nums[small] - 1;
            small = big++;
        }
        return nZero >= gap ? true: false;
    }
};

巧妙的写法

class Solution {
public:
    bool isStraight(vector<int>& nums) {
        int joker = 0;
        sort(nums.begin(), nums.end()); // 数组排序
        for(int i = 0; i < 4; i++) {
            if(nums[i] == 0) joker++; // 统计大小王数量
            else if(nums[i] == nums[i + 1]) return false; // 若有重复,提前返回 false
        }
        return nums[4] - nums[joker] < 5; // 最大牌 - 最小牌 < 5 则可构成顺子
    }
};