- 第 186 篇 -
Date: 2026 - 03- 12 | 周四
Author: 郑龙浩(仟墨)
算法:算法:前缀和,二维前缀和,快慢指针,哈希表set使用技巧,哈希表map使用技巧
2026-03-12-算法打卡day20
算法:前缀和,二维前缀和,快慢指针,哈希表set使用技巧,哈希表map使用技巧
文章目录
今日题集
- 卡农网44-开发商购买土地
- 力扣203-移除链表元素
- 力扣242-有效的字母异位词
- 力扣349-两个数组的交集
- 力扣202-快乐数
- 力扣1-两数之和
- 力扣454-四数相加II
1-卡码网44-开发商购买土地
算法:二维前缀和
【题目】
题目描述
在一个城市区域内,被划分成了n * m个连续的区块,每个区块都拥有不同的权值,代表着其土地价值。目前,有两家开发公司,A 公司和 B 公司,希望购买这个城市区域的土地。
现在,需要将这个城市区域的所有区块分配给 A 公司和 B 公司。
然而,由于城市规划的限制,只允许将区域按横向或纵向划分成两个子区域,而且每个子区域都必须包含一个或多个区块。 为了确保公平竞争,你需要找到一种分配方式,使得 A 公司和 B 公司各自的子区域内的土地总价值之差最小。
注意:区块不可再分。
输入描述
第一行输入两个正整数,代表 n 和 m。
接下来的 n 行,每行输出 m 个正整数。
输出描述
请输出一个整数,代表两个子区域内土地总价值之间的最小差距。
输入示例
3 3
1 2 3
2 1 3
1 2 3
输出示例
0
提示信息
如果将区域按照如下方式划分:
1 2 | 3
2 1 | 3
1 2 | 3
两个子区域内土地总价值之间的最小差距可以达到 0。
数据范围:
1 <= n, m <= 100;
n 和 m 不同时为 1。
【思路】
本题算法:二维前缀和
先求出前缀和来,然后分别按行划分,按列划分,
求出差值最小的来就ok了
【代码】
cpp
/* 1-卡码网44-开发商购买土地
算法:二维前缀和
Author:郑龙浩
Date:2026-03-12
用时:40min 很简单的一个题,就是修改错误细节修改了半天
*/
#include "bits/stdc++.h"
using namespace std;
int main() {
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
int n, m; cin >> n >> m;
vector <vector <long long>> nums(n, vector <long long>(m, 0));
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
cin >> nums[i][j];
}
}
for (int i = 1; i < m; i++) nums[0][i] += nums[0][i - 1];// 计算行
for (int i = 1; i < n; i++) nums[i][0] += nums[i - 1][0];// 计算列
// 计算土地的前缀和
for (int i = 1; i < n; i++)
for (int j = 1; j < m; j++)
nums[i][j] += nums[i - 1][j] + nums[i][j - 1] - nums[i - 1][j - 1];
long long Min = LLONG_MAX; // 存储最小差值
// 横向划分
long long A, B; // A 面积 和 B 面积
for (int i = 0; i < n; i++) { //(i位置算做A的)
A = nums[i][m - 1];
B = nums[n - 1][m - 1] - A;
Min = min(Min, llabs(B - A));
}
// 纵向划分
for (int j = 0; j < m; j++) { //(j位置算做A的)
A = nums[n - 1][j];
B = nums[n - 1][m - 1] - A;
Min = min(Min, llabs(B - A));
}
cout << Min;
return 0;
}
2-力扣203-移除链表元素
数据结构:链表
【题目】
给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点 。
示例 1:

输入: head = [1,2,6,3,4,5,6], val = 6
输出: [1,2,3,4,5]
示例 2:
输入: head = [], val = 1
输出: []
示例 3:
输入: head = [7,7,7,7], val = 7
输出: []
提示:
- 列表中的节点数目在范围
[0, 104]内 1 <= Node.val <= 500 <= val <= 50
【思路】
太久没写链表的题了,写起来非常的生疏,非常的慢
【代码】
cpp
/* 2-力扣203-移除链表元素-链表
Author:郑龙浩
Date:2026-03-12
用时:42min 修改小细节用了很久,服了
*/
#include "bits/stdc++.h"
using namespace std;
/**
* 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) {}
* };
*/
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* removeElements(ListNode* head, int val) {
// 如果头结点的值是val的话,要提前处理
while (head != NULL && head->val == val) { // 如果头结点不是NULL && 头结点的值是val,就要删除当前节点
ListNode* temp = head; // 以防丢失
head = head->next; // 将头结点改为下一个节点
delete temp; // 删除原头结点
}
if (head == NULL) { // 如果链表为空,直接返回head
return head;
}
// 处理头结点以外的节点
ListNode* cur = head; // 遍历到的节点
while (cur != NULL && cur->next != NULL) { // 找到要删除节点的前驱节点,否则前驱节点会丢失,后面的节点会全部断裂
// cur != NULL没有必要加,加是因为力扣在检查时候,编译器认为cur = head这行代码在head为NULL时不会被访问,但后续的while (cur->next != NULL)在编译优化时,UBSan可能认为cur有NULL的可能性,所以报错。
if (cur->next->val == val) { // 如果下一个节点的值是val,就要将下一个节点删除,并将下下个节点给cur->next
ListNode* next = cur->next;
cur->next = next->next;
delete next;
} else { // 如果cur没有更新next,那就手动更新
cur = cur->next;
}
}
return head;
}
};
int main() {
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
return 0;
}
3-力扣242-有效的字母异位词
技巧:没有
【题目】
给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的 字母异位词。
示例 1:
输入: s = "anagram", t = "nagaram"
输出: true
示例 2:
输入: s = "rat", t = "car"
输出: false
提示:
1 <= s.length, t.length <= 5 * 104s和t仅包含小写字母
【思路】
字母异位词是通过重新排列不同单词或短语的字母而形成的单词或短语,并使用所有原字母一次。
【代码】
cpp
/* LeetCode 242-有效的字母异位词
(字母异位词是通过重新排列不同单词或短语的字母而形成的单词或短语,并使用所有原字母一次。)
Author:郑龙浩
Date:2026-03-12
用时:5min
*/
#include "bits/stdc++.h"
using namespace std;
class Solution {
public:
bool isAnagram(string s, string t) {
vector<int> sCnt(26, 0); // 记录字符串s中每个小写字母出现的次数
vector<int> tCnt(26, 0); // 记录字符串t中每个小写字母出现的次数
// 统计字符串s中每个字母出现的频次
for (char ch : s) {
sCnt[ch - 'a']++; // 将字符映射到索引0-25,并增加计数
}
// 统计字符串t中每个字母出现的频次
for (char ch : t) {
tCnt[ch - 'a']++; // 将字符映射到索引0-25,并增加计数
}
// 比较两个字符串的字母频次是否完全相同
for (int i = 0; i < 26; i++) {
if (sCnt[i] != tCnt[i]) {
return false; // 如果任一字母的计数不同,则不是有效的字母异位词
}
}
return true; // 所有字母频次均相同,是有效的字母异位词
}
};
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
return 0;
}
4-力扣349-两个数组的交集
技巧:哈希表
【题目】
给定两个数组 nums1 和 nums2 ,返回 它们的 交集 。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序 。
示例 1:
输入: nums1 = [1,2,2,1], nums2 = [2,2]
输出: `[2]
示例 2:
输入: nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出: [9,4]
解释: [4,9] 也是可通过的
提示:
1 <= nums1.length, nums2.length <= 10000 <= nums1[i], nums2[i] <= 1000
【思路】
这道题主要用到的技巧就是「哈希表」,其他没有什么技巧
就是刚开始做的时候看成了「并集」,算出来不对才意识到记错了,然后按照交集做的
- 创建两个哈希表分别存储nums1的数据和ans结果
- 先将nums1存储至哈希表nums(哈希表使用find时候速度会比较快)
- 然后遍历nums2中的每个元素,只要遇到nums1中也有的,就存入ans中
- ans也是哈希表,因为是交集,所以也要保证ans中存储的元素不可以出现两次,万一nums1与nums2中有多个相同元素,也是只能存储一个的
- reruen结果,return的时候,需要将哈希表转换为vector去返回
【代码】
cpp
/* 4-力扣349-两个数组的交集
Author:郑龙浩
Date:2026-03-12
哈希表
用时:9min
*/
#include "bits/stdc++.h"
using namespace std;
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
unordered_set <int> nums(nums1.begin(), nums1.end());
unordered_set <int> ans;
for (int item : nums2) {
if (nums.find(item) != nums.end()) {
ans.insert(item);
}
}
return vector <int> (ans.begin(), ans.end());
}
};
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
return 0;
}
5-力扣202-快乐数
技巧:快慢指针 + 哈希表
【题目】
编写一个算法来判断一个数 n 是不是快乐数。
「快乐数」 定义为:
- 对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
- 然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。
- 如果这个过程 结果为 1,那么这个数就是快乐数。
如果 n 是 快乐数 就返回 true ;不是,则返回 false 。
示例 1:
**输入:**n = 19
**输出:**true
解释:
12 + 92 = 82
82 + 22 = 68
62 + 82 = 100
12 + 02 + 02 = 1
示例 2:
**输入:**n = 2
**输出:**false
提示:
1 <= n <= 231 - 1
【思路】
这道题的循环过程是简单的,题目描述也很容易明白,乍一看还挺好写的
但是最关键的点是:如何确定出现了「无限循环」
这里题目给出了一个暗示,无限循环,也就意味着,如果出现了「无限循环」,那么此前出现过的结果,后面可能还会出现,那么我只需要将前面出现的所有结果都记录下来,只要第2次遇到相同结果的时候,就证明出现了「无限循环」,此时就可以return false
- 因为要存储结果,并且每次出现新结果的时候查找此前是否出现过当前的结果
- 所以要用哈希表去存储结果,目的就是快速find
代码过程:
- 写两个循环
- 第一个循环是死循环,只有在出现「无限循环」or「结果为1」的情况下,才会return,也就是退出了停止了代码执行了
- 第二个循环在第一个循环内部,主要目的是计算N的每一位的平方和
- 第一个循环还负责
- 在每次计算出新的result的时候,都比较是否出现了
- 「结果为1」也就是result是否为1,如果为1,立马就return true
- 「无限循环」也就是result是否第二次出现,如果第二次出现了,就直接返回false
- 更新N的值,确保每次N都是以新的result出现(更新位置,在外层循环的末尾更新)
- 更新result为0,因为每次新的N,都要重新计算每个位的平方和,此时就要保证result是0(在开头就要更新)
- 在每次计算出新的result的时候,都比较是否出现了
- 注意:N和result的更新位置一定要注意,不要将位置颠倒
【代码】
cpp
/* 5-力扣202-快乐数
Author:郑龙浩
Date:2026-03-12
快慢指针 + 哈希表
用时:13min
很简单的一个题,为什么用了这么久呢
因为不小心将result += 写成了 *=,求的是平方和,我不小心写成了每个位的平方相乘了
*/
#include "bits/stdc++.h"
using namespace std;
class Solution {
public:
bool isHappy(int n) {
unordered_set <long long> results; // 存储出现过的结果
long long N = n, result = 0;
while (1) {
result = 0;
while (N) { // 计算每个数字的result
result += (N % 10) * (N % 10);
N /= 10;
}
if (result == 1) {
return true;
} else if (results.find(result) != results.end()) { // 如果当前结果此前找到过,说明进入了死循环, 此时应该return false
cout << result << '\n';
return false;
}
results.insert(result);// 将第一次遇到的result放入results中去
N = result; // 更新下一个结果
}
}
};
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
Solution sol;
bool ans = sol.isHappy(19);
cout << ans;
return 0;
}
6-力扣1-两数之和
技巧:哈希表
【题目】
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案,并且你不能使用两次相同的元素。
你可以按任意顺序返回答案。
示例 1:
输入: nums = [2,7,11,15], target = 9
输出: [0,1]
解释: 因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。
示例 2:
输入: nums = [3,2,4], target = 6
输出: [1,2]
示例 3:
输入: nums = [3,3], target = 6
输出: [0,1]
提示:
2 <= nums.length <= 104-109 <= nums[i] <= 109-109 <= target <= 109- 只会存在一个有效答案
【思路】
该题依然使用了「哈希表」去做
只不过使用的是map而不是set
大概思路如下:
先将所有的nums存入map中,且键是元素值,值是元素下标
这样的话,就可以利用元素值去寻找对应的下标了
然后写一个循环,直接找到与元素nums[i]对应的另一个元素
如果找到了对应的元素,且对应的元素不是自己(下标不是i)
就说明找到了对应的元素i,此时就应该返回i 和 找到的元素下标
返回的时候因为要求返回的是vector,且只有两个值
return {a, b} 即可
【代码】
cpp
/* 6-力扣1-两数之和
Author:郑龙浩
Date:2026-03-12
快慢指针 + 哈希表
用时:14min
*/
#include "bits/stdc++.h"
using namespace std;
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
int len = nums.size();
unordered_map <int, int> NumsIndex; // 键是nums元素,值是nums下标
for (int i = 0; i < len; i++) NumsIndex[nums[i]] = i;
// 寻找符合条件的两个数
for (int i = 0; i < len; i++) {
// 寻找与当前数配对的另一个数
auto hubushu = NumsIndex.find(target - nums[i]); // 可能的互补数
// 找到配对数字,且不是自身元素
if (hubushu != NumsIndex.end() && hubushu->second != i) { // 要保证是nums[i]的互补数,且不是nums[i]元素本身
return {i, NumsIndex.find(target - nums[i])->second};
}
}
return {}; //如果没找到,就返回空的vector <int>
}
};
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
return 0;
}
7-力扣454-四数相加II
【题目】
给你四个整数数组 nums1、nums2、nums3 和 nums4 ,数组长度都是 n ,请你计算有多少个元组 (i, j, k, l) 能满足:
0 <= i, j, k, l < nnums1[i] + nums2[j] + nums3[k] + nums4[l] == 0
示例 1:
输入: nums1 = [1,2], nums2 = [-2,-1], nums3 = [-1,2], nums4 = [0,2]
输出: 2
解释:
两个元组如下:
(0, 0, 0, 1) -> nums1[0] + nums2[0] + nums3[0] + nums4[1] = 1 + (-2) + (-1) + 2 = 0(1, 1, 0, 0) -> nums1[1] + nums2[1] + nums3[0] + nums4[0] = 2 + (-1) + (-1) + 0 = 0
示例 2:
输入: nums1 = [0], nums2 = [0], nums3 = [0], nums4 = [0]
输出: 1
提示:
n == nums1.lengthn == nums2.lengthn == nums3.lengthn == nums4.length1 <= n <= 200-228 <= nums1[i], nums2[i], nums3[i], nums4[i] <= 228
【思路】
这道题对我来说有点难度,第一时间只想到了「暴力」,但是并没有太多其他的思路
暴力的话就是写四个循环,然后只要遇到了a + b + c + d,就是遇到了满足条件的 四个元素,就cnt++就OK了
但是这样写大概率是会超时的
看了题解后,思路大概如下:
写两个循环,先将A 与 B数组中所有的元素和求出来,并且存到哈希表SumCount中,并且也将该元素和出现的次数记录下来
下面再进行两个循环,此时计算的是C和D数组中所有元素和,
c 和 d 是C 与 D中的两个元素
在内层循环中,求出c + d后
- 如果在哈希表sum中找到了
0 - (c + d)的结果,就表明找到了对应的 a + b - 然后将对应的a + b的数量加入到cnt中去(因为数量有多少个就有多少个符合条件的 a + b,只是顺序不同,这些不同顺序的a + b 都要算在内的)
【代码】
cpp
/* 7-力扣454-四数相加II
Author:郑龙浩
Date:2026-03-12
快慢指针 + 哈希表
用时:21 min (整理记录思路 + 写代码)
*/
#include "bits/stdc++.h"
using namespace std;
class Solution {
public:
int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
unordered_map <int, int> SumCount;
for (int a : nums1) {
for (int b : nums2) {
SumCount[a + b]++;
}
}
int count = 0;
for (int c : nums3) {
for (int d : nums4) {
auto temp = SumCount.find(0 - (c + d));
if (temp != SumCount.end()) {
count += temp->second;
}
}
}
return count;
}
};
int main() {
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
return 0;
}