【leetcode刷题之路】剑指Offer(2)——栈与队列+模拟+查找算法

文章目录

      • 4 栈与队列
        • 4.1 【栈】剑指 Offer 09 - 用两个栈实现队列
        • 4.2 【栈】剑指 Offer 30 - 包含min函数的栈
        • 4.3 【队列】剑指 Offer 59 - I - 滑动窗口的最大值
        • 4.4 【队列】剑指 Offer 59 - II - 队列的最大值
      • 5 模拟
        • 5.1 【模拟】剑指 Offer 29 - 顺时针打印矩阵
        • 5.2 【模拟】剑指 Offer 31 - 栈的压入、弹出序列
      • 6 查找算法
        • 6.1 【哈希表】【排序】剑指 Offer 03 - 数组中重复的数字
        • 6.2 【查找】剑指 Offer 53 - I - 在排序数组中查找数字 I
        • 6.3 【查找】剑指 Offer 53 - II - 0~n-1中缺失的数字
        • 6.4 【二分查找】剑指 Offer 04 - 二维数组中的查找
        • 6.5 【二分查找】剑指 Offer 11 - 旋转数组的最小数字
        • 6.6 【哈希表】剑指 Offer 50 - 第一个只出现一次的字符

4 栈与队列

4.1 【栈】剑指 Offer 09 - 用两个栈实现队列

https://leetcode.cn/problems/yong-liang-ge-zhan-shi-xian-dui-lie-lcof/

定义两个栈s1和s2,以s1为主s2为辅构造队列,插入操作均在s1中进行,删除操作如下:
(1)现把s1中的元素依次出栈,再依次入栈s2;
(2)此时s2的栈顶元素即为队列要删除的头部元素,弹出并返回;
(3)s2中剩下的元素依次出栈,再依次入栈s1.

class CQueue {
public:
    stack<int> s1,s2;
    CQueue() {

    }
    
    void appendTail(int value) {
        s1.push(value);
    }
    
    int deleteHead() {
        if(s1.empty()) return -1;
        while(!s1.empty())
        {
            int tmp = s1.top();
            s1.pop();
            s2.push(tmp);
        }
        int ans = s2.top();
        s2.pop();
        while(!s2.empty())
        {
            int tmp = s2.top();
            s2.pop();
            s1.push(tmp);
        }
        return ans;
    }
};

/**
 * Your CQueue object will be instantiated and called as such:
 * CQueue* obj = new CQueue();
 * obj->appendTail(value);
 * int param_2 = obj->deleteHead();
 */

4.2 【栈】剑指 Offer 30 - 包含min函数的栈

https://leetcode.cn/problems/bao-han-minhan-shu-de-zhan-lcof/

  定义两个栈s1和s2,s1是用来存放原始元素的栈,s2是用来记录每次s1入栈操作后此时的最小元素,这里要注意的时候,当每次s1进行pop操作时,s2也要进行pop操作,如果此时s2不为空的话,要把当前的最小值stack_min赋值为s2的栈顶元素,因为s2中出栈的元素有可能就是之前s1中的最小值,但是刚好此时s1出栈的元素就是这个最小值,所以这个最小值就消失了,不应该在s2中。

class MinStack {
public:
    /** initialize your data structure here. */
    stack<int> s1,s2;
    int stack_min;
    MinStack() {

    }
    
    void push(int x) {
        if(s1.empty())
        {
            stack_min = x;
            s1.push(x);
            s2.push(x);
        }
        else
        {
            s1.push(x);
            if(x < stack_min)
            {
                s2.push(x);
                stack_min = x;
            }
            else s2.push(stack_min); 
        }
    }
    
    void pop() {
        s1.pop();
        s2.pop();
        if(!s2.empty()) stack_min = s2.top();
    }
    
    int top() {
        return s1.top();
    }
    
    int min() {
        return s2.top();
    }
};

/**
 * Your MinStack object will be instantiated and called as such:
 * MinStack* obj = new MinStack();
 * obj->push(x);
 * obj->pop();
 * int param_3 = obj->top();
 * int param_4 = obj->min();
 */

4.3 【队列】剑指 Offer 59 - I - 滑动窗口的最大值

https://leetcode.cn/problems/hua-dong-chuang-kou-de-zui-da-zhi-lcof/

  利用优先队列。优先队列默认是大根堆,所以top元素一定是队列中最大的那个,但是这里要注意滑动窗口的判定条件,当目前的最大值和目前遍历到的元素在同一个窗口内时,最大值是不会变的,一旦最大值离开窗口就会发生改变,所以通过计算最大值与当前遍历元素直接的距离作为是否更新最大值的判断条件。

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

4.4 【队列】剑指 Offer 59 - II - 队列的最大值

https://leetcode.cn/problems/dui-lie-de-zui-da-zhi-lcof/

  利用两个队列,普通的队列存放原始元素,双端队列存放目前的两个最大值(降序),保证每次双端队列的第一个元素都是当前的最大值,所以这里主要考虑如果此时pop的就是最大值应该怎么办?

  • 这里保证在双端队列中存放一个次大值就行了,如果此时pop的是最大值,那么双端队列也pop一次,但是一定要保证双端队列内是降序排列;
  • 如果遇到目前push的元素比双端队列的back要大,直接把双端队列内所有比目前push元素小的全部pop即可,因为只要目前的这个数还在队列中,它就是队列目前的最大值或者次大值。
class MaxQueue {
public:
    queue<int> q;
    deque<int> max_q;
    MaxQueue() {

    }
    
    int max_value() {
        if(!max_q.empty()) return max_q.front();
        else return -1;
    }
    
    void push_back(int value) {
        q.push(value);
        if(max_q.empty()) max_q.push_front(value);
        else
        {
            while(!max_q.empty() && value > max_q.back())
                max_q.pop_back();
            max_q.push_back(value);
        }
    }
    
    int pop_front() {
        if(!q.empty())
        {
            int ans = q.front();
            q.pop();
            if(!max_q.empty() && ans == max_q.front()) max_q.pop_front();
            return ans;
        }
        else return -1;
    }
};

/**
 * Your MaxQueue object will be instantiated and called as such:
 * MaxQueue* obj = new MaxQueue();
 * int param_1 = obj->max_value();
 * obj->push_back(value);
 * int param_3 = obj->pop_front();
 */

5 模拟

5.1 【模拟】剑指 Offer 29 - 顺时针打印矩阵

https://leetcode.cn/problems/shun-shi-zhen-da-yin-ju-zhen-lcof/

  直接模拟题目的输出轨迹即可,基本上是按照(0,1),(1,0),(0,-1),(-1,0)这四条轨迹,然后要判断一下方向改变的终止条件即可,除了下标越界以外,如果当前访问的元素之前已经访问过了,方向也要发生变化。

class Solution {
private:
    
public:
    vector<int> spiralOrder(vector<vector<int>>& matrix) {
        if(matrix.size()==0 || matrix[0].size()==0) return {};

        int dirt[4][2] = {{0,1},{1,0},{0,-1},{-1,0}};
        int n = matrix.size() , m = matrix[0].size();
        int count = n * m;
        vector<int> ans(count);
        vector<vector<bool>> visit(n , vector<bool>(m));

        int row = 0 , col = 0 , dirt_num = 0;
        for(int i=0;i<count;i++)
        {
            ans[i] = matrix[row][col];
            visit[row][col] = true;
            int new_row = row + dirt[dirt_num][0] , new_col = col + dirt[dirt_num][1];
            if(new_row < 0 || new_col < 0 || new_row >= n || new_col >= m || visit[new_row][new_col])
            {
                dirt_num = (dirt_num + 1) % 4;
            }
            row += dirt[dirt_num][0];
            col += dirt[dirt_num][1];
        }
        return ans;
    }
};

5.2 【模拟】剑指 Offer 31 - 栈的压入、弹出序列

https://leetcode.cn/problems/zhan-de-ya-ru-dan-chu-xu-lie-lcof/

  模拟栈的压入过程即可,但是每次压入之后都要将top元素与popped中对应的元素进行比较,来模拟是否是合理的出栈序列。

class Solution {
public:
    bool validateStackSequences(vector<int>& pushed, vector<int>& popped) {
        stack<int> s;
        for(int i=0,j=0;i<pushed.size();i++)
        {
            s.push(pushed[i]);
            while(!s.empty() && s.top()==popped[j])
            {
                s.pop();
                j++;
            }
        }
        return s.empty();
    }
};

6 查找算法

6.1 【哈希表】【排序】剑指 Offer 03 - 数组中重复的数字

https://leetcode.cn/problems/shu-zu-zhong-zhong-fu-de-shu-zi-lcof/

(1)第一种解法:直接对数组进行排序,然后挨个遍历看看有没有相同的元素。
(2)第二中解法:利用哈希表,这种做法不用对数组事先排序,只需统计目前遍历到的数字是否在哈希表中出现过即可。

//第一种解法
class Solution {
public:
    int findRepeatNumber(vector<int>& nums) {
        sort(nums.begin(),nums.end());
        for(int i=0;i<nums.size()-1;i++)
        {
            if(nums[i]==nums[i+1]) return nums[i];
        }
        return -1;
    }
};

//第二种解法
class Solution {
public:
    int findRepeatNumber(vector<int>& nums) {
        unordered_map<int,int> hash_table;
        int n = nums.size();
        for(int i=0;i<n;i++)
        {
            if(hash_table.count(nums[i])) return nums[i];
            else hash_table[nums[i]] = 1;
        }
        return -1;
    }
};

6.2 【查找】剑指 Offer 53 - I - 在排序数组中查找数字 I

https://leetcode.cn/problems/zai-pai-xu-shu-zu-zhong-cha-zhao-shu-zi-lcof/

  因为原数组是非递减数组,所以直接顺序遍历即可,当目前遍历到的元素大于target时可以提前终止循环。

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int count = 0;
        for(int i=0;i<nums.size();i++)
        {
            if(nums[i] > target) return count;
            if(nums[i] == target) count++;
        }
        return count;
    }
};

6.3 【查找】剑指 Offer 53 - II - 0~n-1中缺失的数字

https://leetcode.cn/problems/que-shi-de-shu-zi-lcof/

(1)第一种解法:常规思路,直接遍历即可,如果当前元素和下标对不上,就是缺失元素,这里要单独判断一下第一个元素和最后第一个元素缺失的情况。
(2)第二种解法:二分查找,只不过判断条件是看mid的元素值是否等于mid,如果等于的话,说明缺失值在数组右边,如果不等于说明在数组左边。

//第一种解法
class Solution {
public:
    int missingNumber(vector<int>& nums) {
        if(nums[0]==1) return 0;
        for(int i=0;i<nums.size();i++)
        {
            if(nums[i]!=i) return i;
        }
        return nums.size();
    }
};

//第二种解法
class Solution {
public:
    int missingNumber(vector<int>& nums) {
        int left = 0 , right = nums.size() - 1;
        while(left<=right)
        {
            int mid = (left + right) / 2;
            if(nums[mid]==mid) left = mid + 1;
            else right = mid - 1;
        }
        return left;
    }
};

6.4 【二分查找】剑指 Offer 04 - 二维数组中的查找

https://leetcode.cn/problems/er-wei-shu-zu-zhong-de-cha-zhao-lcof/

  因为数组中的每行元素都是非递减顺序,所以直接对每一行进行二分查找即可。

class Solution {
public:
    bool findNumberIn2DArray(vector<vector<int>>& matrix, int target) {
        for(int i=0;i<matrix.size();i++)
        {
            int left = 0 , right = matrix[i].size() - 1;
            while(left<=right)
            {
                int mid = (left + right) / 2;
                if(matrix[i][mid]==target) return true;
                else if(matrix[i][mid] < target) left = mid + 1;
                else right = mid - 1;
            }
        }
        return false;
    }
};

6.5 【二分查找】剑指 Offer 11 - 旋转数组的最小数字

https://leetcode.cn/problems/xuan-zhuan-shu-zu-de-zui-xiao-shu-zi-lcof/

  此题对传统的二分查找进行一下变形即可,可以发现,我们要找的最小元素到数组末尾的元素直接的所有元素,都是数组末尾的元素小,而剩下的元素都比数组某位的元素大,所以依据这个来进行二分查找。

  • 如果区间中间元素mid大于数组末尾元素right,说明最小值在mid右边,更新left为mid+1;
  • 如果区间中间元素mid小于数组末尾元素right,说明最小值在mid左边,更新right为mid;
  • 如果区间中间元素mid等于数组末尾元素right,说明可能是重复元素,right–;
class Solution {
public:
    int minArray(vector<int>& numbers) {
        int n = numbers.size();
        int left = 0 , right = n - 1;
        while(left<=right)
        {
            int mid = left + (right - left) / 2;
            if(numbers[mid] > numbers[right]) left = mid + 1;
            else if(numbers[mid] < numbers[right]) right = mid;
            else right--; 
        }
        return numbers[left];
    }
};

6.6 【哈希表】剑指 Offer 50 - 第一个只出现一次的字符

https://leetcode.cn/problems/di-yi-ge-zhi-chu-xian-yi-ci-de-zi-fu-lcof/

  使用哈希表解决,如果存在出现两次及以上的字符,就把它在哈希表中的值赋为-1,否则为1,最后找哈希表中第一个为1的字符即可,不过这里由于哈希表是逆序存放的,所以在遍历原始字符串时采用的倒序遍历,这样哈希表内存放的顺序就是正序出现的第一个只出现一次的字符了。

class Solution {
public:
    char firstUniqChar(string s) {
        unordered_map<char,int> hash_table;
        for(int i=s.size()-1;i>=0;i--)
        {
            if(hash_table.count(s[i])) hash_table[s[i]] = -1;
            else hash_table[s[i]] = 1;
        }
        char ans = ' ';
        for(auto x:hash_table)
        {
            if(x.second==1)
            {
                ans = x.first;
                break;
            }
        }
        return ans;
    }
};

你可能感兴趣的:(#,力扣,算法,leetcode,c++,数据挖掘)