九章算法第八课,数据结构&设计迭代器&优先队列

575. 字符串解码
这个题用递归比较好做,需要注意的是如何找到前括号匹配的后括号(并不是找到第一个后括号就是对的),还要注意index移动的问题。

class Solution {
public:
    string expressionExpand(string &s) {
        if (s=="") return s;
        
        string res="";
        int i=0;
        int number;
        while (i<s.length()) {
            if (isch(s[i])) {
                res=res+s[i];
                i++;
            }
            else if (isnum(s[i])) {
                number=s[i]-'0';
                //处理多位数字的情况
                while (i+1<s.length() && isnum(s[i+1])) {
                    number=number*10+(s[i+1]-'0');
                    i++;
                }
                i++;
            }
            else if (s[i]=='[') {
                int j=i+1;
                int cnt=1;
                while (cnt!=0) {
                    if (s[j]=='[') cnt++;
                    else if (s[j]==']') cnt--;
                    j++;
                }
                string tmp=s.substr(i+1,j-i-2);
                string expand=expressionExpand(tmp);
                for (int k=1;k<=number;k++) res.append(expand);
                i=j;
            }
        }
        return res;
    }
    bool isch(char c) {
        return c>='a'&&c<='z' || c>='A'&&c<='Z';
    }
    bool isnum(char c) {
        return c>='0'&&c<='9';
    }
};

如果不使用递归而使用栈,就采用顺序处理的算法。只要找到一个重复字段的结束符号[,就向前找到应该展开的字符,将其展开后重新压栈。这样当字符串处理到末尾,所有压缩字符串都将被展开。

class Solution {
public:
    string expressionExpand(string &s) {
        //使用两个栈,一个放字符,一个放数字
        stack<char> character; 
        stack<int> copynum;
        
        int i=0;
        int number;
        while (i<s.length()) {
            if (isch(s[i])) {
                character.push(s[i]);
            }
            else if (isnum(s[i])) {
                number=s[i]-'0';
                while (i+1<s.length() && isnum(s[i+1])) {
                    number=number*10+(s[i+1]-'0');
                    i++;
                }
                //数字的位置用特殊字符‘ ’代替,实际数字放在copynumber里
                character.push(' ');
                copynum.push(number);
            }
            else if (s[i]=='[') {
            }
            else if (s[i]==']') {
                //找到应该展开的字段
                //为了得到正确顺序的字符串,使用两个栈进行操作。
                string newstr=popstack(character);
                int n=copynum.top();
                //展开字符串
                for (int k=0;k<n;k++)
                    pushstack(character,newstr);
                copynum.pop();    
                
            }
            i++;
        }
        
        return popstack(character);
    }
    string popstack(stack<char> &st) {
        stack<char> tmp;
        while (!st.empty() && st.top()!=' ') {
            tmp.push(st.top());
            st.pop();
        }
        st.pop();
        string res="";
        while (!tmp.empty()) {
            res=res+tmp.top();
            tmp.pop();
        }
        return res;
    }
    void pushstack(stack<char> &st, string &s) {
        for (int i=0;i<s.length();i++)
            st.push(s[i]);
    }
    bool isch(char c) {
        return c>='a'&&c<='z' || c>='A'&&c<='Z';
    }
    bool isnum(char c) {
        return c>='0'&&c<='9';
    }
};

40. 用栈实现队列
一开始的想法是用两个栈是s1,s2,每次pop()和top()的时候先把s1倒进s2,top()和pop()之后再倒回s1去。但是这样运行速度很低。其实不必每次都倒,将s1看作最新push进来的数字段,s2看作可以出队的栈,只要s2不为空,可以一直用s2进行出队操作,而s1屯着push进来的数字,当s2为空(不够出了)的时候再一起倒进来就行了。

class MyQueue {
public: 
    stack<int> s1;
    stack<int> s2;

    MyQueue() {
    }
    void push(int element) {
        s1.push(element);
    }
    int pop() {
        //如果s2没了,就把s1刚存进来的数字全部倒到s2
       if (s2.empty()) {
           while (!s1.empty()) {
               s2.push(s1.top());
               s1.pop();
           }
       }
       
       int a=s2.top();
       s2.pop();
       return a;
    }
    int top() {
        //如果s2没了,就把s1刚存进来的数字全部倒到s2
        if (s2.empty()) {
           while (!s1.empty()) {
               s2.push(s1.top());
               s1.pop();
           }
       }
       
       return s2.top();
    }
};

494. 双队列实现栈
我的思路是弄两个队列,每次要出栈的时候就从一个倒腾到另一个,留下最后那个队位元素就是栈顶元素。方法通过了但也是比较慢,好的思路是从push开始就把队列里的顺序做成栈的顺序。把新来的元素放在空队队首,再把后面的从另一个队列倒过去(倒过去之前已经在上次push时排好栈的顺序了)。

class Stack {
public:
    queue<int> q1;
    queue<int> q2;
    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();
            }
        }
    }
    void pop() {
       if (q1.empty()) q2.pop();
       else q1.pop();
    }

    int top() {
       if (q1.empty()) return q2.front();
       else return q1.front(); 
    }

    bool isEmpty() {
        return q1.empty()&&q2.empty();
    }
};

528. 摊平嵌套的列表
接口说明

 /* class NestedInteger {
 *   public:
 *     // Return true if this NestedInteger holds a single integer,
 *     // rather than a nested list.
 *     bool isInteger() const;
 *
 *     // Return the single integer that this NestedInteger holds,
 *     // if it holds a single integer
 *     // The result is undefined if this NestedInteger holds a nested list
 *     int getInteger() const;
 *
 *     // Return the nested list that this NestedInteger holds,
 *     // if it holds a nested list
 *     // The result is undefined if this NestedInteger holds a single integer
 *     const vector<NestedInteger> &getList() const;
 * };
 */

使用栈解决嵌套问题。由于整个list提前已知,因此直接倒序放进栈里。因为每次只需要找一个值,因此思路是不管后面的嵌套如何,每次只针对栈顶的元素(给出的list的头)进行操作。只要栈顶元素不是整数,就不停的解析栈顶的list直到成为一个整数,弹出。
本来我为了好理解,把这个过程写在next()函数里,而hasnext()里只用了!s.empty()来判断如下代码但是会报错。因为这样只能确定s里面有东西,但是不知道能不能解析出实际的答案来(面对[[],[]]这样的输入),而且我尝试使用了辅助判断的方法也不行,接口里的代码没给出导致不知道该怎么改。

/错误解法!!!!但是方便理解思路
class NestedIterator {
public:
    stack<NestedInteger> s;
    NestedIterator(vector<NestedInteger> &nestedList) {
        for (int i=nestedList.size()-1;i>=0;i--) {
            s.push(nestedList[i]);
        }
    }
    int next() {
        int res; 
        //栈顶是整数就可以直接弹出,否则需要迭代解析,直到把下一个数解析出来
        while (!s.top().isInteger()) {
            vector<NestedInteger> tmp=s.top().getList();
            s.pop();
            for (int i=tmp.size()-1;i>=0;i--) {
                s.push(tmp[i]);
            }   
        }
        res=s.top().getInteger();
        s.pop();
        return res;
        
    }
    bool hasNext() {
        //加上这个判断依然不可以
        //while (!s.empty() && !s.top().isInteger() 
        //        && s.top().getList().size()==0) {
        //  s.pop();
        //}
        return !s.empty();
    }
};

正确代码是将解析放到hasnext()里,只有真正解析出来了下一个数了才能算有下一个数。

class NestedIterator {
public:
    stack<NestedInteger> s;
    NestedIterator(vector<NestedInteger> &nestedList) {
        for (int i=nestedList.size()-1;i>=0;i--) {
            s.push(nestedList[i]);
        }
    }
    int next() {
        int res=s.top().getInteger();
        s.pop();
        return res;
        
    }
    bool hasNext() {
        while (!s.empty()) {
            //直到栈顶解析出能直接pop的整数,才算有下一个数
            //因为如果一个list不为空,一定可以通过多次迭代解析成为整数
            if (s.top().isInteger()) return true;
            vector<NestedInteger> tmp=s.top().getList();
            s.pop();
            for (int i=tmp.size()-1;i>=0;i--) {
                s.push(tmp[i]);
            }   
        }
        return false;
    }
};

86. 二叉查找树迭代器

class BSTIterator {
public:
    stack<TreeNode*> s;
    BSTIterator(TreeNode * root) {
       while (root!=NULL) {
           s.push(root);
           root=root->left;
       }
    }
    bool hasNext() {
        return !(s.empty());
    }
    TreeNode * next() {
       TreeNode* res=s.top();
       TreeNode* node=s.top()->right;
       s.pop();
       while (node!=NULL) {
           s.push(node);
           node=node->left;
       }
       return res;
    }
};

540. 左旋右旋迭代器

class ZigzagIterator {
public:
    int *p1,*p2;
    int *l1,*l2;
    bool flag;
    ZigzagIterator(vector<int>& v1, vector<int>& v2) {
        p1=&v1[0];
        p2=&v2[0];
        
        //注意终止的指针设置在end()的位置
        l1=&v1[v1.size()];
        l2=&v2[v2.size()];
        flag=0;
    }

    int next() {
        int res;
        if (flag && p2!=l2) {
            res=*p2;
            p2=p2+1;
        }
        else if (!flag && p1!=l1){
            res=*p1;
            p1=p1+1;
        }//处理不一样长的情况
        else if (p1!=l1) {
            res=*p1;
            p1=p1+1;
        }
        else if (p2!=l2) {
            res=*p2;
            p2=p2+1;
        }
        flag=!flag;
        return res;
    }

    bool hasNext() {
         return p1!=l1 ||p2!=l2;
    }
};

541. 左旋右旋迭代器 II
和上一题相同,把指针用vector存贮,k是当前要出数的vector序号。注意k要循环。确认是否还有能输出的数需要循环整个vector,因此把找下一个要出的数的代码写在了hasnext里。
另外,注意vecotor<int*>p 是一个数组p,里面存着很多int指针;而vector<int> *p是一个指针,指向一个int数组。

class ZigzagIterator2 {
public:
    vector<int*> p;
    vector<int*> end;
    int k;
    int n;
    ZigzagIterator2(vector<vector<int>>& vecs) {
        n=vecs.size()-1;
        k=0;
        for (int i=0;i<=n;i++) {
            p.push_back(&vecs[i][0]);
            end.push_back(&vecs[i][vecs[i].size()]);
        }
    }

    int next() {
        int res=*p[k]++;
        k=(k+1)%(n+1);
        return res;
    }
    bool hasNext() {
        int count=0;
        while (true) {
            if (p[k]!=end[k]) return true;
            else {count++; k=(k+1)%(n+1);}
            if (count==n+1) return false;
        }
    }
};

601. 摊平二维向量
这个题有很多种不同的做法。1.在初始化的时候直接展开,存入另外一个数据结构中;
2.初始化的时候用this把数组保存下来;
3.用指针;
我分别写了后两个:

class Vector2D {
public:
    int i,j;
    vector<int> end;
    vector<vector<int>> vec2d;
    Vector2D(vector<vector<int>>& vec2d) {
        this->vec2d=vec2d;
        i=0;j=0;
    }
    int next() {
        int res=vec2d[i][j];
        j++;
        if (j==vec2d[i].size()) {
            i++;
            j=0;
        }
        return res;
    }

    bool hasNext() {
        //处理[[],[]]的情况
        while (i<vec2d.size() && vec2d[i].size()==0) i++;
        //每个vector长短不一,不能按照二维数组的方法判断结束。
        if (i<vec2d.size()) return true;
        if (i==vec2d.size()-1) return j<vec2d[vec2d.size()-1].size();
        return false;
    }
};
class Vector2D {
public:
   //区别于上面的vector<int*>,这里的*begin,*end都是指向二维数组每行的行首,指向的是一个数组。
    vector<int> *begin,*end;
    int pos;
    Vector2D(vector<vector<int>>& vec2d) {
        begin=&vec2d[0];
        end=&vec2d[vec2d.size()];
    }
    int next() {
         int res=(*begin)[pos];
         pos++;
         return res;
    }

    bool hasNext() {
        while (begin != end && pos == (*begin).size())
            begin++, pos = 0;
        return begin != end;
    }
};

栈的习题

?12. 带最小值操作的栈
min只返回当前栈内的最小值,因此利用两个栈,一个是正常的,一个是顶部的值代表当前栈内最小值的栈(这个栈只有top()有意义,其他内容只是作为备用)。

class MinStack {
public:
    stack<int> st,minst;
    MinStack() {
    }

    void push(int number) {
        st.push(number);
        if (minst.empty() || number<=minst.top()) {
            minst.push(number);
        }
    }

    int pop() {
        int res=st.top();
        if (minst.top()==res) minst.pop();
        st.pop();
        return res;
    }

    int min() {
        return minst.top();
    }
};

?122. 直方图最大矩形覆盖
最维护一个最长不下降栈。http://www.cnblogs.com/lichen782/p/leetcode_Largest_Rectangle_in_Histogram.html

class Solution {
public:
    int largestRectangleArea(vector<int> &height) {
       if(height.size() == 0) return 0;
        int res = 0;
        height.push_back(0);  // 手动设置一个右边界,
        
        //维护一个单调不下降栈
        stack<int> s;
        for (int i=0;i<height.size();i++) {
            if (s.empty() || !s.empty() && height[i]>=height[s.top()] )
                s.push(i);
            else {//第一个下降点作为右边界(不包含)
                
                //向左找到左边界(第一个比右边界大的数)计算面积
                while (!s.empty() && height[s.top()]>height[i]) {
                   int index=s.top(); s.pop(); //左边界的高度即最矮高度
                   //如果s为空,说明栈底前面、栈底后面到当前i之前的矩形都比栈底高,最长的长度是i,高度是栈底高
                   //如果不为空,找到下一个比当前矩形矮的矩形,他们之间的矩形都是比当前高的,高度即短板是当前栈底高
                   int width=s.empty()?i:i-s.top()-1;//长度
                   res=max(res,height[index]*width);
               } 
               s.push(i);
            }
        }
       return res;
    }
};

510. 最大矩形
把每一行转化为不同高度的直方图,再求每一行的最大面积。

class Solution {
public:
    int maximalRectangle(vector<vector<bool>> &matrix) {
        if (matrix.size()==0) return 0;
        
        vector<vector<int>> height(matrix.size(),vector<int>(matrix[0].size()+1,0));
        
        //每一行做成一个矩形直方图
        //竖着的直方图越长,他末尾一个位置的数字越大,说明此直方图越高
        //例子转化成的直方图矩阵是
        //[1,1,0,0,1,0]
        //[0,2,0,0,2,0]
        //[0,0,1,1,3,0]
        //[0,0,2,2,4,0]
        //[0,0,0,0,5,0]
        //和直方图的算法一样,每行最后一个数添加一个0,以便首尾处理实际的最后一个数
        for (int i=0;i<matrix.size();i++)
            for (int j=0;j<matrix[0].size()+1;j++) 
                if (matrix[i][j]) {
                    height[i][j]= i==0?1:height[i-1][j]+1;
                }
                
        int res=0;
        
        //计算每一行的最大面积
        for (int i=0;i<matrix.size();i++) {
            int area=findArea(height[i]);
            res=max(area,res);
        }
        
        return res;
    }
    int findArea(vector<int> &height) {
        stack<int> s;
        int area=0;
        for (int i=0;i<height.size();i++) {
            if (s.empty() || height[i]>=height[s.top()]) s.push(i);
            else {
                while (!s.empty() && height[s.top()]>height[i]) {
                    int index=s.top();s.pop();
                    int h=height[index];
                    int w= s.empty()?i:i-s.top()-1;
                    area=max(area,w*h);
                }
                s.push(i);
            }
        }
        return area;
    }
};

126. 最大树
最开始的思想是用分治做,会超时:

class Solution {
public:
    TreeNode * maxTree(vector<int> &A) {
        return helper(A,0,A.size()-1);
    }
    TreeNode* helper(vector<int> &A,int start, int end) {
        if (start>end) return NULL;
        int mid=findMax(A,start,end);
        TreeNode* root=new TreeNode(A[mid]);
        if (mid!=start) root->left=helper(A,start,mid-1);
        if (mid!=end) root->right=helper(A,mid+1,end);
        return root;
    }
    int findMax(vector<int> &A,int start, int end) {
        int index=start; int num=A[start];
        for (int i=start+1;i<=end;i++) {
            if (A[i]>num) {
                num=A[i];
                index=i;
            }
        }
        return index;
    }
};

使用单调栈的思想,建立一个单调递减的栈,存放每一个右子树。新来的一个数就不断的和栈顶的值比较,判断是应该接在右子树还是变成根。

class Solution {
public:
    TreeNode * maxTree(vector<int> &A) {
    
    stack<TreeNode*> s;//维护一个根的直单调递减的栈,栈内的树只有左子树
    for (int i=0;i<A.size();i++) {
        TreeNode* tmp=new TreeNode(A[i]);
        //如果没有元素,往里加
        if (s.empty()) {s.push(tmp);continue;}
        
        //如果当前数比栈顶树根大,说明还没找到最大的值,栈顶的树还能再往上添根
        //把这个树取出来添上当前的树做根
        //如[3,2,1,5] 2进入的时候已经和3连好了,所以5先连2再连3不影响
        while (!s.empty() && A[i]>s.top()->val) {
            tmp->left=s.top();
            s.pop();
        }
        
        //栈内如果还有树,此树的根值一定大于当前数,当前数是这个根的右孩子,连上
        if (!s.empty()) {
            s.top()->right=tmp;
        }
        s.push(tmp);
    }
    //由于是根单调递减的栈,栈底的那个是根
    while (s.size()>1) s.pop();
    return s.top();
    }
};

129. 重哈希
一开始我的算法是用set记录下来原有的节点,同时把哈希表内的节点删除,这样就可以完成在原哈希表中的替换。但是和原来的顺序可能不一样,所以还是需要新建一个哈希表。

class Solution {
public:
    vector<ListNode*> rehashing(vector<ListNode*> hashTable) {
        int n=hashTable.size();
        vector<ListNode*> hashTable2(2*n,NULL);
        for (int i=0;i<n;i++) {
            if (hashTable[i]==NULL) continue;
            ListNode* head=hashTable[i];
            while (head!=NULL) {
                addnode(head->val,2*n,hashTable2);
                head=head->next;
            }
        }
        return hashTable2;
    }
    void addnode(int a,int n, vector<ListNode*> &hashTable) {
        int pos=((a%n)+n)%n;
        ListNode* tmp=new ListNode(a);
        if (hashTable[pos]==NULL) hashTable[pos]=tmp;
        else {
            ListNode* head=hashTable[pos];
            while (head->next!=NULL) head=head->next;
            head->next=tmp;
        }
    }
};

?134. LRU缓存策略
考虑到要不断改变存储数据,这个题用了list结构作为cache,另外建立了一个map,map的值是指向key的迭代器(相当于指针)。题解中使用了list的相关函数emplace_front(),spilce(粘贴的位置,原串,原串要复制的位置)等。还要注意指针的写法。

#include<list>
class LRUCache {
public:
    //缓存中的实际内容,key-value对
    list<pair<int,int>> cache;
    //位置,key-指向实际内容的指针,使用map是为了O(1)时间查找
    unordered_map<int,list<pair<int,int>>::iterator> pos;
    int capacity;
    LRUCache(int capacity) {
        this->capacity=capacity;
    }
    int get(int key) {
        if (pos.find(key)==pos.end()) return -1;
        cache.splice(cache.begin(), cache, pos[key]);//把cache从pos[key]的位置的内容放到cache.begin()的位置。
        pos[key] = cache.begin();//修改指针
        return (cache.front().second);
    }
    void set(int key, int value) {
        if (pos.find(key)==pos.end()) {
            //list最前面增加新的kv对
            cache.emplace_front(make_pair(key,value));
            pos[key]=cache.begin();//记录位置
            if (pos.size()>capacity){
                pos.erase(cache.back().first);//删掉位置对照表中相应的字段
                cache.pop_back();//删掉缓存中时间最远的那个   
            }
        }else {
            pos[key]->second = value;//pos[key]指向的是键值为key的pair
            //pos[key]是一个迭代器/指针,所以用->而不是.
            cache.splice(cache.begin(), cache, pos[key]);//把cache从pos[key]的位置插入到cache.begin()的位置。
            pos[key] = cache.begin();//修改指针
        }
    }
};

138. 子数组之和
使用哈希表加快查找subsum的速度。

class Solution {
public:
    vector<int> subarraySum(vector<int> &nums) {
        if (nums.size()==0) return vector<int>();
        
        vector<int> res;
        unordered_map<int,int> subSum;
        int sum=0;
        subSum[0]=-1;
        for (int i=0;i<nums.size();i++) {
            sum+=nums[i];
            if (subSum.find(sum)==subSum.end()) {
                subSum[sum]=i;
            }else{
                res.push_back(subSum[sum]+1);
                res.push_back(i);
                return res;
            }
        }
    }
};

105. 复制带随机指针的链表
可以用哈希表记录旧链表节点和新链表节点的关系。但是最快的方法是把1->2->3->NULL扩建成1->1'->2->2'->3->3'->NULL;

171. 乱序字符串
对于每个字符串,都有一个按顺序排列的标准串(注意O(n)时间排列字符串的方法)。把标准串作为key,set里存入其不同的变化,再把size大于1的set打印出来即可。注意由于字符串有可能重复,应使用unordered_multiset。

class Solution {
public:
    vector<string> anagrams(vector<string> &strs) {
        unordered_map<string,unordered_multiset<string>> hash;
        vector<string> res;
        for (int i=0;i<strs.size();i++) {
            string sortedstr=sort(strs[i]);
            if (hash.find(sortedstr)==hash.end()) {
                unordered_multiset<string> tmp;
                tmp.insert(strs[i]);
                hash[sortedstr]=tmp;
            }else hash[sortedstr].insert(strs[i]);
        }
        
        for (auto it=hash.begin();it!=hash.end();it++) {
            if (it->second.size()>1) {
                for (auto s:it->second)
                    res.push_back(s);
            }
        }
        return res;
    }
    string sort(string s) {
        vector<int> tmp(26,0);
        for (int i=0;i<s.length();i++) {
            tmp[s[i]-'a']++;
        }
        
        string news="";
        for (int i=0;i<26;i++) {
            for (int j=0;j<tmp[i];j++)
               news=news+(char)(i+'a');
        }
        return news;
    }
};

124. 最长连续序列
牛逼算法,应该掌握。

class Solution {
public:
    int longestConsecutive(vector<int> &num) {
        if (num.size()<=1) return num.size();
        
        unordered_set<int> hash(num.begin(),num.end());
        int res=1;
        for (int i=0;i<num.size();i++) {
            if (hash.find(num[i])!=hash.end()) {
                hash.erase(num[i]);
                int pre=num[i]-1;
                int next=num[i]+1;
                while (hash.find(pre)!=hash.end()) {
                    hash.erase(pre);
                    pre--;
                }
                while (hash.find(next)!=hash.end()) {
                    hash.erase(next);
                    next++;
                }
                res=max(res,next-pre-1);
            }
        }
        return res;
    }
};

130. 堆化
有两种顺序:1.从上往下的顺序处理节点,从下往上安置节点:时间复杂度O(nlogn),因为只有第一个节点不用处理和安置。
2.从下往上处理节点,从上往下安置节点:时间复杂度O(n),因为叶子结点均不用处理和安置。

class Solution {
public:
    void heapify(vector<int> &A) {
        if (A.size()<=1) return ;
        //从上往下处理节点
        for (int i=0;i<A.size();i++){
            int j=i;
            //从当前位置往上安排节点
            while ((j-1)/2>=0 && A[j]<A[(j-1)/2]) {
                swap(A[j],A[(j-1)/2]);
                j=(j-1)/2;
            }
       }
    }
};
class Solution {
public:
    void heapify(vector<int> &A) {
        if (A.size()<=1) return ;
        //从下往上处理节点
        for (int i=A.size()/2;i>=0;i--){
            int j=i;
            //从当前位置往下安排节点
            while (j<A.size()) {
                //找到三个点中最小的往下换
                int smallest=j;
                if (j*2+1<A.size() &&A[j*2+1]<A[smallest]) smallest=2*j+1;
                if (j*2+2<A.size() &&A[j*2+2]<A[smallest]) smallest=2*j+2;
                if (j==smallest) break;//已经是最小的了,不用换了。
                swap(A[j],A[smallest]);
                j=smallest;
            }
       }
    }
};

4. 丑数 II
思路不是找出丑数,而是构造丑数。从第一个丑数开始,分别和2,3,5相乘形成新的丑数。但是不一定后乘的数顺序一定排在后面,所以这里使用优先队列,保证每次队首出来的数一定是最小的。
c++里的正常定义的优先队列是一个大根堆,如果想定义成小根堆写法是priority_queue<int,vector<int>,greater<int>>。
哈希表用来去重。

class Solution {
public:
    int nthUglyNumber(int n) {
        priority_queue<long,vector<long>,greater<long>> order;
        unordered_set<long> hash;
        order.push(1);
        hash.insert(1);         
        
        int count=0;
        long number;
        while (count<n) {
            number=order.top();
            order.pop();
            count++;
            if (hash.find(number*2)==hash.end()) {
                hash.insert(number*2);
                order.push(number*2);
            }
            if (hash.find(number*3)==hash.end()) {
                hash.insert(number*3);
                order.push(number*3);
            }
            if (hash.find(number*5)==hash.end()) {
                hash.insert(number*5);
                order.push(number*5);
            }
        }
        return number;
    }    
};

545. 前K大数 II
我的方法是用了一个大根堆,记录所有的值。每次出前k个值。
但是由于实现的结构只有添加和取前k个值两个功能,不在前k个值里的数不需要记录,所以可以用小根堆来做。队首是k个数里的最小值,每次push进来的数和最小值比,然后或添加、或删除。

class Solution {
public:
    priority_queue<int,vector<int>,greater<int>> p;
    int k;
    Solution(int k) {
       this->k=k;
    }

    void add(int num) {
       if (p.size<k) p.push(num);
       else if (num>p.top()) {
           p.pop();
           p.push(num);
       }
    }
    vector<int> topk() {
       /* read p's element and sort */
    }
};

但是这样做,在读取队列的时候还是需要先全部出队,再全部放回去。最好的办法是用multiset,有序且支持重复。

class Solution {
public:
    multiset<int> s;
    int k;
    Solution(int k) {
       this->k=k;
    }
    void add(int num) {
       s.insert(num);
       if (s.size()>k) s.erase(s.begin());
    }
    vector<int> topk() {
       vector<int> res(s.begin(),s.end());
       reverse(res.begin(),res.end());
       return res;
    }
};

613. 优秀成绩

class Solution {
public:
    map<int, double> highFive(vector<Record>& results) {
        map<int,priority_queue<int>> hash;
        map<int,double> res;
        
        for (int i=0;i<results.size();i++) {
            if (hash.find(results[i].id) == hash.end()) {
                hash[results[i].id]=priority_queue<int>();
                res[results[i].id]=0.0;
            }
            hash[results[i].id].push(results[i].score);
        }
        
        for (auto it=hash.begin();it!=hash.end();it++) {
            priority_queue<int> scores=it->second;
            int n=0;
            double count=0;
            while (!scores.empty() && n<5){
                count+=scores.top();
                scores.pop();
                n++;
            }
            res[it->first]=(double)(count/n);
        }
        return res;
    }
};

612. K个最近的点
一开始是想直接重定义sort中的比较函数,但是由于sort用的是不稳定排序,因此输出的顺序和原顺序会不一样。如果想一样的话,需要用到优先队列,且要重定义优先队列
还有一处需要注意的地方,当重写sort中的cmp函数时,若写在类里面,应加上static,相当于一个全局函数,只初始化一次,因此其不可访问类中的成员变量,需要在类外写一个全局变量供其访问。

//都在类的外面
Point global_origin;
bool operator<(Point a, Point b) {//返回true时,说明a的优先级低于b
                                  //若定义的时候使用了greater<T> 则应定义operator>
                                    //此时返回true,说明a的优先级高于b
        int d1=(a.x-global_origin.x)*(a.x-global_origin.x)+(a.y-global_origin.y)*(a.y-global_origin.y);
        int d2=(b.x-global_origin.x)*(b.x-global_origin.x)+(b.y-global_origin.y)*(b.y-global_origin.y);
        int diff=d1-d2;
        if (diff==0)
            diff=a.x-b.x;
        if (diff==0)
            diff=a.y-b.y;
        return diff<0;
}
class Solution {
public:
    vector<Point> kClosest(vector<Point> &points, Point &origin, int k) {
        global_origin=origin;
        priority_queue<Point> p;
        //若定义大根堆priority_queue<Point,vector<Point>,greater<Point>> p;
        //则应重定义oparetor>
       
       for (int i=0;i<points.size();i++) {
           Point tmp = points[i];
           p.push(tmp);
           if (p.size()>k) p.pop();
       }
       
       vector<Point> res;
       while (!p.empty()) {
           res.push_back(p.top());
           p.pop();
       }
       reverse(res.begin(),res.end());
       return res;
    }
};

486. 合并k个排序数组
要求时间复杂度nlogk,说明是每k个数一组进行进行调整。建立一个优先队列,先把k个开头放进去,每出一个数,看看是从哪个数组进来的,就从那个数组再push一个进来。由于出队的时候需要知道来自哪个数组,所以要重新定义一个结构,也要重载运算符。重载运算符有三种方法,给的是struct,改成class也一样。

class point {
public:
    int x,y,val;
    point(int _x,int _y,int _val):x(_x),y(_y),val(_val) {};
    friend bool operator < (class point a,class point b) {
        return a.val>b.val;
    }
};

class Solution {
public:
    vector<int> mergekSortedArrays(vector<vector<int>> &arrays) {

       priority_queue<point> pq;
       map<int,pair<int,int>> pos;
       
       for (int i=0;i<arrays.size();i++) {
           if (arrays[i].size()==0) continue; //处理空列表的情况
           point tmp(i,0,arrays[i][0]);
           pq.push(tmp);
       }
       
       vector<int> res;
       while (!pq.empty()) {
           point tmp=pq.top();
           pq.pop();
           res.push_back(tmp.val);
           if (tmp.y+1<arrays[tmp.x].size()) {
               pq.push(point(tmp.x,tmp.y+1,arrays[tmp.x][tmp.y+1]));
           }
       }
       return res;
    }
};

104. 合并k个排序链表
这个题有两种做法, 一个是分治方法两两归并,还有一个是优先队列的方法。
优先队列方法比数组好的地方在于不用用坐标记录后继,但是不好的地方在于也要重载运算符,而且运算数是两个指针,重载运算符的参数不可以有指针,只能使用引用。但是我的引用会报错。找了一个写的对的。

struct cmp {
    bool operator() (ListNode *a, ListNode *b) {
        return a->val > b->val;
    }
}; 
class Solution {
public:
    ListNode *mergeKLists(vector<ListNode *> &lists) {
       priority_queue<ListNode*> pq;
       
       for (int i=0;i<lists.size();i++) {
           if (lists[i]==NULL) continue;
           pq.push(lists[i]);
       }
       
       ListNode* dummy=new ListNode(0);
       ListNode* head=dummy;
       
       while (!pq.empty()) {
           ListNode* tmp=pq.top();
           pq.pop();
           if (tmp->next!=NULL) pq.push(tmp->next);
           
           head->next=tmp;
           head=head->next;
       }
       return dummy->next;
    }
};

81. 数据流中位数
巨牛逼的算法,用一个大根堆维护排序数组的左边,小根堆维护排序数组的右边。分别向左堆、右堆添加元素(右堆的元素是从左堆选出的最大的),这样左堆的根节点就是中位数。

class Solution {
public:
    /**
     * @param nums: A list of integers
     * @return: the median of numbers
     */
    vector<int> medianII(vector<int> &nums) {
        priority_queue<int> maxheap;//数组左半部分
        priority_queue<int,vector<int>,greater<int>> minheap;//数组右半部分
        
        vector<int> res;
        bool flag=0;
        for (int i=0;i<nums.size();i++) {
            res.push_back(insert(nums[i],maxheap,minheap,flag));
            flag=!flag;
        }
        return res;
    }
   int insert(int num, priority_queue<int> &maxheap,
                 priority_queue<int,vector<int>,greater<int>> &minheap,
                 bool flag) {
        maxheap.push(num);
        if (flag) {
            int tmp=maxheap.top();
            maxheap.pop();
            minheap.push(tmp);
        }
        if (minheap.empty()) return maxheap.top();
        while (maxheap.top()>minheap.top()) {
            int a=maxheap.top(); maxheap.pop();
            int b=minheap.top(); minheap.pop();
            maxheap.push(b);
            minheap.push(a);  
        }
        return maxheap.top();
    }
    
};

544. 前K大数

class Solution {
public:
    vector<int> topk(vector<int> &nums, int k) {
        priority_queue<int,vector<int>,greater<int>> pq;
        
        for (int i=0;i<nums.size();i++) {
            pq.push(nums[i]);
            if (pq.size()>k) pq.pop();
        }
        
        vector<int> res;
        while (!pq.empty()) {
            res.push_back(pq.top());
            pq.pop();
        }
        reverse(res.begin(),res.end());
        return res;
    }
};

401. 排序矩阵中的从小到大第k个数
从右上角开始,每次把左边和下面的数放进优先队列里。出栈出到第k个就是所求元素。
和合并k个排序数组一样,这个也需要记录坐标。另外为了避免重复访问,还需要visit数组记录访问情况。

class point {
public:
    int x,y,val;
    point(int _x,int _y,int _val):x(_x),y(_y),val(_val) {};
    friend bool operator > (class point a,class point b) {
        return a.val>b.val;
    }
};
class Solution {
public:
    int kthSmallest(vector<vector<int>> &matrix, int k) {
        if (matrix.size()==0) return -1;
        
        priority_queue<point,vector<point>,greater<point>> pq;
        pq.push(point(0,0,matrix[0][0]));
        
        vector<vector<bool>> hash(matrix.size(),vector<bool>(matrix[0].size(),true));
        hash[0][0]=false;
        
        int count=0;
        point num=point(0,0,0);
        while (count<k) {
            num=pq.top();
            pq.pop();
            count++;
            if (num.x+1<matrix.size() && hash[num.x+1][num.y]) {
                    hash[num.x+1][num.y]=false;
                    pq.push(point(num.x+1,num.y,matrix[num.x+1][num.y]));
            }
            
            if (num.y+1<matrix[0].size() && hash[num.x][num.y+1]) {
                    hash[num.x][num.y+1]=false;
                    pq.push(point(num.x,num.y+1,matrix[num.x][num.y+1]));
            }
        }
        return num.val;
    }
};

131. 大楼轮廓
??算法:https://www.jianshu.com/p/36dcb18525a1,把每座大楼看作两堵墙,按位置的升序和高度的降序排列。画的时候有一个cur数组记录当前已经画了但还没结束的左墙,每次碰到更高的左墙或匹配的右墙开始画。题解的精髓在于用一个优先队列存贮cur,保证cur是有序的,匹配的时候也是从高到低进行匹配和绘画。但是由于有时候碰到了右墙,其对应的左墙不是最高的,说明被其他墙覆盖了,需要直接从cur中删除左墙,而pq不能做到这一点。题解使用了multiset完成这个工作,multiset在一些条件下可以代替pq。

class Wall {
public:
    int index;
    int height;
    Wall(int _index,int _height): index(_index),height(_height) {}
};
class Solution {
public:
    static bool cmp (Wall a,Wall b) {
        if (a.index!=b.index) return a.index<b.index;
        else return a.height>b.height;
    }
    
    vector<vector<int>> buildingOutline(vector<vector<int>> &buildings) {
        if (buildings.size()<=1) return buildings;
        
        vector<Wall> walls;
        
        for (int i=0;i<buildings.size();i++) {
           walls.push_back(Wall(buildings[i][0],buildings[i][2]));//左墙
           walls.push_back(Wall(buildings[i][1],-buildings[i][2]));//右墙
        }
        sort(walls.begin(),walls.end(),cmp);
        
        multiset<int> cur;//存放目前还没画完的左墙,
        //不存右墙,如果有右墙就直接把对应的左墙删掉,说明画完了
        //使用multiset的好处:1.和优先队列一样有顺序,且可以计数(处理重复高度的情况 *1)
        //2.可以删除中间的元素
        
        
       //开始画 
       int prewall =0;
       vector<vector<int>> res;
       for (int i=0;i<walls.size();i++) {
           if (cur.empty() && walls[i].height>0) { //前面的房子都画完了,重新画左墙
               cur.insert(walls[i].height);
               prewall=walls[i].index;
               continue;
           }
           //左边:只有在当前左墙比前一座左墙高的时候才画楼
           //否则只要把左墙存起来即可
           if (walls[i].height>0) {
               if (walls[i].height>*cur.rbegin() &walls[i].index>prewall) {
                   vector<int> tmp;
                   tmp.push_back(prewall);
                   tmp.push_back(walls[i].index);
                   tmp.push_back(*cur.rbegin());
                   res.push_back(tmp);
                   
                   prewall=walls[i].index;
               }
               cur.insert(walls[i].height);//是左墙就进队
            }
            else {if (walls[i].height==-*cur.rbegin() &&
                     cur.count(-walls[i].height)==1 && 
                     //*1 如果后面还有一座一样高的大楼 也不画
                     walls[i].index>prewall) {
                         vector<int> tmp;
                         tmp.push_back(prewall);
                         tmp.push_back(walls[i].index);
                         tmp.push_back(*cur.rbegin());
                         res.push_back(tmp);
                         
                         prewall=walls[i].index;
                     }
                 cur.erase(cur.lower_bound(-walls[i].height));
            }
       }
        return res;
    }
};


 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值