Bootstrap

堆栈相关应用算法

栈的压入、弹出序列

题目描述:输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。(注意:这两个序列的长度是相等的)

算法思想: 将第一个序列数据先压栈,如果最上面的数据与第二个需要弹出的数据相同,进行判断下一个栈顶数据是否为需要弹出的数据,如果是继续进行这样的判断,如果不是就跳出循环将第一个序列的数据压栈。最终如果弹出数列全都遍历完了,说明是压栈序列的弹出序列。

class Solution {
public:
    bool IsPopOrder(vector<int> pushV,vector<int> popV) {
        int pushVsize = pushV.size();
        stack<int> stacktemp;
        int j = 0;
        for(int i = 0; i < pushVsize; i++) {
            stacktemp.push(pushV[i]);
            while(stacktemp.top() == popV[j]) {
                stacktemp.pop();
                j++;
                if(stacktemp.empty()) break;
            }
        }
        return j==pushVsize;
    }
};

包含min函数的栈

定义栈的数据结构,请在该类型中实现一个能够得到栈最小元素的min函数。在该栈中调用push、pop、top、min的时间复杂度都是o(1)。

定义一个辅助栈,用于存放数据栈每次入栈时的最小元素,辅助栈的栈顶元素是数据栈内最小值。

class Solution {
public:
    void push(int value)
    {
        data.push(value);
        
        if(data.empty() || value<smin.top())
            smin.push(value);
        else
            smin.push(smin.top());
    }
     
    void pop()
    {
        if(!data.empty() && !smin.empty())
        {
            data.pop();
            smin.pop();
        }
    }
     
    int top()
    {
        return data.top();
    }
     
    int min()
    {
        return smin.top();
    }
private:
    stack<int> data;
    stack<int> smin;
};

两个栈实现队列

class Solution
{
public:
    void push(int node) {
        stack1.push(node);  //实现入队
    }
                   //出队思想:
    int pop() {    //队列出队的底层实现:先将元素依次从1栈弹出,再依次压入至2栈,
        int a;     //再 每次调用pop()出队列,返回一个元素。
        if(stack2.empty()){
            while(!stack1.empty()){
                a = stack1.top();
                stack2.push(a);
                stack1.pop();
            }
        }
        a = stack2.top();
        stack2.pop();
        return a;
    }
    private:
    stack<int> stack1;
    stack<int> stack2;
};

两个队列实现栈

两个队列q1, q2。不管是插入还是弹出,保证总有一个队列为空。那么:

  • 队列入栈:将元素插入空队列,同时将非空队列的元素依次插入到空队列。此时之前的非空队列变为空队列,空队列变为非空队列。

  • 队列出栈:将非空队列的队首弹出即可。

class QueueStack {
public:
	// 保证每个时刻都有一个队列为空队列
 
	// Push element x onto stack.
	void push(int x) {
		if(q1.empty())
		{
			q1.push(x);
 
			while(!q2.empty())
			{
				q1.push(q2.front());
				q2.pop();
			}
		}
		else
		{
			q2.push(x);
 
			while(!q1.empty())
			{
				q2.push(q1.front());
				q1.pop();
			}
		}
	}
 
	// Removes the element on top of the stack.
	void pop() {
		if(q1.empty())
		{
			q2.pop();
		}
		else
		{
			q1.pop();
		}
	}
 
	// Get the top element.
	int top() {
		if(q1.empty())
		{
			return q2.front();
		}
		else
		{
			return q1.front();
		}
	}
 
	// Return whether the stack is empty.
	bool empty() {
		return (q1.empty() && q2.empty());
	}
 
	queue<int> q1;
	queue<int> q2;
};

小顶堆的实现

void AdjustDown(int arr[], int i, int n)
{
    int j = i * 2 + 1;//子节点 
    while (j<n)
    {
        if (j+1<n && arr[j] > arr[j + 1])//子节点中找较小的
        {
            j++;
        }
        if (arr[i] < arr[j])
        {
            break;
        }
        swap(arr[i],arr[j]);
        i = j;
        j = i * 2 + 1;
    }
}
void MakeHeap(int arr[], int n)//建堆
{
    int i = 0;
    for (i = n / 2 - 1; i >= 0; i--)//((n-1)*2)+1 =n/2-1
    {
        AdjustDown(arr, i, n);
    } 
}

void HeapSort(int arr[],int len)
{
    int i = 0;
    for (i = n / 2 - 1; i >= 0; i--)//((n-1)*2)+1 =n/2-1
    {
        AdjustDown(arr, i, n);
    }
    for (i = len - 1; i >= 0; i--)
    {
        swap(arr[i], arr[0]);
        AdjustDown(arr, 0, i);
    }

}

大顶堆实现

// 递归方式构建大根堆(len是arr的长度,index是第一个非叶子节点的下标)
void adjust(vector<int> &arr, int index,int len)
{
    int left = 2*index + 1; // index的左子节点
    int right = 2*index + 2;// index的右子节点
 
    int maxIdx = index;
    if(left<len && arr[left] > arr[maxIdx])     maxIdx = left;
    if(right<len && arr[right] > arr[maxIdx])     maxIdx = right;
 
    if(maxIdx != index)
    {
        swap(arr[maxIdx], arr[index]);
        adjust(arr, maxIdx, len);
    }
 
} 
// 堆排序
void heapSort(vector<int> &arr, int size)
{
    // 构建大根堆(从最后一个非叶子节点向上)
    for(int i=size/2 - 1; i >= 0; i--)
    {
        adjust(arr, i ,size);
    }
 
    // 调整大根堆
    for(int i = size - 1; i >= 1; i--)
    {
        swap(arr[0], arr[i]);           // 将当前最大的放置到数组末尾
        adjust(arr,0, i-1);              // 将未完成排序的部分继续进行堆排序
    }
}


定义 : priority_queue<Type, Container, Functional>

其中Type 为数据类型,Container为保存数据的容器,Functional 为元素比较方式。

默认 : 大顶堆,比较方式默认用operator< ,所以如果把后面2个参数缺省的话,优先队列就是大顶堆(降序),队头元素最大

push:插入元素到队尾
pop: 弹出队头元素
top:访问队头元素

一般使用 :

// 大顶堆
priority_queue<int, vector<int>, less<int> > q;

// 小顶堆
priority_queue<int, vector<int>, greater<int> > q;

有序矩阵中第K小的元素

给定一个 n x n 矩阵,其中每行和每列元素均按升序排序,找到矩阵中第k小的元素。 请注意,它是排序后的第k小元素,而不是第k个元素。

示例:
matrix = [
[ 1, 5, 9],
[10, 11, 13],
[12, 13, 15]
],
k = 8, 返回 13。

说明: 你可以假设k 的值永远是有效的, 1 ≤ k ≤ n2 。
1、遍历矩阵: 用一个优先级队列保存数组,然后遍历矩阵,维护优先级队列大小为k,这样遍历完,直接取出优先级队列top即可

class Solution {
public:
    int kthSmallest(vector<vector<int>>& matrix, int k) {
        int n=matrix.size();
        priority_queue<int> q;
        for(int i=0;i<n;i++){
            for(int j=0;j<n;j++){
                q.push(matrix[i][j]);
                //默认大顶堆,弹出的是最大的,遍历完剩余的是最小的K个。
                if(q.size()>k) q.pop();
            }

        }
        return q.top();
    }
};

2、二分法:二分法一般常用的有两种做法,一个是常规的有序数组,然后索引二分查找目标元素,如lower_bound、upper_bound等,另外一种是无序数组,但是知道了上下限之后,通过二分上下限查找目标元素,这一题采用的就是这种方法。

  • lower_bound(起始地址,结束地址,要查找的数值) ,函数用于在指定区域内查找不小于(大于等于)目标值的第一个元素。

  • upper_bound(起始地址,结束地址,要查找的数值) 返回的是 第一个大于待查找数值 出现的位置。


class Solution {
public:
    int kthSmallest(vector<vector<int>>& matrix, int k) {
        int n=matrix.size(),l=matrix[0][0],r=matrix[n-1][n-1]+1;
        int mid=l;
        while(l<r){
            mid=l+(r-l)/2;
            int cnt=0,cnt2=0;
            for(int i=0;i<n;i++){
                auto &v=matrix[i];
                cnt+=lower_bound(v.begin(),v.end(),mid)-v.begin();
                cnt2+=upper_bound(v.begin(),v.end(),mid)-v.begin();
            }
            if(cnt<k&&cnt2>=k) return mid;
            if(cnt<k) l=mid+1;
            else r=mid;
        }
        return mid;
    }
};

字符串的top k统计

题目描述:给定一个字符串数组,再给定整数k,请返回出现次数前k名的字符串和对应的次数。返回的答案应该按字符串出现频率由高到低排序。如果不同的字符串有相同出现频率,按字典序排序。

  • 对于两个字符串,大小关系取决于两个字符串从左到右第一个不同字符的 ASCII 值的大小关系。比如"ah1x"小于"ahb",“231”<”32“

  • 字符仅包含数字和字母

算法分析:

  • 对于这道题,首先可以用哈希表在遍历的过程中得到每个字符串的个数,然后是根据哈希表的值进行排序的问题。

  • 采用C++中优先队列priority_queue构建自定义比较方式的小顶堆,我们写结构体cmp重载()运算符,比较方式为哈希表的值,当值相同时,比较哈希表的键。

  • 遍历完哈希表中的所有键值对后,优先队列中存放的就是前k大的字符串及次数值。题目要求存放在vector中,我们遍历优先队列,逐个取出放入vector中,由于是小顶堆,先出来的是前k大中最小的,所以根据题目要求,需要将vector的元素逆序转换一下。

struct cmp{
    bool operator()(pair<string,int>&p1,pair<string,int>&p2){
        return p1.second>p2.second||(p1.second==p2.second&&p1.first<p2.first);
    }
};
class Solution {
public:
    /**
     * return topK string
     * @param strings string字符串vector strings
     * @param k int整型 the k
     * @return string字符  串vector<vector<>>
     */
    vector<vector<string> > topKstrings(vector<string>& strings, int k) {
        // write code here
        vector<vector<string>>res;
        if(k>strings.size()||strings.size()==0||k<=0) return res;
        unordered_map<string,int>temp;
        priority_queue<pair<string,int>,vector<pair<string, int>>,cmp>pq;
        for(int i=0;i<strings.size();i++)
            temp[strings[i]]++;
        for(auto iter=temp.begin();iter!=temp.end();iter++)
        {
            if(pq.size()<k)
                pq.push(make_pair(iter->first,iter->second));
            else if(iter->second>pq.top().second||(iter->second==pq.top().second&&iter->first<pq.top().first))
                    {
                    //小顶堆,弹出的是最小的,遍历完剩余的是最大的K个。
                        pq.pop(); //把最小的pop掉
                        pq.push(make_pair(iter->first,iter->second));
                    }
        }
        while(!pq.empty())
        {
            res.push_back(vector<string>{pq.top().first,to_string(pq.top().second)});
            pq.pop();
        }
        reverse(res.begin(),res.end());
        return res;
    }
};
;