LeetCode 探索主页 高级算法 C++ 解法

文章目录

  • LeetCode 探索主页 高级算法 C++ 解法
    • 数组和字符串
      • 1.Product of Array Except Self
      • 2.螺旋矩阵
      • 3.四数相加 II
      • 4.盛最多水的容器
      • 5.生命游戏 -- todo
      • 6.第一个缺失的正数
      • 7.最长连续序列 -- important
      • 8.寻找重复数
      • 9.基本计算器 II
      • 10.滑动窗口最大值 --- important
      • 11.最小覆盖子串 -- important
    • 链表
      • 1.合并K个元素的有序链表
      • 2.链表排序
      • 3.复制带随机指针的链表 todo
    • 树和图
      • 1.单词接龙 -- important
      • 2.被围绕的区域
      • 3.二叉树的最近公共祖先
      • 4.二叉树中的最大路径和 -- important
      • 5.Friend Circles -- todo
      • 6.课程表 -- important
      • 7.课程表 II
      • 8.矩阵中的最长递增路径 -- important
      • 9.计算右侧小于当前元素的个数 -- important
    • 回溯算法
      • 1.分割回文串 -- important
      • 2.单词搜索 II -- important
      • 3.删除无效的括号 -- important
      • 4.通配符匹配 -- important
      • 5.正则表达式匹配 -- important
    • 排序和搜索
      • 1.摆动排序 II
      • 2.Kth Smallest Element in a Sorted Matrix
      • 3.寻找两个有序数组的中位数
    • 动态规划 -- todo
    • 设计问题
      • 1.LRU 缓存机制 -- todo
      • 2.实现 Trie (前缀树) -- important
      • 3.扁平化嵌套列表迭代器 -- todo
      • 4.数据流的中位数
    • 数学
      • 1.最大数
      • 2.直线上最多的点数 -- todo
    • 其他
      • 1.Queue Reconstruction by Height
      • 2.接雨水
      • 3.天际线问题 -- todo
      • 4.柱状图中最大的矩形 -- important

LeetCode 探索主页 高级算法 C++ 解法

题目网址: LeetCode 探索主页 高级算法

记录我的一些解法,题目内容不会写在这里,不然实在写不下了,我尽可能在保证理解的基础上,精简下代码。如果有任何问题,欢迎评论,一起探讨算法。

数组和字符串

1.Product of Array Except Self

考察前缀后缀的题目

class Solution {
public:
    vector<int> productExceptSelf(vector<int>& nums) {
        vector<int> l = nums,r = nums;
        for(int i=1;i<l.size();i++)
            l[i] = l[i-1]*l[i];
        for(int i=r.size()-2;i>=0;i--)
            r[i] = r[i+1]*r[i];
        for(int i=0;i<nums.size();i++)
            if(i == 0) nums[i] = r[i+1];
            else if(i == nums.size()-1) nums[i] = l[i-1];
            else nums[i] = l[i-1]*r[i+1];
        return nums;
    }
};

2.螺旋矩阵

一种巧妙的递归方法,避免了烦人的边界判断。

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

        res.insert(res.end(),old_matrix[0].begin(),old_matrix[0].end());
        // matrix 去掉第一行 逆时针90度
        new_matrix.clear();
        for(int j=old_matrix[0].size()-1;j>=0;j--){
            vector<int> temp;
            for(int i=1;i<old_matrix.size();i++)
                temp.push_back(old_matrix[i][j]);
            new_matrix.push_back(temp);
        }
        return spiralOrder(new_matrix);
    }
};

3.四数相加 II

用unordered_map保存两数相加的中间结果。

class Solution {
public:
    int fourSumCount(vector<int>& A, vector<int>& B, vector<int>& C, vector<int>& D) {
        unordered_map<int,int> ab;
        for(int i=0;i<A.size();i++)
            for(int j=0;j<B.size();j++)
                ab[A[i]+B[j]]++;
        
        int res = 0;
        for(int i=0;i<C.size();i++)
            for(int j=0;j<D.size();j++){
                int temp = -C[i]-D[j];
                if(ab.find(temp) != ab.end())
                    res += ab[temp];
            }
        return res;
    }
};

4.盛最多水的容器

双指针解法

class Solution {
public:
    int maxArea(vector<int>& height) {
        int l = 0, r = height.size()-1;
        int res = 0;
        while(l < r){
            res = max(res, min(height[l],height[r])*(r-l));
            if(height[l] < height[r]) l++;
            else r--;
        }
        return res;
    }
};

5.生命游戏 – todo

6.第一个缺失的正数

直接用hash法或者set解决

class Solution {
public:
    int firstMissingPositive(vector<int>& nums) {
        set<int> st;
        for(auto item:nums)
            if(item > 0) st.insert(item);
        
        int res = 1;
        for(auto item:st){
            if(item != res) return res;
            res++;
        }
        return res;
    }
};

7.最长连续序列 – important

参考评论的思路,非常漂亮,st.find(item-1) == st.end()表示集合里面没有出现item-1的数,那么是一个新的连续序列的起点。

class Solution {
public:
    int longestConsecutive(vector<int>& nums) {
        unordered_set<int> st(nums.begin(),nums.end());
        int res = 0;
        for(auto item:nums){
            // 属于一个新的序列
            if(st.find(item-1) == st.end()){
                int num = item+1;
                while(st.find(num) != st.end()) num++;
                res = max(res,num-item);
            }
        }
        return res;
    }
};

8.寻找重复数

竟然可以抽象为判断链表环的入口的问题。对于链表环的问题一般采用快慢指针。

假设链表环的入口为s1,设快慢指针相遇时位置为s2,那么s2绕一圈到s1的距离等于从起点到s2的距离。然后就可以根据这个推出s1的位置。

class Solution {
public:
    int findDuplicate(vector<int>& nums) {
        int res = 0,fast = 0;
        while(res != fast || fast == 0){
            res = nums[res];
            fast = nums[nums[fast]];
        }
        cout << res << endl;
        for(int i=0;res != i;){
            res = nums[res];
            i = nums[i];
        }
        return res;
    }
};

9.基本计算器 II

用的数据栈和符号栈,可以进行通用的表达式计算,比如带负数、括号等。这里直接考虑加减就行。

class Solution {
public:
    stack<int> data;
    stack<char> ops;
    void clac(){
        int b = data.top(); data.pop();
        int a = data.top(); data.pop();
        char c = ops.top(); ops.pop();
        if(c == '+') data.push(a+b);
        if(c == '-') data.push(a-b);
        if(c == '*') data.push(a*b);
        if(c == '/') data.push(a/b);
    }
    
    int calculate(string s) {
         for(int i=0;i<s.size();i++){
             if('0'<=s[i] && s[i]<='9'){
                int num = 0;
                while(i<s.size() && '0'<=s[i] && s[i]<='9'){
                    num *= 10;
                    num += s[i++] - '0';
                }
                i--;
                data.push(num);
            }else if(s[i] == '+' || s[i] == '-'){
                while(ops.size() && (ops.top()=='*' || ops.top() == '/' || ops.top()=='+' || ops.top() == '-'))
                    clac();
                ops.push(s[i]);
            }else if(s[i] == '*' || s[i] == '/'){
                while(ops.size() && (ops.top()=='*' || ops.top() == '/'))
                    clac();
                ops.push(s[i]);
            }
         }
        while(ops.size()) clac();
        return data.top();
    }
};

10.滑动窗口最大值 — important

有点单调队列的思想,构建一个双端队列满足下面条件:

存储下标,下标单调增,下标索引的值单调减。同时若队列的头下标超出窗口,弹出值。

class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        if(nums.size() < 2) return nums;
        deque<int> q; // 存储下标,单调增,下标索引的值单调减。
        vector<int> res;
        for(int i=0;i<nums.size();i++){
            while(q.size() && nums[q.back()] <= nums[i])
                q.pop_back();
            q.push_back(i);
            
            while(i-q.front() >= k)
                q.pop_front();
            if(i >= k-1)
                res.push_back(nums[q.front()]);
        }
        return res;
    }
};

11.最小覆盖子串 – important

参考了评论的思路,主要思想为双指针法,右指针一直向右找到可行解,左指针向左找到最优解。

需要注意的时,通过定义更新变量len,巧妙的判断当前l、r区间是否已经包含t了。

class Solution {
public:
    string minWindow(string s, string t) {
        if(t.size() > s.size()) return "";
        
        unordered_map<char,int> mp;
        for(auto item:t) mp[item]++;
        
        int l = 0,r = 0,len = 0;
        int min_l = -1,min_ = 99999999;
        while(r < s.size()){
            // 表示s[r]属于t
            if(mp[s[r]]-- > 0) len++;
            r++;
            
            if(len < t.size()) continue;

            while(l<r && len == t.size()){
                // 表示s[l]属于t,只有这样才会为正
                if(++mp[s[l]] > 0) len--;
                l++;
            }
            
            if(r-l < min_){
                min_l = l-1,min_ = r-l+1;
            }
        }
        if(min_l == -1) return "";
        return s.substr(min_l,min_);
    }
};

链表

1.合并K个元素的有序链表

每次取最后两个合并即可。

class Solution {
public:
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        if(lists.size() == 0) return NULL;
        if(lists.size() == 1) return lists[0];        
        
        ListNode* a = lists[lists.size()-1];
        lists.pop_back();
        ListNode* b = lists[lists.size()-1];
        lists.pop_back();
        
        ListNode* c = new ListNode(0);
		ListNode* temp = c;
        while(a && b){
            if(a->val < b->val){
                c->next = a;
                a = a->next;
            }else{
                c->next = b;
                b = b->next;
            }
            c = c->next; 
        }
        if(a) c->next = a;
        if(b) c->next = b;     
        lists.push_back(temp->next);
        delete temp;
        
        return mergeKLists(lists);
    }
};

2.链表排序

就这样吧 ( ̄_, ̄ )

class Solution {
public:
    ListNode* sortList(ListNode* head) {
        vector<int> vec;
        ListNode* it = head;
        while(it){
            vec.push_back(it->val);
            it = it->next;
        }
        sort(vec.begin(),vec.end());
        it = head;
        int i=0;
        while(it){
            it->val = vec[i++];
            it = it->next;
        }
        return head;
    }
};

3.复制带随机指针的链表 todo

树和图

1.单词接龙 – important

第一次用的dfs,直接超时了。

class Solution {
public:
    int res = 9999999;
    bool vis[1010] = {false};
    
    bool is_ok(string word1,string word2){
        int flag = 0;
        for(int i=0;i<word1.size();i++)
            if(word1[i] != word2[i])
                flag++;
        if(flag == 1) return true;
        else return false;
    }
    
    void dfs(int step,string beginWord,string endWord,vector<string>& wordList){
        if(step >= res) return;
        if(beginWord == endWord){
            res = min(res,step);
            return;
        }
        
        for(int i=0;i<wordList.size();i++)
            if(!vis[i] && is_ok(wordList[i],beginWord)){
                vis[i] = true;
                dfs(step+1,wordList[i],endWord,wordList);
                vis[i] = false;
            }
    }
    
    int ladderLength(string beginWord, string endWord, vector<string>& wordList) {
        dfs(0,beginWord,endWord,wordList);
        if(res == 9999999) return 0;
        return res+1;
    }
};

评论区大神的双向搜索解法确实很漂亮,这里参考了他的思路。

之前在算法竞赛进阶指南上写过一道题目叫送礼物的题,基本思想是先搜索前一半,保存中间结果,再搜索后一半,更新最终结果。和这里的解法还是有区别。

// class Solution {
// public:
//     int ladderLength(string beginWord, string endWord, vector& wordList) {
//         unordered_set st(wordList.begin(),wordList.end());
//         if(st.count(endWord) == 0) return 0;
        
//         unordered_set st1{beginWord};
//         int step = 1;
//         while(st1.size()){
//             step++;
//             unordered_set tempSt;
//             for(auto item:st1){
//                 for(int i=0;i
//                     string str = item;
//                     for(char c = 'a';c <= 'z';c++){
//                         str[i] = c;
//                         if(st.count(str) == 0) continue;
//                         if(str == endWord) return step;
//                         // 这里不能直接给st1加,因为BFS就是要一层一层的遍历。
//                         // 应该也可以用queue,但是要考虑重复等等。
//                         tempSt.insert(str);
//                         st.erase(str);
//                     }
//                 }
//             }
//             st1 = tempSt;
//         }
//         return 0;
//     }
// };
class Solution {
public:
    int ladderLength(string beginWord, string endWord, vector<string>& wordList) {
        unordered_set<string> st(wordList.begin(),wordList.end());
        if(st.count(endWord) == 0) return 0;
        
        unordered_set<string> st1{beginWord},st2{endWord};
        int step = 1;
        while(st1.size()){
            step++;
            unordered_set<string> tempSt;
            for(auto &item:st1)
                st.erase(item);
            
            for(auto item:st1){
                for(int i=0;i<item.size();i++){
                    string str = item;
                    for(char c = 'a';c <= 'z';c++){
                        str[i] = c;    
                        if(st.count(str) == 0) continue;
                        // 这样可以直接得到最优解
                        if(st2.count(str)) return step;
                        tempSt.insert(str);                        
                    }
                }
            }
            
            // 这里就需要根据情况,更新st1了
            // 保证每次st1集合最小,这样就可以每次从最小的那一端开始搜索
            if(tempSt.size() < st2.size()) st1 = tempSt;
            else {
                st1 = st2;
                st2 = tempSt;
            }
        }
        return 0;
    }
};

2.被围绕的区域

用的BFS,不知道为什么别人的DFS比我的快。

class Solution {
public:
    bool vis[1010][1010];

    inline bool is_ok(int x,int y,vector<vector<char>>& board){
        if(x <= 0 || x >= board.size()-1 || y <= 0 || y >= board[0].size()-1
           || vis[x][y] || board[x][y] != 'O') return false;
        return true;
    }

    void bfs(int x,int y,vector<vector<char>>& board){
        int X[] = {0,0,-1,1};
        int Y[] = {1,-1,0,0};
        memset(vis,0,sizeof vis);
        queue<pair<int,int>> q;
        q.push({x,y});
        vis[x][y] = true;
        board[x][y] = '_';
        while(q.size()){
            int len = q.size();
            while(len--){
                auto item = q.front();
                q.pop();
                for(int i=0;i<4;i++){
                    int new_x = item.first+X[i], new_y = item.second+Y[i];
                    if(is_ok(new_x,new_y,board)){
                        q.push({new_x,new_y});
                        vis[new_x][new_y] = true;
                        board[new_x][new_y] = '_';
                    }
                }
            }
        }
    }

    void solve(vector<vector<char>>& board) {
        if(board.size() == 0 || board[0].size() == 0) return;

        // 在边界的地方BFS board[][] == 'O' 的块,设置为'_' 表示不会设置为'X'
        for(int i=0;i<board.size();i++){
            if(board[i][0] == 'O') bfs(i,0,board);
            if(board[i][board[0].size()-1] == 'O') bfs(i,board[0].size()-1,board);
        }
        for(int i=0;i<board[0].size();i++){
            if(board[0][i] == 'O') bfs(0,i,board);
            if(board[board.size()-1][i] == 'O') bfs(board.size()-1,i,board);
        }
        
        for(auto &item1:board)
            for(auto &item:item1)
                if(item == '_') item = 'O';
                else if(item == 'O') item = 'X';
    }
};

3.二叉树的最近公共祖先

常规解法,考察二叉树的DFS

这里有DFS相关的通用小技巧,当求解的结果为唯一的结果时,如这一道题,设置返回值为bool类型,这样可以在得到结果后快速返回。

而在求解结果为最优结果时,设置返回值为void类型,并让一个形参保存当前结果,一个全局变量更新最优结果。

class Solution {
public:
    bool dfs(TreeNode* root,TreeNode* ans,vector<TreeNode*>& vec){
        if(root == NULL) return false;
        if(root->val == ans->val) return true;
        
        vec.push_back(root->left);
        if(dfs(root->left,ans,vec)) return true;
        vec.pop_back();

        vec.push_back(root->right);
        if(dfs(root->right,ans,vec)) return true;
        vec.pop_back();
        return false;
    }
    
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        vector<TreeNode*> vec1{root},vec2{root};
        dfs(root,p,vec1);
        dfs(root,q,vec2);
        
        TreeNode* res = NULL;
        int len = min(vec1.size(),vec2.size());
        for(int i=0;i<len;i++)
            if(vec1[i] == vec2[i]) res = vec1[i];
            else return res;
        return res;
    }
};

4.二叉树中的最大路径和 – important

这个递归太不容易想到了。

class Solution {
public:
    /**
        对于任意一个节点, 如果最大和路径包含该节点, 那么只可能是两种情况:
        1. 其左右子树中所构成的和路径值较大的那个加上该节点的值后向父节点回溯构成最大路径
        2. 左右子树都在最大路径中, 加上该节点的值构成了最终的最大路径
    **/
    int res = INT_MIN;
    int dfs(TreeNode* root){
        if(root == NULL) return 0;
        int l = max(0,dfs(root->left));
        int r = max(0,dfs(root->right));
        res = max(res,l+r+root->val);
        return max(l,r)+root->val;
    }
    
    int maxPathSum(TreeNode* root) {
        dfs(root);
        return res;
    }
};

5.Friend Circles – todo

6.课程表 – important

通过BFS进行拓扑排序,很经典的题

class Solution {
public:
    bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
        vector<int> g[numCourses];
        int numIn[numCourses];
        memset(numIn,0,sizeof numIn);
        
        for(auto &item:prerequisites){
            g[item[1]].push_back(item[0]);
            numIn[item[0]]++;
        }
        
        queue<int> q;
        int res = 0;
        for(int i=0;i<numCourses;i++)
            if(numIn[i] == 0) q.push(i);
        
        while(q.size()){
            int len = q.size();
            while(len--){
                int x = q.front();
                q.pop();
                res++;
                for(auto &item:g[x]){
                    if(--numIn[item] == 0) q.push(item);
                }
            }
        }
        
        return res == numCourses;
    }
};

7.课程表 II

和上一题没什么区别

class Solution {
public:
    vector<int> findOrder(int numCourses, vector<vector<int>>& prerequisites) {
        vector<int> g[numCourses];
        int numIn[numCourses];
        memset(numIn,0,sizeof numIn);
        
        for(auto &item:prerequisites){
            g[item[1]].push_back(item[0]);
            numIn[item[0]]++;
        }
        
        queue<int> q;
        int nums = 0;
        vector<int> res;
        for(int i=0;i<numCourses;i++)
            if(numIn[i] == 0) q.push(i);
        
        while(q.size()){
            int len = q.size();
            while(len--){
                int x = q.front();
                q.pop();
                nums++;
                res.push_back(x);
                for(auto &item:g[x]){
                    if(--numIn[item] == 0) q.push(item);
                }
            }
        }
        
        if(nums != numCourses) return {};
        return res;
    }
};

8.矩阵中的最长递增路径 – important

直接DFS超时了,得动态规划才行。这种缓存DFS中间结果的方法要熟练才行。

class Solution {
public:
    int dp[1010][1010];

    inline bool is_ok(vector<vector<int>>& matrix,int x,int y){
        if(x < 0 || x >= matrix.size() || y < 0 || y >= matrix[0].size()) return false;
        return true;
    }
    
    int dfs(vector<vector<int>>& matrix,int x,int y){
        if(dp[x][y]) return dp[x][y];
            
        static int X[] = {-1,1,0,0};
        static int Y[] = {0,0,1,-1};
        int temp = 1;
        for(int i=0;i<4;i++){
            int newx = x+X[i],newy = y+Y[i];
            if(is_ok(matrix,newx,newy) && matrix[newx][newy] > matrix[x][y])
                temp = max(temp,1+dfs(matrix,newx,newy));
        }
        dp[x][y] = temp;
        return temp;
    }
    
    int longestIncreasingPath(vector<vector<int>>& matrix) {
        memset(dp,0,sizeof dp);
        
        int res = 0;
        for(int i=0;i<matrix.size();i++)
            for(int j=0;j<matrix[0].size();j++)
                res = max(res,dfs(matrix,i,j));
        return res;
    }
};

9.计算右侧小于当前元素的个数 – important

用的vector插入排序,二分查找O(logN),vector insert插入O(N),整个大概是O(N^2),没有二叉排序树BST(O(NlogN))的解法好。

class Solution {
public:
    vector<int> countSmaller(vector<int>& nums) {
        vector<int>counts(nums.size(),0);
        vector<int>temp;//插入排序数组
        for(int i=nums.size()-1;i>=0;i--)
        {
            vector<int>::iterator it=lower_bound(temp.begin(),temp.end(),nums[i]);
            counts[i]=it-temp.begin();
            temp.insert(it,nums[i]);
            cout << int(it-temp.begin()) << endl;
        }
        return counts;
    }
};

回溯算法

1.分割回文串 – important

很典型的一道题,注意递归参数的设计。

class Solution {
public:
    vector<string> tempRes;
    vector<vector<string>> res;
        
    inline bool is_ok(string nowStr){
        string s2 = nowStr;
        reverse(s2.begin(),s2.end());
        return nowStr == s2;
    }
    
    void dfs(int begin,int end,string& s){
        if(begin >= end){
            res.push_back(tempRes);
            return;
        }
        for(int i=begin+1;i<=end;i++){
            if(is_ok(s.substr(begin,i-begin))){
                tempRes.push_back(s.substr(begin,i-begin));
                dfs(i,end,s);
                tempRes.pop_back();
            }
        }
    }
    
    vector<vector<string>> partition(string s) {
        dfs(0,s.size(),s);
        return res;
    }
};

2.单词搜索 II – important

我还是太天真了,老老实实的用DFS,结果用了2088 ms艰难过。看别人几乎都用的Trie

class Solution {
public:
    bool vis[1010][1010];
    
    bool is_ok(int x,int y,vector<vector<char>>& board){
        if(x < 0 || x >= board.size() || y < 0 || y >= board[0].size()) return false;
        if(vis[x][y]) return false;
        return true;
    }
    
    bool dfs(vector<vector<char>>& board,string& word,int pos,int x,int y){
        if(pos == word.size()) return true;
        
        static int X[] = {0,0,-1,1};
        static int Y[] = {-1,1,0,0};
        for(int i=0;i<4;i++){
            int newx = x+X[i], newy = y+Y[i];
            if(is_ok(newx,newy,board))
                if(board[newx][newy] == word[pos]){
                    vis[x][y] = true;
                    if(dfs(board,word,pos+1,newx,newy)) return true;
                    vis[x][y] = false;
                }   
        }
        return false;
    }
    
    vector<string> findWords(vector<vector<char>>& board, vector<string>& words) {
        set<string> res;
        for(auto word:words){
            memset(vis,0,sizeof vis);
            for(int i=0;i<board.size();i++)
                for(int j=0;j<board[0].size();j++)
                    if(board[i][j] == word[0])
                        if(dfs(board,word,1,i,j)){
                            res.insert(word);
                        }
        }
        vector<string> res2(res.begin(),res.end());
        return res2;
    }
};

Trie 树可以在线性时间下查找字符串。整体思路是配合Trie 树DFS,优化到652 ms,不知道别人为什么那么快。Trie相关代码在设计问题下有。

class Trie {
public:
    int trie[100010][26], con;
    bool vis[100010];
    
    /** Initialize your data structure here. */
    Trie() {
        memset(trie,0,sizeof trie);
        memset(vis,0,sizeof vis);
        con = 1;     
    }
    
    /** Inserts a word into the trie. */
    void insert(string word) {
        int p = 1;
        for(int i=0;i<word.size();i++){
            int temp = word[i] - 'a';
            if(trie[p][temp] == 0) trie[p][temp] = ++con;
            p = trie[p][temp];
        }
        vis[p] = true;
    }
    
    /** Returns if the word is in the trie. */
    bool search(string word) {
        int p = 1;
        for(int i=0;i<word.size();i++){
            int temp = word[i] - 'a';
            p = trie[p][temp];
            if(p == 0) return false;
        }
        return vis[p];
    }
    
    /** Returns if there is any word in the trie that starts with the given prefix. */
    bool startsWith(string prefix) {
        int p = 1;
        for(int i=0;i<prefix.size();i++){
            int temp = prefix[i] - 'a';
            p = trie[p][temp];
            if(p == 0) return false;
        }
        return true;
    }
};

class Solution {
public:
    bool vis[1010][1010];
    Trie trie;     
    set<string> res;
    
    inline bool is_ok(int x,int y,vector<vector<char>>& board){
        if(x < 0 || x >= board.size() || y < 0 || y >= board[0].size()) return false;
        if(vis[x][y]) return false;
        return true;
    }
    
    void dfs(vector<vector<char>>& board,string str,int x,int y){
        // return 条件为当前字符串在Trie树中无此前缀
        if(!trie.startsWith(str)) return;
        if(trie.search(str)) res.insert(str);
        if(!is_ok(x,y,board)) return;
        
        static int X[] = {0,0,-1,1};
        static int Y[] = {-1,1,0,0};
        for(int i=0;i<4;i++){
            int newx = x+X[i], newy = y+Y[i];
            vis[x][y] = true;
            dfs(board,str+board[x][y],newx,newy);
            vis[x][y] = false;
        }
    }
    
    vector<string> findWords(vector<vector<char>>& board, vector<string>& words) {
        for(auto word:words) trie.insert(word);

        for(int i=0;i<board.size();i++)
            for(int j=0;j<board[0].size();j++){
                memset(vis,0,sizeof vis);
                if(trie.startsWith({board[i][j]})){
                    dfs(board,"",i,j);
                }
            }
                
        vector<string> res2(res.begin(),res.end());
        return res2;
    }
};

3.删除无效的括号 – important

之前用BFS超时,结果看别人python都用这种方法过了,瞬间无语 (+_+)?

结果发现别人python在初始化BFS的容器时用的level = {s},即使用的集合set,而我使用的queue,这样我的就没有去重,然后又改了改代码,使用了set。(unordered_set更快耶)

set基于红黑树实现,红黑树具有自动排序的功能,因此set内部所有的数据,在任何时候,都是有序的。

unordered_set基于哈希表,数据插入和查找的时间复杂度很低,几乎是常数时间,而代价是消耗比较多的内存,无自动排序功能。底层实现上,使用一个下标范围比较大的数组来存储元素,形成很多的桶,利用hash函数对key进行映射到不同区域进行保存。

用DFS有更快的写法,但是感觉设计递归好复杂。

class Solution {
public:
    inline bool is_ok(const string& s){
        int test = 0;
        for(auto item:s)
            if(item == '(') test++;
            else if(item == ')'){
                if(test == 0) return false;
                test--;  
            }
        return test == 0;
    }
    
    vector<string> removeInvalidParentheses(string s) {
        unordered_set<string> res;
        unordered_set<string> st;
        st.insert(s);
        while(st.size()){
            for(const string& s:st)
                if(is_ok(s)) res.insert(s);
            if(res.size()) return {res.begin(),res.end()};
            
            unordered_set<string> st2;
            for(const string& s:st)
                for(int i=0;i<s.size();i++)
					if (s[i] == '(' || s[i] == ')'){
                        string temp = s;
						st2.insert(temp.erase(i, 1));
                    }
            st = st2;
        }
        return {res.begin(),res.end()};
    }
};

4.通配符匹配 – important

参考了评论的解法,非递归的回溯(;´д`)ゞ,还有一种动态规划的解法,希望后面可以补上。

class Solution {
public:
    bool isMatch(string s, string p) {
        int i = 0,j = 0;
        int starPosi = -1,starPosj = -1;
        while(i<s.size()){
            if(j<p.size()){
                if(s[i] == p[j] || p[j] == '?'){
                    i++,j++;
                    continue;
                }
                if(p[j] == '*'){
                    starPosj = j++; // 第一次进入直接j++,表示*什么都不匹配
                    starPosi = i;
                    continue;
                }
            }
            if(starPosj != -1){ // 到达这里表示上一次的*匹配有问题,硬核回溯
                j = starPosj;
                i = starPosi+1;
                continue;
            }
            return false;
        }
        while(j<p.size() && p[j] == '*') j++;
        return j == p.size();
    }
};

5.正则表达式匹配 – important

竟然你们用系统库,在下输了。

参考了评论大神的动态规划的思路,太难考虑周全了:

	如果 p[j] == s[i] 或者 p[j] == '.' :
		dp[i][j] = dp[i-1][j-1];
	
	如果 p[j] == '*' :
		如果 p[j-1] != s[i] : 
			dp[i][j] = dp[i][j-2] //in this case, a* only counts as empty
		如果 p[j-1] == s[i] or p[j-1] == '.':
			dp[i][j] = dp[i-1][j] //in this case, a* counts as multiple a
			or dp[i][j] = dp[i][j-1] // in this case, a* counts as single a
			or dp[i][j] = dp[i][j-2] // in this case, a* counts as empty
class Solution {
public:
    bool isMatch(string s, string p) {
        bool dp[s.size()+1][p.size()+1];
        memset(dp,0,sizeof dp);
        dp[0][0] = true;
        
        //当s = ""时 和p的匹配关系初始化
        // p[1] == '*' p[3] == '*' ... 时都 dp[i][j] = dp[i][j-2] 
        for(int i=0;i+2<=p.size();i+=2)
            if(p[i+1] == '*') dp[0][i+2] = dp[0][i];
        
        for(int i=1;i<=s.size();i++)
            for(int j=1;j<=p.size();j++){
                char sp = p[j-1],ss = s[i-1];
                
                if(sp == '.' || sp == ss)
                    dp[i][j] = dp[i-1][j-1];
                else if(sp == '*')
                    if(dp[i][j-2]) dp[i][j] = true;
                    else if(p[j-2] == '.' || p[j-2] == ss)
                        dp[i][j] = (dp[i][j-1] || dp[i-1][j]);
            }
        
        return dp[s.size()][p.size()];
    }
};

排序和搜索

1.摆动排序 II

严格的小大小大…,比想象的麻烦不少,参考了评论的思路

class Solution {
public:
    void wiggleSort(vector<int>& nums) {
        if(nums.size() <= 1) return;
        sort(nums.begin(),nums.end());
        
        int pos = (nums.size()+1)/2;
        reverse(nums.begin(),nums.begin()+pos);
        reverse(nums.begin()+pos,nums.end());
        
        vector<int> res;
        int i=0,j=pos;
        while(j<nums.size()){
            res.push_back(nums[i++]);
            res.push_back(nums[j++]);
        }
        if(nums.size()&1) res.push_back(nums[i]);
        nums = res;
    }
};

2.Kth Smallest Element in a Sorted Matrix

就这样吧

class Solution {
public:
    int kthSmallest(vector<vector<int>>& matrix, int k) {
        vector<int> out;
        for(auto &item1:matrix)
            for(auto item:item1) out.push_back(item);
        sort(out.begin(),out.end());
        return out[k-1];
    }
};

3.寻找两个有序数组的中位数

思路为两个指针扫描。

class Solution {
public:
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
        int allLen = nums1.size() + nums2.size();
        int len = allLen/2;

        if(nums1.size() == 0)
            if(allLen&1) return nums2[len];
            else return (nums2[len-1]+nums2[len])/2.0;
        if(nums2.size() == 0)
            if(allLen&1) return nums1[len];
            else return (nums1[len-1]+nums1[len])/2.0;
        
        vector<int> a;
        int i=0,j=0,k=0;
        bool flag; // true 表示nums1指针后移,false 表示nums2指针后移
        while(i<=nums1.size() && j<=nums2.size()){
            if(j==nums2.size()) flag = true;
            else if(i==nums1.size()) flag = false;
            else if(nums1[i] < nums2[j]) flag = true;
            else flag = false;
            
            if(flag){
                if(k == len-1 || k == len) a.push_back(nums1[i]);
                i++;
            }else {
                if(k == len-1 || k == len) a.push_back(nums2[j]);
                j++;
            }
            k++;
        }
        
        if(allLen&1) return a[1];
        else return (a[0]+a[1])/2.0;
    }
};

动态规划 – todo

设计问题

1.LRU 缓存机制 – todo

2.实现 Trie (前缀树) – important

参考《算法竞赛进阶指南》的代码,非常简洁。

class Trie {
public:
    int trie[100010][28], con;
    bool vis[100010];
    
    /** Initialize your data structure here. */
    Trie() {
        memset(trie,0,sizeof trie);
        memset(vis,0,sizeof vis);
        con = 1;     
    }
    
    /** Inserts a word into the trie. */
    void insert(string word) {
        int p = 1;
        for(int i=0;i<word.size();i++){
            int temp = word[i] - 'a';
            if(trie[p][temp] == 0) trie[p][temp] = ++con;
            p = trie[p][temp];
        }
        vis[p] = true;
    }
    
    /** Returns if the word is in the trie. */
    bool search(string word) {
        int p = 1;
        for(int i=0;i<word.size();i++){
            int temp = word[i] - 'a';
            p = trie[p][temp];
            if(p == 0) return false;
        }
        return vis[p];
    }
    
    /** Returns if there is any word in the trie that starts with the given prefix. */
    bool startsWith(string prefix) {
        int p = 1;
        for(int i=0;i<prefix.size();i++){
            int temp = prefix[i] - 'a';
            p = trie[p][temp];
            if(p == 0) return false;
        }
        return true;
    }
};

3.扁平化嵌套列表迭代器 – todo

4.数据流的中位数

用的multiset,用了1628 ms,貌似正确解法是两个优先堆

class MedianFinder {
public:
    multiset<int> st;
    /** initialize your data structure here. */
    MedianFinder() {
    }
    
    void addNum(int num) {
        st.insert(num);
    }
    
    double findMedian() {
        int i=0,last;
        for(auto item:st){
            if(i == st.size()/2) 
                if(st.size() & 1) return item;
                else return (item+last)/2.0;
            last = item;
            i++;
        }
        return -1;
    }
};

数学

1.最大数

关键就是字符串比较条件为a+b > b+a;

class Solution {
public:
    static bool cmp(string a,string b){
        return a+b > b+a;
    }
    
    string largestNumber(vector<int>& nums) {
        vector<string> vecStr;
        for(auto item:nums) vecStr.push_back(to_string(item));
        
        sort(vecStr.begin(),vecStr.end(),cmp);
        
        string res;
        bool flag = false;
        for(auto &item:vecStr){
            if(item == "0" && !flag) continue;
            res += item;
            flag = true;
        }
        if(res == "") return "0";
        return res;
    }
};

2.直线上最多的点数 – todo

其他

1.Queue Reconstruction by Height

这道题的思路,我参考的评论的思路:

先对输入数组排序,h升序,k降序 从头循环遍历 当前这个人就是剩下未安排的人中最矮的人,他的k值就代表他在剩余空位的索引值。

class Solution {
public:
    static bool cmp(vector<int> a,vector<int> b){
        if(a[0] == b[0]) return a[1] > b[1];
        return a[0] < b[0];
    }
    
    vector<vector<int>> reconstructQueue(vector<vector<int>>& people) {
        sort(people.begin(),people.end(),cmp);
        vector<vector<int>> res = people;
        for(auto &item:res) item = {-1};
        for(auto &item:people){
            int index = item[1];
            int i = 0,j = 0;
            while(j < index+1){
                if(res[i][0] == -1) j++;
                i++;
            } 
            res[i-1] = item; 
        } 
        return res; 
    }
};

2.接雨水

分别记录从左到右最大值和从右到左最大值。那么每个位置的雨水就是max(0,min(l[i],r[i]) - height[i])

class Solution {
public:
    int trap(vector<int>& height) {
        vector<int> l = height,r = height;
        for(int i=1;i<height.size();i++) 
            l[i] = max(height[i],l[i-1]);
        for(int i=height.size()-2;i>=0;i--)
            r[i] = max(height[i],r[i+1]);
        int res = 0;
        for(int i=0;i<height.size();i++)
            res += max(0,min(l[i],r[i]) - height[i]);
        return res;
    }
};

3.天际线问题 – todo

4.柱状图中最大的矩形 – important

我们需要得到某个柱子的左边第一个小于它的柱子下标和右边第一个小于它的柱子下标,然后得到最大面积。

这是一道考察单调栈的问题,它会让时间复杂度由之前的暴力循环O(N^2) 下降到 O(N)。单调栈定义:只保存高度递增的柱子下标。

class Solution {
public:
    int largestRectangleArea(vector<int>& heights) {
        // 让下标从1开始
        vector<int> heights2{0};
        heights2.insert(heights2.end(),heights.begin(),heights.end());

        stack<int> st;
        vector<int> l,r;
        
        st.push(0);
        for(int i=1;i<heights2.size();i++){
            while(st.size() != 1 && heights2[st.top()] >= heights2[i]) st.pop();
            l.push_back(st.top()); st.push(i);
        }

        while(st.size()) st.pop();
        
        st.push(heights2.size());
        for(int i=heights2.size()-1;i>=1;i--){
            while(st.size() != 1 && heights2[st.top()] >= heights2[i]) st.pop();
            r.push_back(st.top()); st.push(i);
        }
        reverse(r.begin(),r.end());
        
        int res = 0;
        for(int i=0;i<heights2.size()-1;i++){
            res = max(res,heights2[1+i]*(r[i]-l[i]-1));
        }
        return res;
    }
};

你可能感兴趣的:(算法)