leetcode刷题记录2:进度64, 2021.10.23

文章目录


此前已完成137道简单题目。

  1. 数组的度(题目编号697:link

  • 数组正序逆序遍历,找到每个数字第一次和最后一次出现的位置,统计每个元素出现的次数。
  • 注意可以在单次遍历的时候就可以统计出数字的最大次数,以及符合这个次数的所有数字。
  • vector没有自带find方法,但是可以使用algorithm库里的find函数,find(vector.begin(), vector.end(), target),如果找不到,返回的是vector.end()。
#include<unordered_map>
class Solution {
public:
    int findShortestSubArray(vector<int>& nums) {
        unordered_map<int, int> map_pos, map_count;
        for(int i = 0; i < nums.size(); i++){
            if(map_pos.find(nums[i]) == map_pos.end()){
                map_pos[nums[i]] = i;
                map_count[nums[i]] = 1;
            }
            else{
                map_count[nums[i]]++;
            }
        }
        int degree = 0;
        vector<int> target_nums;
        for(unordered_map<int, int>::iterator it = map_count.begin(); it != map_count.end(); it++){
            if(it->second == degree){
                target_nums.push_back(it->first);
            }
            else if(it->second > degree){
                target_nums.clear();
                target_nums.push_back(it->first);
                degree = it->second;
            }
        }
        unordered_map<int, int> map_pos_inverse;
        for(int i = nums.size() - 1; i >= 0; i--){
            if(find(target_nums.begin(), target_nums.end(), nums[i]) == target_nums.end()) continue;
            if(map_pos_inverse.find(nums[i]) == map_pos_inverse.end()){
                map_pos_inverse[nums[i]] = i;
            }
        }
        int min_length = nums.size();
        for(int i = 0; i < target_nums.size(); i++){
            min_length = min(min_length, map_pos_inverse[target_nums[i]] - map_pos[target_nums[i]] + 1);
        }
        return min_length;
    }
};
  1. 二叉搜索树(题目编号700:link

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    TreeNode* searchBST(TreeNode* root, int val) {
        if(root == NULL) return NULL;
        if(root->val == val) return root;
        if(root->val < val) return searchBST(root->right, val);
        return searchBST(root->left, val);
    }
};
  1. 数据流中的第K大元素(题目编号703:link

  • 使用栈来记录排序好的K大元素,会超时。
class KthLargest {
public:
	stack<int> s1, s2;
	int K = 0;
	KthLargest(int k, vector<int>& nums) {
		this->K = k;
        for (int i = 0; i < nums.size(); i++) {
			if (s1.size() == 0) {
				s1.push(nums[i]);
			}
			else {
				this->add(nums[i]);
			}
		}
	}

	int add(int val) {
		if (s1.size() == this->K) {
			if (val <= s1.top()) return s1.top();
			s1.pop();
		}
		while (s1.size() > 0 && s1.top() < val) {
			s2.push(s1.top());
			s1.pop();
		}
		s1.push(val);
		while (s2.size() > 0) {
			s1.push(s2.top());
			s2.pop();
		}
		return s1.top();
	}
};
  • 使用multiset容器,会自动升序排序,插入元素是insert,当size()大于K时,调用erase(multiset.begin())丢弃最小的值。add函数返回的是multiset.begin()指针对应的值。
class KthLargest {
public:
	multiset<int> topK;
	int K = 0;
	KthLargest(int k, vector<int>& nums) {
		this->K = k;
        for(int num: nums){
            add(num);
        }
	}

	int add(int val) {
		this->topK.insert(val);
        if(this->topK.size() > this->K){
            this->topK.erase(this->topK.begin());
        }
        return *(this->topK).begin();
	}
};
  • 还可以用优先队列,greater,小顶堆。也是自动排好序的。
class KthLargest {
public:
    priority_queue<int, vector<int>, greater<int>> pq;
    int K;
    KthLargest(int k, vector<int>& nums) {
        K = k;
        for(int i = 0; i < nums.size(); i++)
        {
            add(nums[i]);
        }
    }
    
    int add(int val) {
        pq.push(val);
        if(pq.size() > K){
            pq.pop();
        }
        return pq.top();
    }
};

/**
 * Your KthLargest object will be instantiated and called as such:
 * KthLargest* obj = new KthLargest(k, nums);
 * int param_1 = obj->add(val);
 */
  1. 二分查找(题目编号704:link

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int left = 0, right = nums.size() - 1, mid;
        while(left <= right){
            mid = left + (right - left) / 2;
            if(nums[mid] == target){
                return mid;
            }
            if(nums[mid] < target){
                left = mid + 1;
            }
            else{
                right = mid - 1;
            }
        }
        return -1;
    }
};
  1. 转换成小写字母(题目编号709:link

  • 题目要求是大写字母转换成小写字母,其他字符应当保持不变吧,需要注意一下。
class Solution {
public:
    string toLowerCase(string str) {
        int dif_lower, dif_higher;
        for(int i = 0; i < str.size(); ++i){
            dif_lower = str[i] - 'a';
            dif_higher = str[i] - 'A';
            if(dif_lower >= 0 && dif_lower <= 26){
                continue;
            }
            else if(dif_higher >= 0 && dif_higher <= 26){
                str[i] = 'a' + dif_higher;
            }
        }
        return str;
    }
};
  1. 1比特与2比特字符(题目编号717:link

  • 从第0位置开始,如果是0,设置flag为true,并将指针后移1格,表示当前字符片段结束,如果是1,则设置flag为false,将指针后移2格。
class Solution {
public:
    bool isOneBitCharacter(vector<int>& bits) {
        if(bits.size() == 0) return false;
        bool flag;
        int idx = 0;
        while(idx <= bits.size() - 1){
            if(bits[idx] == 0){
                flag = true;
                ++idx;
            }
            else{
                flag = false;
                idx += 2;
            }
        }
        return flag;
    }
};
  1. 图像渲染(题目编号733:link

  • 使用队列遍历即可,每次弹出一个点,遍历它的上下左右四个点,复合要求的放到队尾,同时用一个visited变量记录每个点是不是已经访问过了,避免重复计算,死循环。
  • 切记要把最开始给的那个点的颜色给改了。
class Solution {
public:
    vector<vector<int>> floodFill(vector<vector<int>>& image, int sr, int sc, int newColor) {
        vector<vector<int>> visited(image.size(), vector<int>(image[0].size()));
        queue<vector<int>> q;
        vector<int> point;
        point.push_back(sr);
        point.push_back(sc);
        q.push(point);
        int old_color = image[sr][sc], x, y;
        while(q.size() > 0){
            point = q.front();
            q.pop();
            x = point[0];
            y = point[1];
            check_color(x - 1, y, image, old_color, visited, q, newColor);
            check_color(x + 1, y, image, old_color, visited, q, newColor);
            check_color(x, y - 1, image, old_color, visited, q, newColor);
            check_color(x, y + 1, image, old_color, visited, q, newColor);
        }
        return image;
    }

    bool check_coor(int x, int y, int size_x, int size_y){
        if(x >= 0 && x < size_x && y >= 0  && y < size_y){
            return true;
        }
        return false;
    }

    void check_color(int x, int y, vector<vector<int>>& image, int old_color, vector<vector<int>>& visited, queue<vector<int>>& q, int newColor){
        if(check_coor(x, y, image.size(), image[0].size()) && (!visited[x][y]) && image[x][y] == old_color){
            visited[x][y] = 1;
            vector<int> point;
            point.push_back(x);
            point.push_back(y);
            q.push(point);
            image[x][y] = newColor;
        }
    }
};
  1. 设计哈希集合(题目编号705:link

  • 用取余哈希建立给定数字到桶下标的映射,如果出现哈希冲突,就采用链表的形式。后面就是链表的尾插,删除,查找算法。
  • 为什么说桶数量为质数时冲突会少一些?
  • 注意题目说的是集合,所以不会有重复数字出现的。插入的时候要注意检查是否已经出现过。保证没有重复值,删除的时候代码只需删一次。
class MyHashSet {
private:
    class Node {
    public:
        Node(int value){
            val = value;
            next = NULL;
        }
        int val;
        Node* next;
    };

public:
    /** Initialize your data structure here. */
    int bucket_num = 769;
    vector<Node*> buckets;

    MyHashSet() {
        buckets = vector<Node*>(bucket_num, NULL);
        buckets.clear();
    }

    int _hash(int value){
        return value % bucket_num;
    }
    
    void add(int key) {
        int bucket_id = _hash(key);
        Node* bucket_head = buckets[bucket_id];
        if(bucket_head == NULL){
            buckets[bucket_id] = new Node(key);
        }
        else{
            while(bucket_head != NULL){
                if(bucket_head->val == key){
                    return;
                }
                if(bucket_head->next == NULL){
                    break;
                }
                bucket_head = bucket_head->next;
            }
            bucket_head->next = new Node(key);
        }
    }
    
    void remove(int key) {
        int bucket_id = _hash(key);
        Node* bucket_head = buckets[bucket_id];
        if(bucket_head != NULL){
            if(bucket_head->val == key){
                Node* to_delete = bucket_head;
                buckets[bucket_id] = bucket_head->next;
                delete to_delete;
            }
            else{
                while(bucket_head->next != NULL && bucket_head->next->val != key){
                    bucket_head = bucket_head->next;
                }
                if(bucket_head->next != NULL){
                    Node* to_delete = bucket_head->next;
                    bucket_head->next = bucket_head->next->next;
                    delete to_delete;
                }
            }
            
        }
    }
    
    /** Returns true if this set contains the specified element */
    bool contains(int key) {
        int bucket_id = _hash(key);
        Node* bucket_head = buckets[bucket_id];
        if(bucket_head == NULL){
            return false;
        }
        else{
            while(bucket_head != NULL){
                if(bucket_head->val == key){
                    return true;
                }
                bucket_head = bucket_head->next;
            }
            return false;
        }
    }

};

/**
 * Your MyHashSet object will be instantiated and called as such:
 * MyHashSet* obj = new MyHashSet();
 * obj->add(key);
 * obj->remove(key);
 * bool param_3 = obj->contains(key);
 */
  1. 建立哈希映射(题目编号706:link

  • 这题就是把key: value分别用上一道题目的哈希表存储,还可以把Node设计成可以同时保存key和value,写代码的时候写的是key和value分开存储,后来想想还是把Node的设计改一下更方便。
  • 注意添加数据时,遇到已经出现的key值时是需要更新value。
class MyHashMap {
private:
    class Node {
    public:
        Node(int value){
            val = value;
            next = NULL;
        }
        int val;
        Node* next;
    };

public:
    /** Initialize your data structure here. */
    int bucket_num = 769;
    vector<Node*> buckets;
    vector<Node*> buckets_value; 

    MyHashMap() {
        buckets = vector<Node*>(bucket_num, NULL);
        buckets_value = vector<Node*>(bucket_num, NULL);        
    }

    int _hash(int value){
        return value % bucket_num;
    }
    
    /** value will always be non-negative. */
    void put(int key, int value) {
        int bucket_id = _hash(key);
        Node* bucket_head = buckets[bucket_id];
        Node* bucket_value_head = buckets_value[bucket_id];
        if(bucket_head == NULL){
            buckets[bucket_id] = new Node(key);
            buckets_value[bucket_id] = new Node(value);
        }
        else{
            while(bucket_head != NULL){
                if(bucket_head->val == key){
                    bucket_value_head->val = value;
                    return;
                }
                if(bucket_head->next == NULL){
                    break;
                }
                bucket_head = bucket_head->next;
                bucket_value_head = bucket_value_head->next;
            }
            bucket_head->next = new Node(key);
            bucket_value_head->next = new Node(value);
        }
    }
    
    /** Returns the value to which the specified key is mapped, or -1 if this map contains no mapping for the key */
    int get(int key) {
        int bucket_id = _hash(key);
        Node* bucket_head = buckets[bucket_id];
        Node* bucket_value_head = buckets_value[bucket_id];
        if(bucket_head == NULL){
            return -1;
        }
        else{
            while(bucket_head != NULL){
                if(bucket_head->val == key){
                    return bucket_value_head->val;
                }
                bucket_head = bucket_head->next;
                bucket_value_head = bucket_value_head->next;
            }
            return -1;
        }
    }
    
    /** Removes the mapping of the specified value key if this map contains a mapping for the key */
    void remove(int key) {
        int bucket_id = _hash(key);
        Node* bucket_head = buckets[bucket_id];
        Node* bucket_value_head = buckets_value[bucket_id];
        if(bucket_head != NULL){
            if(bucket_head->val == key){
                Node* to_delete = bucket_head;
                buckets[bucket_id] = bucket_head->next;
                delete to_delete;
                to_delete = bucket_value_head;
                buckets_value[bucket_id] = bucket_value_head->next;
                delete to_delete;
            }
            else{
                while(bucket_head->next != NULL && bucket_head->next->val != key){
                    bucket_head = bucket_head->next;
                    bucket_value_head = bucket_value_head->next;
                }
                if(bucket_head->next != NULL){
                    Node* to_delete = bucket_head->next;
                    bucket_head->next = bucket_head->next->next;
                    delete to_delete;
                    to_delete = bucket_value_head->next;
                    bucket_value_head->next = bucket_value_head->next->next;
                    delete to_delete;
                }
            }
            
        }
    }

};

/**
 * Your MyHashMap object will be instantiated and called as such:
 * MyHashMap* obj = new MyHashMap();
 * obj->put(key,value);
 * int param_2 = obj->get(key);
 * obj->remove(key);
 */
  1. 翻转字符串里的单词(题目编号151:link)

  • 先局部每个单词内部翻转,然后全部再翻转一次。
  • 需要注意的是空格隔开的单词。
  • 原地算法,需要拷贝新找到的字符串到紧跟着前一个单词的位置,以跳过空格。
  • 注意最后还需要判断是否还有一个单词没翻转。
class Solution {
public:
    string reverseWords(string s) {
        int start = -1, len = 0;
        string res;
        for(int i = 0; i < s.size(); ++i){
            if(start == -1){
                if(s[i] != ' '){
                    start = i;
                }
            }
            else{
                if(s[i] == ' '){
                    reverse(s, start, i - 1);
                    copy(s, start, i - start, len);
                    len += i - start;
                    s[len] = ' ';
                    ++len;
                    start = -1;
                }
            }
        }
        if(start != -1){
            reverse(s, start, s.size() - 1);
            copy(s, start, s.size() - start, len);
            len += s.size() - start;
        }
        else{
            --len;
        }
        reverse(s, 0, len - 1);
        return s.substr(0, len);
    }

    void reverse(string& s, int i, int j){
        while(i < j){
            char c = s[i];
            s[i] = s[j];
            s[j] = c;
            ++i;
            --j;
        }
    }

    void copy(string& s, int src, int len, int dest){
        if(src == dest){
            return;
        }
        while(len > 0){
            s[dest] = s[src];
            ++dest;
            ++src;
            --len;
        }
    }
};
  1. 面试题 16.07. 最大数值(link)

  • 不用if-else和比较运算符比较a和b的大小,利用int类型正数右移31位为0,负数右移31位为-1的性质来表示a和b的大小关系。由于相减可能导致溢出,故先转为long型,右移63位。
class Solution {
public:
    int maximum(int a, int b) {
        long c = a, d = b, k = 1 + ((c - d) >> 63);
        return k * a + !k * b;
    }
};
  1. 在线选举(题目编号911:link

  • 记录每次leader变动时的获胜者和时间,在查询的时候进行二分查找。
  • 注意更新获胜者票数和更新获胜者的条件不完全一样。
class TopVotedCandidate {
public:
    vector<vector<int>> result;
    TopVotedCandidate(vector<int>& persons, vector<int>& times) {
        unordered_map<int, int> mp;
        int count_of_winner = 0, winner = -1;
        for(int i = 0; i < persons.size(); ++i){
            ++mp[persons[i]];
            if(mp[persons[i]] >= count_of_winner){
                if(persons[i] != winner){
                    result.push_back(vector<int>{persons[i], times[i]});
                }
                count_of_winner = mp[persons[i]];
                winner = persons[i];
            }
        }
    }
    
    int q(int t) {
        if(result.size() == 0) return -1;
        int lo = 1, hi = result.size();
        while(lo < hi){
            int mid = lo + (hi - lo) / 2;
            if(result[mid][1] <= t){
                lo = mid + 1;
            }
            else{
                hi = mid;
            }
        }
        return result[lo - 1][0];
    }
};

/**
 * Your TopVotedCandidate object will be instantiated and called as such:
 * TopVotedCandidate* obj = new TopVotedCandidate(persons, times);
 * int param_1 = obj->q(t);
 */
  1. 前 K 个高频元素(题目编号347:link)

  • 之前做过第K大的数,跟这个很类似。
  • 这里尝试一下堆的做法。
  • 利用优先队列,需要好好学习一下这个堆
  • 这次是参考题解写的。
  • 注意cmp函数必须是static的,之前也遇到过这个问题,会导致编译不通过。
class Solution {
public:
    static bool cmp(pair<int, int> p, pair<int, int> q){
        return p.second > q.second;
    }

    vector<int> topKFrequent(vector<int>& nums, int k) {
        unordered_map<int, int> mp;
        for(int n: nums){
            mp[n]++;
        }
        priority_queue<pair<int, int>, vector<pair<int, int>>, decltype(&cmp)> q(cmp);
        for(auto& [num, count]: mp){
            if(q.size() == k){
                if(q.top().second < count){
                    q.pop();
                    q.emplace(num, count);
                }
            }
            else{
                q.emplace(num, count);
            }
        }
        vector<int> res;
        while(!q.empty()){
            res.emplace_back(q.top().first);
            q.pop();
        }
        return res;
    }
};
  1. 形成两个异或相等数组的三元组数目(题目编号1442:link)

  • 题解讲得很好,利用异或运算的性质,来减少计算量,降低计算量到O(n^2)和O(n)。
class Solution {
public:
    int countTriplets(vector<int>& arr) {
        int ans = 0;
        for(int i = 0; i < arr.size() - 1; ++i){
            int x = arr[i];
            for(int k = i + 1; k < arr.size(); ++k){
                x ^= arr[k];
                if(x == 0){
                    ans += k - i;
                }
            }
        }
        return ans;
    }
};
class Solution {
public:
    int countTriplets(vector<int>& arr) {
        unordered_map<int, int> cnt = {{0, 1}}, indsum = {{0, 0}};
        int x = 0, ans = 0;
        for(int k = 0; k < arr.size(); ++k){
            x ^= arr[k];
            if(cnt[x] > 0){
                ans += cnt[x] * k - indsum[x];
            }
            ++cnt[x];
            indsum[x] += (k + 1);
        }
        return ans;
    }
};
  1. 最大数(题目编号179:link)

  • 思路很简单,自定义比较函数,数字两两比较谁放在前边组成的数字大。
  • 非常需要注意的是:比较函数里得是return s1>s2,如果写成s1>=s2,就会报heap overflow,堆溢出错误。
class Solution {
public:
    string largestNumber(vector<int>& nums) {
        if(nums.size() == 0) return "0";
        sort(nums.begin(), nums.end(), cmp);
        string ans;
        if(nums[0] == 0) return "0";
        for(int n: nums){
            ans += to_string(n);
        }
        return ans;
    }

    static int cmp(int n1, int n2){
        string sa = to_string(n1);
        string sb = to_string(n2);
        string s1 = sa + sb;
        string s2 = sb + sa;
        return s1 > s2;
    }
};
  1. 不同路径(题目编号62:link

  • 这题本来想的是直接采用组合数C_{m+n-2}^{m-1},但好像不是这样子的。
  • 使用递归或者动态规划,递推公式,到(i, j)点只有从(i-1,j)和(i,j-1)两个位置,那么到达(i,j)的方法数就等于到达这两个前序点的方法数之和。
  • 递归超时,改为使用动态规划。
class Solution {
public:
    int uniquePaths(int m, int n) {
        vector<vector<int>> f(m, vector<int>(n));
        f[0][0] = 1;
        for(int i = 1; i < m; ++i){
            f[i][0] = 1;
        }
        for(int i = 1; i < n; ++i){
            f[0][i] = 1;
        }
        for(int i = 1; i < m; ++i){
            for(int j = 1; j < n; ++j){
                f[i][j] = f[i - 1][j] + f[i][j - 1];
            }
        }
        return f[m - 1][n - 1];
        // return func(m, n);
    }

    int func(int m, int n){
        if(m == 1 && n == 1){
            return 1;
        }
        if(m == 1 || n == 1){
            return 1;
        }
        return func(m - 1, n) + func(m, n - 1);
    }
};
  1. 不同路径II(题目编号63:link

  • 2021.3.24
  • 这题相当于上题中某些位置由于是阻碍,导致到达该点的方法是必定为0。
  • 注意再第一行和第一列,当该点不是阻碍时,只要前面有一个点是阻碍,那么该点都是无法达到的,这点要尤其注意。还可以提前跳出循环。
  • 注意测试用例中有变态的,(0,0)起点位置是阻碍的,既然考虑到起点可能是阻碍,那么终点也考虑一下。如果起点或终点是阻碍,那么直接返回结果0。
class Solution {
public:
    int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {
        int m = obstacleGrid.size(), n = obstacleGrid[0].size();
        vector<vector<int>> f(m, vector<int>(n));
        if(obstacleGrid[0][0] == 1 || obstacleGrid[m - 1][n - 1] == 1){
            return 0;
        }
        f[0][0] = 1;
        for(int i = 1; i < m; ++i){
            if(obstacleGrid[i][0] == 1){
                break;
            }
            f[i][0] = 1;
        }
        for(int i = 1; i < n; ++i){
            if(obstacleGrid[0][i] == 1){
                break;
            }
            f[0][i] = 1;
        }
        for(int i = 1; i < m; ++i){
            for(int j = 1; j < n; ++j){
                if(obstacleGrid[i][j] == 0){
                    f[i][j] = f[i - 1][j] + f[i][j - 1];
                }
            }
        }
        return f[m - 1][n - 1];
    }
};
  1. 组合(题目编号77:link

  • 2种解法,一种是递推,利用C_{n-1}{k}和C_{n}{k-1}(每组结果再加上一个数字n)进行合并得到,耗费内存比较多,且有重复计算的问题。另一种就是DFS搜索,使用visited数组,这次一次就写对了,也不需要调试修改代码,有进步~
  • 下面代码段注释掉的就是第一种解法
class Solution {
public:
    vector<vector<int>> combine(int n, int k) {
        if(k < 0 || k > n){
            return vector<vector<int>>{};
        }
        if(n == k){
            vector<int> ans = vector<int>(n);
            for(int i = 0; i < n; ++i){
                ans[i] = i + 1;
            }
            return vector<vector<int>>{ans};
        }
        // vector<vector<int>> ans = combine(n - 1, k);
        // vector<vector<int>> ans2 = combine(n - 1, k - 1);
        // for(int i = 0; i < ans2.size(); ++i){
        //     vector<int> tmp = ans2[i];
        //     tmp.push_back(n);
        //     ans.push_back(tmp);
        // }
        // return ans;
        vector<vector<int>> res;
        vector<int> visited(n);
        selection(n, k, 0, 0, visited, res);
        return res;
    }

    void selection(int& n, int& k, int used, int cur_idx, vector<int>& visited, vector<vector<int>>& res){
        if(used == k){
            vector<int> tmp(k);
            for(int i = 0; i < visited.size() && used > 0; ++i){
                if(visited[i]){
                    tmp[k - used] = i + 1;
                    --used;
                }
            }
            res.push_back(tmp);
            return;
        }
        if(n - cur_idx < k - used){
            return;
        }
        visited[cur_idx] = 1;
        ++used;
        selection(n, k, used, cur_idx + 1, visited, res);
        visited[cur_idx] = 0;
        --used;
        selection(n, k, used, cur_idx + 1, visited, res);
    }


};
  1. 单词搜索(题目编号79:link

class Solution {
public:
    bool exist(vector<vector<char>>& board, string word) {
        int rows = board.size(), cols = board[0].size();
        if(rows * cols < word.size()){
            return false;
        }
        vector<int> cnt_arr(52);
        for(int i = 0; i < rows; ++i){
            for(int j = 0; j < cols; ++j){
                if(board[i][j] >= 'a' && board[i][j] <= 'z'){
                    cnt_arr[]
                }
            }
        }
        return search(board, word, vector<vector<int>>(rows, vector<int>(cols)), 0, 0, 0, rows, cols);
    }

    bool search(vector<vector<char>>& board, string& word, vector<vector<int>> visited, int cur_idx, int cur_row, int cur_col, int rows, int cols){
        if(cur_idx == word.size()){
            return true;
        }
        if(cur_row < 0 || cur_row >= rows || cur_col < 0 || cur_col >= cols){
            return false;
        }
        if(cur_idx == 0){
            for(int i = 0; i < rows; ++i){
                for(int j = 0; j < cols; ++j){
                    if(board[i][j] == word[0]){
                        visited[i][j] = 1;
                        bool ans = search(board, word, visited, 1, i+1, j, rows, cols) || search(board, word, visited, 1, i-1, j, rows, cols) || search(board, word, visited, 1, i, j+1, rows, cols) || search(board, word, visited, 1, i, j-1, rows, cols);
                        if(ans){
                            return ans;
                        }
                        visited[i][j] = 0;
                    }
                }
            }
        }
        else{
            if(visited[cur_row][cur_col] || board[cur_row][cur_col] != word[cur_idx]){
                return false;
            }
            visited[cur_row][cur_col] = 1;
            int i = cur_row, j = cur_col;
            return search(board, word, visited, cur_idx + 1, i+1, j, rows, cols) || search(board, word, visited, cur_idx + 1, i-1, j, rows, cols) || search(board, word, visited, cur_idx + 1, i, j+1, rows, cols) || search(board, word, visited, cur_idx + 1, i, j-1, rows, cols);
        }
        return false;
    }
};
  1. 找到K个最接近的元素(题目编号658:link

  • 最开始美看到数组是有序的,就想到用大顶堆和小顶堆来做。用大顶堆维护k个最接近大于等于目标值x的元素,再用小顶堆维护k个最接近且小于等于目标值x的元素。最后再合并两个堆,得到k个最接近的。
  • 又考虑能否只使用一个堆,发现优先队列priority_queue可以使用自定义的数据结构和排序方法,但是和sort函数不太一样,排序函数得到一个数据类型。
priority<pair<int,int>,vector<pair<int,int>>, cmp> q;
struct cmp{
	bool operator()(pair<int,in> a, pair<int,int> b){
		return a.second > b.second;
	}
}
  • 又学到一种。可以用pair记录元素和元素到目标值x的距离,维护k个最接近的元素。
class Solution {
public:
    vector<int> findClosestElements(vector<int>& arr, int k, int x) {
        // priority_queue<int> max_heap;
        // priority_queue<int, vector<int>, greater<int>> min_heap;
        // for(int n: arr){
        //     if(n >= x){
        //         max_heap.push(n);
        //         if(max_heap.size() > k){
        //             max_heap.pop();
        //         }
        //     }
        //     else{
        //         min_heap.push(n);
        //         if(min_heap.size() > k){
        //             min_heap.pop();
        //         }
        //     }
        // }
        priority_queue<pair<int, int>, vector<pair<int, int>>, cmp1> q;
        for(int n: arr){
            q.push(pair<int, int>(n, abs(n - x)));
            if(q.size() > k){
                q.pop();
            }
        }
        vector<int> ans1, ans2;
        while(!q.empty()){
            auto c = q.top();
            if(c.first >= x){
                ans1.push_back(c.first);
            }
            else{
                ans2.push_back(c.first);
            }
            q.pop();
        }
        vector<int> ans;
        for(int i = 0; i < ans2.size(); ++i){
            ans.push_back(ans2[i]);
        }
        for(int i = ans1.size() - 1; i >= 0; --i){
            ans.push_back(ans1[i]);
        }
        return ans;
    }

    static bool cmp(pair<int, int> a, pair<int, int> b){
        return a.second > b.second;
    }

    struct cmp1{
        bool operator()(pair<int, int> a, pair<int, int> b){
            if(a.second == b.second){
                return a.first <= b.first;
            }
            else{
                return a.second < b.second;
            }
        }
    };
};
  • 后来发现数组是有序的,那就可以用二分搜索的方法找到距离x最接近的一个元素,然后向两边搜索。充分利用数组的性质。
#include<algorithm>
class Solution {
public:
    vector<int> findClosestElements(vector<int>& arr, int k, int x) {
        int pos = bsearch(arr, x);
        pos = min(pos, int(arr.size() - 1));
        vector<int> ans1, ans2;
        if(pos + 1 < arr.size() && abs(arr[pos + 1] - x) < abs(arr[pos] - x)){
            ans1.push_back(arr[pos + 1]);
            ++pos;
        }
        else if(pos - 1 >= 0 && abs(arr[pos - 1] - x) <= abs(arr[pos] - x)){
            ans2.push_back(arr[pos - 1]);
            --pos;
        }
        else{
            ans2.push_back(arr[pos]);
        }
        
        int lo = pos - 1, hi = pos + 1;
        while(ans1.size() + ans2.size() < k && (lo >= 0 || hi < arr.size())){
            if(lo >= 0 && hi < arr.size()){
                if(abs(x - arr[lo]) <= abs(arr[hi] - x)){
                    ans2.push_back(arr[lo]);
                    --lo;
                }
                else{
                    ans1.push_back(arr[hi]);
                    ++hi;
                }
            }
            else if(lo >= 0){
                ans2.push_back(arr[lo]);
                --lo;
            }
            else{
                ans1.push_back(arr[hi]);
                ++hi;
            }
        }
        vector<int> ans;
        for(int i = ans2.size() - 1; i >= 0; --i){
            ans.push_back(ans2[i]);
        }
        for(int i = 0; i < ans1.size(); ++i){
            ans.push_back(ans1[i]);
        }
        return ans;
    }
    int bsearch(vector<int> arr, int target){
        int lo = 0, hi = arr.size() - 1;
        while(lo <= hi){
            int mid = lo + (hi - lo) / 2;
            if(arr[mid] == target){
                return mid;
            }
            else if(arr[mid] < target){
                lo = mid + 1;
            }
            else{
                hi = mid - 1;
            }
        }
        return lo;
    }

};
  • 很久没刷新题了,有些松懈了,加油加油。
  1. 目标和(题目编号494:link,2021.7.7)

  • 本来以为是搜索空间压缩就可以解决问题,最后发现还是时间复杂度太高,查阅答案发现是需要动态规划思想。
  • 统计数组和sum,正样本由于数组全是非负整数,假设标记为负数的所有元素绝对值之和是neg,那么sum-neg是剩余数字之和,sum-neg-neg = target,所以target+sum>=0,sum-target是非负偶数。也可以定义标记为正数的所有元素绝对值之和是pos,那么sum-pos是剩余数字之和,pos-(sum-pos) = target,所以target+sum>=0,sum+target是非负偶数。
  • 接着就是判断数组从0到i处这i+1个元素中有多少个组合之和是pos或者neg。
class Solution {
public:
    int findTargetSumWays(vector<int>& nums, int target) {
        int sum = 0;
        for(int i = 0; i < nums.size(); ++i){
            sum += nums[i];
        }
        if(target + sum < 0 || ((target + sum) & 1) == 1) return 0;
        int T = (sum + target) >> 1;
        vector<int> pre = vector<int>(T + 1);
        pre[0] = 1;
        for(int i = 1; i <= nums.size(); ++i){
            vector<int> cur(T + 1);
            for(int j = 0; j <= T; ++j){
                cur[j] = pre[j];
                if(j - nums[i - 1] >= 0){
                    cur[j] += pre[j - nums[i - 1]];
                }
            }
            pre = cur;
        }
        return pre[T];
    }
};
  1. 随机数索引(题目编号398:link,2021.7.7)

  • 一种新的采样算法,在数据流处理中保证每个数被采样的概率都是相等的。
  • 如果当前位置以前,目标数字出现了cnt次,生成一个[1, cnt]的随机数,如果等于cnt,那么替换原有结果。那么到最后目标数字每个位置被采样的概率都是一样的。
class Solution {
private:
    vector<int> nums;
public:
    Solution(vector<int>& nums) {
        this->nums = nums;
    }
    
    int pick(int target) {
        int cnt = 0, ind = -1;
        for(int i = 0; i < this->nums.size(); ++i){
            if(nums[i] == target){
                ++cnt;
                if(rand() % cnt == 0){
                    ind = i;
                }
            }
        }
        return ind;
    }
};

/**
 * Your Solution object will be instantiated and called as such:
 * Solution* obj = new Solution(nums);
 * int param_1 = obj->pick(target);
 */
  1. 两数相加II(题目编号445:link

  • 最容易想到的是链表逆序,相加,然后再逆序过来。之前看别人代码看到一种把两个链表等长不等长在一个while循环里全都考虑的代码,这次直接写,写出来了。
  • 注意a?b:c+d?e:0,这种写法,会把c+d?e:0整体看成三元运算符的最后一项,需要加括号。
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        if(!l1){
            return l2;
        }
        if(!l2){
            return l1;
        }
        ListNode* r1 = reverseLinkList(l1);
        ListNode* r1_bak = r1;
        ListNode* r2 = reverseLinkList(l2);
        int carry = 0;
        ListNode* last;
        while(r1 || r2){
            int res = (r1? r1->val: 0) + (r2? r2->val:0) + carry;
            if(r1){
                r1->val = res % 10;
            }
            else{
                last->next = new ListNode(res % 10);
                r1 = last->next;
            }
            carry = res / 10;
            last = r1;
            if(r1) r1 = r1->next;
            if(r2) r2 = r2->next;
        }
        if(carry){
            last->next = new ListNode(carry);
        }
        return reverseLinkList(r1_bak);
    }

    ListNode* reverseLinkList(ListNode* head){
        if(!head) return head;
        ListNode *pre = nullptr, *cur = head, *next;
        while(cur){
            next = cur->next;
            cur->next = pre;
            pre = cur;
            cur = next;
        }
        return pre;
    }
};
  • 不对链表进行修改的进阶思路就只有使用栈
  1. 移动所有球到每个盒子所需的最小操作数(题目编号1769:link

  • 关键是找到规律,统计左侧/右侧所有小球移动到当前位置的花费,到达下一个位置利用递关系快速计算。到下一个位置时,左侧/右侧所有小球都需要额外的一步来到达当前位置。
class Solution {
public:
    vector<int> minOperations(string boxes) {
        int presum, precost, n = boxes.size();
        vector<int> ans = vector<int>(n), lsum = vector<int>(n), rsum = vector<int>(n), lcost = vector<int>(n), rcost = vector<int>(n);
        for(int i = 0; i < n; ++i){
            presum = (i == 0) ? 0: lsum[i - 1];
            precost = (i <= 1) ? 0: lcost[i - 1];
            if(i > 0 && boxes[i - 1] == '1'){
                lsum[i] = presum + 1;
            }
            else{
                lsum[i] = presum;
            }
            lcost[i] = precost + lsum[i];
            ans[i] += lcost[i];
            // reverse
            presum = (i == 0) ? 0: rsum[n - 1 - (i - 1)];
            precost = (i <= 1) ? 0: rcost[n - 1 - (i - 1)];
            if(i > 0 && boxes[n - 1 - (i - 1)] == '1'){
                rsum[n - 1 - i] = presum + 1;
            }
            else{
                rsum[n - 1 - i] = presum;
            }
            rcost[n - 1 - i] = precost + rsum[n - 1 - i];
            ans[n - 1 - i] += rcost[n - 1 - i];
        }
        return ans;
    }
};
  • 不需要维护那么多vec,可以优化。
  1. 字母板上的路径(题目编号1138:link

  • 题目难点主要是字母z,如果上一个字母是z,那么一定要先垂直移动,再水平移动,其他情况都先水平移动,再垂直移动(已经包括了下一个字母是z的情况)。
class Solution {
public:
    string alphabetBoardPath(string target) {
        if(target.size() == 0) return "";
        vector<string> alphabet = {
            "abcde", "fghij", "klmno", "pqrst", "uvwxy", "z"
        };
        string ans;
        find_next(target, ans, 0, 0, 0);
        return ans;
    }

    void find_next(string target, string& ans, int ind, int x, int y){
        if(ind == target.size()) return;
        int sx = int(target[ind] - 'a') / 5, sy = int(target[ind] - 'a') % 5;
        string vertical = (sx >= x)? "D":"U", horizontal = (sy >= y)? "R":"L";
        if(x == 5){
            append_char(ans, abs(sx - x), vertical);
            append_char(ans, abs(sy - y), horizontal);
        }
        else{
            append_char(ans, abs(sy - y), horizontal);
            append_char(ans, abs(sx - x), vertical);
        }
        ans += "!";
        find_next(target, ans, ind + 1, sx, sy);
    }

    void append_char(string& ans, int num, string s){
        for(int i = 0; i < num; ++i){
            ans += s;
        }
    }
};
  1. 索引处的解码字符串(题目编号880:link

  • 还没有写出来,明天好好理解一下解法。
  1. 安排电影院座位(题目编号1386:link

  • 最重要的是用map保留有人占位的排,剩下的没有人的是不需要存储的,还有用8位的bitmask来记录2-9号座位哪些位置被占用。如果用多个数组来记录的话会超时。
class Solution {
public:
    int maxNumberOfFamilies(int n, vector<vector<int>>& reservedSeats) {
        unordered_map<int, int> mp;
        for(auto v: reservedSeats){
            if(v[1] == 1 || v[1] == 10) continue;
            mp[v[0]] |= (1 << (v[1] - 2));
        } 
        int ans = 2 * (n - mp.size()), left = 0b00001111, middle = 0b11000011, right = 0b11110000;
        for(unordered_map<int, int>::iterator it = mp.begin(); it != mp.end(); ++it){
            if((it->second | left) == left || (it->second | middle) == middle || (it->second | right) == right){
                ++ans;
            }
        }
        return ans;
    }
};
  1. 统计二叉树中好节点的数目(题目编号1448:link

  • 递归,统计从根节点到该节点的历史最大值,跟当前节点的值比较即可。
/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    int goodNodes(TreeNode* root) {
        int ans = 0;
        midTraverse(root, ans, INT_MIN);
        return ans;
    }

    void midTraverse(TreeNode* root, int& ans, int maxVal){
        if(!root) return;
        if(root->val >= maxVal) ++ans;
        maxVal = max(maxVal, root->val);
        midTraverse(root->left, ans, maxVal);
        midTraverse(root->right, ans, maxVal);
    }
};
  1. 飞机座分配概率(题目编号1227:link

  • 通过数学推导获得递推公式。
class Solution {
public:
    double nthPersonGetsNthSeat(int n) {
        return n == 1 ? 1:0.5;
    }
};
  1. 连续的子数组和(题目编号523:link

  • 利用前缀和快速计算子数组和,由于这里只需要判断子数组和是否是k的倍数,所以前缀和只需要保留除k后的余数。前缀和数组中两个位置相等,就表示区间内子数组和是k的倍数,接下来还需要判断区间长度。
  • 此外,还需要注意初始条件mp[0] = -1,还有不能直接赋值mp[nums[0] % k] = 0,因为nums[0]可能刚好是k的倍数,就把mp[0] = -1覆盖掉了。
class Solution {
public:
    bool checkSubarraySum(vector<int>& nums, int k) {
        int n = nums.size();
        if(n <= 1) return false;
        vector<int> prefixSum(n);
        prefixSum[0] = nums[0] % k;
        unordered_map<int, int> mp;
        mp[0] = -1;
        int before = 0;
        for(int i = 0; i < n; ++i){
            prefixSum[i] = (before + nums[i]) % k;
            if(mp.find(prefixSum[i]) != mp.end()){
                if(i - mp[prefixSum[i]] >= 2) return true;
            }
            else{
                mp[prefixSum[i]] = i;
            }
            before = prefixSum[i];
        }
        return false;
    }
};
  1. 整数拆分(题目编号343:link

  • 本题需要使用动态规划,dp[i]表示对i拆分的最大乘积。在对i进行拆分时,依次考虑提取出1,2,3,…,i-1,剩下的部分可以选择拆或者不拆,乘积分别是i-j和dp[i-j]。
  • 还有更复杂一些的数学推导,可以简化计算量。
class Solution {
public:
    int integerBreak(int n) {
        vector<int> dp(n + 1);
        for(int i = 2; i <= n; ++i){
            for(int j = 1; j < i; ++j){
                dp[i] = max(dp[i], j * max(i - j, dp[i - j]));
            }
        }
        return dp[n];
    }
};
  1. 最少侧跳次数(题目编号1824:link,2021.7.28)

  • 动态规划
  • 跟自己的计算方式略有差异,自己的是错的。抽空再分析一下。
class Solution {
public:
    int minSideJumps(vector<int>& obstacles) {
        int n = obstacles.size();
        if(n <= 1) return 0;
        vector<vector<int>> dp(n, vector<int>(3, 0x3fffffff));
        dp[0][0] = dp[0][2] = 1;
        dp[0][1] = 0;
        for(int i = 1; i < n; ++i){
            if(obstacles[i] != 1) dp[i][0] = dp[i - 1][0];
            if(obstacles[i] != 2) dp[i][1] = dp[i - 1][1];
            if(obstacles[i] != 3) dp[i][2] = dp[i - 1][2];

            if(obstacles[i] != 1) dp[i][0] = min(dp[i][0], min(dp[i][1], dp[i][2]) + 1);
            if(obstacles[i] != 2) dp[i][1] = min(dp[i][1], min(dp[i][0], dp[i][2]) + 1);
            if(obstacles[i] != 3) dp[i][2] = min(dp[i][2], min(dp[i][0], dp[i][1]) + 1);
        }
        return min(dp[n-1][0], min(dp[n-1][1], dp[n-1][2]));
    }
};
  1. 重新安排行程(题目编号332:link,2021.7.27)

  • Hierholzer 算法用于在连通图中寻找欧拉路径,其流程如下:从起点出发,进行深度优先搜索。每次沿着某条边从某个顶点移动到另外一个顶点的时候,都需要删除这条边。如果没有可移动的路径,则将所在节点加入到栈中,并返回。
  • 还不懂
class Solution {
private:
    unordered_map<string, priority_queue<string, vector<string>, std::greater<string>>> vec;
    vector<string> stk;
public:
    void dfs(const string& curr){
        while(vec.count(curr) && vec[curr].size() > 0){
            string tmp = vec[curr].top();
            vec[curr].pop();
            dfs(move(tmp));
        }
        stk.emplace_back(curr);
    }


    vector<string> findItinerary(vector<vector<string>>& tickets) {
        for(auto& it: tickets){
            vec[it[0]].emplace(it[1]);
        }
        dfs("JFK");
        reverse(stk.begin(), stk.end());
        return stk;
    }
};
  1. 将二叉搜索树变平衡(题目编号1382:link,2021.7.28)

  • 官方题解使用的是利用数组重建构建一棵平衡树的方法。
  • 实际上构造出来的平衡树应该是不唯一的。
/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
private:
    vector<int> vec;
public:
    void midTraverse(TreeNode* root){
        if(!root) return;
        if(root->left){
            midTraverse(root->left);
        }
        vec.emplace_back(root->val);
        if(root->right){
            midTraverse(root->right);
        }
    }

    TreeNode* buildTree(vector<int>& vec, int left, int right){
        if(left > right){
            return nullptr;
        }
        int mid = left + (right - left) / 2;
        TreeNode* root = new TreeNode(vec[mid]);
        root->left = buildTree(vec, left, mid - 1);
        root->right = buildTree(vec, mid + 1, right);
        return root;
    }

    TreeNode* balanceBST(TreeNode* root) {
        midTraverse(root);
        return buildTree(vec, 0, vec.size() - 1);
    }
};
  1. 课程表IV(题目编号1462:link,2021.7.28)

  • 这个问题就是深度优先搜索,但是需要在遍历过程中记录每次查询的先序课程结果,不只是query中的,还有每一次递归查询中的先序课程。加速程序运行。
class Solution {
public:
    vector<bool> checkIfPrerequisite(int numCourses, vector<vector<int>>& prerequisites, vector<vector<int>>& queries) {
        unordered_map<int, vector<int>> ump;
        for(auto& p: prerequisites){
            ump[p[1]].emplace_back(p[0]);
        }
        map<pair<int, int>, bool> mem;
        vector<bool> res(queries.size(), false);
        for(int i = 0; i < queries.size(); ++i){
            auto& q = queries[i];
            res[i] = dfs(ump, mem, q[1], q[0]);
        }
        return res;
    }

    bool dfs(unordered_map<int, vector<int>>& ump, map<pair<int, int>, bool>& mem, int q, int target){
        if(mem.find(pair<int, int>{target, q}) != mem.end()){
            return mem[pair<int, int>{target, q}];
        }
        bool ans = false;
        if(ump.find(q) == ump.end()){
            ans = false;
        }
        else{
            auto& vec = ump[q];
            if(find(vec.begin(), vec.end(), target) != vec.end()){
                ans = true;
            }
            else{
                for(auto& n: vec){
                    if(dfs(ump, mem, n, target)){
                        ans = true;
                        break;
                    }
                }
            }
        }
        mem[pair<int, int>{target, q}] = ans;
        return ans;
    }
};
  1. 具有所有最深节点的最小子树(题目编号865:link,2021.7.28)

  • 两次深度优先搜索,第一次统计每个节点的深度。第二个判断当前节点左右孩子包含最深节点的情况,来决定返回当前节点还是某个孩子节点。
  • 还有其他的解法,不太好理解。
/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
private:
    map<TreeNode*, int> depth;
    int maxDepth = 0;
public:
    void getDepth(TreeNode* root, int d){
        if(!root) return;
        depth[root] = d;
        maxDepth = max(maxDepth, d);
        getDepth(root->left, d + 1);
        getDepth(root->right, d + 1);
    }

    TreeNode* answer(TreeNode* root){
        if(!root) return nullptr;
        if(depth[root] == maxDepth) return root; 
        TreeNode *left = answer(root->left), *right = answer(root->right);
        if(left && right) return root;
        if(left) return left;
        if(right) return right;
        return nullptr;
    }

    TreeNode* subtreeWithAllDeepest(TreeNode* root) {
        getDepth(root, 0);
        return answer(root);
    }
};
  1. 两个字符串的删除操作(题目编号583:link,2021.7.28)

  • 递归法
class Solution {
private:
    map<pair<int, int>, int> mp;

public:
    int minDistance(string word1, string word2) {
        return word1.size() + word2.size() - 2 * getLCS(word1, word2, word1.size(), word2.size());
    }

    int getLCS(string& word1, string& word2, int len1, int len2){
        int ans = 0;
        if(len1 == 0 || len2 == 0) ans = 0;
        else if(mp[pair<int, int>{len1, len2}] > 0){
            ans = mp[pair<int, int>{len1, len2}];
        }
        else if(word1[len1 - 1] == word2[len2 - 1]){
            ans = getLCS(word1, word2, len1 - 1, len2 - 1) + 1;
        }
        else{
            ans =  max(getLCS(word1, word2, len1, len2 - 1), getLCS(word1, word2, len1 - 1, len2));
        }
        mp[pair<int, int>{len1, len2}] = ans;
        return ans;
    }
};
  • 动态规划法,和递归法思想是一样的,但是比递归法快很多。
class Solution {
public:
    int minDistance(string word1, string word2) {
        int n1 = word1.size(), n2 = word2.size();
        vector<vector<int>> dp(n1 + 1, vector<int>(n2 + 1, 0));
        for(int i = 1; i <= n1; ++i){
            for(int j = 1; j <= n2; ++j){
                if(word1[i - 1] == word2[j - 1]){
                    dp[i][j] = dp[i - 1][j - 1] + 1; 
                }
                else{
                    dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
                }
            }
        }
        return n1 + n2 - 2 * dp[n1][n2];
    }
};
  • 动态规划法2,直接令dp[i][j]表示word1前i个位置和word2前j个位置需要的最小删除次数。最后返回dp[n1][n2]。
class Solution {
public:
    int minDistance(string word1, string word2) {
        int n1 = word1.size(), n2 = word2.size();
        vector<vector<int>> dp(n1 + 1, vector<int>(n2 + 1, 0));
        for(int i = 0; i <= n1; ++i){
            for(int j = 0; j <= n2; ++j){
                if(i == 0 || j == 0){
                    dp[i][j] = i + j;
                    continue;
                }
                if(word1[i - 1] == word2[j - 1]){
                    dp[i][j] = dp[i - 1][j - 1]; 
                }
                else{
                    dp[i][j] = min(dp[i - 1][j], dp[i][j - 1]) + 1;
                }
            }
        }
        return dp[n1][n2];
    }
};
  1. 剑指Offer II020 回文字符串的个数(链接:link,2021.8.22)

  • 核心思想将每个位置都视为回文字符串的中心,向两边扩展。注意有可能没有中心字符,即i和i+1作为中心。
class Solution {
public:
    int countSubstrings(string s) {
        int n = s.size(), ans = 0;
        for(int i = 0; i < n; ++i){
            ans += count(s, n, i, i);
            ans += count(s, n, i, i + 1);
        }
        return ans;
    }

    int count(string& s, int n, int i, int j){
        int cnt = 0;
        while(i >= 0 && j < n && s[i] == s[j]){
            ++cnt;
            --i;
            ++j;
        }
        return cnt;
    }
};
  1. 剑指Offer II没有重复元素集合的全排列(题目编号083,链接:link

  • 回溯法,深度优先搜索。但是时间效率比较低,可能还有更高效的剪枝或者其他方法。
class Solution {
public:
    vector<vector<int>> permute(vector<int>& nums) {
        int len = nums.size();
        vector<int> cur;
        vector<bool> visited(len, false);
        vector<vector<int>> ans;
        dfs(nums, len, 0, cur, visited, ans);
        return ans;
    }

    void dfs(vector<int>& nums, int len, int cur_ind, vector<int> cur, vector<bool> visited, vector<vector<int>>& ans){
        if(cur_ind == len){
            ans.push_back(cur);
            return;
        }
        ++cur_ind;
        for(int i = 0; i < len; ++i){
            if(!visited[i]){
                visited[i] = true;
                cur.push_back(nums[i]);
                dfs(nums, len, cur_ind, cur, visited, ans);
                visited[i] = false;
                cur.pop_back();
            }
        }
    }
};
  1. 多次搜索(题目编号:面试题17.17,link,2021.8.22)

  • 采取了简单的匹配策略,先统计big字符串中a~z出现的位置,然后遍历small。这样存储占用空间还好,但时间效率比较低。
  • 还有对small建立前缀树的方法
class Solution {
public:
    vector<vector<int>> multiSearch(string big, vector<string>& smalls) {
        int n = big.size();
        vector<vector<int>> char_pos(26);
        for(int i = 0; i < n; ++i){
            char_pos[big[i] - 'a'].push_back(i);
        }
        int m = smalls.size();
        vector<vector<int>> ans;
        for(int i = 0; i < m; ++i){
            int cur_size = smalls[i].size();
            vector<int> cur_ans;
            if(cur_size > 0){
                for(auto pos: char_pos[smalls[i][0] - 'a']){
                    bool match = true;
                    if(pos + cur_size > n){
                        continue;
                    }
                    for(int j = pos; j < pos + cur_size; ++j){
                        if(big[j] != smalls[i][j - pos]){
                            match = false;
                            break;
                        }
                    }
                    if(match){
                        cur_ans.push_back(pos);
                    }
                }
            }
            ans.push_back(cur_ans);
        }
        return ans;
    }
};
  • 前缀树法
class Solution {
public:
    static const int N = 100000 + 10;
    int childs[N][26];
    int values[N];
    int nodes_cnt;
    vector<vector<int>> multiSearch(string big, vector<string>& smalls) {
        vector<vector<int>> ans(smalls.size());
        memset(childs, 0, sizeof(childs));
        memset(values, 0xff, sizeof(values));
        nodes_cnt = 0;
        for(int i = 0; i < smalls.size(); ++i){
            insert(smalls[i], i);
        }
        for(int i = 0; i < big.size(); ++i){
            find(big, i, ans);
        }
        return ans;
    }

    void insert(const string& s, int index_of_s){
        int r = 0;
        for(int i = 0; i < s.size(); ++i){
            int idx = s[i] - 'a';
            int& next = childs[r][idx];
            if(next == 0){
                next = ++nodes_cnt;
            }
            r = next;
        }
        values[r] = index_of_s;
    }

    void find(const string& t, int beg, vector<vector<int>>& ans){
        int r = 0;
        for(int j = beg; j < t.size(); ++j){
            int idx = t[j] - 'a';
            const int& next = childs[r][idx];
            if(next == 0){
                return;
            }
            if(values[next] >= 0){
                ans[values[next]].push_back(beg);
            }
            r = next;
        }
    }
};
  1. 堆盘子(题目编号:面试题03.03,link

  • 注意vector为空和某个stack变空之和需要erase。本题没有要求调用popAt,从中间位置弹出元素时重新调整各个stack,但代码也写好了,运行过没有报错,但不确定是否一定符合要求,简单验证了一下,是对的。输入:
["StackOfPlates", "push", "push", "push", "push", "push", "push", "popAt", "popAt", "popAt"]
[[2], [1], [2], [3], [4], [5], [6], [0], [0], [1]]
  • 输出:
[null,null,null,null,null,null,null,2,3,6]
class StackOfPlates {
public:
    vector<stack<int>> vec_stk;
    int stk_cap = 0;
    StackOfPlates(int cap) {
        stk_cap = cap;
        // stack<int> stk;
        // vec_stk.push_back(stk);
    }
    
    void push(int val) {
        if(stk_cap == 0) return;
        if(vec_stk.empty() || vec_stk.back().size() == stk_cap){
            stack<int> stk;
            stk.push(val);
            vec_stk.push_back(stk);
        }
        else{
            vec_stk.back().push(val);
        }
    }
    
    int pop() {
        if(stk_cap == 0) return -1;
        if(vec_stk.empty()){
            return -1;
        }
        int val = vec_stk.back().top();
        vec_stk.back().pop();
        if(vec_stk.back().size() == 0) vec_stk.pop_back();
        return val;
    }
    
    int popAt(int index) {
        if(stk_cap == 0) return -1;
        int n = vec_stk.size();
        if(index < n && vec_stk[index].size() > 0){
            int val = vec_stk[index].top();
            vec_stk[index].pop();
            if(vec_stk[index].empty()){
                vec_stk.erase(vec_stk.begin() + index);
            }
            // reshape_vec_stk();
            return val;
        }
        return -1;
    }

    void reshape_vec_stk(){
        int n = vec_stk.size();
        int cur_stk_ind = 0;
        for(int i = 0; i < n; ++i){
            if(cur_stk_ind == i && vec_stk[i].size() == stk_cap){
                continue;
            }
            if(vec_stk[i].size() == 0){
                continue;
            }
            else{
                stack<int> tmp;
                while(vec_stk[i].size() > 0){
                    tmp.push(vec_stk[i].top());
                    vec_stk[i].pop();
                }
                while(tmp.size() > 0 && vec_stk[cur_stk_ind].size() < stk_cap){
                    vec_stk[cur_stk_ind].push(tmp.top());
                    tmp.pop();
                }
                while(tmp.size() > 0){
                    vec_stk[i].push(tmp.top());
                    tmp.pop();
                }
                if(vec_stk[cur_stk_ind].size() == stk_cap){
                    ++cur_stk_ind;
                }
            }
        }
    }
};

/**
 * Your StackOfPlates object will be instantiated and called as such:
 * StackOfPlates* obj = new StackOfPlates(cap);
 * obj->push(val);
 * int param_2 = obj->pop();
 * int param_3 = obj->popAt(index);
 */
  1. 实现一个魔法字典(题目编号676:link,2021.8.22)

  • 利用字典,记录dictionary中每个单词每一位替换成空格的结果出现的次数,得到“替换字典”,同时用字典记录完整的dictionary,得到“原始字典”。
  • 搜索时,将目标词每一位替换成空格,判断“替换字典”中是否出现至少2次(表示dictionary中至少有2个单词可以只替换一位得到目标词,如果只出现1次,则需要判断目标词在原始字典中是否出现)
class MagicDictionary {
    unordered_map<string, int> ump;
    unordered_map<string, int> dict;
public:
    /** Initialize your data structure here. */
    MagicDictionary() {

    }
    
    void buildDict(vector<string> dictionary) {
        char tmp;
        for(auto& word: dictionary){
            dict[word] = 1;
            for(int i = 0; i < word.size(); ++i){
                tmp = word[i];
                word[i] = ' ';
                ++ump[word];
                word[i] = tmp;
            }
        }
    }
    
    bool search(string searchWord) {
        char tmp;
        string oriWord = searchWord;
        for(int i = 0; i < searchWord.size(); ++i){
            tmp = searchWord[i];
            searchWord[i] = ' ';
            if(ump[searchWord] > 1 || (ump[searchWord] == 1 && dict[oriWord] == 0)){
                return true;
            }
            searchWord[i] = tmp;
        }
        return false;
    }
};

/**
 * Your MagicDictionary object will be instantiated and called as such:
 * MagicDictionary* obj = new MagicDictionary();
 * obj->buildDict(dictionary);
 * bool param_2 = obj->search(searchWord);
 */
  • 小里程碑,题目通过500道。
    在这里插入图片描述
  1. 从英文中重建数字(题目编号:423:link,2021.8.27)

  • 乍一看题目有点难,仔细一想,每个数字的英文字母是有一些特殊字符的,比如five的v,在其他数字中是没有的,因此可以一次一个从26个字母出现次数中提取出独一无二的特殊字符。
class Solution {
public:
    string originalDigits(string s) {
        // zero one two three four five six seven eight nine
        vector<vector<string>> pairs{
            {"zero", "z", "0"},
            {"eight", "g", "8"},
            {"four", "u", "4"},
            {"five", "f", "5"},
            {"three", "h", "3"},
            {"two", "t", "2"},
            {"six", "x", "6"},
            {"nine", "i", "9"},
            {"seven", "v", "7"},
            {"one", "o", "1"}
        };
        vector<int> cnt(26, 0);
        vector<int> num_cnt(10, 0);
        for(char c: s){
            ++cnt[c - 'a'];
        }
        for(auto item: pairs){
            char sp_char = item[1][0];
            int real_num = item[2][0] - '0';
            num_cnt[real_num] = cnt[sp_char - 'a'];
            for(char c: item[0]){
                cnt[c - 'a'] -= num_cnt[real_num];
            }
        }
        string ans;
        for(int i = 0; i <= 9; ++i){
            if(num_cnt[i] > 0) ans += string(num_cnt[i], '0' + i);
        }
        return ans;
    }
};
  1. 零钱兑换II(题目编号518:link,2021.8.27)

  • 关键是如何才不会重复计算。
class Solution {
public:
    int change(int amount, vector<int>& coins) {
        vector<int> dp(amount + 1);
        dp[0] = 1;
        for(int c: coins){
            for(int i = c; i <= amount; ++i){
                dp[i] += dp[i - c];
            }
        }
        return dp[amount];
    }
};
  1. 剑指Offer II 085. 生成匹配的括号(链接:link,2021.8.27)

  • 回溯法
class Solution {
public:
    vector<string> generateParenthesis(int n) {
        vector<string> ans;
        dfs(ans, "", 0, n);
        return ans;
    }

    void dfs(vector<string>& ans, string cur, int left_cnt, const int n){
        if(cur.size() == n * 2){
            ans.push_back(cur);
            return;
        }
        if(left_cnt < n){
            cur += "(";
            dfs(ans, cur, left_cnt + 1, n);
            cur.pop_back();
        }
        if(left_cnt * 2 > cur.size()){
            cur += ")";
            dfs(ans, cur, left_cnt, n);
        }
    }
};
  1. 吃苹果的最大数目(题目编号1705:link,2021.8.27)

  • 核心思路是用优先队列或者map存储日期与这天会过期的苹果数量映射,每天优先吃快过期的。
class Solution {
public:
    int eatenApples(vector<int>& apples, vector<int>& days) {
        map<int, int> day2cnt;
        int res = 0;
        int n = apples.size();
        for(int i = 0; i < n || !day2cnt.empty(); ++i){
            day2cnt.erase(i);
            if(i < n && apples[i] > 0){
                day2cnt[i + days[i]] += apples[i];
            }
            if(!day2cnt.empty()){
                auto iter = day2cnt.begin();
                --iter->second;
                ++res;
                if(iter->second == 0){
                    day2cnt.erase(iter);
                }
            }
        }
        return res;
    }
};
  1. 剑指Offer II 046. 二叉树的右侧视图(链接:link,2021.8.27)

  • 记录当前达到的最大深度,如果当前深度突破了,那么就可以在右视图中看到,记录下来。注意是右视图,因此深度优先遍历从右孩子先处理。
/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    vector<int> rightSideView(TreeNode* root) {
        vector<int> ans;
        int maxDepth = 0;
        dfs(ans, root, maxDepth, 0);
        return ans;
    }

    void dfs(vector<int>& ans, TreeNode* root, int& maxDepth, int curDepth){
        if(!root) return;
        ++curDepth;
        if(curDepth > maxDepth){
            ans.push_back(root->val);
            maxDepth = curDepth; 
        }
        dfs(ans, root->right, maxDepth, curDepth);
        dfs(ans, root->left, maxDepth, curDepth);
    }
};
  1. 网络延迟时间(题目编号743:link,2021.8.27)

  • 最开始自己写的算法,可以通过一半的样例。注意其中的visited,避免重复走。最后超时了才想起来要用Dijstra算法。
class Solution {
public:
    int networkDelayTime(vector<vector<int>>& times, int n, int k) {
        map<int, vector<int>> node2adj;
        map<vector<int>, int> conn_cost;
        vector<int> min_cost(n, INT_MAX);
        for(auto v: times){
            node2adj[v[0] - 1].push_back(v[1] - 1);
            conn_cost[vector<int>{v[0] - 1, v[1] - 1}] = v[2];
        }
        vector<int> adj = node2adj[k - 1];
        vector<bool> visited(n);
        min_cost[k - 1] = 0;
        if(adj.size() > 0){
            visited[k - 1] = true;
            for(int tar: adj){
                update(min_cost, node2adj, conn_cost, k - 1, tar, visited);
            }
        }
        int ans = *max_element(min_cost.begin(), min_cost.end());
        return ans == INT_MAX ? -1: ans;
    }

    void update(vector<int>& min_cost, map<int, vector<int>>& node2adj, map<vector<int>, int>& conn_cost, int src, int tar, vector<bool> visited){
        if(visited[tar]) return;
        visited[tar] = true;
        min_cost[tar] = min(min_cost[tar], min_cost[src] + conn_cost[vector<int>{src, tar}]);
        for(int t: node2adj[tar]){
            if(t != src && !visited[t]){
                update(min_cost, node2adj, conn_cost, tar, t, visited);
            }
        }
    }
};
  • Dijstra算法:单源最短路径算法,将所有节点分成两类:已确定从起点到当前点的最短路长度的节点,以及未确定从起点到当前点的最短路长度的节点(下面简称「未确定节点」和「已确定节点」)。每次从「未确定节点」中取一个与起点距离最短的点,将它归类为「已确定节点」,并用它「更新」从起点到其他所有「未确定节点」的距离。直到所有点都被归类为「已确定节点」。
class Solution {
public:
    int networkDelayTime(vector<vector<int>>& times, int n, int k) {
        const int inf = INT_MAX / 2;
        vector<vector<int>> g(n, vector<int>(n, inf));
        for(auto& t: times){
            int x = t[0] - 1, y = t[1] - 1;
            g[x][y] = t[2];
        }
        vector<int> dist(n, inf);
        dist[k - 1] = 0;
        vector<int> used(n);
        for(int i = 0; i < n; ++i){
            int x = -1;
            for(int y = 0; y < n; ++y){
                if(!used[y] && (x == -1 || dist[y] < dist[x])){
                    x = y;
                }
            }
            used[x] = true;
            for(int y = 0; y < n; ++y){
                dist[y] = min(dist[y], dist[x] + g[x][y]);
            }
        }
        int ans = *max_element(dist.begin(), dist.end());
        return ans == inf ? -1: ans;
    }
};
  1. 插入、删除和随机访问都是 O(1) 的容器(题目编号:剑指offerII 面试题30,link)

  • 数据用数组存储才能随机访问O(1)、插入O(1)、删除O(1)。需要查询数据是否存在也是O(1)的话需要用哈希表。
class RandomizedSet {
private:
    unordered_map<int, int> ump;
    vector<int> nums;

public:
    /** Initialize your data structure here. */
    RandomizedSet() {

    }
    
    /** Inserts a value to the set. Returns true if the set did not already contain the specified element. */
    bool insert(int val) {
        if(ump.count(val)){
            return false;
        }
        nums.push_back(val);
        ump[val] = nums.size() - 1;
        return true;
    }
    
    /** Removes a value from the set. Returns true if the set contained the specified element. */
    bool remove(int val) {
        if(ump.count(val)){
            int remove_pose = ump[val];
            nums[remove_pose] = nums.back();
            ump[nums.back()] = remove_pose;
            nums.pop_back();
            ump.erase(val);
            return true;
        }
        return false;
    }
    
    /** Get a random element from the set. */
    int getRandom() {
        return nums[rand() % nums.size()];
    }
};

/**
 * Your RandomizedSet object will be instantiated and called as such:
 * RandomizedSet* obj = new RandomizedSet();
 * bool param_1 = obj->insert(val);
 * bool param_2 = obj->remove(val);
 * int param_3 = obj->getRandom();
 */
  1. 获取好友已观看的视频(题目编号1311:link

  • 第k层好友关系用广度优先遍历得到
  • 注意排序时频次相等时的排序规则
using PSI = pair<string, int>;
class Solution {
public:
    vector<string> watchedVideosByFriends(vector<vector<string>>& watchedVideos, vector<vector<int>>& friends, int id, int level) {
        int n = watchedVideos.size();
        vector<string> ans;
        if(id < 0 || id >= n || level < 1 || level > n) return ans;
        unordered_map<int, bool> visited;
        queue<int> kfriends;
        kfriends.push(id);
        visited[id] = true;
        int round = 0;
        while(round++ < level){
            int sz = kfriends.size();
            while(sz--){
                int cur_id = kfriends.front();
                kfriends.pop();
                for(auto& k: friends[cur_id]){
                    if(visited[k]){
                        continue;
                    }
                    visited[k] = true;
                    kfriends.push(k);
                }
            }
        }
        unordered_map<string, int> count;
        while(kfriends.size()){
            int cur_id = kfriends.front();
            kfriends.pop();
            for(auto& k: watchedVideos[cur_id]){
                ++count[k];
            }
        }
        vector<PSI> videos(count.begin(), count.end());
        sort(videos.begin(), videos.end(), func);
        for(auto& item:videos){
            ans.push_back(item.first);
        }
        return ans;
    }

    static bool func(PSI a, PSI b){
        return a.second < b.second || (a.second == b.second && a.first < b.first);
    }
};
  1. 子字符串突变后可能得到的最大整数(题目编号1946:link

  • 贪心法,从前往后找第一个会变大的数开始,然后往后找第一个会变小的数停止。
class Solution {
public:
    string maximumNumber(string num, vector<int>& change) {
        int n = num.size();
        for(int i = 0; i < n; ++i){
            if(change[num[i] - '0'] > num[i] - '0'){
                while(i < n && change[num[i] - '0'] >= num[i] - '0'){
                    num[i] = '0' + change[num[i] - '0'];
                    ++i;
                }
                break;
            }
        }
        return num;
    }
};
  1. 解码异或后的排列(题目编号1734:link,2021.9.26)

  • 异或问题就要利用异或的2个性质:a ^ 0 = a,a ^ a=0,a表示任意数字。
  • encoded数组构成为perm[0] ^ perm[1],perm[1] ^ perm[2], perm[2] ^ perm[3]…分析可以发现,由于n是奇数,那么encoded数组的长度是偶数,那么如果间隔取encoded数组的值相与,取0、2、4位置处,那么就只剩下perm[n-1]未参与“与”运算,得到的值记为xor_except_last,而1~n所有数字的与记为xor_all,那么两个xor值再进行与运算,就只有perm[n-1]只参与了1次与运算,最后的结果就是perm[n-1]。有了perm[n-1],就可以利用encoded数组倒序推断出perm[n-2]到perm[0]。
class Solution {
public:
    vector<int> decode(vector<int>& encoded) {
        int xor_all = 0, n = encoded.size() + 1;
        for(int i = 1; i <= n; ++i){
            xor_all ^= i;
        }
        int xor_except_last = 0;
        for(int i = 0; i < n - 1; i += 2){
            xor_except_last ^= encoded[i];
        }
        vector<int> perm(n);
        perm[n - 1] = xor_except_last ^ xor_all;
        for(int i = n - 2; i >= 0; --i){
            perm[i] = encoded[i] ^ perm[i + 1];
        }
        return perm;
    }
};
  1. 二叉搜索树与双向链表(题目编号:剑指offer36,link,2021.9.26)

  • 树的题目很多采用递归法求解,递归法关键是找到递归终止条件,以及终止返回上一层次后如何处理。本题选择使用vector记录当前已经转换完成的双向链表的头结点和尾节点。题目要求是双向循环链表,因此最后还要将头尾节点连起来。
  • 该方法速度很快,但内存占用比较多。
/*
// Definition for a Node.
class Node {
public:
    int val;
    Node* left;
    Node* right;

    Node() {}

    Node(int _val) {
        val = _val;
        left = NULL;
        right = NULL;
    }

    Node(int _val, Node* _left, Node* _right) {
        val = _val;
        left = _left;
        right = _right;
    }
};
*/
class Solution {
public:
    Node* treeToDoublyList(Node* root) {
        if(!root) return root;
        vector<Node*> ans = func(root);
        ans[0]->left = ans[1];
        ans[1]->right = ans[0];
        return ans[0];
    }

    vector<Node*> func(Node* root){
        vector<Node*> vec{root, root};
        if(!root) return vec;
        if(root->left){
            vector<Node*> tmp_vec = func(root->left);
            tmp_vec[1]->right = root;
            root->left = tmp_vec[1];
            vec[0] = tmp_vec[0];
        }
        if(root->right){
            vector<Node*> tmp_vec = func(root->right);
            root->right = tmp_vec[0];
            tmp_vec[0]->left = root;
            vec[1] = tmp_vec[1];
        }
        return vec;
    }

};
  • 参考题解区的答案,获得内存上更优的方法。代码更简洁。
/*
// Definition for a Node.
class Node {
public:
    int val;
    Node* left;
    Node* right;

    Node() {}

    Node(int _val) {
        val = _val;
        left = NULL;
        right = NULL;
    }

    Node(int _val, Node* _left, Node* _right) {
        val = _val;
        left = _left;
        right = _right;
    }
};
*/
class Solution {
private:
    Node *pre = nullptr, *head = nullptr;
public:
    Node* treeToDoublyList(Node* root) {
        if(!root) return root;
        dfs(root);
        pre->right = head;
        head->left = pre;
        return head;
    }

    void dfs(Node* root){
        if(!root) return;
        dfs(root->left);
        if(!pre){
            head = root;
        }
        else{
            pre->right = root;
        }
        root->left = pre;
        pre = root;
        dfs(root->right);
    }

};
  1. 最长连续序列(题目编号:剑指Offer II 119,link,2021.9.26)

  • 利用map/set的快速搜索特性,从中取出一个数字,然后往前往后判断数字是否存在,计算长度,并从map/set中删除对应的连续数字。注意,要记得删除当前数字,否则会死循环。
class Solution {
public:
    int longestConsecutive(vector<int>& nums) {
        unordered_set<int> st;
        for(auto& n: nums){
            st.insert(n);
        }
        int ans = 0;
        while(st.size()){
            int n = *st.begin();
            int left = n - 1;
            while(st.count(left)){
                st.erase(left);
                --left;
            }
            int right = n + 1;
            while(st.count(right)){
                st.erase(right);
                ++right;
            }
            ans = max(ans, right - left - 1);
            st.erase(n);
        }
        return ans;
    }
};
  1. 扣分后的最大得分(link,2021.9.29)

  • 这题肯定要用动态规划的方法,用dp[i][j]表示最后一次选第i行第j列的最大得分,但是求解dp[i][j]需要遍历dp[i-1][j’],因此时间复杂度是mn^2。dp[i][j] = max_j’{dp[i-1][j’]+|j’-j|}+points[i][j]。
  • 上述动态规划算法可以优化,对j’<j和j’>j,正向和逆向遍历求解dp[i][j],时间复杂度O(mn)。
  • 思路不太好理解,以后再思考一下。
class Solution {
public:
    long long maxPoints(vector<vector<int>>& points) {
        int m = points.size();
        if(m == 0) return 0;
        int n = points[0].size();
        vector<long long> f(m);
        for(int i = 0; i < m; ++i){
            vector<long long> g(n);
            long long best = LLONG_MIN;
            for(int j = 0; j < n; ++j){
                best = max(best, f[j] + j);
                g[j] = max(g[j], best + points[i][j] - j);
            }
            best = LLONG_MIN;
            for(int j = n - 1; j >= 0; --j){
                best = max(best, f[j] - j);
                g[j] = max(g[j], best + points[i][j] + j);
            }
            f = move(g);
        }
        return *max_element(f.begin(), f.end());
    }
};
  1. 火柴拼正方形(题目编号473:link,2021.9.29)

  • 回溯法,用4维数据维护每条边的长度。关键是剪枝的技巧,如果当前边的长度超过1/4总长,跳过,如果当前边和上一条边的长度一样,也跳过,上一条边这个长度搜不到,当前边也搜不到。
class Solution {
public:
    bool makesquare(vector<int>& matchsticks) {
        int sum = 0;
        for(auto& num: matchsticks){
            sum += num;
        }
        if(sum == 0 || sum % 4 != 0) return false; //剪枝
        vector<int> adds(4, 0);
        sort(matchsticks.begin(), matchsticks.end(), greater<int>());
        return dfs(0, adds, matchsticks, sum);
    }

    bool dfs(int beg, vector<int>& adds, vector<int>& matchsticks, int sum){
        if(*max_element(adds.begin(), adds.end()) > sum / 4) return false;
        if(beg == matchsticks.size()){
            if(adds[0] == adds[1] && adds[1] == adds[2] && adds[2] == adds[3]){
                return true;
            }
            return false;
        }
        for(int i = 0; i < 4; ++i){
            if(adds[i] + matchsticks[beg] > sum / 4 || (i > 0 && adds[i] == adds[i - 1])) continue; //关键的剪枝
            adds[i] += matchsticks[beg];
            if(dfs(beg+1, adds, matchsticks, sum)) return true;
            adds[i] -= matchsticks[beg];
        }
        return false;
    }
};
  1. 设计地铁系统(题目编号1396:link,2021.9.29)

  • 题目并不难,用哈希表记录即可。
class UndergroundSystem {
private:
    map<int, pair<string, int>> mp_in;
    map<pair<string, string>, pair<double, int>> mp_time;
public:
    UndergroundSystem() {

    }
    
    void checkIn(int id, string stationName, int t) {
        mp_in[id] = pair<string, int>(stationName, t);
    }
    
    void checkOut(int id, string stationName, int t) {
        string startStationName = mp_in[id].first;
        int startTime = mp_in[id].second;
        double meanTime = mp_time[pair<string, string>(startStationName, stationName)].first;
        int count = mp_time[pair<string, string>(startStationName, stationName)].second;
        pair<double, int> new_result = pair<double, int>(count * 1.0 / (count + 1) * meanTime + (t - startTime) * 1.0 / (count + 1), count + 1);
        mp_time[pair<string, string>(startStationName, stationName)] = new_result;
    }
    
    double getAverageTime(string startStation, string endStation) {
        return mp_time[pair<string, string>(startStation, endStation)].first;
    }
};

/**
 * Your UndergroundSystem object will be instantiated and called as such:
 * UndergroundSystem* obj = new UndergroundSystem();
 * obj->checkIn(id,stationName,t);
 * obj->checkOut(id,stationName,t);
 * double param_3 = obj->getAverageTime(startStation,endStation);
 */
  1. 爱吃香蕉的珂珂(题目编号875:link,2021.10.6)

  • 最容易想到的方法是从小到大遍历K,看哪个速度可以吃完。
  • 二分搜索法:若最终答案为T,那么对于K<T,都是吃不完,对于K>=T,是都可以吃完的。单调函数可以采用二分搜索的方法,加速搜索。
class Solution {
public:
    int minEatingSpeed(vector<int>& piles, int h) {
        int lo = 1, hi = pow(10, 9);
        while(lo < hi){
            int mid = lo + (hi - lo) / 2;
            if(check(piles, h, mid)){
                hi = mid;
            }
            else{
                lo = mid + 1;
            }
        }
        return hi;
    }

    bool check(vector<int>& piles, int h, int K){
        int time = 0;
        for(auto& p: piles){
            time += (p - 1) / K + 1;
        }
        return time <= h;
    }
};
  1. 单词长度的最大乘积(题目编号:剑指Offer II 005,link,2021.10.6)

  • 用一个26位int数字hash值表示字符串出现过哪些字符,两个hash值按位与,如果结果为0,则说明没有重复字符。
class Solution {
public:
    int maxProduct(vector<string>& words) {
        int n = words.size();
        if(n <= 1) return 0;
        vector<int> hash(n);
        for(int i = 0; i < n; ++i){
            for(auto& c: words[i]){
                hash[i] |= 1 << (c - 'a');
            }
        }
        int ans = 0;
        for(int i = 0; i < n - 1; ++i){
            for(int j = i + 1; j < n; ++j){
                int tmp = words[i].size() * words[j].size();
                if(tmp > ans && ((hash[i] & hash[j]) == 0)){
                    ans = tmp;
                }
            }
        }
        return ans;
    }
};
  1. 将字符串拆成递减的连续值(题目编号:1849,link,2021.10.6)

  • 这道题目耗费了很长时间,主要在解决各种特殊用例和整形数越界的问题。
  • 思路是先确定递减数列的第一个数num,然后后续依次搜索n-1,n-2,等等。
  • 特殊情况包括:
    降序后某一个数变成0,后续只需全0即可,不需再寻找-1。
    第一个数就是0,这种情况是不符合题目要求的。
class Solution {
public:
    bool splitString(string s) {
        int n = s.size();
        for(int digit = 1; digit <= n - 1; ++digit){
            unsigned long long num = 0;
            for(int i = 0; i < digit; ++i){
                num = num * 10 + (s[i] - '0');
            }
            if(num == 0) continue; //解决第一个数字就是0的问题
            int ind = digit;
            bool ans_flag = true, complete_flag = false;
            while(ind < n){
                unsigned long long t_num = 0;
                complete_flag = false;
                while(ind < n){
                    if(num == 0){
                        while(ind < n && s[ind] == '0'){
                            ++ind;
                        }
                        //解决降序数变为0的特殊情况,之后只要全是0就行。
                        if(ind == n){
                            return true;
                        }
                        ans_flag = false;
                        break;
                    }
                    t_num = t_num * 10 + (s[ind] - '0');
                    ++ind;
                    if(t_num == num - 1){
                        complete_flag = true;
                        break;
                    }
                    else if(t_num > num - 1){
                        ans_flag = false;
                        break;
                    }
                }
                if(!ans_flag){
                    break;
                }
                num = t_num;
            }
            if(ans_flag && complete_flag){
                return true;
            }
        }
        return false;
    }
};
  1. 删除最短的子数组使剩余数组有序(题目编号1574:link,2021.10.23)

  • 关键是要理解题目,子数组是连续的,子序列一般指不连续的。
  • 先找到左边递增序列和右边的递增数列,如果连起来是递增的,就把中间删掉,如果不严格递增,就要找到哪里是最合适的连接点。
  • 遍历左边的递增序列,在右边的递增序列里找到对应的连接点,可以采用二分搜索,
class Solution {
public:
    int findLengthOfShortestSubarray(vector<int>& arr) {
        int left = 1, n = arr.size(), right = n - 1;
        while(left < n && arr[left - 1] <= arr[left]) ++left;
        while(right >= 1 && arr[right - 1] <= arr[right]) --right;
        if(left > right) return 0;
        int ans = right;
        for(int i = 0; i < left; ++i){
            int e = arr[i];
            int pos = lower_bound(arr.begin() + right, arr.end(), e) - arr.begin();
            ans = min(ans, pos - i - 1);
        }
        return ans;
    }
};
  1. 所有子集(题目编号:剑指Offer II 079,link,2021.10.23)

  • 这个就是简单的遍历搜索,分为用和不用当前数字,然后递归搜索下一位。
class Solution {
public:
    vector<vector<int>> subsets(vector<int>& nums) {
        vector<vector<int>> ans;
        int n = nums.size();
        traverse(ans, nums, n, 0, vector<int>());
        return ans;
    }

    void traverse(vector<vector<int>>& ans, vector<int>& nums, int& n, int cur_ind, vector<int> cur_subset){
        if(cur_ind == n){
            ans.push_back(cur_subset);
            return;
        }
        traverse(ans, nums, n, cur_ind + 1, cur_subset);
        cur_subset.push_back(nums[cur_ind]);
        traverse(ans, nums, n, cur_ind + 1, cur_subset);
    }
};
  1. 到达目的地的方案数(题目编号:1976,link,2021.10.23)

  • 题目的解决分为几步,首先是寻找起点到终点的最短距离,使用dijstra算法,然后构造有向图g,如果满足dist[0][j]-dist[0][i]=dist[i][j],那么存在从i到j的边(可证明是无环的),接着采用动态规划,求解最终的方案数,用数组f表示,f[n-1]=1,倒序前推,对于i,遍历g中有边连接的下一个节点j,对方案数求和,最终的f[0]即为答案。
  • 这道题综合了最短路径和动态规划,适合面试
  • 下面解法涉及到了function模板和lambda函数,都是知识点参考此博客
class Solution {
private:
    int mod = pow(10, 9) + 7;
public:
    int countPaths(int n, vector<vector<int>>& roads) {
        vector<vector<long long>> dist(n, vector<long long>(n, LLONG_MAX / 2));
        for(int i = 0; i < n; ++i) dist[i][i] = 0;
        for(auto&& info: roads){
            int a = info[0], b = info[1], d = info[2];
            dist[a][b] = dist[b][a] = d;
        }
        //Dijstra
        vector<bool> used(n);
        for(int _ = 0; _ < n; ++_){
            int u = -1;
            for(int i = 0; i < n; ++i){
                if(!used[i] && (u == -1 || dist[0][i] < dist[0][u])){
                    u = i;
                }
            }
            used[u] = true;
            for(int i = 0; i < n; ++i){
                dist[0][i] = min(dist[0][i], dist[0][u] + dist[u][i]);
            }
        }
        //构造图G
        vector<vector<int>> g(n);
        for(auto&& info: roads){
            int a = info[0], b = info[1], d = info[2];
            if(dist[0][b] - dist[0][a] == d){
                g[a].push_back(b);
            }
            else if(dist[0][a] - dist[0][b] == d){
                g[b].push_back(a);
            }
        }
        //最终结果
        vector<int> f(n, -1);
        function<int(int)> dfs = [&](int u){
            if(u == n - 1) return 1;
            if(f[u] != -1) return f[u];
            f[u] = 0;
            for(auto& v: g[u]){
                f[u] += dfs(v);
                if(f[u] > mod){
                    f[u] -= mod;
                }
            }
            return f[u];
        };
        return dfs(0);
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值