LeetCode——双指针
双指针主要用于遍历数组,两个指针指向不同的元素,从而协同完成任务。
T167. 两数之和 II - 输入有序数组
class Solution {
public:
vector<int> twoSum(vector<int>& numbers, int target) {
int left = 0;
int right = numbers.size()-1;
while(left < right){
int num = numbers[left] + numbers[right];
if(num == target) return {left+1, right+1};
else if(num < target) left++;
else right--;
}
return {};
}
};
T633. 平方数之和
class Solution {
public:
bool judgeSquareSum(int c) {
long l = 0;
long r = sqrt(c);
while(l <= r){
int num = l * l + r * r;
if(num == c)
return true;
else if(num < c)
l++;
else
r--;
}
return false;
}
};
T345. 反转字符串中的元音字母
class Solution {
public:
string reverseVowels(string s) {
int l = 0;
int r = s.size()-1;
while(l < r){
if(isOrigin(s[l]) && isOrigin(s[r])){
char tmp = s[l];
s[l] = s[r];
s[r] = tmp;
l++;
r--;
}
else if(isOrigin(s[l]))
r--;
else
l++;
}
return s;
}
bool isOrigin(char c){
if(c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u' || c == 'A' || c == 'E' || c == 'I' || c == 'O' || c == 'U')
return true;
else
return false;
}
};
T680. 验证回文字符串 Ⅱ
class Solution {
public:
bool validPalindrome(string s) {
int i = 0;
int j = s.size()-1;
while(i < j){
if(s[i] != s[j]){
return isValid(s, i+1, j) || isValid(s, i, j-1);
}
else{
++i;
--j;
}
}
return true;
}
bool isValid(string s, int i, int j){
while(i < j){
if(s[i] != s[j]) return false;
else{
++i;
--j;
}
}
return true;
}
};
T88. 合并两个有序数组
class Solution {
public:
void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
int k = m + n - 1;
int i = m - 1;
int j = n - 1;
while(i>=0 && j>=0){
if(nums1[i] >= nums2[j]){
nums1[k] = nums1[i];
k--;
i--;
}
else{
nums1[k] = nums2[j];
k--;
j--;
}
}
while(j >= 0){
nums1[k] = nums2[j];
k--;
j--;
}
}
};
T141. 环形链表
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
bool hasCycle(ListNode *head) {
ListNode* fast = head;
ListNode* slow = head;
while(fast != NULL && fast->next != NULL){
slow = slow->next;
fast = fast->next->next;
if(slow == fast)
return true;
}
return false;
}
};
T524. 通过删除字母匹配到字典里最长单词
class Solution {
public:
string findLongestWord(string s, vector<string>& d) {
string res="";
for(string str : d){
int i = 0;
for(char c : s){
if(i<str.size() && c==str[i]){
++i;
}
if(i==str.size() && str.size() >= res.size()){
if(str.size() > res.size() || str < res){
res = str;
}
}
}
}
return res;
}
};
LeetCode——排序
T215. 数组中的第K个最大元素
快速排序
class Solution {
public:
int findKthLargest(vector<int>& nums, int k) {
int left = 0;
int right = nums.size() - 1;
//注意while里面的循环条件
while (true) {
int pos = partition(nums, left, right);
if (pos == k - 1) return nums[pos];
else if (pos > k - 1) right = pos - 1;
else left = pos + 1;
}
}
int partition(vector<int>& nums, int left, int right) {
int pivot = nums[left];
int l = left + 1;
int r = right;
while (l <= r) {
//注意大小比较条件
if (nums[l] < pivot && nums[r] > pivot) {
swap(nums[l++], nums[r--]);
}
else if(nums[l] < pivot) r--;
else l++;
}
swap(nums[left], nums[r]);
return r;
}
};
T347. 前 K 个高频元素
桶排序
class Solution {
public:
vector<int> topKFrequent(vector<int>& nums, int k) {
unordered_map<int, int> m;
vector<vector<int>> bucket(nums.size() + 1);
vector<int> res;
for(auto a : nums) ++m[a];
for(auto it : m)
bucket[it.second].push_back(it.first);
for(int i = nums.size(); i>=0; --i){
for(int j=0; j<bucket[i].size(); ++j){
res.push_back(bucket[i][j]);
if(res.size()==k) return res;
}
}
return res;
}
};
最大堆
T451. 根据字符出现频率排序
class Solution {
public:
string frequencySort(string s) {
string res="";
priority_queue<pair<int, char>> q;
unordered_map<char, int> m;
for(char c : s) ++m[c];
for(auto a : m)
q.push({a.second, a.first});
while(!q.empty()){
auto t = q.top();
q.pop();
res.append(t.first, t.second);
}
return res;
}
};
荷兰国旗问题
T75. 颜色分类
class Solution {
public:
void sortColors(vector<int>& nums) {
vector<int> colours(3);
for(int num : nums) ++colours[num];
for(int i=0,cur=0; i<3; ++i){
for(int j=0; j<colours[i]; ++j){
nums[cur++] = i;
}
}
}
};
class Solution {
public:
void sortColors(vector<int>& nums) {
int red = 0, blue = nums.size() - 1;
for (int i = 0; i <= blue; ++i) {
if (nums[i] == 0) {
swap(nums[i], nums[red++]);
} else if (nums[i] == 2) {
swap(nums[i--], nums[blue--]);
}
}
}
};
LeetCode——贪心思想
保证每次操作都是局部最优的,并且最后得到的结果是全局最优的。
T455. 分发饼干
class Solution {
public:
int findContentChildren(vector<int>& g, vector<int>& s) {
int res = 0;
int p = 0;
sort(g.begin(), g.end());
sort(s.begin(), s.end());
for (int i = 0; i < s.size(); ++i) {
if (s[i] >= g[p]) {
++res;
++p;
}
if (p >= g.size()) break;
}
return res;
}
};
T435. 无重叠区间
这道题给了我们一堆区间,让求需要至少移除多少个区间才能使剩下的区间没有重叠,那么首先要给区间排序,根据每个区间的 start 来做升序排序,然后开始要查找重叠区间,判断方法是看如果前一个区间的 end 大于后一个区间的 start,那么一定是重复区间,此时结果 res 自增1,我们需要删除一个,那么此时究竟该删哪一个呢,为了保证总体去掉的区间数最小,我们去掉那个 end 值较大的区间,而在代码中,我们并没有真正的删掉某一个区间,而是用一个变量 last 指向上一个需要比较的区间,我们将 last 指向 end 值较小的那个区间;如果两个区间没有重叠,那么此时 last 指向当前区间,继续进行下一次遍历。
class Solution {
public:
int eraseOverlapIntervals(vector<vector<int>>& intervals) {
int res=0;
int n=intervals.size();
int last=0;
sort(intervals.begin(),intervals.end());
for(int i=1; i<n; ++i){
if(intervals[i][0] < intervals[last][1]){
++res;
if(intervals[i][1] < intervals[last][1]) last=i;
}
else{
last = i;
}
}
return res;
}
};
T452. 用最少数量的箭引爆气球
这道题给了我们一堆大小不等的气球,用区间范围来表示气球的大小,可能会有重叠区间。然后我们用最少的箭数来将所有的气球打爆。那么这道题是典型的用贪婪算法来做的题,因为局部最优解就等于全局最优解,我们首先给区间排序,我们不用特意去写排序比较函数,因为默认的对于pair的排序,就是按第一个数字升序排列,如果第一个数字相同,那么按第二个数字升序排列,这个就是我们需要的顺序,所以直接用即可。然后我们将res初始化为1,因为气球数量不为0,所以怎么也得先来一发啊,然后这一箭能覆盖的最远位置就是第一个气球的结束点,用变量end来表示。然后我们开始遍历剩下的气球,如果当前气球的开始点小于等于end,说明跟之前的气球有重合,之前那一箭也可以照顾到当前的气球,此时我们要更新end的位置,end更新为两个气球结束点之间较小的那个,这也是当前气球和之前气球的重合点,然后继续看后面的气球;如果某个气球的起始点大于end了,说明前面的箭无法覆盖到当前的气球,那么就得再来一发,既然又来了一发,那么我们此时就要把end设为当前气球的结束点了,这样贪婪算法遍历结束后就能得到最少的箭数了。
class Solution {
public:
int findMinArrowShots(vector<vector<int>>& points) {
if (points.empty()) return 0;
sort(points.begin(), points.end());
int res = 1;
int left = points[0][1];
for (int i = 1; i < points.size(); ++i) {
if (points[i][0] <= left) {
left = min(left, points[i][1]);
} else {
++res;
left = points[i][1];
}
}
return res;
}
};
T406. 根据身高重建队列
这道题给了我们一个队列,队列中的每个元素是一个 pair,分别为身高和前面身高不低于当前身高的人的个数,让我们重新排列队列,使得每个 pair 的第二个参数都满足题意。首先来看一种超级简洁的方法,给队列先排个序,按照身高高的排前面,如果身高相同,则第二个数小的排前面。然后新建一个空的数组,遍历之前排好序的数组,然后根据每个元素的第二个数字,将其插入到 res 数组中对应的位置。
class Solution {
public:
vector<vector<int>> reconstructQueue(vector<vector<int>>& people) {
sort(people.begin(), people.end(), [](vector<int>& a, vector<int>& b) {
return a[0] > b[0] || (a[0] == b[0] && a[1] < b[1]);
});
vector<vector<int>> res;
for (auto a : people) {
res.insert(res.begin() + a[1], a);
}
return res;
}
};
T121. 买卖股票的最佳时机
class Solution {
public:
int maxProfit(vector<int>& prices) {
int res = 0, buy = INT_MAX;
for (int price : prices) {
buy = min(buy, price);
res = max(res, price - buy);
}
return res;
}
};
T122. 买卖股票的最佳时机 II
class Solution {
public:
int maxProfit(vector<int>& prices) {
int res = 0, n = prices.size();
for (int i = 0; i < n - 1; ++i) {
if (prices[i] < prices[i + 1]) {
res += prices[i + 1] - prices[i];
}
}
return res;
}
};
T605. 种花问题
class Solution {
public:
bool canPlaceFlowers(vector<int>& flowerbed, int n) {
for (int i = 0; i < flowerbed.size(); ++i) {
if (n == 0) return true;
if (flowerbed[i] == 0) {
int next = (i == flowerbed.size() - 1 ? 0 : flowerbed[i + 1]);
int pre = (i == 0 ? 0 : flowerbed[i - 1]);
if (next + pre == 0) {
flowerbed[i] = 1;
--n;
}
}
}
return n <= 0;
}
};
T392. 判断子序列
class Solution {
public:
bool isSubsequence(string s, string t) {
int i = 0;
for (int j = 0; j < t.size() && i < s.size(); ++j) {
if (s[i] == t[j]) ++i;
}
return i == s.size();
}
};
class Solution {
public:
bool isSubsequence(string s, string t) {
if(s.empty() && t.empty()) return true;
int i = 0;
for(char c : t){
if(s[i] == c) ++i;
if(i >= s.size()) return true;
}
return false;
}
};
T665. 非递减数列
class Solution {
public:
bool checkPossibility(vector<int>& nums) {
int cnt = 1, n = nums.size();
for (int i = 1; i < n; ++i) {
if (nums[i] < nums[i - 1]) {
if (cnt == 0) return false;
if (i == 1 || nums[i] >= nums[i - 2]) nums[i - 1] = nums[i];
else nums[i] = nums[i - 1];
--cnt;
}
}
return true;
}
};
T53. 最大子序和
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int res = INT_MIN, curSum = 0;
for (int num : nums) {
curSum = max(curSum + num, num);
res = max(res, curSum);
}
return res;
}
};
class Solution {
public:
int maxSubArray(vector<int>& nums) {
if (nums.empty()) return 0;
return helper(nums, 0, (int)nums.size() - 1);
}
int helper(vector<int>& nums, int left, int right) {
if (left >= right) return nums[left];
int mid = left + (right - left) / 2;
int lmax = helper(nums, left, mid - 1);
int rmax = helper(nums, mid + 1, right);
int mmax = nums[mid], t = mmax;
for (int i = mid - 1; i >= left; --i) {
t += nums[i];
mmax = max(mmax, t);
}
t = mmax;
for (int i = mid + 1; i <= right; ++i) {
t += nums[i];
mmax = max(mmax, t);
}
return max(mmax, max(lmax, rmax));
}
};
T763. 划分字母区间
class Solution {
public:
vector<int> partitionLabels(string S) {
vector<int> res;
int n = S.size(), start = 0, last = 0;
unordered_map<char, int> m;
for (int i = 0; i < n; ++i) m[S[i]] = i;
for (int i = 0; i < n; ++i) {
last = max(last, m[S[i]]);
if (i == last) {
res.push_back(i - start + 1);
start = i + 1;
}
}
return res;
}
};
LeetCode——二分查找
T69. x的平方根
class Solution {
public:
int mySqrt(int x) {
if (x <= 1) return x;
int left = 0;
int right = x;
while (left < right) {
int mid = left + (right - left) / 2;
if (x / mid >= mid) left = mid + 1;
else right = mid;
}
return right - 1;
}
};
T744. 寻找比目标字母大的最小字母
class Solution {
public:
char nextGreatestLetter(vector<char>& letters, char target) {
if (target >= letters.back()) return letters[0];
int left = 0;
int right = letters.size();
while(left < right){
int mid = left + (right - left) / 2;
if(target >= letters[mid]) left = mid + 1;
else right = mid;
}
return letters[right];
}
};
T540. 有序数组中的单一元素
class Solution {
public:
int singleNonDuplicate(vector<int>& nums) {
int lo = 0;
int hi = nums.size() - 1;
while (lo < hi) {
int mid = lo + (hi - lo) / 2;
bool halvesAreEven = (hi - mid) % 2 == 0;
if (nums[mid + 1] == nums[mid]) {
if (halvesAreEven) {
lo = mid + 2;
} else {
hi = mid - 1;
}
} else if (nums[mid - 1] == nums[mid]) {
if (halvesAreEven) {
hi = mid - 2;
} else {
lo = mid + 1;
}
} else {
return nums[mid];
}
}
return nums[lo];
}
};
T278. 第一个错误版本
// The API isBadVersion is defined for you.
// bool isBadVersion(int version);
class Solution {
public:
int firstBadVersion(int n) {
int left = 1;
int right = n;
while (left < right) {
int mid = left + (right - left) / 2;
if (isBadVersion(mid)) right = mid;
else left = mid + 1;
}
return left;
}
};
T153. 寻找旋转排序数组中的最小值
class Solution {
public:
int findMin(vector<int>& nums) {
int l = 0;
int r = nums.size()-1;
while(l < r)
{
int m = l + (r - l) / 2;
if(nums[m] > nums[r]) l = m + 1;
else r = m;
}
return nums[r];
}
};
T34. 在排序数组中查找元素的第一个和最后一个位置
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target) {
int idx = search(nums, 0, nums.size() - 1, target);
if (idx == -1) return {-1, -1};
int left = idx, right = idx;
while (left > 0 && nums[left - 1] == nums[idx]) --left;
while (right < nums.size() - 1 && nums[right + 1] == nums[idx]) ++right;
return {left, right};
}
int search(vector<int>& nums, int left, int right, int target) {
if (left > right) return -1;
int mid = left + (right - left) / 2;
if (nums[mid] == target) return mid;
if (nums[mid] < target) return search(nums, mid + 1, right, target);
else return search(nums, left, mid - 1, target);
}
};
LeetCode——分治
T241. 为运算表达式设计优先级
这道题貌似默认输入都必须是合法的,虽然题目中没有明确的指出这一点,所以我们也就不必进行 valid 验证了。先从最简单的输入开始,若 input 是空串,那就返回一个空数组。若 input 是一个数字的话,那么括号加与不加其实都没啥区别,因为不存在计算,但是需要将字符串转为整型数,因为返回的是一个整型数组。当然,input 是一个单独的运算符这种情况是不存在的,因为前面说了这道题默认输入的合法的。下面来看若 input 是数字和运算符的时候,比如 “1+1” 这种情况,那么加不加括号也没有任何影响,因为只有一个计算,结果一定是2。再复杂一点的话,比如题目中的例子1,input 是 “2-1-1” 时,就有两种情况了,(2-1)-1 和 2-(1-1),由于括号的不同,得到的结果也不同,但如果我们把括号里的东西当作一个黑箱的话,那么其就变为 ()-1 和 2-(),其最终的结果跟括号内可能得到的值是息息相关的,那么再 general 一点,实际上就可以变成 () ? () 这种形式,两个括号内分别是各自的表达式,最终会分别计算得到两个整型数组,中间的问号表示运算符,可以是加,减,或乘。那么问题就变成了从两个数组中任意选两个数字进行运算,瞬间变成我们会做的题目了有木有?而这种左右两个括号代表的黑盒子就交给递归去计算,像这种分成左右两坨的 pattern 就是大名鼎鼎的分治法 Divide and Conquer 了,是必须要掌握的一个神器。我们不用新建递归函数,就用其本身来递归就行,先建立一个结果 res 数组,然后遍历 input 中的字符,根据上面的分析,我们希望在每个运算符的地方,将 input 分成左右两部分,从而扔到递归中去计算,从而可以得到两个整型数组 left 和 right,分别表示作用两部分各自添加不同的括号所能得到的所有不同的值,此时我们只要分别从两个数组中取数字进行当前的运算符计算,然后把结果存到 res 中即可。当然,若最终结果 res 中还是空的,那么只有一种情况,input 本身就是一个数字,直接转为整型存入结果 res 中即可。
class Solution {
public:
vector<int> diffWaysToCompute(string input) {
vector<int> res;
for (int i = 0; i < input.size(); ++i) {
if (input[i] == '+' || input[i] == '-' || input[i] == '*') {
vector<int> left = diffWaysToCompute(input.substr(0, i));
vector<int> right = diffWaysToCompute(input.substr(i + 1));
for (int j = 0; j < left.size(); ++j) {
for (int k = 0; k < right.size(); ++k) {
if (input[i] == '+') res.push_back(left[j] + right[k]);
else if (input[i] == '-') res.push_back(left[j] - right[k]);
else res.push_back(left[j] * right[k]);
}
}
}
}
if (res.empty()) res.push_back(stoi(input));
return res;
}
};
T95. 不同的二叉搜索树 II
/**
* 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:
vector<TreeNode*> generateTrees(int n) {
if (n == 0) return {};
return helper(1, n);
}
vector<TreeNode*> helper(int start, int end) {
if (start > end) return {nullptr};
vector<TreeNode*> res;
for (int i = start; i <= end; ++i) {
auto left = helper(start, i - 1);
auto right = helper(i + 1, end);
for (auto a : left) {
for (auto b : right) {
TreeNode *node = new TreeNode(i);
node->left = a;
node->right = b;
res.push_back(node);
}
}
}
return res;
}
};
LeetCode——数学
素数分解
整除
最大公约数最小公倍数
T204. 计数质数
class Solution {
public:
int countPrimes(int n) {
int res = 0;
vector<bool> prime(n, true);
for (int i = 2; i < n; ++i) {
if (!prime[i]) continue;
++res;
for (int j = 2; i * j < n; ++j) {
prime[i * j] = false;
}
}
return res;
}
};
最大公约数
使用位操作和减法求解最大公约数
进制转换
T504. 七进制数
class Solution {
public:
string convertToBase7(int num) {
if(num == 0){
return "0";
}
bool flag = true; //是否为正数
if(num < 0){
flag = false;
num = -num;
}
string ans;
while(num != 0){
ans.push_back(char(num % 7 + '0'));
num /= 7;
}
if(!flag){ //为负数
ans.push_back('-');
}
reverse(ans.begin(), ans.end()); //反转字符串才为结果
return ans;
}
};
T405. 数字转换为十六进制数
class Solution {
public:
string toHex(int num) {
string res = "";
for (int i = 0; num && i < 8; ++i) {
int t = num & 0xf;//取出最右边四位
if (t >= 10) res = char('a' + t - 10) + res;
else res = char('0' + t) + res;
num >>= 4;//num向右移四位
}
return res.empty() ? "0" : res;
}
};
T168. Excel表列名称
class Solution {
public:
string convertToTitle(int n) {
string res = "";
while (n) {
if (n % 26 == 0) {
res += 'Z';
n -= 26;
} else {
res += n % 26 - 1 + 'A';
n -= n % 26;
}
n /= 26;
}
reverse(res.begin(), res.end());
return res;
}
};
阶乘
T172. 阶乘后的零
这道题并没有什么难度,是让求一个数的阶乘末尾0的个数,也就是要找乘数中 10 的个数,而 10 可分解为2和5,而2的数量又远大于5的数量(比如1到 10 中有2个5,5个2),那么此题即便为找出5的个数。仍需注意的一点就是,像 25,125,这样的不只含有一个5的数字需要考虑进去。
class Solution {
public:
int trailingZeroes(int n) {
int res = 0;
while (n) {
res += n / 5;
n /= 5;
}
return res;
}
};
字符串加法减法
T67. 二进制求和
class Solution {
public:
string addBinary(string a, string b) {
string res = "";
int m = a.size() - 1, n = b.size() - 1, carry = 0;
while (m >= 0 || n >= 0) {
int p = m >= 0 ? a[m--] - '0' : 0;
int q = n >= 0 ? b[n--] - '0' : 0;
int sum = p + q + carry;
res = to_string(sum % 2) + res;
carry = sum / 2;
}
return carry == 1 ? "1" + res : res;
}
};
T415. 字符串相加
class Solution {
public:
string addStrings(string num1, string num2) {
string res = "";
int m = num1.size(), n = num2.size(), i = m - 1, j = n - 1, carry = 0;
while (i >= 0 || j >= 0) {
int a = i >= 0 ? num1[i--] - '0' : 0;
int b = j >= 0 ? num2[j--] - '0' : 0;
int sum = a + b + carry;
res.insert(res.begin(), sum % 10 + '0');
carry = sum / 10;
}
return carry ? "1" + res : res;
}
};
相遇问题
T462. 最少移动次数使数组元素相等 II
我们首先给数组排序,那么我们最终需要变成的相等的数字就是中间的数,如果数组有奇数个,那么就是最中间的那个数字;如果是偶数个,那么就是中间两个数的区间中的任意一个数字。而两端的数字变成中间的一个数字需要的步数实际上就是两端数字的距离,那么我们就两对两对的累加它们的差值就可以了。
class Solution {
public:
int minMoves2(vector<int>& nums) {
int res = 0, i = 0, j = (int)nums.size() - 1;
sort(nums.begin(), nums.end());
while (i < j) {
res += nums[j--] - nums[i++];
}
return res;
}
};
多数投票问题
T169. 多数元素
这是到求大多数的问题,有很多种解法,其中我感觉比较好的有两种,一种是用哈希表,这种方法需要 O(n) 的时间和空间,另一种是用一种叫摩尔投票法 Moore Voting,需要 O(n) 的时间和 O(1) 的空间,比前一种方法更好。这种投票法先将第一个数字假设为过半数,然后把计数器设为1,比较下一个数和此数是否相等,若相等则计数器加一,反之减一。然后看此时计数器的值,若为零,则将下一个值设为候选过半数。以此类推直到遍历完整个数组,当前候选过半数即为该数组的过半数。不仔细弄懂摩尔投票法的精髓的话,过一阵子还是会忘记的,首先要明确的是这个叼炸天的方法是有前提的,就是数组中一定要有过半数的存在才能使用,下面来看本算法的思路,这是一种先假设候选者,然后再进行验证的算法。现将数组中的第一个数假设为过半数,然后进行统计其出现的次数,如果遇到同样的数,则计数器自增1,否则计数器自减1,如果计数器减到了0,则更换下一个数字为候选者。这是一个很巧妙的设定,也是本算法的精髓所在,为啥遇到不同的要计数器减1呢,为啥减到0了又要更换候选者呢?首先是有那个强大的前提存在,一定会有一个出现超过半数的数字存在,那么如果计数器减到0了话,说明目前不是候选者数字的个数已经跟候选者的出现个数相同了,那么这个候选者已经很 weak,不一定能出现超过半数,此时选择更换当前的候选者。那有可能你会有疑问,那万一后面又大量的出现了之前的候选者怎么办,不需要担心,如果之前的候选者在后面大量出现的话,其又会重新变为候选者,直到最终验证成为正确的过半数。
class Solution {
public:
int majorityElement(vector<int>& nums) {
int res = 0, cnt = 0;
for (int num : nums) {
if (cnt == 0) {
res = num;
++cnt;
}
else (num == res) ? ++cnt : --cnt;
}
return res;
}
};
其他
T367. 有效的完全平方数
class Solution {
public:
bool isPerfectSquare(int num) {
int i = 1;
while (num > 0) {
num -= i;
i += 2;
}
return num == 0;
}
};
T326. 3的幂
class Solution {
public:
bool isPowerOfThree(int n) {
return (n > 0 && int(log10(n) / log10(3)) - log10(n) / log10(3) == 0);
//在c++中判断数字a是否为整数,我们可以用 a - int(a) == 0 来判断
}
};
class Solution {
public:
bool isPowerOfThree(int n) {
while (n && n % 3 == 0) {
n /= 3;
}
return n == 1;
}
};
T238. 除自身以外数组的乘积
对于某一个数字,如果我们知道其前面所有数字的乘积,同时也知道后面所有的数乘积,那么二者相乘就是我们要的结果,所以我们只要分别创建出这两个数组即可,分别从数组的两个方向遍历就可以分别创建出乘积累积数组。
class Solution {
public:
vector<int> productExceptSelf(vector<int>& nums) {
int n = nums.size();
vector<int> fwd(n, 1), bwd(n, 1), res(n);
for (int i = 0; i < n - 1; ++i) {
fwd[i + 1] = fwd[i] * nums[i];
}
for (int i = n - 1; i > 0; --i) {
bwd[i - 1] = bwd[i] * nums[i];
}
for (int i = 0; i < n; ++i) {
res[i] = fwd[i] * bwd[i];
}
return res;
}
};
T628. 三个数的最大乘积
class Solution {
public:
int maximumProduct(vector<int>& nums) {
int n = nums.size();
sort(nums.begin(), nums.end());
int p = nums[0] * nums[1] * nums[n - 1];
return max(p, nums[n - 1] * nums[n - 2] * nums[n - 3]);
}
};
LeetCode——动态规划
递归和动态规划都是将原问题拆成多个子问题然后求解,他们之间最本质的区别是,动态规划保存了子问题的解,避免重复计算。
斐波那契数列
T70. 爬楼梯
class Solution {
public:
int climbStairs(int n) {
if (n <= 1) return 1;
vector<int> dp(n);
dp[0] = 1; dp[1] = 2;
for (int i = 2; i < n; ++i) {
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp.back();
}
};
T198. 打家劫舍
这道题的本质相当于在一列数组中取出一个或多个不相邻数,使其和最大。那么对于这类求极值的问题首先考虑动态规划 Dynamic Programming 来解,维护一个一位数组 dp,其中 dp[i] 表示 [0, i] 区间可以抢夺的最大值,对当前i来说,有抢和不抢两种互斥的选择,不抢即为 dp[i-1](等价于去掉 nums[i] 只抢 [0, i-1] 区间最大值),抢即为 dp[i-2] + nums[i](等价于去掉 nums[i-1])。再举一个简单的例子来说明一下吧,比如说 nums为{3, 2, 1, 5},那么来看 dp 数组应该是什么样的,首先 dp[0]=3 没啥疑问,再看 dp[1] 是多少呢,由于3比2大,所以抢第一个房子的3,当前房子的2不抢,则dp[1]=3,那么再来看 dp[2],由于不能抢相邻的,所以可以用再前面的一个的 dp 值加上当前的房间值,和当前房间的前面一个 dp 值比较,取较大值当做当前 dp 值,这样就可以得到状态转移方程 dp[i] = max(num[i] + dp[i - 2], dp[i - 1]), 且需要初始化 dp[0] 和 dp[1],其中 dp[0] 即为 num[0],dp[1] 此时应该为 max(num[0], num[1])。
class Solution {
public:
int rob(vector<int>& nums) {
if(nums.empty()) return 0;
if(nums.size() == 1) return nums[0];
vector<int> dp(nums.size());
dp[0] = nums[0];
dp[1] = max(nums[0], nums[1]);
for (int i = 2; i < nums.size(); ++i) {
dp[i] = max(dp[i-1], dp[i-2]+nums[i]);
}
return dp.back();
}
};
T213. 打家劫舍 II
这道题是之前那道 House Robber 的拓展,现在房子排成了一个圆圈,则如果抢了第一家,就不能抢最后一家,因为首尾相连了,所以第一家和最后一家只能抢其中的一家,或者都不抢,那这里变通一下,如果把第一家和最后一家分别去掉,各算一遍能抢的最大值,然后比较两个值取其中较大的一个即为所求。那只需参考之前的 House Robber 中的解题方法,然后调用两边取较大值。
class Solution {
public:
int rob(vector<int>& nums) {
if(nums.empty()) return 0;
if(nums.size() == 1) return nums[0];
return max(rob(nums, 0, nums.size() - 1), rob(nums, 1, nums.size()));
}
int rob(vector<int> &nums, int left, int right) {
if (right - left <= 1) return nums[left];
vector<int> dp(right);
dp[left] = nums[left];
dp[left + 1] = max(nums[left], nums[left + 1]);
for (int i = left + 2; i < right; ++i) {
dp[i] = max(nums[i] + dp[i - 2], dp[i - 1]);
}
return dp.back();
}
};
信件错排
程序员代码面试指南 P181——母牛生产
矩阵路径
T64. 最小路径和
class Solution {
public:
int minPathSum(vector<vector<int>>& grid) {
if (grid.empty() || grid[0].empty()) return 0;
int m = grid.size(), n = grid[0].size();
vector<vector<int>> dp(m, vector<int>(n));
dp[0][0] = grid[0][0];
for (int i = 1; i < m; ++i) dp[i][0] = grid[i][0] + dp[i - 1][0];
for (int j = 1; j < n; ++j) dp[0][j] = grid[0][j] + dp[0][j - 1];
for (int i = 1; i < m; ++i) {
for (int j = 1; j < n; ++j) {
dp[i][j] = grid[i][j] + min(dp[i - 1][j], dp[i][j - 1]);
}
}
return dp[m - 1][n - 1];
}
};
T62. 不同路径
class Solution {
public:
// 状态定义:dp[i][j]是到达i,j的路径数。
// 递推方程:dp[i][j] = dp[i-1][j] + dp[i][j-1]。向右走一步或者向下走一步就可以到达i,j的方案数之和
int uniquePaths(int m, int n) {
vector<vector<int> > dp(m, vector<int>(n));
for(int i = 0; i < m; ++i) {
for(int j = 0; j < n; ++j) {
if(i == 0 || j == 0) dp[i][j] = 1;
else dp[i][j] = dp[i-1][j] + dp[i][j-1];
}
}
return dp[m-1][n-1];
}
};
class Solution {
public:
int uniquePaths(int m, int n) {
vector<int> dp(n, 1);
for (int i = 1; i < m; ++i) {
for (int j = 1; j < n; ++j) {
dp[j] += dp[j - 1];
}
}
return dp[n - 1];
}
};
数组区间
T303. 区域和检索 - 数组不可变
class NumArray {
public:
NumArray(vector<int> &nums) {
dp = nums;
for (int i = 1; i < nums.size(); ++i) {
dp[i] += dp[i - 1];
}
}
int sumRange(int i, int j) {
return i == 0? dp[j] : dp[j] - dp[i - 1];
}
private:
vector<int> dp;
};
T413. 等差数列划分
class Solution {
public:
int numberOfArithmeticSlices(vector<int>& A) {
int res = 0, len = 2, n = A.size();
for (int i = 2; i < n; ++i) {
if (A[i] - A[i - 1] == A[i - 1] - A[i - 2]) {
++len;
} else {
if (len > 2) res += (len - 1) * (len - 2) * 0.5;
len = 2;
}
}
if (len > 2) res += (len - 1) * (len - 2) * 0.5;
return res;
}
};
class Solution {
public:
int numberOfArithmeticSlices(vector<int>& A) {
int res = 0, n = A.size();
vector<int> dp(n, 0);
for (int i = 2; i < n; ++i) {
if (A[i] - A[i - 1] == A[i - 1] - A[i - 2]) {
dp[i] = dp[i - 1] + 1;
}
res += dp[i];
}
return res;
}
};
分隔整数
T343. 整数拆分
这道题给了我们一个正整数n,让拆分成至少两个正整数之和,使其乘积最大。最简单粗暴的方法自然是检查所有情况了,但是拆分方法那么多,怎么才能保证能拆分出所有的情况呢?感觉有点像之前那道 Coin Change,当前的拆分方法需要用到之前的拆分值,这种重现关系就很适合动态规划 Dynamic Programming 来做,我们使用一个一维数组 dp,其中 dp[i] 表示数字i拆分为至少两个正整数之和的最大乘积,数组大小为 n+1,值均初始化为1,因为正整数的乘积不会小于1。可以从3开始遍历,因为n是从2开始的,而2只能拆分为两个1,乘积还是1。i从3遍历到n,对于每个i,需要遍历所有小于i的数字,因为这些都是潜在的拆分情况,对于任意小于i的数字j,首先计算拆分为两个数字的乘积,即j乘以 i-j,然后是拆分为多个数字的情况,这里就要用到 dp[i-j] 了,这个值表示数字 i-j 任意拆分可得到的最大乘积,再乘以j就是数字i可拆分得到的乘积,取二者的较大值来更新 dp[i],最后返回 dp[n] 即可。
class Solution {
public:
int integerBreak(int n) {
vector<int> dp(n + 1, 1);
for (int i = 3; i <= n; ++i) {
for (int j = 1; j < i; ++j) {
dp[i] = max(dp[i], max(j * (i - j), j * dp[i - j]));
}
}
return dp[n];
}
};
T91. 解码方法
class Solution {
public:
int numDecodings(string s) {
if (s.empty() || s[0] == '0') return 0;
vector<int> dp(s.size() + 1, 0);
dp[0] = 1;
for (int i = 1; i < dp.size(); ++i) {
dp[i] = (s[i - 1] == '0') ? 0 : dp[i - 1];
if (i > 1 && (s[i - 2] == '1' || (s[i - 2] == '2' && s[i - 1] <= '6'))) {
dp[i] += dp[i - 2];
}
}
return dp.back();
}
};
最长递增子序列
T300. 最长上升子序列
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
vector<int> dp(nums.size(), 1);
int res = 0;
for (int i = 0; i < nums.size(); ++i) {
for (int j = 0; j < i; ++j) {
if (nums[i] > nums[j]) {
dp[i] = max(dp[i], dp[j] + 1);
}
}
res = max(res, dp[i]);
}
return res;
}
};
T646. 最长数对链
class Solution {
public:
int findLongestChain(vector<vector<int>>& pairs) {
sort(pairs.begin(), pairs.end());
int last = pairs[0][1];
int res = 1;
for(int i = 1; i < pairs.size(); i++){
if(pairs[i][0] > last){
last = pairs[i][1];
res++;
}
else{
last = min(pairs[i][1], last);
}
}
return res;
}
};
T376. 摆动序列
class Solution {
public:
int wiggleMaxLength(vector<int>& nums) {
if (nums.empty()) return 0;
vector<int> p(nums.size(), 1);
vector<int> q(nums.size(), 1);
for (int i = 1; i < nums.size(); ++i) {
for (int j = 0; j < i; ++j) {
if (nums[i] > nums[j]) p[i] = max(p[i], q[j] + 1);
else if (nums[i] < nums[j]) q[i] = max(q[i], p[j] + 1);
}
}
return max(p.back(), q.back());
}
};
最长公共子序列
T1143. 最长公共子序列
class Solution {
public:
int longestCommonSubsequence(string text1, string text2) {
int m = text1.size();
int n = text2.size();
vector<vector<int>> dp(m+1, vector<int>(n+1, 0));
for(int i = 1; i <= m; i++){
for(int j = 1; j <= n; j++){
if(text1[i-1] == text2[j-1]) dp[i][j] = dp[i-1][j-1]+1;
else dp[i][j] = max(dp[i-1][j], dp[i][j-1]);
}
}
return dp[m][n];
}
};
0-1背包
T416. 分割等和子集
class Solution {
public:
bool canPartition(vector<int>& nums) {
int sum = 0;
for(auto ele:nums) sum += ele;
if(sum%2) return false;//数组和为奇数,则无法划分成两个和相等的集合
sum /= 2;//数组和的一半
int size = nums.size();
//dp[i][j] = x 表示
//对于前 i 个数,当前背包的容量为 j 时(i从1-size)
//若 x 为 true,则说明可以挑选一些数恰好将背包装满
//若 x 为 false,则说明不能挑选出一些数恰好将背包装满
vector<vector<bool>> dp(size+1,vector<bool>(sum+1,false));
for(int i=0;i<size+1;i++)
dp[i][0] = true;//背包容量为0,不管从哪些元素中挑,只要不挑选就能满足将背包装满
for(int i=1;i<size+1;i++)
{
for(int j=1;j<=sum;j++)
{
if(j - nums[i-1] < 0)//背包容量放不下第i个数
dp[i][j] = dp[i-1][j];//不将第i个数放入背包
else
dp[i][j] = dp[i-1][j] || dp[i-1][j-nums[i-1]];//逻辑与 前面表示第i个数不放入背包,后面表示放入背包
}
}
return dp[size][sum];//表示 对于数组前size个数,当前背包容量为sum时,有没有办法选择一些数让背包装满
}
};
T494. 目标和
class Solution {
public:
int findTargetSumWays(vector<int>& nums, int S) {
return dfs(nums, S, 0);
}
int dfs(vector<int> &nums, uint target, int left) {
if (target == 0 && left == nums.size()) return 1;
if (left >= nums.size()) return 0;
int ans = 0;
ans += dfs(nums, target - nums[left], left + 1);
ans += dfs(nums, target + nums[left], left + 1);
return ans;
}
};
T494. 目标和
class Solution {
public:
int findTargetSumWays(vector<int>& nums, int S) {
int sum = 0;
for(int i : nums) sum += i;
if(sum < S || (sum + S) % 2 == 1){
return 0;
}
return subSets(nums, (sum + S) / 2);
}
// 满足要求的子集数
int subSets(vector<int>& nums, int sum){
int n = nums.size();
vector<vector<int>> dp(n + 1, vector<int>(sum + 1, 0));
for(auto& v : dp) v[0] = 1;
for(int i = 1; i < n + 1; ++i)
{
int val = nums[i - 1];
for(int j = 0; j <= sum; ++j)
{
if(j >= val)
{
// 选择当前物品的组合数和不选择当前物品的组合数的和
dp[i][j] = dp[i - 1][j] + dp[i - 1][j - val];
}
else{
// 只能选择不拿当前物品
dp[i][j] = dp[i - 1][j];
}
}
}
return dp[n][sum];
}
};
T474. 一和零
class Solution {
public:
int findMaxForm(vector<string>& strs, int m, int n) {
vector<vector<int>> dp(m + 1, vector<int>(n + 1, 0));
for (string str : strs) {
int zeros = 0, ones = 0;
for (char c : str) (c == '0') ? ++zeros : ++ones;
for (int i = m; i >= zeros; --i) {
for (int j = n; j >= ones; --j) {
dp[i][j] = max(dp[i][j], dp[i - zeros][j - ones] + 1);
}
}
}
return dp[m][n];
}
};
T322. 零钱兑换
class Solution {
public:
int coinChange(vector<int>& coins, int amount) {
vector<int> dp(amount + 1, amount + 1);
dp[0] = 0;
for (int i = 1; i <= amount; ++i) {
for (int j = 0; j < coins.size(); ++j) {
if (coins[j] <= i) {
dp[i] = min(dp[i], dp[i - coins[j]] + 1);
}
}
}
return (dp[amount] > amount) ? -1 : dp[amount];
}
};
T518. 零钱兑换 II
class Solution {
public:
int change(int amount, vector<int>& coins) {
vector<vector<int>> dp(coins.size() + 1, vector<int>(amount + 1, 0));
dp[0][0] = 1;
for (int i = 1; i <= coins.size(); ++i) {
dp[i][0] = 1;
for (int j = 1; j <= amount; ++j) {
dp[i][j] = dp[i - 1][j] + (j >= coins[i - 1] ? dp[i][j - coins[i - 1]] : 0);
}
}
return dp[coins.size()][amount];
}
};
T139. 单词拆分
class Solution {
public:
bool wordBreak(string s, vector<string>& wordDict) {
unordered_set<string> wordSet(wordDict.begin(), wordDict.end());
vector<bool> dp(s.size() + 1);
dp[0] = true;
for (int i = 0; i < dp.size(); ++i) {
for (int j = 0; j < i; ++j) {
if (dp[j] && wordSet.count(s.substr(j, i - j))) {
dp[i] = true;
break;
}
}
}
return dp.back();
}
};
T377. 组合总和 Ⅳ
class Solution {
public:
int combinationSum4(vector<int>& nums, int target) {
vector<unsigned int> dp(target + 1);
dp[0] = 1;
for (int i = 1; i <= target; ++i) {
for (auto a : nums) {
if (i >= a) dp[i] += dp[i - a];
}
}
return dp.back();
}
};
股票交易
字符串编辑
LeetCode——搜索
BFS
T1091. 二进制矩阵中的最短路径
struct Node {
int x;
int y;
};
class Solution {
public:
int shortestPathBinaryMatrix(vector<vector<int>>& grid) {
int ans = 0;
queue<Node> myQ; // BFS一般通过队列方式解决
int M = grid.size();
int N = grid[0].size();
// 先判断边界条件,很明显,这两种情况下都是不能到达终点的。
if (grid[0][0] == 1 || grid[M - 1][N - 1] == 1) {
return -1;
}
// 备忘录,记录已经走过的节点
vector<vector<int>> mem(M, vector<int>(N, 0));
myQ.push({0, 0});
mem[0][0] = 1;
// 以下是标准BFS的写法
while (!myQ.empty()) {
int size = myQ.size();
for (int i = 0; i < size; i++) {
Node currentNode = myQ.front();
int x = currentNode.x;
int y = currentNode.y;
// 判断是否满足退出的条件
if (x == (N - 1) && y == (M - 1)) {
return (ans + 1);
}
// 下一个节点所有可能情况
vector<Node> nextNodes = {{x + 1, y}, {x - 1, y}, {x + 1, y - 1}, {x + 1, y + 1},
{x, y + 1}, {x, y - 1}, {x - 1, y - 1}, {x - 1, y + 1}};
for (auto n : nextNodes) {
// 过滤条件1: 边界检查
if (n.x < 0 || n.x >= N || n.y < 0 || n.y >= M) {
continue;
}
// 过滤条件2:备忘录检查
if (mem[n.y][n.x] == 1) {
continue;
}
// 过滤条件3:题目中的要求
if (grid[n.y][n.x] == 1) {
continue;
}
// 通过过滤筛选,加入队列!
mem[n.y][n.x] = 1;
myQ.push(n);
}
myQ.pop();
}
ans++;
}
return -1;
}
};
T279. 完全平方数
class Solution
{
public:
/*返回小于n的平方序列: 1, 4, 9...*/
vector<int> getSquares(int n)
{
vector<int> res;
for(int i = 1; i*i <= n; ++i)
{
res.push_back(i*i);
}
return res;
}
int numSquares(int n)
{
vector<int> squares = getSquares(n);
vector<bool> visited(n+1); //记录已访问过的节点
queue<int> q;
q.push(n);
int res = 0;
visited[n] = true;
while(!q.empty())
{
int size = q.size();
res++;
while(size--)
{
int curr = q.front();
q.pop();
/*每次跨越的间隔为平方数*/
for(int num: squares)
{
int next = curr - num;
if(next < 0)
{
break;
}
if(next == 0)
{
return res;
}
if(visited[next])
{
continue;
}
visited[next] = true;
q.push(next);
}
}
}
return n;
}
};
T127. 单词接龙
class Solution {
public:
int ladderLength(string beginWord, string endWord, vector<string>& wordList) {
unordered_set<string> wordSet(wordList.begin(), wordList.end());
if (!wordSet.count(endWord)) return 0;
unordered_map<string, int> pathCnt{{{beginWord, 1}}};
queue<string> q{{beginWord}};
while (!q.empty()) {
string word = q.front(); q.pop();
for (int i = 0; i < word.size(); ++i) {
string newWord = word;
for (char ch = 'a'; ch <= 'z'; ++ch) {
newWord[i] = ch;
if (wordSet.count(newWord) && newWord == endWord) return pathCnt[word] + 1;
if (wordSet.count(newWord) && !pathCnt.count(newWord)) {
q.push(newWord);
pathCnt[newWord] = pathCnt[word] + 1;
}
}
}
}
return 0;
}
};
DFS
T695. 岛屿的最大面积
class Solution {
public:
int maxAreaOfIsland(vector<vector<int>>& grid) {
int ans = 0;
for (int i = 0; i != grid.size(); ++i)
for (int j = 0; j != grid[0].size(); ++j) {
int cur = 0;
stack<int> stacki;
stack<int> stackj;
stacki.push(i);
stackj.push(j);
while (!stacki.empty()) {
int cur_i = stacki.top(), cur_j = stackj.top();
stacki.pop();
stackj.pop();
if (cur_i < 0 || cur_j < 0 || cur_i == grid.size() || cur_j == grid[0].size() || grid[cur_i][cur_j] != 1)
continue;
++cur;
grid[cur_i][cur_j] = 0;
int di[4] = {0, 0, 1, -1};
int dj[4] = {1, -1, 0, 0};
for (int index = 0; index != 4; ++index) {
int next_i = cur_i + di[index], next_j = cur_j + dj[index];
stacki.push(next_i);
stackj.push(next_j);
}
}
ans = max(ans, cur);
}
return ans;
}
};
class Solution {
public:
vector<vector<int>> dirs{{0,-1},{-1,0},{0,1},{1,0}};
int maxAreaOfIsland(vector<vector<int>>& grid) {
int m = grid.size(), n = grid[0].size(), res = 0;
for (int i = 0; i < m; ++i) {
for (int j = 0; j < n; ++j) {
if (grid[i][j] != 1) continue;
int cnt = 0;
queue<pair<int, int>> q{{{i, j}}};
grid[i][j] *= -1;
while (!q.empty()) {
auto t = q.front(); q.pop();
res = max(res, ++cnt);
for (auto dir : dirs) {
int x = t.first + dir[0], y = t.second + dir[1];
if (x < 0 || x >= m || y < 0 || y >= n || grid[x][y] <= 0) continue;
grid[x][y] *= -1;
q.push({x, y});
}
}
}
}
return res;
}
};
T200. 岛屿数量
class Solution {
private:
void dfs(vector<vector<char>>& grid, int r, int c) {
int nr = grid.size();
int nc = grid[0].size();
grid[r][c] = '0';
if (r - 1 >= 0 && grid[r-1][c] == '1') dfs(grid, r - 1, c);
if (r + 1 < nr && grid[r+1][c] == '1') dfs(grid, r + 1, c);
if (c - 1 >= 0 && grid[r][c-1] == '1') dfs(grid, r, c - 1);
if (c + 1 < nc && grid[r][c+1] == '1') dfs(grid, r, c + 1);
}
public:
int numIslands(vector<vector<char>>& grid) {
int nr = grid.size();
if (!nr) return 0;
int nc = grid[0].size();
int num_islands = 0;
for (int r = 0; r < nr; ++r) {
for (int c = 0; c < nc; ++c) {
if (grid[r][c] == '1') {
++num_islands;
dfs(grid, r, c);
}
}
}
return num_islands;
}
};
T547. 朋友圈
这道题让我们求朋友圈的个数,题目中对于朋友圈的定义是可以传递的,比如A和B是好友,B和C是好友,那么即使A和C不是好友,那么他们三人也属于一个朋友圈。那么比较直接的解法就是 DFS 搜索,对于某个人,遍历其好友,然后再遍历其好友的好友,那么就能把属于同一个朋友圈的人都遍历一遍,同时标记出已经遍历过的人,然后累积朋友圈的个数,再去对于没有遍历到的人在找其朋友圈的人,这样就能求出个数。
class Solution {
public:
int findCircleNum(vector<vector<int>>& M) {
int n = M.size();
int res = 0;
vector<bool> visited(n, false);
for (int i = 0; i < n; ++i) {
if (visited[i]) continue;
helper(M, i, visited);
++res;
}
return res;
}
void helper(vector<vector<int>>& M, int k, vector<bool>& visited) {
visited[k] = true;
for (int i = 0; i < M.size(); ++i) {
if (!M[k][i] || visited[i]) continue;
helper(M, i, visited);
}
}
};
T130. 被围绕的区域
这是道关于 XXOO 的题,有点像围棋,将包住的O都变成X,但不同的是边缘的O不算被包围,可以用 DFS 来解。大家普遍的做法是扫矩阵的四条边,如果有O,则用 DFS 遍历,将所有连着的O都变成另一个字符,比如
,
这
样
剩
下
的
O
都
是
被
包
围
的
,
然
后
将
这
些
O
变
成
X
,
把
,这样剩下的O都是被包围的,然后将这些O变成X,把
,这样剩下的O都是被包围的,然后将这些O变成X,把变回O就行了。
class Solution {
public:
void solve(vector<vector<char>>& board) {
if (board.empty() || board[0].empty()) return;
int m = board.size(), n = board[0].size();
for (int i = 0; i < m; ++i) {
for (int j = 0; j < n; ++j) {
if (i == 0 || i == m - 1 || j == 0 || j == n - 1) {
if (board[i][j] == 'O') dfs(board, i , j);
}
}
}
for (int i = 0; i < m; ++i) {
for (int j = 0; j < n; ++j) {
if (board[i][j] == 'O') board[i][j] = 'X';
if (board[i][j] == '$') board[i][j] = 'O';
}
}
}
void dfs(vector<vector<char>> &board, int x, int y) {
int m = board.size(), n = board[0].size();
vector<vector<int>> dir{{0,-1},{-1,0},{0,1},{1,0}};
board[x][y] = '$';
for (int i = 0; i < dir.size(); ++i) {
int dx = x + dir[i][0], dy = y + dir[i][1];
if (dx >= 0 && dx < m && dy > 0 && dy < n && board[dx][dy] == 'O') {
dfs(board, dx, dy);
}
}
}
};
T417. 太平洋大西洋水流问题
这道题给了我们一个二维数组,说是数组的左边和上边是太平洋,右边和下边是大西洋,假设水能从高处向低处流,问我们所有能流向两大洋的点的集合。最开始想的是对于每个点都来搜索是否能到达边缘,只不过搜索的目标点不再是一个单点,而是所有的边缘点,照这种思路写出的代码无法通过 OJ 大数据集,那么就要想办法来优化代码,优化的方法跟之前那道 Surrounded Regions 很类似,都是换一个方向考虑问题,既然从每个点向中间扩散会 TLE,那么我们就把所有边缘点当作起点开始遍历搜索,然后标记能到达的点为 true,分别标记出 pacific 和 atlantic 能到达的点,那么最终能返回的点就是二者均为 true 的点。我们可以先用DFS来遍历二维数组。
class Solution {
private:
vector<vector<int>> res;
int m, n;
int dis[4][2] = { {1,0},{0,1},{-1,0},{0,-1} };
public:
vector<vector<int>> pacificAtlantic(vector<vector<int>>& matrix) {
if (matrix.empty() || matrix[0].empty()) return {};
m = matrix.size(), n = matrix[0].size();
vector<vector<bool>> pacific(m, vector<bool>(n, false));
vector<vector<bool>> atlantic(m, vector<bool>(n, false));
for (int i = 0; i < m; ++i) {
dfs(matrix, pacific, INT_MIN, i, 0);
dfs(matrix, atlantic, INT_MIN, i, n - 1);
}
for (int i = 0; i < n; ++i) {
dfs(matrix, pacific, INT_MIN, 0, i);
dfs(matrix, atlantic, INT_MIN, m - 1, i);
}
for (int i = 0; i < m; ++i) {
for (int j = 0; j < n; ++j) {
if (pacific[i][j] && atlantic[i][j]) {
res.push_back({i, j});
}
}
}
return res;
}
void dfs(vector<vector<int>>& matrix, vector<vector<bool>>& visited, int pre, int i, int j) {
if (i < 0 || i >= m || j < 0 || j >= n || visited[i][j] || matrix[i][j] < pre) return;//规定水流只能从高到低或者在同等高度上流动。
visited[i][j] = true;
for (int k = 0; k < 4; k++) {
int newx = i + dis[k][0];
int newy = j + dis[k][1];
dfs(matrix, visited, matrix[i][j], newx, newy);
}
}
};
回溯
Backtracking(回溯)属于 DFS。
普通 DFS 主要用在 可达性问题 ,这种问题只需要执行到特点的位置然后返回即可。
而 Backtracking 主要用于求解 排列组合 问题,例如有 { ‘a’,‘b’,‘c’ } 三个字符,求解所有由这三个字符排列得到的字符串,这种问题在执行到特定的位置返回之后还会继续执行求解过程。因为 Backtracking 不是立即返回,而要继续求解,因此在程序实现时,需要注意对元素的标记问题:
在访问一个新元素进入新的递归调用时,需要将新元素标记为已经访问,这样才能在继续递归调用时不用重复访问该元素;但是在递归返回时,需要将元素标记为未访问,因为只需要保证在一个递归链中不同时访问一个元素,可以访问已经访问过但是不在当前递归链中的元素。
T17. 电话号码的字母组合
class Solution {
public:
vector<string> letterCombinations(string digits) {
if (digits.empty()) return {};
vector<string> res;
vector<string> dict{"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
letterCombinationsDFS(digits, dict, 0, "", res);
return res;
}
void letterCombinationsDFS(string& digits, vector<string>& dict, int level, string out, vector<string>& res) {
if (level == digits.size()) {
res.push_back(out);
return;
}
string str = dict[digits[level] - '0'];
for (int i = 0; i < str.size(); ++i) {
letterCombinationsDFS(digits, dict, level + 1, out + str[i], res);
}
}
};
class Solution {
public:
vector<string> letterCombinations(string digits) {
if (digits.empty()) return {};
vector<string> res{""};
vector<string> dict{"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
for (int i = 0; i < digits.size(); ++i) {
vector<string> t;
string str = dict[digits[i] - '0'];
for (int j = 0; j < str.size(); ++j) {
for (string s : res) t.push_back(s + str[j]);
}
res = t;
}
return res;
}
};
T93. 复原IP地址
class Solution {
public:
vector<string> restoreIpAddresses(string s) {
vector<string> res;
restore(s, 4, "", res);
return res;
}
void restore(string s, int k, string out, vector<string> &res) {
if (k == 0) {
if (s.empty()) res.push_back(out);
}
else {
for (int i = 1; i <= 3; ++i) {
if (s.size() >= i && isValid(s.substr(0, i))) {
if (k == 1) restore(s.substr(i), k - 1, out + s.substr(0, i), res);
else restore(s.substr(i), k - 1, out + s.substr(0, i) + ".", res);
}
}
}
}
bool isValid(string s) {
if (s.empty() || s.size() > 3 || (s.size() > 1 && s[0] == '0')) return false;
int res = atoi(s.c_str());
return res <= 255 && res >= 0;
}
};
class Solution {
public:
vector<string> restoreIpAddresses(string s) {
vector<string> res;
for (int a = 1; a < 4; ++a)
for (int b = 1; b < 4; ++b)
for (int c = 1; c < 4; ++c)
for (int d = 1; d < 4; ++d)
if (a + b + c + d == s.size()) {
int A = stoi(s.substr(0, a));
int B = stoi(s.substr(a, b));
int C = stoi(s.substr(a + b, c));
int D = stoi(s.substr(a + b + c, d));
if (A <= 255 && B <= 255 && C <= 255 && D <= 255) {
string t = to_string(A) + "." + to_string(B) + "." + to_string(C) + "." + to_string(D);
if (t.size() == s.size() + 3) res.push_back(t);
}
}
return res;
}
};