二分查找
lc.34 排序数组中查找元素第一和最后一个位置
给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。
如果数组中不存在目标值 target,返回 [-1, -1]。
- 二分
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target) {
vector<int> ans;
if(!nums.empty()){
int l = 0 , r = nums.size() - 1 ;
while(l < r){
int mid = l + r >> 1;
if(nums[mid] >= target) r = mid;
else l = mid + 1;
}
if(nums[l] != target) ans.emplace_back(-1);
else ans.emplace_back(l);
l = 0 , r = nums.size() - 1 ;
while(l < r){
int mid = l + r + 1>> 1;
if(nums[mid] <= target) l = mid;
else r = mid - 1;
}
if(nums[l] != target) ans.emplace_back(-1);
else ans.emplace_back(l);
}
else{
ans.emplace_back(-1);
ans.emplace_back(-1);
}
return ans;
}
};
得考虑一下数组空的情况。
lc.33 搜索旋转排序数组
整数数组 nums 按升序排列,数组中的值 互不相同 。
在传递给函数之前,nums 在预先未知的某个下标 k(0 <= k < nums.length)上进行了 旋转,使数组变为 [nums[k], nums[k+1], …, nums[n-1], nums[0], nums[1], …, nums[k-1]](下标 从 0 开始 计数)。例如, [0,1,2,4,5,6,7] 在下标 3 处经旋转后可能变为 [4,5,6,7,0,1,2] 。
给你 旋转后 的数组 nums 和一个整数 target ,如果 nums 中存在这个目标值 target ,则返回它的下标,否则返回 -1 。
- 二分
class Solution {
public:
int search(vector<int>& nums, int target) {
int min = 1e4 , xbmin = 0 ;
for(int i = 0 ; i < nums.size() ; i ++){
if(nums[i] < min){
min = nums[i];
xbmin = i ;
}
}
int l = 0 , r = xbmin - 1 ;
while(l < r){
int mid = l + r >> 1;
if(nums[mid] >= target) r = mid;
else l = mid + 1;
}
if(nums[l] == target) return l;
l = xbmin , r = nums.size() - 1;
while(l < r){
int mid = l + r >> 1;
if(nums[mid] >= target) r = mid;
else l = mid + 1;
}
if(nums[l] == target) return l;
return -1;
}
};
lc.74 搜索二维矩阵
编写一个高效的算法来判断 m x n 矩阵中,是否存在一个目标值。该矩阵具有如下特性:
每行中的整数从左到右按升序排列。
每行的第一个整数大于前一行的最后一个整数。
- 二分
class Solution {
public:
bool searchMatrix(vector<vector<int>>& matrix, int target) {
int m=matrix.size();
int n=matrix[0].size();
int size=m*n;
int T=size-1;
int H=0;
while(H<T){
int mid=(H+T)/2;
int X=mid%n;
int Y=mid/n;
if(matrix[Y][X]<target){
H=mid+1;
}
else if(matrix[Y][X]>target){
T=mid-1;
}
else
return true;
}
int X=H%n;
int Y=H/n;
if(matrix[Y][X]==target)
return true;
return false;
}
};
lc.153 寻找旋转排序数组中的最小值
已知一个长度为 n 的数组,预先按照升序排列,经由 1 到 n 次 旋转 后,得到输入数组。例如,原数组 nums = [0,1,2,4,5,6,7] 在变化后可能得到:
若旋转 4 次,则可以得到 [4,5,6,7,0,1,2]
若旋转 7 次,则可以得到 [0,1,2,4,5,6,7]
注意,数组 [a[0], a[1], a[2], …, a[n-1]] 旋转一次 的结果为数组 [a[n-1], a[0], a[1], a[2], …, a[n-2]] 。
给你一个元素值 互不相同 的数组 nums ,它原来是一个升序排列的数组,并按上述情形进行了多次旋转。请你找出并返回数组中的 最小元素 。
- 找头
class Solution {
public:
int findMin(vector<int>& nums) {
for(int i = 0 ; i < nums.size() - 1 ; i ++){
if(nums[i] >= nums[i + 1]) return nums[i + 1];
}
return nums[0];
}
};
都没有用到二分。
- 二分
class Solution {
public:
int findMin(vector<int>& nums) {
int low = 0;
int high = nums.size() - 1;
while (low < high) {
int pivot = low + (high - low) / 2;
if (nums[pivot] < nums[high]) {
high = pivot;
}
else {
low = pivot + 1;
}
}
return nums[low];
}
};
和判断一样快。理论与实际的差别吧。
lc.162 寻找峰值
峰值元素是指其值严格大于左右相邻值的元素。
给你一个整数数组 nums,找到峰值元素并返回其索引。数组可能包含多个峰值,在这种情况下,返回 任何一个峰值 所在位置即可。
你可以假设 nums[-1] = nums[n] = -∞ 。
你必须实现时间复杂度为 O(log n) 的算法来解决此问题。
和 lc.153 一样,也可以用一次 O(n) 的遍历解决。不过二分理论上更快。
- 二分查找
class Solution {
public:
int findPeakElement(vector<int>& nums) {
int l = 0 , r = nums.size() -1 ;
while(l < r){
int mid = l + r >> 1;
if(nums[mid+1] < nums[mid]) r = mid;
else l = mid + 1;
}
return l;
}
};
从中间值看起,哪侧更大往哪侧搜索。
双指针
lc.82 删除排序链表的重复元素
存在一个按升序排列的链表,给你这个链表的头节点 head ,请你删除链表中所有存在数字重复情况的节点,只保留原始链表中 没有重复出现 的数字。
返回同样按升序排列的结果链表。
/**
* 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* deleteDuplicates(ListNode* head) {
if (!head) {
return head;
}
ListNode* dummy = new ListNode(0, head);
ListNode* cur = dummy;
while (cur->next && cur->next->next) {
if (cur->next->val == cur->next->next->val) {
int x = cur->next->val;
while (cur->next && cur->next->val == x) {
cur->next = cur->next->next;
}
}
else {
cur = cur->next;
}
}
return dummy->next;
}
};
由于头结点可能被删除,所以在头结点之前建立一个哑节点。
lc.15 三数之和
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组。
- 超时(?)
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> ans;
vector<int> thi;
int cnt = 0;
sort(nums.begin(),nums.end());
for(int i = 0 ; i < nums.size() ; i ++){
int l = i + 1 , r = nums.size() - 1 ;
while(l < r){
if(nums[l] + nums[r] + nums[i] < 0) l ++;
else if(nums[l] + nums[r] + nums[i] > 0) r --;
else {
thi.emplace_back(nums[i]);
thi.emplace_back(nums[l]);
thi.emplace_back(nums[r]);
for(auto& it : ans){
if(it == thi) cnt ++;
}
if(cnt == 0) ans.emplace_back(thi);
thi.clear();
cnt = 0 ;
l++ ; r -- ;
}
}
}
return ans;
}
};
超了,但没完全超。过了,但也没通过。
- 官方题解
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
int n = nums.size();
sort(nums.begin(), nums.end());
vector<vector<int>> ans;
// 枚举 a
for (int first = 0; first < n; ++first) {
// 需要和上一次枚举的数不相同
if (first > 0 && nums[first] == nums[first - 1]) {
continue;
}
// c 对应的指针初始指向数组的最右端
int third = n - 1;
int target = -nums[first];
// 枚举 b
for (int second = first + 1; second < n; ++second) {
// 需要和上一次枚举的数不相同
if (second > first + 1 && nums[second] == nums[second - 1]) {
continue;
}
// 需要保证 b 的指针在 c 的指针的左侧
while (second < third && nums[second] + nums[third] > target) {
--third;
}
// 如果指针重合,随着 b 后续的增加
// 就不会有满足 a+b+c=0 并且 b<c 的 c 了,可以退出循环
if (second == third) {
break;
}
if (nums[second] + nums[third] == target) {
ans.push_back({nums[first], nums[second], nums[third]});
}
}
}
return ans;
}
};
判断与上次枚举的数不同,以排除结果里相同的解。就不需要通过再遍历一遍来去重。
lc.844 比较含退格符的字符串
- 开两个 vector 存退格结束的字符串
class Solution {
public:
bool backspaceCompare(string s, string t) {
vector<char> tmps , tmpt;
for(int i = 0 ; i < s.size() ; i ++){
if(s[i] == '#'){
if(!tmps.empty()) tmps.pop_back();
}
else tmps.push_back(s[i]);
}
for(int i = 0 ; i < t.size() ; i ++){
if(t[i] == '#'){
if(!tmpt.empty()) tmpt.pop_back();
}
else tmpt.push_back(t[i]);
}
if(tmps.size() != tmpt.size()) return false;
for(int i = 0 ; i < tmps.size() ; i ++){
if(tmps[i] != tmpt[i]) return false;
}
return true;
}
};
也可以用栈实现。
- 双指针
class Solution {
public:
bool backspaceCompare(string S, string T) {
int i = S.length() - 1, j = T.length() - 1;
int skipS = 0, skipT = 0;
while (i >= 0 || j >= 0) {
while (i >= 0) {
if (S[i] == '#') {skipS++, i--;}
else if (skipS > 0) {skipS--, i--;}
else break;
}
while (j >= 0) {
if (T[j] == '#') {skipT++, j--;}
else if (skipT > 0) {skipT--, j--;}
else break;
}
if (i >= 0 && j >= 0) {
if (S[i] != T[j]) {
return false;
}
} else {
if (i >= 0 || j >= 0) {
return false;
}
}
i--, j--;
}
return true;
}
};
理论上时间和第一种都是 O( M + N ) ,而空间第一种 O ( M + N ),第二种 O( 1 ) 。
然而实际上运算结果空间的占用情况两者没有差异。
lc.986 区间列表的交集
给定两个由一些 闭区间 组成的列表,firstList 和 secondList ,其中 firstList[i] = [starti, endi] 而 secondList[j] = [startj, endj] 。每个区间列表都是成对 不相交 的,并且 已经排序 。
返回这 两个区间列表的交集 。
形式上,闭区间 [a, b](其中 a <= b)表示实数 x 的集合,而 a <= x <= b 。
两个闭区间的 交集 是一组实数,要么为空集,要么为闭区间。例如,[1, 3] 和 [2, 4] 的交集为 [2, 3] 。
- 归并
class Solution {
public:
vector<vector<int>> intervalIntersection(vector<vector<int>>& firstList, vector<vector<int>>& secondList) {
vector<vector<int>> ans;
vector<int> tmp;
int vi = 0 , vj = 0 ;
while(vi < firstList.size() && vj < secondList.size()){
int fb = firstList[vi][0], sb = secondList[vj][0], fe = firstList[vi][1], se = secondList[vj][1];
tmp.emplace_back(max(fb,sb));
tmp.emplace_back(min(fe,se));
if(tmp[0] <= tmp[1]) {
ans.emplace_back(tmp);
tmp.clear();
}
if(fe < se) vi ++;
else vj ++;
tmp.clear();
}
return ans;
}
};
只需要判断每个区间的尾巴就好,比头小一定比尾巴小。智慧的光辉在闪烁。
lc.11 盛最多水的容器
给你 n 个非负整数 a1,a2,…,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0) 。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。
说明:你不能倾斜容器。
- 双指针
class Solution {
public:
int maxArea(vector<int>& height) {
int l = 0 , r = (int)height.size() - 1;
int ans = 0 , tmp = 0;
while(l < r){
tmp = (r - l) * min(height[l] , height[r]);
ans = max(ans , tmp);
if(height[l] < height[r]) l ++;
else r --;
}
return ans;
}
};
滑动窗口
lc.713 乘积小于k的子数组
给定一个正整数数组 nums和整数 k 。
请找出该数组内乘积小于 k 的连续的子数组的个数。
- 遍历 + 滑动
class Solution {
public:
int numSubarrayProductLessThanK(vector<int>& nums, int k) {
int n = nums.size() , cnt = 0 ;
int allpro = (1 + n) * n / 2;
if(n == 1){
if(nums[0] < k) return 1;
else return 0;
}
for(int i = 0 ; i < n ; i ++){
int j = i + 1 , pro = nums[i] ;
if(nums[i] >= k){
cnt += n - i ;
continue;
}
while(j < n){
pro *= nums[j];
if(pro >= k){
cnt += n - j ;
break;
}
j ++;
}
}
return allpro - cnt;
}
};
- 滑动窗口
class Solution {
public:
int numSubarrayProductLessThanK(vector<int>& nums, int k) {
if (k <= 1) return 0;
int prod = 1, ans = 0, left = 0;
for (int right = 0; right < nums.size(); right++) {
prod *= nums[right];
while (prod >= k) prod /= nums[left++];
ans += right - left + 1;
}
return ans;
}
};
lc.209 长度最小的子数组
给定一个含有 n 个正整数的数组和一个正整数 target 。
找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl+1, …, numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0 。
- 滑动窗口 + 一堆倒霉的边界条件
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
int n = nums.size() ;
if(n == 1) {
if(nums[0] >= target) return 1;
else return 0 ;
}
if(nums[0] >= target) return 1;
int l = 0 , r = 1 , sum = nums[0] + nums[1] , ans = n + 1;
while(r < n && l <= r){
if(sum < target){
if(r == n - 1) break;
r ++;
sum += nums[r];
}
else{
ans = min(ans , r - l + 1);
sum -= nums[l];
l ++;
}
}
if(ans < n + 1) return ans;
else return 0 ;
}
};
DFS/BFS
lc.200 岛屿数量
给你一个由 ‘1’(陆地)和 ‘0’(水)组成的的二维网格,请你计算网格中岛屿的数量。
岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。
此外,你可以假设该网格的四条边均被水包围。
- DFS
class Solution {
public:
void dfs(vector<vector<char>>& grid , int i , int j){
int m = grid.size() , n = grid[0].size();
grid[i][j] = '0';
if(i + 1 < m && grid[i + 1][j] == '1') dfs(grid, i + 1 , j);
if(i - 1 > -1 && grid[i - 1][j] == '1') dfs(grid, i - 1 , j);
if(j + 1 < n && grid[i][j + 1] == '1') dfs(grid, i , j + 1);
if(j - 1 > -1 && grid[i][j - 1] == '1') dfs(grid, i , j - 1);
}
int numIslands(vector<vector<char>>& grid) {
int m = grid.size() , n = grid[0].size();
if(m == 0) return 0 ;
int cnt = 0 ;
for(int i = 0 ; i < m ; i ++){
for(int j = 0 ; j < n ; j ++){
if(grid[i][j] == '1'){
cnt ++;
dfs(grid , i , j);
}
}
}
return cnt;
}
};
每一个点都搜一遍,如果是 ‘1’ 就 DFS 一下,并且全变成 ‘0’ 。
- BFS
class Solution {
public:
int numIslands(vector<vector<char>>& grid) {
int m = grid.size() , n = grid[0].size();
if(m == 0) return 0 ;
int cnt = 0 ;
for(int i = 0 ; i < m ; i ++){
for(int j = 0 ; j < n ; j ++){
if(grid[i][j] == '1'){
cnt ++;
grid[i][j] = '0';
queue<pair<int,int>> isL;
isL.push({i,j});
while(!isL.empty()){
auto tmp = isL.front();
isL.pop();
int x = tmp.first , y = tmp.second;
if(x + 1 < m && grid[x + 1][y] == '1') {
isL.push({x + 1 , y}); grid[x + 1][y] = '0';
}
if(x - 1 > -1 && grid[x - 1][y] == '1'){
isL.push({x - 1 , y}); grid[x - 1][y] = '0';
}
if(y + 1 < n && grid[x][y + 1] == '1'){
isL.push({x , y + 1}); grid[x][y + 1] = '0';
}
if(y - 1 > -1 && grid[x][y - 1] == '1'){
isL.push({x , y - 1}); grid[x][y - 1] = '0';
}
}
}
}
}
return cnt;
}
};
用 queue 实现 , 注意写法queue<pair<int,int>> isL;
, int x = tmp.first , y = tmp.second;
- 并查集
class UnionFind {
public:
UnionFind(vector<vector<char>>& grid) {
count = 0;
int m = grid.size();
int n = grid[0].size();
for (int i = 0; i < m; ++i) {
for (int j = 0; j < n; ++j) {
if (grid[i][j] == '1') {
parent.push_back(i * n + j);
++count;
}
else {
parent.push_back(-1);
}
rank.push_back(0);
}
}
}
int find(int i) {
if (parent[i] != i) {
parent[i] = find(parent[i]);
}
return parent[i];
}
void unite(int x, int y) {
int rootx = find(x);
int rooty = find(y);
if (rootx != rooty) {
if (rank[rootx] < rank[rooty]) {
swap(rootx, rooty);
}
parent[rooty] = rootx;
if (rank[rootx] == rank[rooty]) rank[rootx] += 1;
--count;
}
}
int getCount() const {
return count;
}
private:
vector<int> parent;
vector<int> rank;
int count;
};
class Solution {
public:
int numIslands(vector<vector<char>>& grid) {
int nr = grid.size();
if (!nr) return 0;
int nc = grid[0].size();
UnionFind uf(grid);
int num_islands = 0;
for (int r = 0; r < nr; ++r) {
for (int c = 0; c < nc; ++c) {
if (grid[r][c] == '1') {
grid[r][c] = '0';
if (r - 1 >= 0 && grid[r-1][c] == '1') uf.unite(r * nc + c, (r-1) * nc + c);
if (r + 1 < nr && grid[r+1][c] == '1') uf.unite(r * nc + c, (r+1) * nc + c);
if (c - 1 >= 0 && grid[r][c-1] == '1') uf.unite(r * nc + c, r * nc + c - 1);
if (c + 1 < nc && grid[r][c+1] == '1') uf.unite(r * nc + c, r * nc + c + 1);
}
}
}
return uf.getCount();
}
};
lc.547 省份数量
有 n 个城市,其中一些彼此相连,另一些没有相连。如果城市 a 与城市 b 直接相连,且城市 b 与城市 c 直接相连,那么城市 a 与城市 c 间接相连。
省份 是一组直接或间接相连的城市,组内不含其他没有相连的城市。
给你一个 n x n 的矩阵 isConnected ,其中 isConnected[i][j] = 1 表示第 i 个城市和第 j 个城市直接相连,而 isConnected[i][j] = 0 表示二者不直接相连。
返回矩阵中 省份 的数量。
- BFS
class Solution {
public:
int findCircleNum(vector<vector<int>>& isConnected) {
int n = isConnected.size() , cnt = 0 ;
vector<int> city(n,0);
queue<int> que;
for(int i = 0 ; i < n ; i ++){
if(city[i] == 0){
que.push(i);
city[i] = 1;
while(!que.empty()){
auto x = que.front();
que.pop();
for(int j = 0 ; j < n ; j ++){
if(isConnected[x][j] == 1 && city[j] == 0){
que.push(j);
city[j] = 1;
}
}
}
cnt ++;
}
}
return cnt;
}
};
- DFS
class Solution {
public:
void dfs(vector<vector<int>>& isConnected , int i , vector<int>& city){
int n = isConnected.size();
for(int j = 0 ; j < n ; j ++){
if(isConnected[i][j] == 1 && city[j] == 0){
city[j] = 1;
dfs(isConnected , j , city);
}
}
}
int findCircleNum(vector<vector<int>>& isConnected) {
int n = isConnected.size() , cnt = 0 ;
vector<int> city(n,0);
for(int i = 0 ; i < n ; i ++){
if(city[i] == 0){
dfs(isConnected , i , city);
cnt ++;
}
}
return cnt;
}
};
lc.117 填充每个节点的下一个右侧结点 II
给定一个二叉树
struct Node {
int val;
Node *left;
Node *right;
Node *next;
}
填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL。
初始状态下,所有 next 指针都被设置为 NULL。
/*
// Definition for a Node.
class Node {
public:
int val;
Node* left;
Node* right;
Node* next;
Node() : val(0), left(NULL), right(NULL), next(NULL) {}
Node(int _val) : val(_val), left(NULL), right(NULL), next(NULL) {}
Node(int _val, Node* _left, Node* _right, Node* _next)
: val(_val), left(_left), right(_right), next(_next) {}
};
*/
class Solution {
public:
Node* connect(Node* root) {
if (!root) {
return nullptr;
}
queue<Node*> q;
q.push(root);
while (!q.empty()) {
int n = q.size();
Node *last = nullptr;
for (int i = 1; i <= n; ++i) {
Node *f = q.front();
q.pop();
if (f->left) {
q.push(f->left);
}
if (f->right) {
q.push(f->right);
}
if (i != 1) {
last->next = f;
}
last = f;
}
}
return root;
}
};
lc.572 另一棵树的子数
给你两棵二叉树 root 和 subRoot 。检验 root 中是否包含和 subRoot 具有相同结构和节点值的子树。如果存在,返回 true ;否则,返回 false 。
二叉树 tree 的一棵子树包括 tree 的某个节点和这个节点的所有后代节点。tree 也可以看做它自身的一棵子树。
/**
* 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:
static constexpr int MAX_N = 1000 + 5;
static constexpr int MOD = int(1E9) + 7;
bool vis[MAX_N];
int p[MAX_N], tot;
void getPrime() {
vis[0] = vis[1] = 1; tot = 0;
for (int i = 2; i < MAX_N; ++i) {
if (!vis[i]) p[++tot] = i;
for (int j = 1; j <= tot && i * p[j] < MAX_N; ++j) {
vis[i * p[j]] = 1;
if (i % p[j] == 0) break;
}
}
}
struct Status {
int f, s; // f 为哈希值 | s 为子树大小
Status(int f_ = 0, int s_ = 0)
: f(f_), s(s_) {}
};
unordered_map <TreeNode *, Status> hS, hT;
void dfs(TreeNode *o, unordered_map <TreeNode *, Status> &h) {
h[o] = Status(o->val, 1);
if (!o->left && !o->right) return;
if (o->left) {
dfs(o->left, h);
h[o].s += h[o->left].s;
h[o].f = (h[o].f + (31LL * h[o->left].f * p[h[o->left].s]) % MOD) % MOD;
}
if (o->right) {
dfs(o->right, h);
h[o].s += h[o->right].s;
h[o].f = (h[o].f + (179LL * h[o->right].f * p[h[o->right].s]) % MOD) % MOD;
}
}
bool isSubtree(TreeNode* s, TreeNode* t) {
getPrime();
dfs(s, hS);
dfs(t, hT);
int tHash = hT[t].f;
for (const auto &[k, v]: hS) {
if (v.f == tHash) {
return true;
}
}
return false;
}
};
lc.1091 二进制矩阵中的最短路径
给你一个 n x n 的二进制矩阵 grid 中,返回矩阵中最短 畅通路径 的长度。如果不存在这样的路径,返回 -1 。
二进制矩阵中的 畅通路径 是一条从 左上角 单元格(即,(0, 0))到 右下角 单元格(即,(n - 1, n - 1))的路径,该路径同时满足下述要求:
路径途经的所有单元格都的值都是 0 。
路径中所有相邻的单元格应当在 8 个方向之一 上连通(即,相邻两单元之间彼此不同且共享一条边或者一个角)。
畅通路径的长度 是该路径途经的单元格总数。
- BFS wrong ans
class Solution {
public:
int shortestPathBinaryMatrix(vector<vector<int>>& grid) {
vector<vector<int>> direction = {{0,1} , {-1,1} , {1,0} , {1,-1} , {0,-1} , {-1,-1} , {-1,0} , {1,1}};
int step = 1 , n = grid.size();
if(n == 0) return -1;
if(grid[0][0] == 1 || grid[n - 1][n - 1] == 1) return -1;
queue<pair<int,int>> que;
que.push( {0,0} );
grid[0][0] == 1;
while(!que.empty()){
int Times = que.size() ;
while(Times > 0){
auto tmp = que.front();
que.pop();
int x = tmp.first , y = tmp.second ;
for(int i = 0 ; i < 8 ; i ++){
int nextx = x + direction[i][0] , nexty = y + direction[i][1];
if(nextx < 0 || nextx >= n || nexty < 0 || nexty >= n || grid[nextx][nexty] == 1)
continue;
que.push({nextx,nexty});
grid[nextx][nexty] = 1;
}
Times --;
}
step ++ ;
if(grid[n - 1][n - 1] == 1) return step;
}
return -1;
}
};
判断语句 if(grid[n - 1][n - 1] == 1) return step;
使得测试用例 [[0]] 无法通过。
- BFS
class Solution {
public:
int shortestPathBinaryMatrix(vector<vector<int>>& grid) {
vector<vector<int>> direction = {{0,1} , {-1,1} , {1,0} , {1,-1} , {0,-1} , {-1,-1} , {-1,0} , {1,1}};
int step = 1 , n = grid.size();
if(n == 0) return -1;
if(grid[0][0] == 1 || grid[n - 1][n - 1] == 1) return -1;
queue<pair<int,int>> que;
que.push( {0,0} );
while(!que.empty()){
int Times = que.size() ;
while(Times > 0){
auto tmp = que.front();
que.pop();
int x = tmp.first , y = tmp.second ;
if(x == n - 1 && y == n - 1) return step;
for(int i = 0 ; i < 8 ; i ++){
int nextx = x + direction[i][0] , nexty = y + direction[i][1];
if(nextx >= 0 && nextx < n && nexty >= 0 && nexty < n && grid[nextx][nexty] == 0){
que.push({nextx , nexty});
grid[nextx][nexty] = 1;
}
}
Times --;
}
step ++ ;
}
return -1;
}
};
lc.130 被围绕的区域
给你一个 m x n 的矩阵 board ,由若干字符 ‘X’ 和 ‘O’ ,找到所有被 ‘X’ 围绕的区域,并将这些区域里所有的 ‘O’ 用 ‘X’ 填充。
- BFS wrong ans
class Solution {
public:
void solve(vector<vector<char>>& board) {
vector<vector<int>> direction = {{0,1},{0,-1},{1,0},{-1,0}};
queue<pair<int , int>> que ;
int m = board.size() , n = board[0].size() ;
if(m <= 2 || n <= 2) return ;
for(int i = 1 ; i < m - 1 ; i ++){
for(int j = 1 ; j < n - 1 ; j ++){
if(board[i][j] == 'O'){
board[i][j] = 'X';
que.push({i,j});
while(!que.empty()){
auto tmp = que.front();
que.pop();
int x = tmp.first , y = tmp.second ;
for(int pre = 0 ; pre < 4 ; pre ++){
int nx = x + direction[pre][0] , ny = y + direction[pre][1];
if(nx > 0 && nx < m - 1 && ny > 0 && ny < n - 1 && board[nx][ny] == 'O'){
board[nx][ny] = 'X';
que.push({nx,ny});
}
}
}
}
}
}
return ;
}
};
没考虑到第一个位置的合理性。
- BFS
class Solution {
public:
const int dx[4] = {1, -1, 0, 0};
const int dy[4] = {0, 0, 1, -1};
void solve(vector<vector<char>>& board) {
int n = board.size();
if (n == 0) {
return;
}
int m = board[0].size();
queue<pair<int, int>> que;
for (int i = 0; i < n; i++) {
if (board[i][0] == 'O') {
que.emplace(i, 0);
board[i][0] = 'A';
}
if (board[i][m - 1] == 'O') {
que.emplace(i, m - 1);
board[i][m - 1] = 'A';
}
}
for (int i = 1; i < m - 1; i++) {
if (board[0][i] == 'O') {
que.emplace(0, i);
board[0][i] = 'A';
}
if (board[n - 1][i] == 'O') {
que.emplace(n - 1, i);
board[n - 1][i] = 'A';
}
}
while (!que.empty()) {
int x = que.front().first, y = que.front().second;
que.pop();
for (int i = 0; i < 4; i++) {
int mx = x + dx[i], my = y + dy[i];
if (mx < 0 || my < 0 || mx >= n || my >= m || board[mx][my] != 'O') {
continue;
}
que.emplace(mx, my);
board[mx][my] = 'A';
}
}
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (board[i][j] == 'A') {
board[i][j] = 'O';
} else if (board[i][j] == 'O') {
board[i][j] = 'X';
}
}
}
}
};
直接对每个边上的‘O’搜索,如果搜到了都保持原状,其余的全成‘X’。
- DFS
class Solution {
public:
int n, m;
void dfs(vector<vector<char>>& board, int x, int y) {
if (x < 0 || x >= n || y < 0 || y >= m || board[x][y] != 'O') {
return;
}
board[x][y] = 'A';
dfs(board, x + 1, y);
dfs(board, x - 1, y);
dfs(board, x, y + 1);
dfs(board, x, y - 1);
}
void solve(vector<vector<char>>& board) {
n = board.size();
if (n == 0) {
return;
}
m = board[0].size();
for (int i = 0; i < n; i++) {
dfs(board, i, 0);
dfs(board, i, m - 1);
}
for (int i = 1; i < m - 1; i++) {
dfs(board, 0, i);
dfs(board, n - 1, i);
}
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (board[i][j] == 'A') {
board[i][j] = 'O';
} else if (board[i][j] == 'O') {
board[i][j] = 'X';
}
}
}
}
};
lc.797 所有可能的路径
给你一个有 n 个节点的 有向无环图(DAG),请你找出所有从节点 0 到节点 n-1 的路径并输出(不要求按特定顺序)
graph[i] 是一个从节点 i 可以访问的所有节点的列表(即从节点 i 到节点 graph[i][j]存在一条有向边)。
- DFS wrong ans
class Solution {
public:
void dfs(int i , vector<int>& tmp , vector<vector<int>>& ans , vector<vector<int>>& graph) {
if(graph[i].size() == 0){
tmp.emplace_back(i);
ans.emplace_back(tmp);
tmp.pop_back();
return;
}
tmp.emplace_back(i);
for(int j = 0 ; j < graph[i].size() ; j ++){
dfs(graph[i][j] , tmp , ans , graph);
}
tmp.pop_back();
}
vector<vector<int>> allPathsSourceTarget(vector<vector<int>>& graph) {
vector<vector<int>> ans;
vector<int> tmp;
dfs(0 , tmp , ans , graph);
return ans;
}
};
题目要求结尾是 n - 1 。
- DFS wrong ans
class Solution {
public:
void dfs(int i , vector<int>& tmp , vector<vector<int>>& ans , vector<vector<int>>& graph) {
if(graph[i].size() == 0 && i == graph.size() - 1){
tmp.emplace_back(i);
ans.emplace_back(tmp);
tmp.pop_back();
return;
}
tmp.emplace_back(i);
for(int j = 0 ; j < graph[i].size() ; j ++){
dfs(graph[i][j] , tmp , ans , graph);
}
tmp.pop_back();
}
vector<vector<int>> allPathsSourceTarget(vector<vector<int>>& graph) {
vector<vector<int>> ans;
vector<int> tmp;
dfs(0 , tmp , ans , graph);
return ans;
}
};
服了。到 n - 1 就截止了,后面不搜了。
- DFS
class Solution {
public:
void dfs(int i , vector<int>& tmp , vector<vector<int>>& ans , vector<vector<int>>& graph) {
if(i == graph.size() - 1){
tmp.emplace_back(i);
ans.emplace_back(tmp);
tmp.pop_back();
return;
}
tmp.emplace_back(i);
for(int j = 0 ; j < graph[i].size() ; j ++){
dfs(graph[i][j] , tmp , ans , graph);
}
tmp.pop_back();
}
vector<vector<int>> allPathsSourceTarget(vector<vector<int>>& graph) {
vector<vector<int>> ans;
vector<int> tmp;
dfs(0 , tmp , ans , graph);
return ans;
}
};
读懂题意还是很重要的。wdnmd。
递归与回溯
lc.78 子集 全组合
给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
- DFS
class Solution {
public:
bool is[11] = {0};
void dfs(int x , vector<int>& nums , vector<int>& tmp , vector<vector<int>>& ans){
ans.emplace_back(tmp);
for(int i = x ; i < nums.size() ; i ++){
if(!is[i]){
is[i] = true ;
tmp.emplace_back(nums[i]);
dfs(i + 1 , nums , tmp , ans);
is[i] = false ;
tmp.pop_back();
}
}
}
vector<vector<int>> subsets(vector<int>& nums) {
vector<vector<int>> ans;
vector<int> tmp;
dfs(0 , nums , tmp , ans);
return ans;
}
};
lc.90 子集II
给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。
- DFS lc.78基础上剪枝
class Solution {
public:
bool is[11] = {0};
void dfs(int x , vector<int>& nums , vector<int>& tmp , vector<vector<int>>& ans){
ans.emplace_back(tmp);
for(int i = x ; i < nums.size() ; i ++){
if(i > 0 && nums[i - 1] == nums[i] && is[i - 1] == false) continue;
if(!is[i]){
is[i] = true ;
tmp.emplace_back(nums[i]);
dfs(i + 1 , nums , tmp , ans);
is[i] = false ;
tmp.pop_back();
}
}
}
vector<vector<int>> subsetsWithDup(vector<int>& nums) {
vector<vector<int>> ans;
vector<int> tmp;
sort(nums.begin(),nums.end());
dfs(0 , nums , tmp , ans);
return ans;
}
};
if(i > 0 && nums[i - 1] == nums[i] && is[i - 1] == false) continue;
剪枝。
注意到不同于排列,组合不考虑所有的顺序,所以需要先 sort 一下。
lc.39 组合总和
给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。
candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。
对于给定的输入,保证和为 target 的不同组合数少于 150 个。
class Solution {
public:
void dfs(vector<int>& candidates, int target, vector<vector<int>>& ans, vector<int>& combine, int idx) {
if (idx == candidates.size()) {
return;
}
if (target == 0) {
ans.emplace_back(combine);
return;
}
// 直接跳过
dfs(candidates, target, ans, combine, idx + 1);
// 选择当前数
if (target - candidates[idx] >= 0) {
combine.emplace_back(candidates[idx]);
dfs(candidates, target - candidates[idx], ans, combine, idx);
combine.pop_back();
}
}
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
vector<vector<int>> ans;
vector<int> combine;
dfs(candidates, target, ans, combine, 0);
return ans;
}
};
lc.40 组合总和 II
给定一个候选人编号的集合 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中只能使用 一次 。
注意:解集不能包含重复的组合。
class Solution {
private:
vector<pair<int, int>> freq;
vector<vector<int>> ans;
vector<int> sequence;
public:
void dfs(int pos, int rest) {
if (rest == 0) {
ans.push_back(sequence);
return;
}
if (pos == freq.size() || rest < freq[pos].first) {
return;
}
dfs(pos + 1, rest);
int most = min(rest / freq[pos].first, freq[pos].second);
for (int i = 1; i <= most; ++i) {
sequence.push_back(freq[pos].first);
dfs(pos + 1, rest - i * freq[pos].first);
}
for (int i = 1; i <= most; ++i) {
sequence.pop_back();
}
}
vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
sort(candidates.begin(), candidates.end());
for (int num: candidates) {
if (freq.empty() || num != freq.back().first) {
freq.emplace_back(num, 1);
} else {
++freq.back().second;
}
}
dfs(0, target);
return ans;
}
};
lc.17 电话号码的字母组合
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
- DFS(也不知道对不对,debug开头就有问题)
class Solution {
public:
vector<vector<string>> ex = {{},{},{"a","b","c"},{"d","e","f"},{"g","h","i"},{"j","k","l"},{"m","n","o"},{"p","q","r","s"},{"t","u","v"},{"w","x","y","z"}};
void dfs(string tmp, vector<string>& ans, string digits, int x){
if(x == digits.size()){
ans.emplace_back(tmp);
return ;
}
for(int i = 0 ; i < ex[x].size() ; i ++){
tmp += ex[x][i];
dfs(tmp, ans, digits, x + 1);
tmp.pop_back();
}
}
vector<string> letterCombinations(string digits) {
vector<string> ans;
string tmp = "";
dfs(tmp, ans, digits, 0);
return ans;
}
};
这是什么情况···不管输入什么测试用例结局都是直接返回空。
我是蠢货,x 是 digits 的下标,不是 ex 的下标。
上图是运行到此的数值,再往下运行一步, n = 2 了。
- 0ms
class Solution {
public:
vector<vector<string>> ex = {{},{},{"a","b","c"},{"d","e","f"},{"g","h","i"},{"j","k","l"},{"m","n","o"},{"p","q","r","s"},{"t","u","v"},{"w","x","y","z"}};
void dfs(string tmp, vector<string>& ans, string digits, int x){
if(x == digits.size()){
ans.emplace_back(tmp);
return ;
}
int n = digits[x] - '0';
for(int i = 0 ; i < ex[n].size() ; i ++){
tmp += ex[n][i];
dfs(tmp, ans, digits, x + 1);
tmp.pop_back();
}
}
vector<string> letterCombinations(string digits) {
vector<string> ans;
if(digits.size() == 0) return ans;
string tmp = "";
int n = digits.size();
dfs(tmp, ans, digits, 0);
return ans;
}
};
- 回溯
class Solution {
public:
vector<string> letterCombinations(string digits) {
vector<string> combinations;
int n = (int)digits.length();
if (digits.empty()) {
return combinations;
}
unordered_map<char, string> phoneMap{
{'2', "abc"},
{'3', "def"},
{'4', "ghi"},
{'5', "jkl"},
{'6', "mno"},
{'7', "pqrs"},
{'8', "tuv"},
{'9', "wxyz"}
};
string combination;
backtrack(combinations, phoneMap, digits, 0, combination);
return combinations;
}
void backtrack(vector<string>& combinations, const unordered_map<char, string>& phoneMap, const string& digits, int index, string& combination) {
if (index == digits.length()) {
combinations.push_back(combination);
} else {
char digit = digits[index];
const string& letters = phoneMap.at(digit);
for (const char& letter: letters) {
combination.push_back(letter);
backtrack(combinations, phoneMap, digits, index + 1, combination);
combination.pop_back();
}
}
}
};
题解length的调用就没有问题。
lc.22 括号生成
数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。
- DFS
class Solution {
public:
void dfs(string tmp, vector<string>& ans, int n, int countleft, int countright){
if(countleft == n && countright == n){
ans.emplace_back(tmp);
return ;
}
if(countleft < n){
tmp += "(" ;
dfs(tmp, ans, n, countleft + 1 , countright);
tmp.pop_back();
}
if(countright < n && countright < countleft){
tmp += ")" ;
dfs(tmp, ans, n, countleft , countright + 1);
tmp.pop_back();
}
}
vector<string> generateParenthesis(int n) {
string tmp ;
vector<string> ans;
int countleft = 0 , countright = 0 ;
dfs(tmp, ans, n, countleft, countright);
return ans;
}
};
越写越快,只要理清条件,只能说轻轻松松。
lc.79 单词搜索
给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 false 。
单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。
- BFS WRONG
class Solution {
public:
bool exist(vector<vector<char>>& board, string word) {
vector<vector<int>> direction = {{0,1},{0,-1},{1,0},{-1,0}};
queue<pair<int,int>> que;
int m = board.size() , n = board[0].size();
for(int i = 0 ; i < m ; i ++){
for(int j = 0 ; j < n ; j ++){
bool isVisited[m][n] = {0};
int iword = 0 ;
char tmpa = board[i][j] , tmpb = word[iword];
if(board[i][j] == word[iword]){
que.push({i,j});
isVisited[i][j] = true;
iword ++;
while(!que.empty()){
int times = que.size();
while(times > 0){
auto tmp = que.front(); que.pop();
int x = tmp.first , y = tmp.second ;
isVisited[x][y] = true;
for(int i = 0 ; i < 4 ; i ++){
int prex = x + direction[i][0] , prey = y + direction[i][1];
if(prex >= 0 && prex < m && prey >= 0 && prey < n && board[prex][prey] == word[iword] && isVisited[prex][prey] == false){
que.push({prex,prey});
}
}
times --;
}
iword ++ ;
}
if(iword == word.size() + 1) return true;
}
}
}
return false;
}
};
一眼看上去是一个 BFS 的问题,然而假如是这样一个情况:
但是如果不开 bool 数组记录是否走过,就会往回搜索,造成错误。
- 虽然DFS回溯对于这种图来说很慢,但是这题不得不这样做
class Solution {
public:
bool check(vector<vector<char>>& board, vector<vector<int>>& visited, int i, int j, string& s, int k) {
if (board[i][j] != s[k]) {
return false;
} else if (k == s.length() - 1) {
return true;
}
visited[i][j] = true;
vector<pair<int, int>> directions{{0, 1}, {0, -1}, {1, 0}, {-1, 0}};
bool result = false;
for (const auto& dir: directions) {
int newi = i + dir.first, newj = j + dir.second;
if (newi >= 0 && newi < board.size() && newj >= 0 && newj < board[0].size()) {
if (!visited[newi][newj]) {
bool flag = check(board, visited, newi, newj, s, k + 1);
if (flag) {
result = true;
break;
}
}
}
}
visited[i][j] = false;
return result;
}
bool exist(vector<vector<char>>& board, string word) {
int h = board.size(), w = board[0].size();
vector<vector<int>> visited(h, vector<int>(w));
for (int i = 0; i < h; i++) {
for (int j = 0; j < w; j++) {
bool flag = check(board, visited, i, j, word, 0);
if (flag) {
return true;
}
}
}
return false;
}
};
- 最快的剪枝
class Solution {
public:
bool exist(vector<vector<char>>& board, string word) {
int m = board.size(), n = board[0].size();
vector<int> cnt(128, 0);
for (auto c : word)
++cnt[c];
if (cnt[word[0]] > cnt[word[word.size()-1]]) reverse(word.begin(), word.end());
for (auto v : board)
for (auto c : v)
--cnt[c];
for (int i = 0; i < 128; ++i)
if (cnt[i] > 0) return false;
for (int i = 0; i < m; ++i) {
for (int j = 0; j < n; ++j) {
if (board[i][j] == word[0] && dfs(board, word, 0, i, j)) {
return true;
}
}
}
return false;
}
bool dfs(vector<vector<char>>& m, string s, int idx, int x, int y) {
if (x < 0 || x >= m.size() || y < 0 || y >= m[0].size() || m[x][y] != s[idx]) return false;
if (++idx == s.size()) return true;
m[x][y] += 128;
if (dfs(m, s, idx, x, y+1)) return true;
if (dfs(m, s, idx, x, y-1)) return true;
if (dfs(m, s, idx, x-1, y)) return true;
if (dfs(m, s, idx, x+1, y)) return true;
m[x][y] -= 128;
return false;;
}
};
这居然 0ms 。
位运算
lc.201 数字范围按位与
给你两个整数 left 和 right ,表示区间 [left, right] ,返回此区间内所有数字 按位与 的结果(包含 left 、right 端点)。
ELSE
lc.384 打乱数组
给你一个整数数组 nums ,设计算法来打乱一个没有重复元素的数组。打乱后,数组的所有排列应该是 等可能 的。
实现 Solution class:
Solution(int[] nums) 使用整数数组 nums 初始化对象
int[] reset() 重设数组到它的初始状态并返回
int[] shuffle() 返回数组随机打乱后的结果
class Solution {
public:
Solution(vector<int>& nums) {
this->nums = nums;
this->original.resize(nums.size());
copy(nums.begin(), nums.end(), original.begin());
}
vector<int> reset() {
copy(original.begin(), original.end(), nums.begin());
return nums;
}
vector<int> shuffle() {
for (int i = 0; i < nums.size(); ++i) {
int j = i + rand() % (nums.size() - i);
swap(nums[i], nums[j]);
}
return nums;
}
private:
vector<int> nums;
vector<int> original;
};
lc.149 直线上最多的点数
给你一个数组 points ,其中 points[i] = [xi, yi] 表示 X-Y 平面上的一个点。求最多有多少个点在同一条直线上。
class Solution {
public:
int gcd(int a, int b) {
return b ? gcd(b, a % b) : a;
}
int maxPoints(vector<vector<int>>& points) {
int n = points.size();
if (n <= 2) {
return n;
}
int ret = 0;
for (int i = 0; i < n; i++) {
if (ret >= n - i || ret > n / 2) {
break;
}
unordered_map<int, int> mp;
for (int j = i + 1; j < n; j++) {
int x = points[i][0] - points[j][0];
int y = points[i][1] - points[j][1];
if (x == 0) {
y = 1;
} else if (y == 0) {
x = 1;
} else {
if (y < 0) {
x = -x;
y = -y;
}
int gcdXY = gcd(abs(x), abs(y));
x /= gcdXY, y /= gcdXY;
}
mp[y + x * 20001]++;
}
int maxn = 0;
for (auto& [_, num] : mp) {
maxn = max(maxn, num + 1);
}
ret = max(ret, maxn);
}
return ret;
}
};