目录
〇,前言
DFS、BFS一般用来解决树和图的搜索问题,对象空间是不是良序的,
而数组和集合的搜索,对象空间是良序的。
DFS、BFS一般把对象空间搜索一遍就能得到答案,比较复杂的问题往往复杂度在于如何高效剪枝,
而数组和集合的搜索,往往搜索一遍还得不到答案,
比如寻找2个数的和为给定数的所有数对,需要两层的嵌套搜索,暴力时间是O(n^2),
一些技巧,比如二分等,可以用在数组和集合的搜索问题上,但不能用于DFS、BFS
一,数组和集合基于计算的搜索
CodeForces 702B Powers of Two
题目:
Description
You are given n integers a1, a2, ..., an. Find the number of pairs of indexes i, j (i < j) that ai + aj is a power of 2 (i. e. some integer xexists so that ai + aj = 2^x).
Input
The first line contains the single positive integer n (1 ≤ n ≤ 10^5) — the number of integers.
The second line contains n positive integers a1, a2, ..., an (1 ≤ ai ≤ 10^9).
Output
Print the number of pairs of indexes i, j (i < j) that ai + aj is a power of 2.
Sample Input
Input
4
7 3 2 1
Output
2
Input
3
1 1 1
Output
3
#include<iostream>
#include<string.h>
#include<algorithm>
using namespace std;
int list1[100001];
int list2[100001];
int n;
long long f(int m)
{
for (int i = 0; i < n; i++)list2[i] = m - list1[n-1-i];
long long sum = 0;
long long temp1, temp2;
for (int i = 0, j = 0; i < n && j < n;)
{
if (list1[i] < list2[j])i++;
else if (list1[i] > list2[j])j++;
else
{
temp1 = 1;
temp2 = 1;
while (i + 1 < n && list1[i + 1] == list2[j])
{
i++;
temp1++;
}
while (j + 1 < n && list2[j + 1] == list1[i])
{
j++;
temp2++;
}
sum += temp1*temp2;
i++;
j++;
}
}
return sum;
}
int main()
{
cin >> n;
int max = 0;
for (int i = 0; i < n; i++)cin >> list1[i];
sort(list1, list1 + n);
int mi = 1;
long long sum = 0;
for (int i = 0; i < 30; i++)
{
mi *= 2;
sum += f(mi);
}
for (int i = 0; i < n; i++)
if (list1[i] == (list1[i] & (-list1[i])))sum--;
cout << sum / 2;
return 0;
}
力扣 136. 只出现一次的数字(基于计算的搜索)
题目:
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
说明:
你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?
示例 1:
输入: [2,2,1]
输出: 1
示例 2:
输入: [4,1,2,1,2]
输出: 4
代码:
class Solution {
public:
int singleNumber(vector<int>& nums) {
int ans = 0;
for (auto it = nums.begin(); it != nums.end(); it++)ans ^= *it;
return ans;
}
};
力扣 137. 只出现一次的数字 II
题目:
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现了三次。找出那个只出现了一次的元素。
说明:
你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?
示例 1:
输入: [2,2,3,2]
输出: 3
示例 2:
输入: [0,1,0,1,0,1,99]
输出: 99
代码:
class Solution {
public:
long long yiHuoAtBase3(long long a, long long b)
{
for (long long k = 1; k <= b; k *= 3){
a -= (a / k % 3 - (a / k + b / k) % 3)*k;
}
return a;
}
int singleNumber(vector<int>& nums) {
long long ans1 = 0, ans2 = 0;
for (auto it = nums.begin(); it != nums.end(); it++){
long long tem = *it;
if (tem >= 0){
ans1 = yiHuoAtBase3(ans1, tem);
}
else{
ans2 = yiHuoAtBase3(ans2, -tem);
}
}
return ans1-ans2;
}
};
力扣 260. 只出现一次的数字 III
题目:
给定一个整数数组 nums,其中恰好有两个元素只出现一次,其余所有元素均出现两次。 找出只出现一次的那两个元素。
示例 :
输入: [1,2,1,3,2,5]
输出: [3,5]
注意:
结果输出的顺序并不重要,对于上面的例子, [5, 3] 也是正确答案。
你的算法应该具有线性时间复杂度。你能否仅使用常数空间复杂度来实现?
代码:
class Solution {
public:
vector<int> singleNumber(vector<int>& nums) {
int s1, s2;
long long s = 0;
for (int i = 0; i < nums.size(); i++)s ^= nums[i];
s = s & -s;
s1 = 0, s2 = 0;
for (int i = 0; i < nums.size(); i++)
{
if (s&nums[i])s1 ^= nums[i];
else s2 ^= nums[i];
}
return vector<int>{s1, s2};
}
};
力扣 268. 缺失数字
题目:
给定一个包含 0, 1, 2, ..., n 中 n 个数的序列,找出 0 .. n 中没有出现在序列中的那个数。
示例 1:
输入: [3,0,1]
输出: 2
示例 2:
输入: [9,6,4,2,3,5,7,0,1]
输出: 8
说明:
你的算法应具有线性时间复杂度。你能否仅使用额外常数空间来实现?
代码:
class Solution {
public:
int missingNumber(vector<int>& nums) {
int n = nums.size();
int ans = 0;
for (int i = 1; i <= n; i++)ans ^= (i^nums[i - 1]);
return ans;
}
};
力扣 2965. 找出缺失和重复的数字
给你一个下标从 0 开始的二维整数矩阵 grid
,大小为 n * n
,其中的值在 [1, n2]
范围内。除了 a
出现 两次,b
缺失 之外,每个整数都 恰好出现一次 。
任务是找出重复的数字a
和缺失的数字 b
。
返回一个下标从 0 开始、长度为 2
的整数数组 ans
,其中 ans[0]
等于 a
,ans[1]
等于 b
。
示例 1:
输入:grid = [[1,3],[2,2]]
输出:[2,4]
解释:数字 2 重复,数字 4 缺失,所以答案是 [2,4] 。
示例 2:
输入:grid = [[9,1,7],[8,9,2],[3,4,6]]
输出:[9,5]
解释:数字 9 重复,数字 5 缺失,所以答案是 [9,5] 。
提示:
2 <= n == grid.length == grid[i].length <= 50
1 <= grid[i][j] <= n * n
- 对于所有满足
1 <= x <= n * n
的x
,恰好存在一个x
与矩阵中的任何成员都不相等。 - 对于所有满足
1 <= x <= n * n
的x
,恰好存在一个x
与矩阵中的两个成员相等。 - 除上述的两个之外,对于所有满足
1 <= x <= n * n
的x
,都恰好存在一对i, j
满足0 <= i, j <= n - 1
且grid[i][j] == x
。
class Solution {
public:
vector<int> findMissingAndRepeatedValues(vector<vector<int>>& grid) {
set<int>s;
int a=0,n=grid.size(),ss=-(n*n+1)*n*n/2;
for(auto v:grid){
for(auto x:v){
if(s.find(x)!=s.end())a=x;
s.insert(x);
ss+=x;
}
}
return vector<int>{a,a-ss};
}
};
力扣 面试题 17.19. 消失的两个数字
给定一个数组,包含从 1 到 N 所有的整数,但其中缺了两个数字。你能在 O(N) 时间内只用 O(1) 的空间找到它们吗?
以任意顺序返回这两个数字均可。
示例 1:
输入: [1]
输出: [2,3]
示例 2:
输入: [2,3]
输出: [1,4]
提示:
nums.length <= 30000
class Solution {
public:
vector<int> missingTwo(vector<int>& nums) {
int s1, s2;
long long s = 0;
for (int i = 0; i < nums.size(); i++)s ^= nums[i];
for (int i = 1; i <= nums.size() + 2; i++)s ^= i;
s = s & -s;
s1 = 0, s2 = 0;
for (int i = 0; i < nums.size(); i++)
{
if (s&nums[i])s1 ^= nums[i];
else s2 ^= nums[i];
}
for (int i = 1; i <= nums.size() + 2; i++) {
if (s&i)s1 ^= i;
else s2 ^= i;
}
return vector<int>{s1, s2};
}
};
二,数组和集合的搜索else
力扣 35. 搜索插入位置
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
请必须使用时间复杂度为 O(log n)
的算法。
示例 1:
输入: nums = [1,3,5,6], target = 5 输出: 2
示例 2:
输入: nums = [1,3,5,6], target = 2 输出: 1
示例 3:
输入: nums = [1,3,5,6], target = 7 输出: 4
提示:
1 <= nums.length <= 104
-104 <= nums[i] <= 104
nums
为 无重复元素 的 升序 排列数组-104 <= target <= 104
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
return lower_bound(nums.begin(),nums.end(),target)-nums.begin();
}
};
力扣 805. 数组的均值分割
给定你一个整数数组 nums
我们要将 nums
数组中的每个元素移动到 A
数组 或者 B
数组中,使得 A
数组和 B
数组不为空,并且 average(A) == average(B)
。
如果可以完成则返回true
, 否则返回 false
。
注意:对于数组 arr
, average(arr)
是 arr
的所有元素除以 arr
长度的和。
示例 1:
输入: nums = [1,2,3,4,5,6,7,8] 输出: true 解释: 我们可以将数组分割为 [1,4,5,8] 和 [2,3,6,7], 他们的平均值都是4.5。
示例 2:
输入: nums = [3,1] 输出: false
提示:
1 <= nums.length <= 30
0 <= nums[i] <= 104
class Solution {
public:
bool splitArraySameAverage(vector<int>& nums) {
if (nums.size() < 2)return false;
double s = GetVecSum(nums);
vector<int>v(nums.size());
for (int i = 0; i < nums.size(); i++)v[i] = nums[i]* nums.size() - s;
vector<int>vs(1);
vs[0] = 0;
for (int i = 0; i < v.size() / 2; i++) {
int len = vs.size();
for (int j = 0; j < len; j++)vs.push_back(vs[j] + v[i]);
}
vector<int>vs2(1);
vs2[0] = 0;
for (int i = v.size() / 2; i < v.size(); i++) {
int len = vs2.size();
for (int j = 0; j < len; j++)vs2.push_back(vs2[j] + v[i]);
}
map<int, int>m;
for (int i = 1; i < vs.size();i++)m[vs[i]] = 1;
for (int i = 1; i < vs2.size() - 1; i++)if (vs2[i] == 0 || m[-vs2[i]])return true;
return m[0]|| vs2.back() == 0;
}
};
力扣 1658. 将 x 减到 0 的最小操作数
给你一个整数数组 nums 和一个整数 x 。每一次操作时,你应当移除数组 nums 最左边或最右边的元素,然后从 x 中减去该元素的值。请注意,需要 修改 数组以供接下来的操作使用。
如果可以将 x 恰好 减到 0 ,返回 最小操作数 ;否则,返回 -1 。
示例 1:
输入:nums = [1,1,4,2,3], x = 5
输出:2
解释:最佳解决方案是移除后两个元素,将 x 减到 0 。
示例 2:
输入:nums = [5,6,7,8,9], x = 4
输出:-1
示例 3:
输入:nums = [3,2,20,1,1,3], x = 10
输出:5
解释:最佳解决方案是移除后三个元素和前两个元素(总共 5 次操作),将 x 减到 0 。
提示:
1 <= nums.length <= 105
1 <= nums[i] <= 104
1 <= x <= 109
class Solution {
public:
int minOperations(vector<int>& nums, int x) {
int s = 0;
map<int, int>ms;
for (int i = 0; i < nums.size(); i++)s += nums[i], ms[s] = i;
x = s - x, s = 0;
int ans = -1;
if (x == 0)ans = 0;
if (x == nums[0])ans = 1;
if (ms[x] > 0)ans = ms[x]+1;
for (int i = 0; i < nums.size(); i++) {
s += nums[i];
if (ms[s + x] > i)ans = max(ans, ms[s + x] - i);
}
if (ans == -1)return -1;
return nums.size() - ans;
}
};
剑指 Offer 03. 数组中重复的数字
找出数组中重复的数字。
在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。
示例 1:
输入:
[2, 3, 1, 0, 2, 5, 3]
输出:2 或 3
限制:
2 <= n <= 100000
第一个代码:
class Solution {
public:
int findRepeatNumber(vector<int>& nums) {
for(int i=0;i<nums.size();i++)
{
if(nums[i]-i)
{
int j=nums[i];
if(nums[i]==nums[j])return nums[i];
nums[i]^=nums[j]^=nums[i]^=nums[j];
}
}
return -1;
}
};
AC了,但是总感觉不太对,然后我自己找出来了反例:
[2, 3, 1, 0,1]
修正代码:
class Solution {
public:
int findRepeatNumber(vector<int>& nums) {
for(int i=0;i<nums.size();i++)
{
while(nums[i]-i)
{
int j=nums[i];
if(nums[i]==nums[j])return nums[i];
nums[i]^=nums[j]^=nums[i]^=nums[nums[i]];
}
}
return -1;
}
};
二,二维表格搜索
力扣 764. 最大加号标志
在一个 n x n
的矩阵 grid
中,除了在数组 mines
中给出的元素为 0
,其他每个元素都为 1
。mines[i] = [xi, yi]
表示 grid[xi][yi] == 0
返回 grid
中包含 1
的最大的 轴对齐 加号标志的阶数 。如果未找到加号标志,则返回 0
。
一个 k
阶由 1
组成的 “轴对称”加号标志 具有中心网格 grid[r][c] == 1
,以及4个从中心向上、向下、向左、向右延伸,长度为 k-1
,由 1
组成的臂。注意,只有加号标志的所有网格要求为 1
,别的网格可能为 0
也可能为 1
。
示例 1:
输入: n = 5, mines = [[4, 2]] 输出: 2 解释: 在上面的网格中,最大加号标志的阶只能是2。一个标志已在图中标出。
示例 2:
输入: n = 1, mines = [[0, 0]] 输出: 0 解释: 没有加号标志,返回 0 。
提示:
1 <= n <= 500
1 <= mines.length <= 5000
0 <= xi, yi < n
- 每一对
(xi, yi)
都 不重复
思路:2个方向单独算即可,时间复杂度是O(n^2)
class Solution {
public:
vector<vector<int>> orderOfLargestLine(vector<vector<int>>& m)
{
vector<vector<int>>ans;
vector<int>v(m[0].size());
for (int i = 0; i < m.size(); i++) {
int s = 0, e = 0;
for (int j = 0; j < m[i].size(); j++) {
if (m[i][j] == 0) {
v[j] = 0;
continue;
}
int k = j;
while (k < m[i].size() && m[i][k] == 1)k++;
k--;
for (int t = j; t <= k; t++)v[t] = min(t - j, k - t) + 1;
j = k;
}
ans.push_back(v);
}
return ans;
}
int orderOfLargestPlusSign(int n, vector<vector<int>>& mines) {
vector<vector<int>>m(n, vector<int>(n, 1));
for (auto mi : mines)m[mi[0]][mi[1]] = 0;
vector<vector<int>> v = orderOfLargestLine(m);
auto m2 = Foverturn(m);
vector<vector<int>> v2 = orderOfLargestLine(m2);
int ans = 0;
for (int i = 0; i < v.size(); i++)for (int j = 0; j < v[i].size(); j++)ans = max(ans, min(v[i][j], v2[j][i]));
return ans;
}
};
力扣 1391. 检查网格中是否存在有效路径
给你一个 m x n 的网格 grid。网格里的每个单元都代表一条街道。grid[i][j] 的街道可以是:
1 表示连接左单元格和右单元格的街道。
2 表示连接上单元格和下单元格的街道。
3 表示连接左单元格和下单元格的街道。
4 表示连接右单元格和下单元格的街道。
5 表示连接左单元格和上单元格的街道。
6 表示连接右单元格和上单元格的街道。
你最开始从左上角的单元格 (0,0) 开始出发,网格中的「有效路径」是指从左上方的单元格 (0,0) 开始、一直到右下方的 (m-1,n-1) 结束的路径。该路径必须只沿着街道走。
注意:你 不能 变更街道。
如果网格中存在有效的路径,则返回 true,否则返回 false 。
示例 1:
输入:grid = [[2,4,3],[6,5,2]]
输出:true
解释:如图所示,你可以从 (0, 0) 开始,访问网格中的所有单元格并到达 (m - 1, n - 1) 。
示例 2:
输入:grid = [[1,2,1],[1,2,1]]
输出:false
解释:如图所示,单元格 (0, 0) 上的街道没有与任何其他单元格上的街道相连,你只会停在 (0, 0) 处。
示例 3:
输入:grid = [[1,1,2]]
输出:false
解释:你会停在 (0, 1),而且无法到达 (0, 2) 。
示例 4:
输入:grid = [[1,1,1,1,1,1,3]]
输出:true
示例 5:
输入:grid = [[2],[2],[2],[2],[2],[2],[6]]
输出:true
提示:
m == grid.length
n == grid[i].length
1 <= m, n <= 300
1 <= grid[i][j] <= 6
思路:
如果左上角不是4,那么就只有一条路出来,后面一直是一条路没有分叉。
如果左上角是4,那就是2条独立的无分叉路。
class Solution {
public:
int id(int x, int y)
{
return x * col + y;
}
vector<int> getNeighbor4(int k)
{
vector<int>ans;
if (k >= col)ans.push_back(k - col);
if (k < (row - 1) * col)ans.push_back(k + col);
if (k % col)ans.push_back(k - 1);
if (k % col < col - 1)ans.push_back(k + 1);
return ans;
}
bool isConnect(const vector<vector<int>>& grid, int s, int t)
{
if (t - s < 0)return isConnect(grid, t, s);
int xs = grid[s / col][s % col];
int xt = grid[t / col][t % col];
if (t - s == col)return (xs == 2 || xs == 3 || xs == 4) && (xt == 2 || xt == 5 || xt == 6);
if (t - s == 1)return (xs == 1 || xs == 4 || xs == 6) && (xt == 1 || xt == 3 || xt == 5);
return false;
}
bool hasValidPath2(const vector<vector<int>>& grid)
{
row = grid.size();
col = grid[0].size();
map<int, int>m;
m[0] = 1;
int k = 0;
while (m[id(row - 1, col - 1)] == 0) {
vector<int> v = getNeighbor4(k);
bool flag = false;
for (auto vi : v) {
if (isConnect(grid, k, vi) && m[vi] == 0) {
m[vi] = 1, k = vi;
flag = true;
break;
}
}
if (!flag)return false;
}
return true;
}
bool hasValidPath(vector<vector<int>>& grid)
{
if (hasValidPath2(grid))return true;
if (grid[0][0] == 4) {
grid[0][0] = 1;
return hasValidPath2(grid);
}
return false;
}
int row;
int col;
};