LeetCode 323周赛

news2024/12/23 11:08:19

2500. 删除每行中的最大值

给你一个 m x n 大小的矩阵 grid ,由若干正整数组成。

执行下述操作,直到 grid 变为空矩阵:

  • 从每一行删除值最大的元素。如果存在多个这样的值,删除其中任何一个。
  • 将删除元素中的最大值与答案相加。

注意 每执行一次操作,矩阵中列的数据就会减 1 。

返回执行上述操作后的答案。

提示:

  • m == grid.length
  • n == grid[i].length
  • 1 <= m, n <= 50
  • 1 <= grid[i][j] <= 100

示例:


输入:grid = [[1,2,4],[3,3,1]]
输出:8
解释:上图展示在每一步中需要移除的值。
- 在第一步操作中,从第一行删除 4 ,从第二行删除 3(注意,有两个单元格中的值为 3 ,我们可以删除任一)。在答案上加 4 。
- 在第二步操作中,从第一行删除 2 ,从第二行删除 3 。在答案上加 3 。
- 在第三步操作中,从第一行删除 1 ,从第二行删除 1 。在答案上加 1 。
最终,答案 = 4 + 3 + 1 = 8 。

思路:

直接对每一行元素进行排序,然后遍历一次即可。时间复杂度 O ( m n l o g n ) O(mnlogn) O(mnlogn)

class Solution {
public:
    int deleteGreatestValue(vector<vector<int>>& grid) {
        int m = grid.size(), n = grid[0].size();
        // 每一行元素从小到大排序
        for (auto& row : grid) sort(row.begin(), row.end());
        int ans = 0;
        // 从最右侧的列开始, 每次删除某列, 并取该列的最大值
        for (int i = n - 1; i >= 0; i--) {
            int mx = 0;
            for (int j = 0; j < m; j++) mx = max(mx, grid[j][i]);
            ans += mx;
        }
        return ans;
    }
};

2501. 数组中最长的方波

给你一个整数数组 nums 。如果 nums 的子序列满足下述条件,则认为该子序列是一个 方波

  • 子序列的长度至少为 2 ,并且
  • 将子序列从小到大排序 之后 ,除第一个元素外,每个元素都是前一个元素的 平方

返回 nums最长方波 的长度,如果不存在 方波 则返回 -1

子序列 也是一个数组,可以由另一个数组删除一些或不删除元素且不改变剩余元素的顺序得到。

提示:

  • 2 <= nums.length <= 10^5
  • 2 <= nums[i] <= 10^5

示例:

输入:nums = [4,3,6,16,8,2]
输出:3
解释:选出子序列 [4,16,2] 。排序后,得到 [2,4,16] 。
- 4 = 2 * 2.
- 16 = 4 * 4.
因此,[4,16,2] 是一个方波.
可以证明长度为 4 的子序列都不是方波。

思路:

排序+递推

由于方波子序列的内部是顺序无关的,只要排序后满足后一个数是前一个数的平方即可。那么,很自然的,我们可以先对整个数组排个序。

我们可以用动态规划的思维来看待这道题。

f[i]表示以nums[i]作为结尾的方波子序列的最大长度。我们考虑状态转移时,对于f[i],既然是以nums[i]结尾,那么我们考虑方波子序列的倒数第二个数。倒数第二个数只能是nums[i]的平方根。

考虑到这,好像f[i]中的i表示下标不是很合适。应该用i直接表示数字。即,f[i]表示,以数字i结尾的,最长的方波子序列的长度。状态转移为:f[i] = f[sqrt(i)] + 1

这样来看,需要开根号,为了避免开根号产生可能的浮点数,我们不开根号(并且开根号的运算量并不是 O ( 1 ) O(1) O(1)的),而选用开根号的逆运算,平方。

那我们的状态表示就要倒着来了,设f[i]表示以数字i开头的,最长的方波子序列的长度。状态转移,我们考虑子序列的第二个数,则f[i] = f[i * i] + 1

i的状态依赖于i * i,那么我们状态的计算要从大到小进行。

由于状态表示中的i是数字,而不是下标。数字与下标不同,下标是连续的,我们可以开一个数组来存储状态;而数字是离散的,那么我们采用一个哈希表来存储状态即可。

总的时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn),主要是排序的时间开销,后续状态计算由于使用了哈希表,只需要一次遍历,复杂度为 O ( n ) O(n) O(n)

typedef long long LL;
class Solution {
public:
    int longestSquareStreak(vector<int>& nums) {
        int n = nums.size(), ans = 1;
        sort(nums.begin(), nums.end());
        unordered_map<int, int> f; // f[i] = x 表示以数字i开头的方波子序列的最长长度为x
        // 从大到小遍历整个数组, 并更新哈希表
        for (int i = n - 1; i >= 0; i--) {
            int x = nums[i];
            // 若 x * x 超过 int 限制, 或者 f 中不存在 x * x, 则初始化其长度为1
            if (x >= INT_MAX / x || !f.count(x * x)) f[x] = 1;
            else f[x] = f[x * x] + 1;
            ans = max(ans, f[x]); 
        }
        return ans == 1 ? -1 : ans;
    }
};

2502. 设计内存分配器

给你一个整数 n ,表示下标从 0 开始的内存数组的大小。所有内存单元开始都是空闲的。

请你设计一个具备以下功能的内存分配器:

  1. 分配 一块大小为 size 的连续空闲内存单元并赋 id mID
  2. 释放 给定 id mID 对应的所有内存单元。

注意:

  • 多个块可以被分配到同一个 mID
  • 你必须释放 mID 对应的所有内存单元,即便这些内存单元被分配在不同的块中。

实现 Allocator 类:

  • Allocator(int n) 使用一个大小为 n 的内存数组初始化 Allocator 对象。
  • int allocate(int size, int mID) 找出大小为 size 个连续空闲内存单元且位于 最左侧 的块,分配并赋 id mID 。返回块的第一个下标。如果不存在这样的块,返回 -1
  • int free(int mID) 释放 id mID 对应的所有内存单元。返回释放的内存单元数目。

提示:

  • 1 <= n, size, mID <= 1000
  • 最多调用 allocatefree 方法 1000

示例:

输入
["Allocator", "allocate", "allocate", "allocate", "free", "allocate", "allocate", "allocate", "free", "allocate", "free"]
[[10], [1, 1], [1, 2], [1, 3], [2], [3, 4], [1, 1], [1, 1], [1], [10, 2], [7]]
输出
[null, 0, 1, 2, 1, 3, 1, 6, 3, -1, 0]

解释
Allocator loc = new Allocator(10); // 初始化一个大小为 10 的内存数组,所有内存单元都是空闲的。
loc.allocate(1, 1); // 最左侧的块的第一个下标是 0 。内存数组变为 [1, , , , , , , , , ]。返回 0 。
loc.allocate(1, 2); // 最左侧的块的第一个下标是 1 。内存数组变为 [1,2, , , , , , , , ]。返回 1 。
loc.allocate(1, 3); // 最左侧的块的第一个下标是 2 。内存数组变为 [1,2,3, , , , , , , ]。返回 2 。
loc.free(2); // 释放 mID 为 2 的所有内存单元。内存数组变为 [1, ,3, , , , , , , ] 。返回 1 ,因为只有 1 个 mID 为 2 的内存单元。
loc.allocate(3, 4); // 最左侧的块的第一个下标是 3 。内存数组变为 [1, ,3,4,4,4, , , , ]。返回 3 。
loc.allocate(1, 1); // 最左侧的块的第一个下标是 1 。内存数组变为 [1,1,3,4,4,4, , , , ]。返回 1 。
loc.allocate(1, 1); // 最左侧的块的第一个下标是 6 。内存数组变为 [1,1,3,4,4,4,1, , , ]。返回 6 。
loc.free(1); // 释放 mID 为 1 的所有内存单元。内存数组变为 [ , ,3,4,4,4, , , , ] 。返回 3 ,因为有 3 个 mID 为 1 的内存单元。
loc.allocate(10, 2); // 无法找出长度为 10 个连续空闲内存单元的空闲块,所有返回 -1 。
loc.free(7); // 释放 mID 为 7 的所有内存单元。内存数组保持原状,因为不存在 mID 为 7 的内存单元。返回 0 。

思路:

比赛时,我的想法是:用链表来维护空闲的内存块和已经被分配出去的内存块,其中对于某一个id所持有的你存块,用一个哈希表来存储。

内存分配时,只要沿着空闲内存块的链表依次进行遍历查找,找到第一块大小足够的内存块,然后进行切分出去就好了。难点主要在于内存回收时,当一块内存被释放,则需要将其重新加入到空闲链表中,而如果这个内存块与前面或者后面的内存块相邻,则需要进行内存块的合并。

先暂且不考虑时间复杂度,这样的做法应当是行得通的,然而我可能是一些细节和边界没有处理好,提交一直WA,一直坐牢到比赛结束。现在尝试用这种思路重写一遍代码(改用更熟悉的 Java 来写这道题)。提交报了WA,对照着错误数据打断点进行debug,一行一行的观察,最后发现是释放内存时,将内存块重新插入空闲链表时,判断条件写错了。

这种类型的题目,真的很考察编码功底和细节处理能力。稍不注意就会出错。(一开始free方法里我都忘了从哈希表中将对应的键值进行移除,真是惭愧)

力扣上类似的题目还有,实现一个LRULFU

// Java
// 10ms  42.3MB
class Allocator {

    	// 内存块节点
		class Node {

			int begin;

			int end;

			Node pre;

			Node next;

			Node(int begin, int end) {
				this.begin = begin;
				this.end = end;
			}
		}

    	// 空闲内存链表
		Node freeHead;

		Node freeTail;

    	// 已被占用的内存块
		Map<Integer, List<Node>> used;


    	// 将一个内存块重新插入空闲链表
		private void insertToFreeList(Node e) {
			Node pre = freeHead, cur = freeHead.next;
			// 找到需要插入的位置
			while (cur.end <= e.begin) {
                // 刚开始这里的判断条件写错了, 写成 > 了, 调试了半天
                pre = pre.next;
                cur = cur.next;
            }
			// 插入这个节点
			e.pre = pre;
			e.next = pre.next;
			e.pre.next = e;
			e.next.pre = e;
			// 进行可能的合并
			for (int i = 0; i < 2; i++) {
				if (pre.end == e.begin) {
					// 合并
					pre.end = e.end;
					pre.next = e.next;
					e.next.pre = pre;
					e.next = e.pre = null; // 彻底断干净
					e = pre.next;
				} else {
					pre = e;
					e = e.next;
				}
			}
		}
		
    	// 分配内存时, 从一个已找到的内存块中进行切割
		private void cutOff(Node e, int mID, int size) {
			int cutBegin = e.begin, cutEnd = e.begin + size;
			if (cutEnd == e.end) {
				// 整块直接切掉
				e.pre.next = e.next;
				e.next.pre = e.pre;
				e.next = e.pre = null; // 断干净
			} else {
				// 切掉后还剩一小块
				e.begin = cutEnd;
			}
			// 将切下的这一块内存加入到哈希表
			if (!used.containsKey(mID)) used.put(mID, new ArrayList<>());
			used.get(mID).add(new Node(cutBegin, cutEnd));
		}

		public Allocator(int n) {
			freeHead = new Node(-1, -1);
			freeTail = new Node(n + 1, n + 1);
			freeHead.next = freeTail;
			freeTail.pre = freeHead;
			insertToFreeList(new Node(0, n)); // 空闲链表中初始化一块内存
			used = new HashMap<>();
		}

		public int allocate(int size, int mID) {
			Node cur = freeHead.next;
			// 找到第一块>= size的空闲内存块
			while (cur != freeTail) {
				if (cur.end - cur.begin >= size) break;
				else cur = cur.next;
			}
			// 没找到
			if (cur.end - cur.begin < size) return -1;
			// 找到了
			int ret = cur.begin;
            // 切掉一块内存
			cutOff(cur, mID, size);
			return ret;
		}

		public int free(int mID) {
			// 不存在mID对应的内存单元
			if (!used.containsKey(mID) || used.get(mID).isEmpty()) return 0;
			// 重新插入到 freeList
			int sum = 0;
			for (Node e : used.get(mID)) {
                sum += e.end - e.begin;
				insertToFreeList(e);
			}
            used.get(mID).clear(); // 从哈希表中删除
			return sum;
		}
	}

随后,我看了排名靠前的几位大佬的代码,给跪了!大佬们的思路都很简洁,只开了一个数组,然后如果某个内存块被分配给了某个mID,则在对应的位置打上标记。真的太简洁了,在打周赛时,应该采用尽可能简单的思路,在短时间内通过题目才是正道。(这道题目数据范围比较小(1000的数据范围,可以直接用 O ( n 2 ) O(n^2) O(n2) 的做法),所以可以直接暴力做)

如果数据范围大的话,考察的就是数据结构了,可能需要用到平衡树等来做,就比较难了。

// Java
// 24ms 42.3MB
class Allocator {

    private int[] A;

    public Allocator(int n) {
        A = new int[n];
    }
    
    public int allocate(int size, int mID) {
        int cnt = 0;
        for (int i = 0; i < A.length; i++) {
            if (A[i] == 0) cnt++;
            else cnt = 0;
            if (cnt == size) {
                for (int j = i - size + 1; j <= i; j++) A[j] = mID;
                return i - size + 1;
            }
        }
        return -1;
    }
    
    public int free(int mID) {
        int cnt = 0;
        for (int i = 0; i < A.length; i++) {
            if (A[i] == mID) {
                A[i] = 0;
                cnt++;
            }
        }
        return cnt;
    }
}

2503. 矩阵查询可获得的最大分数

给你一个大小为 m x n 的整数矩阵 grid 和一个大小为 k 的数组 queries

找出一个大小为 k 的数组 answer ,且满足对于每个整数 queres[i] ,你从矩阵 左上角 单元格开始,重复以下过程:

  • 如果 queries[i] 严格 大于你当前所处位置单元格,如果该单元格是第一次访问,则获得 1 分,并且你可以移动到所有 4 个方向(上、下、左、右)上任一 相邻 单元格。
  • 否则,你不能获得任何分,并且结束这一过程。

在过程结束后,answer[i] 是你可以获得的最大分数。注意,对于每个查询,你可以访问同一个单元格 多次

返回结果数组 answer

提示:

  • m == grid.length
  • n == grid[i].length
  • 2 <= m, n <= 1000
  • 4 <= m * n <= 10^5
  • k == queries.length
  • 1 <= k <= 10^4
  • 1 <= grid[i][j], queries[i] <= 10^6

示例:

输入:grid = [[1,2,3],[2,5,7],[3,5,1]], queries = [5,6,2]
输出:[5,8,1]
解释:上图展示了每个查询中访问并获得分数的单元格。

思路:

这其实是一个连通块问题,对于每个询问x,可以把矩阵中所有大于x的格子都看成障碍物,然后此次询问的答案就是从左上角能到达的连通块的大小。(每个格子只有在第一次访问时才获得1分,并且走过的格子可以重复走,也就是说,某一次的queries[i],要求的时从左上角开始走,能走到的连通块的格子的数量,并且连通块里所有格子都不能超过queries[i]

很明显,若有queries[i] < queries[j],那么,在queris[i]限制下能走到的全部格子,在queries[j]下也能全部走到。

所以,我们可以先将queries数组从小到大排个序,由于后一个queries一定比前一个queris大,那么能走到的格子数总是递增的。

对于访问格子,我们可以

  • 用并查集对相邻的格子进行合并,并维护连通块的大小;

  • 也可以用一个小根堆,来存储当前所有可能访问的格子,并每次针对当前queries的限制,不断从小根堆中取出不超过queries的格子进行访问。

两种思路的核心都在于:要先观察出,需要对查询进行离线处理(不是每次查询都要做一次操作,而是先把全部查询缓存下来,然后一次性求出所有查询的结果)

解法一:离线查询 + 并查集

// 348ms 
// 维护点权
class Solution {
public:

    vector<int> p;
    
    vector<int> cnt; // 连通块中点的个数

    int find(int x) {
        if (x != p[x]) p[x] = find(p[x]);
        return p[x];
    }

    vector<int> maxPoints(vector<vector<int>>& grid, vector<int>& queries) {
        int dx[] = {1, -1, 0, 0};
        int dy[] = {0, 0, 1, -1};
        int k = queries.size(), m = grid.size(), n = grid[0].size(), mn = m * n;

        vector<int> qPos(k);
        vector<int> gPos(mn);
        for (int i = 0; i < k; i++) qPos[i] = i;
        for (int i = 0; i < mn; i++) gPos[i] = i;

        // 对queries从小到大排序
        sort(qPos.begin(), qPos.end(), [&](int a, int b) {
            return queries[a] < queries[b];
        });

        // 对矩阵中全部的点, 按照val从小到大排序, 二维坐标一维化
        sort(gPos.begin(), gPos.end(), [&](int a, int b) {
            return grid[a / n][a % n] < grid[b / n][b % n];
        });

        // 并查集初始化
        p.resize(mn);
        cnt.resize(mn);

        for (int i = 0; i < mn; i++) {
            p[i] = i;
            cnt[i] = 1;
        }

        vector<int> ans(k);

        // 从小到大处理每个queries
        for (int i = 0, j = 0; i < k; i++) {
            int limit = queries[qPos[i]];
            int s = j; // 记录矩阵中点的开始位置
            while (j < mn && grid[gPos[j] / n][gPos[j] % n] < limit) j++;
            // 对于 [s, j) 中的所有点, 都可以进行并查集合并(往4个方向)
            for (int t = s; t < j; t++) {
                int u = gPos[t];
                int x = u / n, y = u % n;
                for (int o = 0; o < 4; o++) {
                    int nx = x + dx[o];
                    int ny = y + dy[o];
                    if (nx < 0 || nx >= m || ny < 0 || ny >= n || grid[nx][ny] >= limit) continue;
                    // 尝试合并
                    int nu = nx * n + ny;
                    int pu = find(u), pnu = find(nu);
                    if (pu != pnu) {
                        p[pu] = pnu;
                        cnt[pnu] += cnt[pu];
                    }
                }
            }
            // 操作结束后, 看下左上角的格子所在的连通块的情况即可
            int root = find(0);
            if (grid[0][0] >= limit) ans[qPos[i]] = 0; // 左上角格子本身>=limit, 则答案不应该是1, 而是0
            else ans[qPos[i]] = cnt[root];
        }
        return ans;
    }
};

有另一种做法可以稍加记录,改成维护边,当某两个相邻的点,这两个点的值都小于queris时,则这两个点在这次询问中可以被纳入。

我们将每两个点之间连一条边,设这个边的权重为两个点中较大的值。那么当某条边小于queries时,可以将这条边上的两个点进行合并。

对于如何建边,我们可以对于每个点,只建其左侧和上侧的边,这样就能保证不重复建边,并且2个点之间的边,我们只建一条,而不建2条无向边。

// 296ms
typedef pair<int, pair<int ,int>> PIII;
class Solution {
public:

    vector<int> p;

    vector<int> cnt;

    int find(int x) {
        if (x != p[x]) p[x] = find(p[x]);
        return p[x];
    }

    vector<int> maxPoints(vector<vector<int>>& grid, vector<int>& queries) {
        int k = queries.size(), m = grid.size(), n = grid[0].size(), mn = m * n;
        vector<int> qPos(k);
        for (int i = 0; i < k; i++) qPos[i] = i;
        sort(qPos.begin(), qPos.end(), [&](int a, int b) {
            return queries[a] < queries[b];
        });

        // 每条边, 需要维护边权(用于从小到大排序), 也要维护左右两个端点(用于合并), 所以需要3个数
        vector<PIII> edges;
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                int u = i * n + j; // 二维坐标一维化
                // 对当前点的上方邻接点进行建边
                if (i > 0) edges.push_back({max(grid[i][j], grid[i - 1][j]), {u, u - n}});
                // 对当前点的左侧邻接点进行建边
                if (j > 0) edges.push_back({max(grid[i][j], grid[i][j - 1]), {u, u - 1}});
            }
        }

        // 对边按照边权从小到大排序
        sort(edges.begin(), edges.end());

        // 并查集初始化
        p.resize(mn);
        cnt.resize(mn);
        for (int i = 0; i < mn; i++) {
            p[i] = i;
            cnt[i] = 1;
        }

        vector<int> ans(k);

        for (int i = 0, j = 0; i < k; i++) {
            int limit = queries[qPos[i]];
            int s = j;
            while (j < edges.size() && edges[j].first < limit) j++;
            // 找到当前可以合并的最后一条边

            for (int t = s; t < j; t++) {
                int a = edges[t].second.first, b = edges[t].second.second;
                int pa = find(a), pb = find(b);
                if (pa != pb) {
                    p[pa] = pb;
                    cnt[pb] += cnt[pa];
                }
            }

            if (grid[0][0] >= limit) ans[qPos[i]] = 0;
            else ans[qPos[i]] = cnt[find(0)];
        }
        return ans;
    }
};

解法二:离线查询 + 小根堆

这里对于queries的处理换一种方式,换成使用pair来同时保存元素大小和数组下标

typedef pair<int, int> PII;
class Solution {
public:
    vector<int> maxPoints(vector<vector<int>>& grid, vector<int>& queries) {
        int k = queries.size(), m = grid.size(), n = grid[0].size(), mn = m * n;
        vector<PII> q(k);
        for (int i = 0; i < k; i++) q[i] = {queries[i], i};
        sort(q.begin(), q.end());

        priority_queue<PII, vector<PII>, greater<PII>> heap;
        vector<bool> st(mn); // 标记某个格子是否被访问过了

        heap.push({grid[0][0], 0}); // 把第一个点插进去
        st[0] = true;

        int dx[] = {1, -1, 0, 0};
        int dy[] = {0, 0, 1, -1};

        vector<int> ans(k);
        int cnt = 0; // 当前已经访问了多少个点了
        for (int i = 0; i < k; i++) {
            int limit = q[i].first, pos = q[i].second;
            while (!heap.empty() && heap.top().first < limit) {
                // 可以访问这个点, 并且扩展它
                int u = heap.top().second; // 获取这个点的下标
                heap.pop();
                cnt++;
                int x = u / n, y = u % n;

                for (int j = 0; j < 4; j++) {
                    int nx = x + dx[j], ny = y + dy[j];
                    if (nx < 0 || nx >= m || ny < 0 || ny >= n || st[nx * n + ny]) continue;
                    heap.push({grid[nx][ny], nx * n + ny});
                    st[nx * n + ny] = true;
                }
            }
            ans[pos] = cnt;
        }
        return ans;
    }
};

其实st数组也可以省掉,因为矩阵中元素的值都是大于0的,我们可以在访问后直接将其置为0

typedef pair<int, int> PII;
class Solution {
public:
    vector<int> maxPoints(vector<vector<int>>& grid, vector<int>& queries) {
        int k = queries.size(), m = grid.size(), n = grid[0].size(), mn = m * n;
        vector<PII> q(k);
        for (int i = 0; i < k; i++) q[i] = {queries[i], i};
        sort(q.begin(), q.end());

        priority_queue<PII, vector<PII>, greater<PII>> heap;

        heap.push({grid[0][0], 0}); // 把第一个点插进去
        grid[0][0] = 0;

        int dx[] = {1, -1, 0, 0};
        int dy[] = {0, 0, 1, -1};

        vector<int> ans(k);
        int cnt = 0; // 当前已经访问了多少个点了
        for (int i = 0; i < k; i++) {
            int limit = q[i].first, pos = q[i].second;
            while (!heap.empty() && heap.top().first < limit) {
                // 可以访问这个点, 并且扩展它
                int u = heap.top().second; // 获取这个点的下标
                heap.pop();
                cnt++;
                int x = u / n, y = u % n;

                for (int j = 0; j < 4; j++) {
                    int nx = x + dx[j], ny = y + dy[j];
                    if (nx < 0 || nx >= m || ny < 0 || ny >= n || grid[nx][ny] == 0) continue;
                    heap.push({grid[nx][ny], nx * n + ny});
                    grid[nx][ny] = 0;
                }
            }
            ans[pos] = cnt;
        }
        return ans;
    }
};

这道题又用到了对于数组下标进行排序这种处理技巧,当然也可以使用pair

这种技巧常用于,需要进行排序,但同时需要保留原下标的场景。

另外需要注意,在维护某个点时,我们还用到了二维坐标和一维坐标之间进行相互转换的技巧。

总结

holy shit!本场周赛太拉跨了,只做出2题。

T1是排序+模拟;T2是排序+递推(有点动态规划的意味);T3可以直接暴力做;T4是连通块问题。

本次周赛,T3其实没有T2难,但T3我一直没通过。

一是没注意到数据范围,也没想到可以直接用数组对内存分配进行暴力模拟;二是coding熟练度还不够,一些细节的处理不是特别好,coding中容易写错变量或者判断条件,然后需要花大量时间才能找到问题所在。比较可惜。

T4听完并消化一些大佬的讲解后,发现难度不算大;做不出来,主要还是练的题不够多。T4读完题后我其实有一定感觉,能想到一部分思路,但还是无法把整个思路走通,有些地方还是存在一些思维的差距,仍然需要勤加练习。


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/115834.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Leetcode】101. 对称二叉树、104. 二叉树的最大深度、226. 翻转二叉树

作者&#xff1a;一个喜欢猫咪的的程序员 专栏&#xff1a;《Leetcode》 喜欢的话&#xff1a;世间因为少年的挺身而出&#xff0c;而更加瑰丽。 ——《人民日报》 目录 101. 对称二叉树 104. 二叉树的最大深度 226. 翻转二叉树 101. 对称二…

DJ11 8086系列处理器(第一节课)

目录 一、8086/8088微处理器 二、8086/8088CPU的特点 1. 指令流水线 2. 内存分段管理 3. 支持多处理器系统 三、8088 CPU外部引脚及功能 1. 最小模式下的引脚 2. 最大模式下的引脚 四、8088/8086 CPU 的工作时序 1. 基本概念 2. 总线周期 一、8086/8088微处理器 二、…

软考中级系统集成项目管理工程师怎么自学备考

1、考试内容是什么&#xff1f; 2、备考前要准备什么&#xff1f; 3、如何高效备考&#xff1f; 一、考试内容是什么&#xff1f; 本考试设置的科目包括&#xff1a; &#xff08;1&#xff09;系统集成项目管理基础知识&#xff0c;考试时间为150分钟&#xff0c;笔试&am…

IB体育评估哪些内容?

"IB体育"这个词的内涵太广了&#xff0c;覆盖的课程也很多&#xff01;这个IB体育是一般体育课还是某个具体的IB科目呢&#xff1f;是MYP阶段的体育还是DP阶段的呢&#xff1f;其实很多人都是很懵&#xff0c;通过收集资料&#xff0c;可以分享一下&#xff0c;仅供参…

2022年虚拟电厂行业研究报

第一章 行业概况 虚拟电厂&#xff08;VPP, Virtual Power Plant&#xff09;本质上是将分布式电源&#xff08;发电&#xff09;、可控负荷&#xff08;用电&#xff09;、储能等利用计算机通信网络技术将其聚合成一个虚拟的集中式电厂&#xff0c;来为电网提供需求侧响应的“…

4个封神的电脑工具,颠覆你对免费白嫖的认知,干货奉上

闲话少说&#xff0c;直上干货。 1、TinyWow TinyWow虽说是国外网站工具&#xff0c;但不得不承认真的无敌好用&#xff0c;收纳工具超200个&#xff0c;完全免费&#xff0c;无任何弹屏广告&#xff0c;更为良心的是&#xff0c;不需要注册登录&#xff0c;随用随走&#xff0…

专业的方案公司阐述智能硬件产品开发的全过程

现在市场上的方案公司太多&#xff0c;让人应接不暇&#xff0c;当我们要开发一款智能硬件产品的时候&#xff0c;我们要如何去选择方案公司呢&#xff1f;又怎样判断方案公司是否则专业呢&#xff1f;下面沐渥带大家一起来了解下智能硬件产品开发的全过程&#xff0c;大家就知…

Ubuntu 18.0.4 SonarQube-7.1.x 安装教程 以及错误总结

Ubuntu 18.0.4 SonarQube-7.1.x 安装教程 docker安装未成功 zip安装 1. 下载地址 sonarQube最新版下载地址&#xff1a;&#xff08;最新版不支持mysql&#xff09;https://www.sonarqube.org/downloads/7.1版本下载地址&#xff1a;​ ​https://binaries.sonarsource.com…

【UE4 第一人称射击游戏】10-添加冲刺功能

上一篇&#xff1a; 【UE4 第一人称射击游戏】09-添加蹲伏功能 本篇效果&#xff1a; 步骤&#xff1a; 1.在“Character”文件夹内添加一个混合空间 骨架选择“Swat_Skeleton” 命名为“Sprint_BS” 双击打开“Sprint_BS”&#xff0c;将水平和垂直坐标名称分别设为“Direct…

【java】HashMap底层原理实现原理及面试题

目录一.哈希表(散列)1.什么是哈希表2.什么是哈希冲突(面试题)3.解决哈希冲突的方法(面试题)(1) 开放地址法① 线性探查②二次探查③随机探查(2) 再哈希法(3) 链地址法(4)建立公共溢出区二.HashMap1.HashMap的hash()算法(面试)(1)为什么不是h key.hashCode()直接返回&#xff0…

绘制菜单符号的技法

在上一篇文章中&#xff0c;我们了解了如何绘制主题化的和原始未主题化的单选按钮&#xff0c;我曾提到&#xff0c;绘制菜单符号会更加复杂一些。复杂之处在于&#xff0c;这些符号是通过单色位图实现的&#xff0c;而不是漂亮的全彩色位图。 首先&#xff0c;我们将通过一种错…

linux内核调度子系统随笔(一)

调度子系统组件(1) 调度类用于判断接下来运行哪个进程&#xff0c;内核支持不同的调度策略(完全公平调度,实时调度)&#xff1b;调度类使得能够以模块化方法实现这些策略; (2) 在选中将要选择的进程后&#xff0c;必须执行底层任务切换&#xff1b;需要与cpu的紧密交互&#x…

信息安全管理体系

环境迁移 Platfor m Ops for AI 作为整合了 DataOps、MLOps、ModelOps 的复杂技术平台&#xff0c;在项目开发时仅使用一套系统无法支撑平台的稳定搭建&#xff0c;往往需要开发系统、集成测试系统、正式 环境系统在项目生命周期 中协作配合。将表、索引、并发程序、配置内容等…

HTML5 新增属性

文章目录HTML5 新增属性公共属性hidden属性draggable属性contenteditable属性data-*属性input元素新增属性autocomplete属性autofocus属性placeholder属性required属性pattern属性form元素新增属性novalidate属性HTML5 新增属性 公共属性 HTML5新增的常见公共属性有4个&#…

在今年的数字生态大会上,云原生数据库前进了一大步

云计算时代&#xff0c;数据库上云已成为产业数字化转型的重要动力。近期&#xff0c;在2022腾讯全球数字生态大会云原生数据库技术探索专场上&#xff0c;腾讯云分享了在云原生数据库领域的技术演进与探索&#xff0c;并就其在不同行业场景中的最佳实践进行了详细讲解&#xf…

【C++初阶】stack、queue和priority_queue的模拟实现

文章目录简介stackqueuepriority_queuestack的模拟实现成员变量emptysizetoppushpopqueue的模拟实现成员变量emptysizetoppushpoppriority_queue的模拟实现成员变量emptysizetoppushpop仿函数完整版代码stack.hqueue.hpriority_queue.htest.cpp简介 stack、queue和priority_qu…

四、SpringBoot Starter组件详解

starter组件实际上就是能够实现自动装配的jar包。 1.starter组件创建流程 假设我现在要集成redis,要拿到redisTemplate对象,怎么做呢? 1.引springboot包; 2.创建RedisTemplate类; 3.写配置类; 4.创建spring.factories文件; 5.打成jar包。 示例如下: 1.创建maven项目…

我的世界MOD制作(2)|| 你的第一个MOD

正文&#xff1a;I. 开发环境配置 我们需要一个带mixin的forge开发环境&#xff0c;这一步相当折磨人&#xff0c;网络不好的话半天时间都得砸这上面&#xff0c;但是不要灰心&#xff0c;过了这个坎接下来基本是顺风顺水。 1. 下载资源 & 修改build.gradle 首先去forge官网…

从三万英尺看全链路灰度

作者&#xff1a;卜比 全链路灰度是微服务领域&#xff0c;很实用的企业级场景下的技术能力。 从本期开始&#xff0c;我们将通过《全链路灰度&#xff1a;自顶向下的方法》的系列文章&#xff0c;由远及近的剖析全链路灰度全貌&#xff0c;系列文章分为 4 篇&#xff1a; 《…

无需数据库的笔记flatnotes

本文完成于 10 月底&#xff1b; 什么是 flatnotes&#xff1f; flatnotes 是一个自托管的、无数据库的笔记 Web 应用程序&#xff0c;它利用文件夹存储 Markdown 文件。 官方演示站点&#xff1a;https://demo.flatnotes.io/ 前言 本文介绍的软件很简单&#xff0c;但是有两…