2485. 找出中枢整数

给你一个正整数 ​​n​​ ,找出满足下述条件的 中枢整数​x​​ :

  • ​1​​​ 和 ​​x​​​ 之间的所有元素之和等于 ​​x​​​ 和 ​​n​​ 之间所有元素之和。

返回中枢整数 ​​x​​​ 。如果不存在中枢整数,则返回 ​​-1​​ 。题目保证对于给定的输入,至多存在一个中枢整数。

提示

  • ​1 <= n <= 1000​

示例

输入:n = 8
输出:6
解释:6 是中枢整数,因为 1 + 2 + 3 + 4 + 5 + 6 = 6 + 7 + 8 = 21 。

思路

由于数据范围很小,暴力模拟即可。

class Solution {
public:
int pivotInteger(int n) {
int tot = (1 + n) * n / 2, sum = 0;
for (int i = 1; i <= n; i++) {
sum += i;
if (sum == tot - sum + i) return i;
}
return -1;
}
};

2486. 追加字符以获得子序列

给你两个仅由小写英文字母组成的字符串 ​​s​​​ 和 ​​t​​ 。

现在需要通过向 ​​s​​​ 末尾追加字符的方式使 ​​t​​​ 变成 ​​s​​ 的一个 子序列 ,返回需要追加的最少字符数。

子序列是一个可以由其他字符串删除部分(或不删除)字符但不改变剩下字符顺序得到的字符串。

提示

  • ​1 <= s.length, t.length <= 10^5​
  • ​s​​​ 和 ​​t​​ 仅由小写英文字母组成

示例

输入:s = "coaching", t = "coding"
输出:4
解释:向 s 末尾追加字符串 "ding" ,s = "coachingding" 。
现在,t 是 s ("coachingding") 的一个子序列。
可以证明向 s 末尾追加任何 3 个字符都无法使 t 成为 s 的一个子序列。

思路

双指针,看下​​t​​​中最多有多少个字符可以在​​s​​中得到。

class Solution {
public:
int appendCharacters(string s, string t) {
int n = s.size(), m = t.size();
int i = 0, j = 0;
while (i < n && j < m) {
if (s[i] == t[j]) j++;
i++;
}
return m - j; // 还差多少个
}
};

2487. 从链表移除节点

给你一个链表的头节点 ​​head​​ 。

对于列表中的每个节点 ​​node​​ ,如果其右侧存在一个具有 严格更大 值的节点,则移除 ​​node​​ 。

返回修改后链表的头节点 ​​head​​ 。

提示

  • 给定列表中的节点数目在范围 ​​[1, 10^5]​​ 内
  • ​1 <= Node.val <= 10^5​

示例

输入:head = [5,2,13,3,8]
输出:[13,8]
解释:需要移除的节点是 5 ,2 和 3 。
- 节点 13 在节点 5 右侧。
- 节点 13 在节点 2 右侧。
- 节点 8 在节点 3 右侧。

思路

单调栈,维护一个单调递减的栈即可。

/**
* 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* removeNodes(ListNode* head) {
ListNode* hh = new ListNode(1e5 + 5, head);
stack<ListNode*> stk;
stk.push(hh);
for (ListNode* cur = head; cur != nullptr; cur = cur->next) {
while (!stk.empty() && stk.top()->val < cur->val) stk.pop();
// 中间部分的节点全部要被移除, 只需要连接第一个大于cur的节点即可
stk.top()->next = cur;
stk.push(cur);
}
return hh->next;
}
};

另:递归做法

/**
* 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* removeNodes(ListNode* head) {
if (head->next == nullptr) return head;
ListNode* x = removeNodes(head->next);
return head->val >= x->val ? new ListNode(head->val, x) : x;
}
};

2488. 统计中位数为K的子数组

给你一个长度为 ​​n​​​ 的数组 ​​nums​​​ ,该数组由从 ​​1​​​ 到 ​​n​​ 的 不同 整数组成。另给你一个正整数 ​​k​​ 。

统计并返回 ​​num​​ 中的 中位数 等于 ​​k​​ 的非空子数组的数目。

注意:

  • 数组的中位数是按 递增 顺序排列后位于 中间 的那个元素,如果数组长度为偶数,则中位数是位于中间靠 的那个元素。
  • 例如,​​[2,3,1,4]​​​ 的中位数是 ​​2​​​ ,​​[8,4,3,5,1]​​​ 的中位数是 ​​4​​ 。
  • 子数组是数组中的一个连续部分。

提示:

  • ​n == nums.length​
  • ​1 <= n <= 10^5​
  • ​1 <= nums[i], k <= n​
  • ​nums​​ 中的整数互不相同

示例:

输入:nums = [3,2,1,4,5], k = 4
输出:3
解释:中位数等于 4 的子数组有:[4]、[4,5] 和 [1,4,5] 。

思路

首先找到​​k​​​的下标,假设为​​x​​​,我们需要找到所有的​​[i, j]​​​,其中​​i​​​在​​x​​​左侧,​​j​​​在​​x​​​右侧。设区间​​[i, x]​​​内小于​​k​​​的元素个数为​​leftLessCnt​​​,大于​​k​​​的元素个数为​​leftGreatCnt​​​;同理设区间​​[x, j]​​​中小于​​k​​​的元素个数为​​rightLessCnt​​​,大于​​k​​​的元素个数为​​rightGreatCnt​​​。由于​​k​​​是中位数,那么需要满足​​[i, j]​​​整个区间内,小于​​k​​​的元素个数,与大于​​k​​的元素个数,相等,或者左侧比右侧少1。

那么我们需要找出所有的​​[i, j]​​,使其满足

​leftLessCnt + rightLessCnt = leftGreatCnt + rightGreatCnt​​或者

​leftLessCnt + rightLessCnt = leftGreatCnt + rightGreatCnt - 1​

我周赛当天的想法是对于左右两侧,分别维护一下小于​​k​​​的元素个数与大于​​k​​的元素个数的差值。下面是我周赛当天的代码。

const int N = 1e5 + 10;
class Solution {
public:

vector<vector<int>> rFreq;

int countSubarrays(vector<int>& nums, int k) {
int pos = 0, n = nums.size();
for (int i = 0; i < n; i++) {
if (nums[i] == k) {
pos = i;
break;
}
}

// printf("pos = %d\n", pos);

// f[i][0] 表示 lCnt < gCnt, 相差i
// f[i][1] 表示 lCnt > gCnt, 相差i
rFreq = vector<vector<int>>(n + 1, vector<int>(2, 0));

// 求一下右侧的cnt
int lCnt = 0, gCnt = 0;
for (int i = pos + 1; i < n; i++) {
if (nums[i] < k) lCnt++;
else gCnt++;

if (lCnt == gCnt) rFreq[0][0]++, rFreq[0][1]++;
else if (lCnt < gCnt) rFreq[gCnt - lCnt][0]++;
else rFreq[lCnt - gCnt][1]++;
}
rFreq[0][0]++;
rFreq[0][1]++;

// for (int i = 0; i <= n; i++) {
// if (rFreq[i][0]) printf("freq[%d][0] = %d\n", rFreq[i][0]);
// if (rFreq[i][1]) printf("freq[%d][1] = %d\n", rFreq[i][1]);
// }


int ans = 0; // 长度为1的
// 计算左侧
lCnt = gCnt = 0;
for (int i = pos; i >= 0; i--) {
if (nums[i] < k) lCnt++;
else if (nums[i] > k) gCnt++;

// printf("i = %d, lCnt = %d, gCnt = %d\n", i, lCnt, gCnt);
int d = gCnt - lCnt;
if (d == 0) {
// printf("d = %d, [0][0] = %d, [1][0] = %d\n", d, rFreq[0][0], rFreq[1][0]);
ans += rFreq[0][0] + rFreq[1][0]; // g > l
}
else if (d < 0) {
// printf("d = %d, [-d][0] = %d, [-d+1][0] = %d\n", d, rFreq[-d][0], rFreq[-d+1][0]);
ans += rFreq[-d][0] + rFreq[-d + 1][0];
}
else {
// printf("d = %d, [d][1] = %d, [d - 1][1] = %d\n", d, rFreq[d][1], rFreq[d - 1][1]);
ans += rFreq[d][1] + rFreq[d - 1][1];
}
// printf("ans = %d\n", ans);
}

return ans;
}
};

很遗憾,当时边界问题没处理好,直到12点04分才调试通过,然而12点比赛就结束了,痛失AK机会。并且我代码写的可谓又臭又长,十分难看。下面是今天(2022/12/20)重写的代码。

class Solution {
public:
int countSubarrays(vector<int>& nums, int k) {
int n = nums.size(), x = n;
unordered_map<int, int> freq;

int lCnt = 0, gCnt = 0; // 小于k和大于k的元素个数
for (int i = 0; i < n; i++) {
if (nums[i] == k) x = i;
// 一次遍历同时找出坐标x, 并处理右侧的情况
if (i >= x) {
if (nums[i] < k) lCnt++;
if (nums[i] > k) gCnt++;
freq[gCnt - lCnt]++;
}
}

// 处理左边
int ans = 0;
lCnt = gCnt = 0;
for (int i = x; i >= 0; i--) {
if (nums[i] < k) lCnt++;
if (nums[i] > k) gCnt++;
ans += freq[lCnt - gCnt] + freq[lCnt - gCnt + 1];
}
return ans;
}
};

再提供一个版本,这个版本是今天重做时,没有回看之前代码的情况下,自己重新写的。相对而言更多是推公式。

设​​l[i]​​​表示区间​​[i, x]​​​内小于​​k​​​的元素数量,那么很明显的,​​[i, x]​​​内大于​​k​​​的元素数量应该是​​x - i - l[i]​

同理设​​r[j]​​​表示区间​​[x, j]​​​内小于​​k​​​的元素数量,那么,​​[x, j]​​​内大于​​k​​​的元素数量是​​j - x - r[j]​

我们需要找到所有的​​[i, j]​​对,使得其满足

​l[i] + r[j] = x - i - l[i] + j - x - r[j]​​ 或

​l[i] + r[j] = x - i - l[i] + j - x - r[j] - 1​

整理一下,将​​i​​​相关的项全部移到一边,​​j​​相关的项全部移到另一边,整理一下有

​2 * l[i] + i = j - 2 * r[j]​​ 或

​2 * l[i] + i = j - 2 * r[j] - 1​

那么我们可以处理​​x​​​右侧的所有位置​​j​​​,计算出​​j - 2 * r[j]​​,并计数。

然后遍历​​x​​​左侧的所有位置,对于每个​​i​​​,找到与之匹配的所有​​j​​即可。

class Solution {
public:
int countSubarrays(vector<int>& nums, int k) {
int n = nums.size();
vector<int> l(n), r(n);
// 先找到k这个数的所在位置
int x = 0;
for (int i = 0; i < n; i++) {
if (nums[i] == k) {
x = i;
break;
}
}
// 预处理l
int cnt = 0;
for (int i = x; i >= 0; i--) {
if (nums[i] < k) cnt++;
l[i] = cnt;
}

unordered_map<int, int> m;

// 预处理r
cnt = 0;
for (int i = x; i < n; i++) {
if (nums[i] < k) cnt++;
r[i] = cnt;
int u = i - 2 * r[i];
m[u]++; // 计数+1
}

int ans = 0;
for (int i = 0; i <= x; i++) {
int u = 2 * l[i] + i;
ans += m[u] + m[u + 1];
}
return ans;
}
};

同样的,可以对代码进行一下优化。

class Solution {
public:
int countSubarrays(vector<int>& nums, int k) {
// 先找到k这个数的所在位置
int n = nums.size(), x = n, cnt = 0, ans = 0;
unordered_map<int, int> freq;
for (int i = 0; i < n; i++) {
if (nums[i] == k) x = i;
if (i >= x) {
if (nums[i] < k) cnt++;
freq[i - 2 * cnt]++;
}
}

cnt = 0;
for (int i = x; i >= 0; i--) {
if (nums[i] < k) cnt++;
int u = 2 * cnt + i;
ans += freq[u] + freq[u + 1];
}
return ans;
}
};

总结

LeetCode 321 周赛_子序列

这次的T4难度不大,有机会AK的,但很可惜。

T1简单模拟;T2双指针模拟;T3栈;T4哈希表。