class Solution {
public:
/*
1、求[m, n]的按位与,首先对于n和m这两个数在第i位前面都相同,由于n > m,因此n的第i位是1,m的第i位是0,即n = xxx1...,m = xxx0...,(...表示后面是什么未知),因此一定会存在一个数是t = xxx1000的形式,即这个数的第i位是1,且后面的都是0的形式,并且这个数t一定在[m,n]内
2、这样的话[m,n]中的所有数与t按位与后再第i位以至后面全是0,因此只需求两个数的公共前缀即可。
*/
int rangeBitwiseAnd(int m, int n) {
int res = 0;
for (int i = 30; i >= 0; i -- ) {
if ((m >> i & 1) != (n >> i & 1)) break;//遇到刚开始不一样的位时break
if (m >> i & 1) res += 1 << i;//此时倒数第i位一样,res的这一位和m的这一位相同
}
return res;
}
};
LeetCode 202. 快乐数
class Solution {
public:
/*
最坏情况下,n为2^31-1,假设9999999999(10个9,其实这个数已经比int范围大了),操作之后变为9^2*10==810,所以每次操作完之后,得到的数一定是0到810之间的数,也就是数的个数,或者说链表的节点不超过1000个,当操作超过810多次之后,必然会进入一个循环
这道题其实就是包装后的环形链表,其中慢指针就是每次把数字各位求一次平方和,快指针就是每次把数字各位求两次平方和
有两种情况
是快乐数:最后两个指针都会变成1,重合在一起
不是快乐数:相当于一个环,快慢指针总会在某个数处相遇
即:不管是不是快乐数,快慢指针最终都会汇聚到一个数上,我们只需要判断这个数是不是1即可
*/
int get(int x) {
int res = 0;
while (x) {
res += (x % 10) * (x % 10);
x /= 10;
}
return res;
}
bool isHappy(int n) {
int fast = get(get(n)), slow = get(n);
while (fast != slow) {
fast = get(get(fast));
slow = get(slow);
}
return slow == 1;
}
};
/*
计算每位数字的平方和
结果为1,则返回True
结果已出现过,说明陷入死循环,返回False
其他情况则重新计算每位数字的平方和,并把结果加入set
*/
bool isHappy(int n) {
unordered_set<int> visited;
while (n != 1){
visited.insert(n);
n = cal_next(n);
if (visited.count(n)) return false;
}
return true;
}
int cal_next(int n){
int res = 0;
while (n){
res += (n % 10) * (n % 10);
n /= 10;
}
return res;
}
};
LeetCode 203. 移除链表元素
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
auto dummy = new ListNode(-1);//这里新建一个虚拟头结点是因为头结点可能被删掉,所以为了避免特判
dummy->next = head;
for (auto p = dummy; p; p = p->next) {//从头结点开始遍历
auto q = p->next;//q指向下一段的第一个点
while (q && q->val == val) q = q->next;//略去等于val的点
p->next = q;
}
return dummy->next;
}
};
LeetCode 205. 同构字符串
class Solution {
public:
bool isIsomorphic(string s, string t) {
unordered_map<char, char> st, ts;
for (int i = 0; i < s.size(); i ++ )
{
int a = s[i], b = t[i];
if (st.count(a) && st[a] != b) return false;
st[a] = b;
if (ts.count(b) && ts[b] != a) return false;
ts[b] = a;
}
return true;
}
};
LeetCode 206. 反转链表
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* reverseList(ListNode* head) {
if (!head) return NULL;
auto a = head, b = a->next;//ab两根指针维护相邻两个点,反向后同时往后移动一位,最后将开头next指向空
while (b) {
auto c = b->next;//先缓存
b->next = a;
a = b;
b = c;
}
head->next = NULL;
return a;
}
};
class Solution {
public:
int minSubArrayLen(int s, vector<int>& nums) {
/*
在正整数数组中找到总和大于等于s,长度最小的区间,暴力做法就是枚举两个端点,再求和,时间复杂度O(n^3)
优化:凡是有两个端点的题目,想优化一般是考虑单调性,因为这样就可以用双指针优化
每次枚举右端点i,对于它找到一个最靠右的端点j使得[j,i]满足
如果i往后的话,j必然往后,反证可知(前提是数组中均为正整数)
可以边移动指针边维护区间总和,时间复杂度O(n)
*/
int res = INT_MAX;
for (int i = 0, j = 0, sum = 0; i < nums.size(); i ++ ) {
sum += nums[i];
while (sum - nums[j] >= s) sum -= nums[j ++ ];
if (sum >= s) res = min(res, i - j + 1);
}
if (res == INT_MAX) res = 0;
return res;
}
};
LeetCode 212. 单词搜索 II
/*
上道题只需要搜索一个单词,上道题枚举网格起点,再枚举下一个点的方向
把所有单词维护成一个trie,搜索过程中一定要在trie树中走,搜的过程中判断当前这一步能不能走,也就是搜的过程中判断trie树中当前的点是否存在一条对应的边,另外还要开一个哈希表,把所有遍历到的单词输出出来
也就是dfs中用trie树做剪枝
时间复杂度分析:令L表示单词的平均长度,n表示矩阵边长。则总共有n^2个路径起点,每次路径可以往3个方向延伸(一共4个方向,除了第一次以外,其他时候不能往回走,所以每次只剩3个可选的方向),一共延伸 L次,所以总共有 (n^2)(3^L)条不同路径,最坏情况下,每条路径最终都要记录答案,还需要 O(L)的计算量,所以总时间复杂度是 O((n^2)(3^L)L)
*/
class Solution {
public:
struct Node {
int id;//存编号,要知道当前是哪一个单词
Node *son[26];
Node() {
id = -1;//当前节点为空
for (int i = 0; i < 26; i ++ ) son[i] = NULL;
}
}*root;//定义一个根节点
unordered_set<int> ids;//开一个哈希表存储最后遍历到了哪些单词的编号
vector<vector<char>> g;//把二维矩阵存下来,这样暴力搜索时可以少传一个参数
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};//四个方向
void insert(string& word, int id) {
auto p = root;
for (auto c: word) {
int u = c - 'a';
if (!p->son[u]) p->son[u] = new Node();
p = p->son[u];
}
p->id = id;//标记一下以当前节点结尾的是哪个单词
}
vector<string> findWords(vector<vector<char>>& board, vector<string>& words) {
g = board;
root = new Node();//初始化根节点
for (int i = 0; i < words.size(); i ++ ) insert(words[i], i);//构造trie树
for (int i = 0; i < g.size(); i ++ )
for (int j = 0; j < g[i].size(); j ++ ) {//枚举起点
int u = g[i][j] - 'a';
if (root->son[u])
dfs(i, j, root->son[u]);
}
vector<string> res;
for (auto id: ids) res.push_back(words[id]);
return res;
}
void dfs(int x, int y, Node* p) {
if (p->id != -1) ids.insert(p->id);//说明存在一个单词
char t = g[x][y];
g[x][y] = '.';
for (int i = 0; i < 4; i ++ ) {
int a = x + dx[i], b = y + dy[i];
if (a >= 0 && a < g.size() && b >= 0 && b < g[0].size() && g[a][b] != '.') {
int u = g[a][b] - 'a';
if (p->son[u]) dfs(a, b, p->son[u]);
}
}
g[x][y] = t;
}
};
LeetCode 214. 最短回文串
/*
这道题可以转换为求原字符串的最长回文前缀,转化为求kmp的next数组,求最长前缀后缀
*/
class Solution {
public:
string shortestPalindrome(string s) {
string t(s.rbegin(), s.rend());//把原串翻转
int n = s.size();
s = ' ' + s + '#' + t;//kmp算法习惯下标从1开始
vector<int> ne(n * 2 + 2);//定义next数组
for (int i = 2, j = 0; i <= n * 2 + 1; i ++ ) {
while (j && s[i] != s[j + 1]) j = ne[j];
if (s[i] == s[j + 1]) j ++ ;
ne[i] = j;
}
int len = ne[n * 2 + 1];//这是原字符串的最长回文前缀长度
string left = s.substr(1, len), right = s.substr(1 + len, n - len);
return string(right.rbegin(), right.rend()) + left + right;
}
};
LeetCode 215. 数组中的第K个最大元素
class Solution {
public:
int quick_sort(vector<int>& nums, int l, int r, int k) {
if (l == r) return nums[k];
int x = nums[(l+r)>>1], i = l - 1, j = r + 1;
while (i < j) {
do i ++ ; while (nums[i] > x);//从大到小排序
do j -- ; while (nums[j] < x);
if (i < j) swap(nums[i], nums[j]);
}
if (k <= j) return quick_sort(nums, l, j, k);
else return quick_sort(nums, j + 1, r, k);
}
int findKthLargest(vector<int>& nums, int k) {//从大到小排序后的dik个数,下标为k-1
return quick_sort(nums, 0, nums.size() - 1, k - 1);
}
};
class Solution {
public:
/*
堆 timeO(nlogk)
*/
int findKthLargest(vector<int>& nums, int k) {
int n = nums.size();
priority_queue<int, vector<int>, greater<int>> min_heap;//维护一个小根堆
for (int i = 0; i < n; ++i){
min_heap.push(nums[i]);//把未排序数组中k个最大的元素放入小根堆中,最后的堆顶元素即答案
if (min_heap.size() > k) min_heap.pop();
}
return min_heap.top();
}
};
class Solution {
public:
int cnt;
vector<int> h;
void down(int u)
{
int t = u;//用t表示三个点的最小值的节点编号
//前者是判断有无左右儿子
if (u * 2 <= cnt && h[u * 2] < h[t]) t = u * 2;
if (u * 2 + 1 <= cnt && h[u * 2 + 1] < h[t]) t = u * 2 + 1;
if (u != t)
{
swap(h[u], h[t]);
down(t);
}
}
int findKthLargest(vector<int>& nums, int k) {
int n = nums.size();
h.push_back(0);
for (int i = 1; i <= n; i ++ ) h.push_back(nums[i-1]);//下标从1开始
cnt=n;
for (int i = n / 2; i; i -- ) down(i);//建堆
while(cnt>k){//当堆中元素超过k时,去掉堆顶元素
h[1] = h[cnt -- ];
down(1);
}
return h[1];
}
};
LeetCode 216. 组合总和 III
/*
从1到9中选k个数使得和为n,从前往后选,这样就不会出现重复搜索
*/
class Solution {
public:
vector<vector<int>> ans;
vector<int> path;
vector<vector<int>> combinationSum3(int k, int n) {
dfs(1, n, k);
return ans;
}
//start当前从哪儿开始搜,剩下还能选k个数使得总和为n
void dfs(int start, int n, int k) {
if (!n) {
if (!k) ans.push_back(path);
} else if (k) {
for (int i = start; i <= 9; i ++ )
if (n >= i) {
path.push_back(i);
dfs(i + 1, n - i, k - 1);
path.pop_back();
}
}
}
};
LeetCode 217. 存在重复元素
class Solution {
public:
bool containsDuplicate(vector<int>& nums) {
unordered_set<int> S;
for (auto x: nums)
if (S.count(x)) return true;
else S.insert(x);
return false;
}
};
LeetCode 219. 存在重复元素 II
class Solution {
public:
bool containsNearbyDuplicate(vector<int>& nums, int k) {
unordered_map<int, int> hash;
for (int i = 0; i < nums.size(); i ++ ) {
int x = nums[i];
if (hash.count(x) && i - hash[x] <= k) return true;
hash[x] = i;
}
return false;
}
};
LeetCode 220. 存在重复元素 III
class Solution {
public:
/*
动态维护一个长度为k的滑动窗口,要查看窗口内是否有一个数与当前遍历的数a[i]的差不超过t,也就是要在窗口内找到一个与a[i]最接近的数,也就是找到大于等于它的最小数(lower_bound),和小于等于它的最大数
可以用set维护滑动窗口,因为它支持lower_bound这个操作,而且它也支持插入删除操作
对于当前数,先用lower_bound找到大于等于它的最小数,然后再往前一个数就是小于等于它的最大数,,判断差值是否小于等于t
*/
bool containsNearbyAlmostDuplicate(vector<int>& nums, int k, int t) {
typedef long long LL;//由于计算两数之差时,正无穷减去负无穷可能越界
multiset<LL> S;//区间可能存在相同数
S.insert(1e18), S.insert(-1e18);//放入两个哨兵,这样调用lower_bound时一定不会返回空,ll范围的正负无穷
for (int i = 0, j = 0; i < nums.size(); i ++ ) {
if (i - j > k) S.erase(S.find(nums[j ++ ]));//超出窗口,往后移
//不写S.erase(nums[j ++ ])而写S.erase(S.find(nums[j ++ ]))的原因是防止删掉集合中所有值等于nums[j]的元素
int x = nums[i];
auto it = S.lower_bound(x);//返回一个迭代器,找到大于等于x的最小数
if (*it - x <= t) return true;
-- it;
if (x - *it <= t) return true;
S.insert(x);
}
return false;
}
};
class Solution {
public:
/*
桶排序 timeO(n)
*/
bool containsNearbyAlmostDuplicate(vector<int>& _nums, int _k, int _t) {
long long t = _t, k = _k;//重新定义是为了变成longlong
vector<long long> nums(_nums.begin(), _nums.end());
if (nums.empty() || t < 0 || k < 0) return false;
long long mn = INT_MAX;
for (long long num: nums) mn = min(mn, num);
long long size = t + 1;
unordered_map<int, vector<int>> buckets;
for (int i = 0; i < nums.size(); ++i){
int idx = (nums[i] - mn) / size;
buckets[idx].push_back(i);
}
for (auto idx_vec: buckets){
int idx = idx_vec.first;
for (int j = 0; j < buckets[idx].size(); ++j){
if (j > 0){
if (buckets[idx][j] - buckets[idx][j - 1] <= k) return true;
}
// cout << buckets[idx][j] << " ";
}
// cout << endl;
if (idx > 0){
if (buckets.count(idx - 1) && buckets[idx - 1].size()){
int l = buckets[idx - 1].back(), r = buckets[idx][0];
if (abs(nums[l] - nums[r]) <= t && abs(l - r) <= k) return true;
}
}
}
return false;
}
};
LeetCode 222. 完全二叉树的节点个数
class Solution {
public:
int countNodes(TreeNode* root) {
/*
完全二叉树与满二叉树的区别:
满二叉树是完美的三角形,有n层的话,节点个数2^n-1
完全二叉树的定义如下:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。
完全二叉树主要是不知道最后一层的分界点,如何找到分界点位置,类似于二分
最后一层某个点左边有n层,右边的有n-1层
如果是满二叉树,直接返回
如果不是:
左右均是满二叉树,但层数差1
有且只有一棵不是满二叉树,取决于分界点在哪儿,是的话直接算,否则继续递归
整个过程是二分,完全二叉树的左右子树至少有一棵是满二叉树,直接返回,去判断另外一棵不是满二叉树的子树,
每次把分界点所在的区间缩小近似一半,最多二分logn次,每次二分判定的时候都要从当前这个点往左右两边一直走到底
所以每次二分判定的时候会遍历logn次,整个时间复杂度是O(log^2n)
*/
if (!root) return 0;
auto l = root->left, r = root->right;
int x = 1, y = 1;//往左右走的次数
while (l) l = l->left, x ++ ;
while (r) r = r->right, y ++ ;
if (x == y) return (1 << x) - 1;//层数相等
return countNodes(root->left) + 1 + countNodes(root->right);
}
};
LeetCode 224. 基本计算器
class Solution {
public:
/*
同样是利用栈,由于运算符在两个操作数中间,我们可以在遇到一个运算符时计算上一个运算符,并且我们把减号看成是数字前面的符号,即 1−2=+(1)+(−2)。我们用两个变量res和sign来分别记录到目前为止表达式计算的结果和上一个遇到的运算符,当我们遇到一个左括号时,相当于遇到了一个新的表达式,我们需要将这两个变量压入栈中,并重新给这两个变量赋初值,这类似于函数递归调用的思路,在递归前先保存本地变量,递归返回后再恢复现场。而当我们遇到右括号时说明子表达式已经结束,我们需要将该子表达式计算得到结果累加到遇到该子表达式之前的结果res。
*/
int calculate(string s) {
if (s.empty()) return 0;
stack<int> stk;
int num = 0, sign = 1, res = 0;
for (char c: s){
if (c >= '0' && c <= '9') num = 10 * num + (c - '0');
else if (c == '+') res += sign * num, sign = 1, num = 0;
else if (c == '-') res += sign * num, sign = -1, num = 0;
else if (c == '('){
stk.push(res);
stk.push(sign);
num = 0; sign = 1;
res = 0;
}
else if (c == ')'){
res += sign * num, num = 0, sign = 1;
res *= stk.top(); stk.pop();
res += stk.top(); stk.pop();
}
}
if (num) res += sign * num;
return res;
}
};
LeetCode 226. 翻转二叉树
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
if (!root) return NULL;
swap(root->left, root->right);
invertTree(root->left);
invertTree(root->right);
return root;
}
};
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
if (!root) return nullptr;
queue<TreeNode *> Q; Q.push(root);
while (Q.size()){
TreeNode *node = Q.front(); Q.pop();
if (node->left) Q.push(node->left);
if (node->right) Q.push(node->right);
swap(node->left, node->right);
}
return root;
}
};
LeetCode 227. 基本计算器 II
class Solution {
public:
/*
(栈) O(n)
这道题主要是需要考虑加减和乘除的优先级问题,用栈来处理,遇到加减就把数字压栈,遇到乘除就把栈顶弹出,与数字进行乘除处理。主要注意的是运算符是放在两个数字的中间,而我们想要的是在遇到运算符时,用于运算的两个数字已经被解析出来了,因此用op来记录前一个运算符,在遇到一个新的运算符或者到字符串的结尾时再考虑对前一个运算符进行处理。
*/
int calculate(string s) {
if (s.empty()) return 0;
int num = 0, n = s.size();
char op = '+';
stack<int> stk;
for (int i = 0; i < n; ++i){
char c = s[i];
if (c >= '0' && c <= '9') num = num * 10 + (c - '0');
if (c == '+' || c == '-' || c == '*' || c == '/' || i == n - 1){
if (op == '+') stk.push(num);
else if (op == '-') stk.push(-num);
else if (op == '*') stk.top() *= num;
else if (op == '/') stk.top() /= num;
num = 0;
op = c;
}
}
int res = 0;
while (stk.size()) res += stk.top(), stk.pop();
return res;
}
};
LeetCode 228. 汇总区间
class Solution {
public:
vector<string> summaryRanges(vector<int>& nums) {
vector<string> res;
for (int i = 0; i < nums.size(); i ++ ) {
int j = i + 1;
while (j < nums.size() && nums[j] == nums[j - 1] + 1) j ++ ;
if (j == i + 1) res.push_back(to_string(nums[i]));
else res.push_back(to_string(nums[i]) + "->" + to_string(nums[j - 1]));
i = j - 1;
}
return res;
}
};
LeetCode 230. 二叉搜索树中第K小的元素
class Solution {
public:
/*
求二叉搜索树中第K小的元素,等价于求整棵树中序遍历的第k个数,所以直接中序遍历,定义一个全局变量记录当前遍历第几个数
*/
int k, ans;
int kthSmallest(TreeNode* root, int _k) {
k = _k;
dfs(root);
return ans;
}
bool dfs(TreeNode* root) {
if (!root) return false;//return布尔值是为了提前返回
if (dfs(root->left)) return true;//如果在左边能提前搜到答案,提前返回
if ( -- k == 0) {//当前遍历的数恰好就是第k小元素
ans = root->val;
return true;
}//左子树和当前节点都是答案,递归右边
return dfs(root->right);
}
};
LeetCode 231. 2的幂
class Solution {
public:
bool isPowerOfTwo(int n) {
return n > 0 && (n & -n) == n;
}
};
LeetCode 234. 回文链表
/*
判断数组是否回文,双指针
但单向链表只能往前走,所以可以先把后半部分链表翻转,然后两根指针各自往前判断是否对称,最后复原
*/
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
bool isPalindrome(ListNode* head) {
int n = 0;//首先求链表节点数量
for (auto p = head; p; p = p->next) n ++ ;
if (n <= 1) return true;
int half = n / 2;
auto a = head;
for (int i = 0; i < n - half; i ++ ) a = a->next;
auto b = a->next;
for (int i = 0; i < half - 1; i ++ ) {
auto c = b->next;
b->next = a;
a = b, b = c;
}
auto p = head, q = a;
bool success = true;
for (int i = 0; i < half; i ++ ) {
if (p->val != q->val) {
success = false;
break;
}
p = p->next;
q = q->next;
}
auto tail = a;
b = a->next;
// 将链表恢复原状
for (int i = 0; i < half - 1; i ++ ) {
auto c = b->next;
b->next = a;
a = b, b = c;
}
tail->next = NULL;
return success;
}
};
LeetCode 235. 二叉搜索树的最近公共祖先
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
/*
递归最大深度是树的高度
*/
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
if (p->val > q->val) swap(p, q);//使得搜索过程中节点p值较小
if (p->val <= root->val && q->val >= root->val) return root;
//两点分别在根节点两边的情况,严格小于在左子树,等于在根节点
if (q->val < root->val) return lowestCommonAncestor(root->left, p, q);
//如果较大的点都比根小的话,两点在左子树中,在左子树中找即可
return lowestCommonAncestor(root->right, p, q);
}
};
class Solution {
public:
/*
(暴力) O(h)
由于这是一棵二叉查找树,我们可以利用二叉查找树的性质来从根结点开始寻找。
首先根结点必定是候选公共祖先,接着如果 p 和 q 同时出现在左子树,则我们往左儿子移动;
如果 p 和 q 同时出现在右子树,则我们往右儿子移动;
若发现不满足 2 中的两个条件,则停止寻找,当前结点就是最近公共祖先。
时间复杂度
每次都会降低一层,故最坏时间复杂度也就是树的高度 O(h)。
*/
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
TreeNode *cur = root;
while (1) {
if (p -> val < cur -> val && q -> val < cur -> val)
cur = cur -> left;
else if (p -> val > cur -> val && q -> val > cur -> val)
cur = cur -> right;
else
break;
}
return cur;
}
};
LeetCode 236. 二叉树的最近公共祖先
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
/*
直接拿本身这个函数进行递归,本身这个函数的含义是在以root为根的子树中找到p和q的最近公共祖先
1、若当前节点root == p,则表示q点一定在root的左右子树其中一处,则最近的公共结点肯定是root
2、若当前节点root == q,则表示p点一定在root的左右子树其中一处,则最近的公共结点肯定是root
3、若1和2情况都不是,则p和q的最近公共祖先要么在root的左子树,要么在root的右子树,则直接递归到root.left和root.right进行搜索,若递归完后,左子树返回null表示没找到,那答案肯定是在右子树,同理,右子树返回null表示没找到,那答案肯定是在左子树
4、若3情况中左右子树都找不到p和q的最近公共祖先,则表示p点和q点分别在不同的左右子树,则root就是他们的最近公共祖先
*/
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
if(!root) return NULL;
if(root == p || root == q) return root;
auto left = lowestCommonAncestor(root->left, p , q);
auto right = lowestCommonAncestor(root->right, p, q);
if(left == NULL) return right;
if(right == NULL) return left;
return root;
}
};
class Solution {
public:
/*
当前这道题无法直接判断pq两点在左子树还是右子树,所以只能递归遍历左右两棵子树,才能知道两点在哪棵子树
每次递归两棵子树,最坏情况下遍历整棵树,所以最坏是O(n),上道题只需遍历树的高度O(h)
对于当前遍历的点来说,递归左右子树看是否有p与q,如果在搜索过程中第一次发现以当前点为根的子树中p与q同时搜到,所以当前点是最近公共祖先
*/
TreeNode* ans = NULL;
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
dfs(root, p, q);
return ans;
}
//用二进制表示当前点为根的子树中存在哪些点
int dfs(TreeNode* root, TreeNode* p, TreeNode* q) {
if (!root) return 0;//00表示q与p都没有
int state = dfs(root->left, p, q);//递归左子树,
if (root == p) state |= 1;//多加01这种情况
else if (root == q) state |= 2;//多加10这种情况
state |= dfs(root->right, p, q);//至此找到以root为根的子树中两点的存在情况
if (state == 3 && !ans) ans = root;//如果都存在且ans为null,也就是第一次找到pq的话。更新答案
return state;
}
};
/*
首先想到的是先算出数组所有元素乘积,然后遍历依次除就行
但是要求不能用除法,线性时间复杂度,常数空间复杂度,可以用前后缀分解
先预处理前缀的乘积p(i)=a(0)*a(1)...*a(i-1),然后预处理出来后缀乘积s(i)=a(n-1)*a(n-2)*...*a(i+1)
b(i)=p(i)*s(i) 又由于要求除了输出空间外,要常数空间复杂度,对于前缀乘积开一个数组,用作输出,后缀乘积用一个变量来存
*/
class Solution {
public:
vector<int> productExceptSelf(vector<int>& nums) {
int n = nums.size();
vector<int> p(n, 1);//预处理前缀数组乘积 p(i)表示第i个数前面所有数的乘积
for (int i = 1; i < n; i ++ ) p[i] = p[i - 1] * nums[i - 1];//下标都是从0开始
for (int i = n - 1, s = 1; i >= 0; i -- ) {//后缀乘积用一个变量表示
p[i] *= s;
s *= nums[i];
}
return p;
}
};
LeetCode 242. 有效的字母异位词
class Solution {
public:
bool isAnagram(string s, string t) {
unordered_map<char, int> a, b;
for (auto c: s) a[c] ++ ;
for (auto c: t) b[c] ++ ;
return a == b;
}
};
class Solution {
public:
bool isAnagram(string s, string t) {
int a[26]={0};
int b[26]={0};
for (auto c: s) a[c-'a'] ++ ;
for (auto c: t) b[c-'a']++;
for(int i=0;i<26;i++) {
if(a[i]!=b[i]) return false;
}
return true;
}
};
LeetCode 257. 二叉树的所有路径
class Solution {
public:
/*
(递归回溯) O(n^2)
从根结点开始递归遍历,每个结点仅遍历一次,遍历时需要记录当前路径。
若发现当前结点没有左右儿子,则当前结点为叶子结点,将当前路径加入答案。
时间复杂度
每个结点仅遍历一次,遍历时维护路径所需要的平均时间也和遍历时间成正比。
最坏情况下,每条路径需要 O(n) 的时间存放,共有 O(n) 个叶子节点,故总时间复杂度为 O(n^2)
*/
vector<string> ans;
vector<int> path;
vector<string> binaryTreePaths(TreeNode* root) {
if (root) dfs(root);
return ans;
}
void dfs(TreeNode* root) {
path.push_back(root->val);
if (!root->left && !root->right) {//当前遍历到叶节点
string line = to_string(path[0]);
for (int i = 1; i < path.size(); i ++ )
line += "->" + to_string(path[i]);
ans.push_back(line);
} else {
if (root->left) dfs(root->left);
if (root->right) dfs(root->right);
}
path.pop_back();//回溯
}
};
LeetCode 258. 各位相加
class Solution {
public:
int addDigits(int num) {
while (num >= 10) {
int tot = 0;
for (; num > 0; num /= 10)
tot += num % 10;
num = tot;
}
return num;
}
};
class Solution {
public:
/*
可以证明 num 与 每一位相加 在模 9 的意义下同余,故可以直接用这个数字模 9 的余数作为答案。
判断这个数字模 9 之后的余数
1到8的话直接返回,0和9要特判
*/
int addDigits(int num) {
if (!num) return 0;
if (num % 9) return num % 9;
return 9;
}
};
LeetCode 259. 较小的三数之和
class Solution {
public:
int threeSumSmaller(vector<int>& nums, int target) {
int res=0;
sort(nums.begin(),nums.end());
for(int i=0;i<nums.size();i++){
for(int j=i+1,k=nums.size()-1;j<k;){
if(nums[i]+nums[j]+nums[k]>=target) k--;
else{
res+=k-j;
j++;
}
}
}
return res;
}
};
LeetCode 260. 只出现一次的数字 III
/*
想办法转化成基础问题,假设出现一次的两个数分别是a,b 所有数异或之后得到的是a^b,因为ab不一样,所以两者二进制表示中至少一位不一样,所以可以从两者二进制表示中随便找一位不一样的位,比如第k位,把所有数根据第k位是否为0分为两类,所以啊,b在不同的类中,对于每一类而言,就转换成基础问题,所有元素只有一个数出现一次,其他数出现两次
*/
class Solution {
public:
vector<int> singleNumber(vector<int>& nums) {
int sum = 0;
for (auto x: nums)
sum ^= x;
int k = 0;//在sum中随便找两者不一样的位
while ((sum >> k & 1) == 0) k ++ ;
int s1 = 0, s2 = 0;
for (auto x: nums)
if ((x >> k) & 1)
s1 ^= x;
else
s2 ^= x;
return vector<int>{s1, s2};
}
};
LeetCode 263. 丑数
class Solution {
public:
bool isUgly(int num) {
if (num <= 0) return false;
while (num % 2 == 0) num /= 2;
while (num % 3 == 0) num /= 3;
while (num % 5 == 0) num /= 5;
return num == 1;
}
};
LeetCode 268. 缺失数字
/*
找到0-n的序列缺失的数
*/
class Solution {
public:
int missingNumber(vector<int>& nums) {
// int n = nums.size();
// int res = n * (n + 1) / 2;//计算0-n的总和,减去所有数得到缺失的数
// for (auto x: nums) res -= x;
// return res;
//类似于缺失的正数那道题,一直交换把每个数放在该在的位置
int n=nums.size();
for(int i=0;i<n;i++)while(nums[i]<n&&nums[i]!=nums[nums[i]]) swap(nums[i],nums[nums[i]]);
for(int i=0;i<n;i++)if(nums[i]!=i) return i;
return n;
}
};
LeetCode 273. 整数转换英文表示
/*
找规律
*/
class Solution {
public:
string num0_19[20] = {
"Zero", "One", "Two", "Three", "Four", "Five", "Six", "Seven",
"Eight", "Nine", "Ten", "Eleven", "Twelve", "Thirteen",
"Fourteen", "Fifteen", "Sixteen", "Seventeen", "Eighteen",
"Nineteen",
};
string num20_90[8] = {
"Twenty", "Thirty", "Forty", "Fifty", "Sixty", "Seventy",
"Eighty", "Ninety",
};
string num1000[4] = {
"Billion ", "Million ", "Thousand ", "",
};
string get(int x) { // 返回1 ~ 999的英文表示
string res;
if (x >= 100) {
res += num0_19[x / 100] + " Hundred ";
x %= 100;
}
if (x >= 20) {
res += num20_90[x / 10 - 2] + " ";
x %= 10;
if (x) res += num0_19[x] + ' ';
} else if (x) res += num0_19[x] + ' ';
return res;
}
string numberToWords(int num) {
if (!num) return "Zero";
string res;
for (int i = 1e9, j = 0; i >= 1; i /= 1000, j ++ )//每三位一循环
if (num >= i) {
res += get(num / i) + num1000[j];
num %= i;
}
res.pop_back();
return res;
}
};
LeetCode 274. H 指数
/*
计数排序
如果一篇文章的引用次数超过论文的总数 n,那么将它的引用次数降低为 n 也不会改变hh 指数的值,因为 h 指数一定小于等于 n
*/
class Solution {
public:
int hIndex(vector<int>& c) {
int n = citations.size();
vector<int> buckets(n + 1);
for (int citation: citations)
++buckets[min(citation, n)];//buckets[i]表示引用i次的论文有多少篇
int sum = 0;
for (int i = n; i >= 0; --i){
sum += buckets[i];//有sum篇论文引用了至少i次
if (sum >= i) return i;//刚开始有sum>=i时,必然有i篇论文引用了至少i次
}
return -1;
}
};
class Solution {
public:
int hIndex(vector<int>& c) {
/*
找到最大的h使得有h个数大于等于h
先将数组从大到小排好序,h从大到小枚举,如果h个数大于等于h,则必然第h个数大于等于h
只需要判断是否刚好第h个数大于等于h,这样必然前面所有数都大于等于h
*/
sort(c.begin(), c.end(), greater<int>());
for (int h = c.size(); h; h -- )
if (c[h - 1] >= h)//第h个数大于等于h
return h;
return 0;
}
};
LeetCode 275. H指数 II
class Solution {
public:
int hIndex(vector<int>& c) {
/*
与上道题相比,所给数组是排好序的,且要求对数时间复杂度,二分
可以从h==n开始判断,看是否有h个数大于等于h,也就是判断倒数第h个数是否大于等于h就行(给的数组升序排列)
时间复杂度O(n)
但注意这道题所给的排序是升序,你想要的数逆序,但不要翻转,不然时间复杂度是线性
所以手动做个坐标变换
*/
int n = c.size();
int l = 0, r = n;//可能一个数也没有,可能全都是
while (l < r) {
int mid = l + r + 1 >> 1;
if (c[n - mid] >= mid) l = mid;//你要找到的是满足逆序时c[mid]>=mid的最后一个元素,求最大的h
//n-1是第1个数,n-mid是第mid个数
else r = mid - 1;
}
return r;
}
};
LeetCode 278. 第一个错误的版本
// The API isBadVersion is defined for you.
// bool isBadVersion(int version);
class Solution {
public:
int firstBadVersion(int n) {
//二分查找满足某个性质的第一个元素
int l = 1, r = n;
while (l < r)
{
int mid = (l + 0ll + r) / 2;//可能溢出,转化为longlong
if (isBadVersion(mid)) r = mid;
else l = mid + 1;
}
return r;
}
};
LeetCode 283. 移动零
class Solution {
public:
void moveZeroes(vector<int>& nums) {
int j=0;
for(int i=0;i<nums.size();i++){
if(nums[i]!=0){
nums[j++]=nums[i];
}
}
while(j<nums.size()) nums[j++]=0;
}
};
LeetCode 287. 寻找重复数
class Solution {
public:
int findDuplicate(vector<int>& nums) {
/*
转化为双指针算法找环的入口,所给数组可以看做一条每个节点只存储next指针的链表
比如例子数组是[1,3,4,2,2],对应的链表有0,1,2,3,4五个节点,每个节点存储所指向的下一个节点
0->1->3->2>4->2,这样就转化为求环的入口这道题
*/
int a = 0, b = 0;//都从0开始走
while (true) {
a = nums[a];//这里不会存在走到空节点的情况,直接走就行
b = nums[nums[b]];
if (a == b) {
a = 0;
while (a != b) {
a = nums[a];
b = nums[b];
}
return a;
}
}
return -1;
}
};
LeetCode 289. 生命游戏
class Solution {
public:
/*
给定初始为0或1的二维矩阵,注意出生和死亡是同时发生,不能使用额外空间
做法本身本质上也相当于新开一个数组,每个数是int类型,32位,而每个数本身只有01两种情况,也就是只占32位中的一位
新开一个数组存在倒数第二位即可,这个和LeetCode 661. 图片平滑器第二种位运算的做法很像,但循环包括自己在内的九个格子的时候,这道题不关心中间的格子,那道题关心
*/
void gameOfLife(vector<vector<int>>& board) {
if (board.empty() || board[0].empty()) return;
int n = board.size(), m = board[0].size();
for (int i = 0; i < n; i ++ )
for (int j = 0; j < m; j ++ ) {
int live = 0;//记录当前枚举的细胞周围的活细胞数量
for (int x = max(0, i - 1); x <= min(n - 1, i + 1); x ++ )
for (int y = max(0, j - 1); y <= min(m - 1, j + 1); y ++ )
//不关心当前枚举的细胞,以及只关心周围细胞的最后一位
if ((x != i || y != j) && (board[x][y] & 1))
live ++ ;
int cur = board[i][j] & 1, next;//当前枚举细胞的状态,以及下一个状态
if (cur) {
if (live < 2 || live > 3) next = 0;
else next = 1;
} else {
if (live == 3) next = 1;
else next = 0;
}
board[i][j] += next << 1;//board[i][j] |= next << 1;
}
for (int i = 0; i < n; i ++ )
for (int j = 0; j < m; j ++ )
board[i][j] >>= 1;
}
};
LeetCode 290. 单词规律
class Solution {
public:
/*
证明双射
假设pattern有 n 个字母,str有 n 个单词。相当于给了我们 n 组字母和单词的对应关系,然后问字母和单词是否一一对应,即相同字母对应相同单词,且不同字母对应不同单词。
不同字母对应不同单词,等价于相同单词对应相同字母。
所以我们可以用两个哈希表,分别存储单词对应的字母,以及字母对应的单词。
然后从前往后扫描,判断相同元素对应的值,是否是相同的。
时间复杂度分析:数组和单词仅被遍历一次,所以时间复杂度是线性的。假设str的长度是 L,那么总时间复杂度就是 O(L)。
*/
bool wordPattern(string pattern, string str) {
vector<string> words;
stringstream ssin(str);//stringstream来将str分割成string数组。
string word;
while (ssin >> word) words.push_back(word);
if (pattern.size() != words.size()) return false;
unordered_map<char, string> pw;
unordered_map<string, char> wp;
for (int i = 0; i < pattern.size(); i ++ ) {
auto a = pattern[i];
auto b = words[i];
if (pw.count(a) && pw[a] != b) return false;
pw[a] = b;
if (wp.count(b) && wp[b] != a) return false;
wp[b] = a;
}
return true;
}
};
LeetCode 299. 猜数字游戏
class Solution {
public:
/*
给定两个字符串,统计两个值:
1.在不考虑顺序的情况下,第二个字符串有多少字母在第一个字符串中出现过,用哈希表 总值 tot
2.第二个字符串中有多少个字母与第一个字符串对应位置相同,直接遍历即可 Bulls
*/
string getHint(string secret, string guess) {
unordered_map<char, int> hash;
for (auto c: secret) hash[c] ++ ;
int tot = 0;
for (auto c: guess)
if (hash[c]) {
tot ++ ;
hash[c] -- ;
}
int bulls = 0;
for (int i = 0; i < secret.size(); i ++ )
if (secret[i] == guess[i])
bulls ++ ;
return to_string(bulls) + "A" + to_string(tot - bulls) + "B";
}
};
LeetCode 300. 最长递增子序列
/*
DP
状态表示:
f[i]表示所有以第i个数结尾的上升子序列中长度最大的上升子序列的长度
满足某种条件的某种集合的某种属性
状态计算:
集合划分,这道题f[i]中第i个数一定存在于上升子序列中,
所以根据它的前一个数去分类,前一个数可能没有,可能是数组中第一个元素,
可能是数组中第二个元素,一直到可能是数组第i-1个元素
f[i]=max(f[j]+1) j=0,1,2...i-1;
f[0]到f[i-1]不一定都能取,因为可能有某个数大于f[i]
时间复杂度:O(n^2)
*/
#include<iostream>
using namespace std;
const int N=1010;
int n;
int a[N],f[N];
int main(){
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<=n;i++){
f[i]=1;//只要a[i]一个数
for(int j=1;j<i;j++){
if(a[j]<a[i]) f[i]=max(f[i],f[j]+1);
}
}
int res=0;
for(int i=1;i<=n;i++) res=max(res,f[i]);
cout<<res<<endl;
return 0;
}
//记录转移过程
#include<iostream>
#include<algorithm>
using namespace std;
const int N=1010;
int n;
int a[N],f[N],g[N];//g[N]存储转移过程
int main(){
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<=n;i++){
f[i]=1;//只要a[i]一个数
g[i]=0;//表示第i个数没有从谁转移过来
for(int j=1;j<i;j++){
if(a[j]<a[i]) {
if(f[i]<f[j]+1){
f[i]=f[j]+1;//记录f[i]是从谁转移过来的
g[i]=j;
}
}
}
}
int k=0;//存储最大值的下标
for(int i=1;i<=n;i++){
if(f[k]<f[i]) k=i;
}
cout<<f[k]<<endl;
while(f[k]){
cout<<a[k]<<' ';
k=g[k];
}
return 0;
}
/*
与上道题一样,只是数据范围变大,上道题n^2做法会超时
考虑优化
依次遍历数组每个元素,将前面求得的最长上升子序列按长度分类
存储各个长度的上升子序列结尾的最小值,相同长度的上升子序列中,结尾更大的肯定
没有结尾较小的好,因为如果一个数可以接到较大的后面,也一定可以接到较小的后面
所以较大的没必要存下来,它是可被替换的,较小的适用范围更广
可证明各个长度的上升子序列结尾的最小值单调递增
当前遍历元素可以接到某一个上升子序列后面
换句话说,在不考虑边界情况的前提下,当前遍历元素可以替换这个递增的结尾序列中
第一个大于它的元素,也就是更新某个序列的结尾,
更新的意思是当前遍历的这个元素更适合当结尾
时间复杂度:对于每个元素二分查找 O(nlogn)
*/
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 100010;
int n;
int a[N];
int q[N];
int main()
{
cin>>n;
for (int i = 0; i < n; i ++ ) cin>>a[i];
//通用做法:两种二分查找都可以做,考虑边界情况
q[0]=a[0];
//len表示数组q中最后一个元素的下标,因为从0开始,所有输出为len+1
int len = 0;
for (int i = 1; i < n; i ++ ){
//先插入第一个元素,从第二个元素开始遍历
//在结尾递增序列中找第一个大于等于当前遍历的元素,边界情况是最大元素都比它小
// if(q[len]<a[i]){//边界情况
// q[++len]=a[i];
// }
// else{
// int l = 0, r = len;
// while (l < r)
// {
// int mid = (l + r) >> 1;
// if (q[mid] >= a[i]) r= mid;
// else l = mid + 1;
// }
// q[l] = a[i];
// }
//在结尾递增序列中找最后一个小于当前遍历的元素,边界情况是最小元素都比它大
if(q[0]>=a[i]) q[0]=a[i];
else{
int l = 0, r = len;
while (l < r)
{
int mid = l + r +1>> 1;
if (q[mid] < a[i]) l = mid;
else r = mid - 1;
}
len=max(len,l+1);
q[l+1] = a[i];
}
}
cout<<len+1<<endl;
return 0;
}
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
int n = nums.size();
vector<int> q;
for (auto x: nums) {
if (q.empty() || x > q.back()) q.push_back(x);
else {
if (x <= q[0]) q[0] = x;
else {
int l = 0, r = q.size() - 1;
while (l < r) {
int mid = l + r >> 1;
if (q[mid] >= x) r = mid;//或者<
else l = mid + 1;
}
q[r] = x;
}
}
}
return q.size();
}
};
LeetCode 301. 删除无效的括号
/*
给一个字符串,包含左右小括号和其他字符,要删掉最小数量的某些左右小括号使得剩下序列合法,输出所有合法方案
如何求最小的删除括号数量
合法的括号序列需要满足的条件(需要背):
1.左右括号数量相同
2.括号序列的任意一个前缀中,左括号数量大于等于右括号数量
从前往后扫描整个字符串,记录当前左右括号数量,遇到左括号的话,就左括号数量无条件++
如果遇到右括号的话,需要判断一下:
如果l==r 去掉当前右括号
如果l>r l--
这样扫描一遍就可以知道左右括号最多删多少个
然后暴力搜索,判断当前括号要不要删掉,注意删掉数量不超过lr
还有一个剪枝,如果有连续相同的左右括号,可以人为规定只能从前往后删,也就是不要枚举删哪些,而是枚举删几个,这样就可以剪枝
*/
class Solution {
public:
vector<string> ans;//表示方案
vector<string> removeInvalidParentheses(string s) {
int l = 0, r = 0;//l表示当前未匹配的左括号数量,r表示当前未匹配的右括号数量
for (auto x: s)
if (x == '(') l ++ ;
else if (x == ')') {
if (l == 0) r ++ ;//左右括号一样
else l -- ;
}
//循环结束,lr表示要删除的左右括号数量
dfs(s, 0, "", 0, l, r);
//字符串;枚举到第几个字符;当前删完之后剩余的字符串是什么;path中未匹配的左括号数量;当前可以删掉多少左右括号
return ans;
}
void dfs(string& s, int u, string path, int cnt, int l, int r) {
if (u == s.size()) {//如果搜到了字符串结尾
if (!cnt) ans.push_back(path);
/*如果当前左右括号数量相同cnt==0,主函数中已经求出所有方案中最少需要删除的左右括号数量,所以如果当前方案合法,那么当前方案至少会将l和r删完,所以此时l和r一定是0*/
return;
}
//如果当前字符不是左右括号,那不能删,探索下一个
if (s[u] != '(' && s[u] != ')') dfs(s, u + 1, path + s[u], cnt, l, r);
else if (s[u] == '(') {
int k = u;//看一下当前有多少个连续的左括号,剪枝
while (k < s.size() && s[k] == '(') k ++ ;
l -= k - u;//先把k-u个连续的左括号全部删掉
for (int i = k - u; i >= 0; i -- ) {//枚举当前到底要删掉多少个连续的左括号
if (l >= 0) dfs(s, k, path, cnt, l, r);
path += '(';
cnt ++, l ++ ;//加回来一个左括号之后,左括号数量多了一个,要删除的左括号数量也多了一个
}
} else if (s[u] == ')') {
int k = u;
while (k < s.size() && s[k] == ')') k ++ ;
r -= k - u;
for (int i = k - u; i >= 0; i -- ) {
//还需要cnt>=0,因为要保证序列任意前缀左括号数量都比右括号数量多
if (cnt >= 0 && r >= 0) dfs(s, k, path, cnt, l, r);
path += ')';
cnt --, r ++ ;
}
}
}
};
LeetCode 303. 区域和检索 - 数组不可变
class NumArray {
public:
vector<int> s;//前缀和数组
/*
一维前缀和
*/
NumArray(vector<int>& nums) {
s.resize(nums.size() + 1);//前缀和数组下标改成从1开始
for (int i = 1; i <= nums.size(); i ++ ) s[i] = s[i - 1] + nums[i - 1];//nums下标任然从0开始
}
int sumRange(int i, int j) {
++i, ++j;//++是因为前缀和数组下标从1开始
return s[j] - s[i - 1];
}
};
/**
* Your NumArray object will be instantiated and called as such:
* NumArray* obj = new NumArray(nums);
* int param_1 = obj->sumRange(i,j);
*/
LeetCode 304. 二维区域和检索 - 矩阵不可变
class NumMatrix {
public:
vector<vector<int>> s;
NumMatrix(vector<vector<int>>& matrix) {
if(matrix.size()==0||matrix[0].size()==0) return;
s=vector<vector<int>>(matrix.size()+1,vector<int>(matrix[0].size()+1));
for(int i=1;i<=matrix.size();i++){
for(int j=1;j<=matrix[0].size();j++){
s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1]+matrix[i-1][j-1];
//注意这里matrix下标还是从0开始,所以-1
}
}
}
int sumRegion(int row1, int col1, int row2, int col2) {
row1++,row2++,col1++,col2++;
return s[row2][col2]-s[row1-1][col2]-s[row2][col1-1]+s[row1-1][col1-1];
}
};
/**
* Your NumMatrix object will be instantiated and called as such:
* NumMatrix* obj = new NumMatrix(matrix);
* int param_1 = obj->sumRegion(row1,col1,row2,col2);
*/
LeetCode 306. 累加数
class Solution {
public:
string add(string x, string y) {
vector<int> A, B, C;
for (int i = x.size() - 1; i >= 0; i -- ) A.push_back(x[i] - '0');
for (int i = y.size() - 1; i >= 0; i -- ) B.push_back(y[i] - '0');
for (int i = 0, t = 0; i < A.size() || i < B.size() || t; i ++ ) {
if (i < A.size()) t += A[i];
if (i < B.size()) t += B[i];
C.push_back(t % 10);
t /= 10;
}
string z;
for (int i = C.size() - 1; i >= 0; i -- ) z += to_string(C[i]);
return z;
}
/*
判断所给字符串是否为斐波那契数列,但是所给字符串中没有标点符号,所以需要自己枚举初始两个数是什么,对于溢出的过大的整数输入用高精度来存储
所以可以先枚举第一个数的长度,再枚举第二个数的长度,然后再用高精度加法计算和
*/
bool isAdditiveNumber(string num) {
for (int i = 0; i < num.size(); i ++ )//先枚举第一个数的结尾
for (int j = i + 1; j + 1 < num.size(); j ++ ) {
//再枚举第二个数的结尾,j后面必须要有元素,因为要保证至少三个数
int a = -1, b = i, c = j;
//当前判断的第一个数是从a+1到b,第二个数是从b+1到c,a是第一个数开头的前面那个位置
while (true) {
if (b - a > 1 && num[a + 1] == '0' || c - b > 1 && num[b + 1] == '0') break;
// 判断两个数是否有前导0
auto x = num.substr(a + 1, b - a), y = num.substr(b + 1, c - b);
auto z = add(x, y);//两个字符串相加
if (num.substr(c + 1, z.size()) != z) break; // 下一个数不匹配
a = b, b = c, c += z.size();//更新abc
if (c + 1 == num.size()) return true;
}
}
return false;
}
};
class Solution {
public:
int maxProfit(vector<int>& prices) {
if (prices.empty()) return 0;
int n = prices.size(), INF = 1e8;
vector<vector<int>> f(2, vector<int>(3, -INF));//空间压缩,滚动数组
f[0][2] = 0;
for (int i = 1; i <= n; i ++ )
{
f[i&1][0] = max(f[i - 1&1][0], f[i - 1&1][2] - prices[i-1]);
f[i&1][1] = f[i - 1&1][0] + prices[i-1];
f[i&1][2] = max(f[i - 1&1][2], f[i - 1&1][1]);
}
return max(f[n&1][1], f[n&1][2]);
}
};
LeetCode 316. 去除重复字母
class Solution {
public:
/*
题解:这题的思路是这样的,我们从前往后遍历整个字符串,并用一个栈来维护当前答案:
如果当前字符已经在当前答案中了,直接跳过。
如果没有,那么我们应该将这个字符加入答案中。但是为了字典序最小,我们需要考虑是否需要弹出栈顶元素。弹出的条件则是:当前元素小于栈顶元素,并且当前栈顶元素还在后面出现过,那么栈顶元素在后面再加进来会得到更小的字典序。
这题也可以出成删除重复数字,使得剩下的数字最小。我们使用一个哈希表记录每个字母最后一次出现的位置,再用一个标记数组判断当前字符是否在答案中出现过。
*/
string removeDuplicateLetters(string s) {
string stk;
unordered_map<char, bool> ins;
unordered_map<char, int> last;
for (int i = 0; i < s.size(); i ++ ) last[s[i]] = i;
for (int i = 0; i < s.size(); i ++ ) {
if (ins[s[i]]) continue;
while (stk.size() && stk.back() > s[i] && last[stk.back()] > i) {
ins[stk.back()] = false;
stk.pop_back();
}
stk += s[i];
ins[s[i]] = true;
}
return stk;
}
};
LeetCode 318. 最大单词长度乘积
class Solution {
public:
/*
(位运算) max(O(nL),O(n^2))
首先我们预处理出每个单词所包含的字母集合,该集合用一个int型变量表示,如果包含第 i 个小写字母,则将整型变量的第 i 个二进制位置成1。
然后用两重循环枚举所有单词对,用位运算判断两个单词是否含有相同字母。假设两个单词的字母结集合别是 state1 和 state2,则如果 state1&state2=0,说明不存在任何一位,两个整数都是1,也就是两个单词不包含相同字母。
时间复杂度分析:假设单词总共有 n 个。初始化每个单词的字母集合的计算量是 26n+nL,其中 L 是单词的平均长度,枚举所有单词对的计算量是 n^2,所以总时间复杂度是 max(O(nL),O(n^2))
*/
int maxProduct(vector<string>& words) {
vector<int> state;
for (auto word: words) {
int s = 0;
for (auto c: word)
s |= 1 << (c - 'a');
state.push_back(s);
}
int res = 0;
for (int i = 0; i < words.size(); i ++ )
for (int j = i + 1; j < words.size(); j ++ )
if ((state[i] & state[j]) == 0)
res = max(res, (int)(words[i].size() * words[j].size()));
return res;
}
};
/*
暴力O(n^2)枚举所有有序对(i,j),(注意(i,j)和(j,i)是不一样的),然后再判断w[i]+w[j]合成单词是否为回文串,l是合成单词长度,所有总时间复杂度是O(n^2*l)
考虑优化
枚举较长的串w[i],先算一下有多少个j可以和w[i]组成第一种形式(也就是较长的w[i]在前,w[j]在后),枚举left和right的分界点,判断right是回文串,另外要看一下其他单词翻转之后是否是left,可以用一个哈希表存下来所有单词翻转之后的结果
所以对于每一个w[i]都要枚举分界点,l是单词长度,时间复杂度是O(l),然后再判断又需要O(l)的时间复杂度,所以对于每一个单词,计算量是O(l^2),遍历全部n个单词,所以总时间复杂度O(n*l^2),一般情况下l比n小很多,所以会比暴力快一些,当然这道题不严谨,没有给出具体数据范围。
第二种情况类似(也就是较长的w[i]在后,w[j]在前)
如果两个字符串长度相等,特判一下即可
*/
class Solution {
public:
bool check(string& s) {
for (int i = 0, j = s.size() - 1; i < j; i ++, j -- )
if (s[i] != s[j])
return false;
return true;
}
vector<vector<int>> palindromePairs(vector<string>& words) {
unordered_map<string, int> hash;//开一个哈希表,存储所有单词的逆序和对于的下标
for (int i = 0; i < words.size(); i ++ ) {
auto w = words[i];
reverse(w.begin(), w.end());
hash[w] = i;
}
vector<vector<int>> res;//定义答案
for (int i = 0; i < words.size(); i ++ ) {
auto w = words[i];//枚举w[i]
for (int j = 0; j <= w.size(); j ++ ) {//枚举分界点
auto left = w.substr(0, j), right = w.substr(j);
if (check(right) && hash.count(left) && hash[left] != i) res.push_back({i, hash[left]});
if (check(left) && hash.count(right) && hash[right] != i && w.size() != words[hash[right]].size())//相等情况只在一种情况里特判,避免重复
res.push_back({hash[right], i});
}
}
return res;
}
};
LeetCode 341. 扁平化嵌套列表迭代器
class NestedIterator {
public:
vector<int> q;//定义一个序列和指针
int k;
NestedIterator(vector<NestedInteger> &nestedList) {
k = 0;//指针初始化为0
for (auto& l: nestedList) dfs(l);//遍历整个森林
}
void dfs(NestedInteger& l) {
if (l.isInteger()) q.push_back(l.getInteger());//如果是叶节点,加入
else {
for (auto& v: l.getList()) dfs(v);
}
}
int next() {
return q[k ++ ];
}
bool hasNext() {
return k < q.size();
}
};
LeetCode 342. 4的幂
class Solution {
public:
bool isPowerOfFour(int n) {
if (n <= 0)
return false;
return (n & -n) == n && ((n - 1) % 3 == 0);
}
};
LeetCode 343. 整数拆分
class Solution {
public:
/*
选用尽量多的3,直到剩下2或者4时,用2
*/
int integerBreak(int n) {
if (n <= 3) return 1 * (n - 1);
int res = 1;
if (n % 3 == 1) res = 4, n -= 4;
else if (n % 3 == 2) res = 2, n -= 2;
while (n) res *= 3, n -= 3;
return res;
}
};
LeetCode 344. 反转字符串
class Solution {
public:
void reverseString(vector<char>& s) {
for (int i = 0, j = s.size() - 1; i < j; i ++, j -- )
swap(s[i], s[j]);
}
};
LeetCode 345. 反转字符串中的元音字母
class Solution {
public:
string reverseVowels(string s) {
unordered_set<char> vowels = {'a', 'e', 'i', 'o', 'u', 'A', 'E', 'I', 'O', 'U'};
int n = s.size(), i = 0, j = n - 1;
while(i < j){
while (i < j && !vowels.count(s[i])) ++i;
while (i < j && !vowels.count(s[j])) --j;
if (i < j) swap(s[i++], s[j--]);
}
return s;
}
};
LeetCode 349. 两个数组的交集
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
/*
把一个数组放入unordered_set中,遍历另外一个数组每个元素,判断这个元素是否在另外一个数组中出现过
unordered_set是判重的,每个元素输出一次
*/
unordered_set<int> S;
for (auto x: nums1) S.insert(x);
vector<int> res;
for (auto x: nums2)
if (S.count(x)) {
res.push_back(x);
S.erase(x);
}
return res;
}
};
class Solution {
public:
int maxEnvelopes(vector<vector<int>>& envelopes) {
/*
(动态规划) O(n^2)
先将所有信封按宽的长度从小到大排序,然后问题变成从左到右找一条最长的h严格单调递增的子序列,同时满足w也是严格单调递增的。
类似于最长上升序列问题,可以用动态规划解决。
状态表示:f[i]表示以第i个信封为结尾的单调序列的最大长度。
状态转移:对于f[i],枚举 j=0∼i−1,如果第j个信封的长和宽都小于第i个信封,则用f[j]+1更新f[i]。
超时
*/
sort(envelopes.begin(), envelopes.end());
int n = envelopes.size();
vector<int> f(n);
int res = 0;
for (int i = 0; i < n; i ++ )
{
f[i] = 1;
for (int j = 0; j < i; j ++ )
if (envelopes[i].first > envelopes[j].first && envelopes[i].second > envelopes[j].second)
f[i] = max(f[i], f[j] + 1);
res = max(res, f[i]);
}
return res;
}
};
class Solution {
public:
int maxEnvelopes(vector<vector<int>>& envelopes) {
int n = envelopes.size();
sort(envelopes.begin(), envelopes.end());
vector<int> f(n), g;
g.push_back(0);
int ans = 0;
for (int i = 0, j = 0; i < n; i++) {
if (envelopes[i][0] != envelopes[j][0]) {
while (j < i) {
if (f[j] == g.size())
g.push_back(envelopes[j][1]);
else
g[f[j]] = min(g[f[j]], envelopes[j][1]);
j++;
}
}
int l = 0, r = g.size();
while (l < r) {
int mid = (l + r) >> 1;
if (envelopes[i][1] <= g[mid])
r = mid;
else
l = mid + 1;
}
f[i] = l;
ans = max(ans, f[i]);
}
return ans;
}
};
LeetCode 367. 有效的完全平方数
class Solution {
public:
bool isPerfectSquare(int num) {
/*
二分找平方根,正整数 num
*/
int l = 1, r = num;
while (l < r) {
int mid = l + 1ll + r >> 1;
if (mid <= num / mid) l = mid;//防止溢出
else r = mid - 1;
}
return r * r == num;
}
};
LeetCode 371. 两整数之和
class Solution {
public:
/*
a^b也叫作不进位的加法,所以a+b==不进位的加法+进位
看一下哪些位有进位,有进位代表这一位上都为1,也就是a&b,进位是往前进一位,所以(a&b)<<1就是进位
a+b==a^b+(a&b)<<1(这里计算也需要加法,所以递归求解)
*/
int getSum(int a, int b) {
if (!a) return b;//终止条件
int sum = a ^ b, carry = (unsigned)(a & b) << 1;//无符号正数左移不会报错
return getSum(carry, sum);//这样会转化多少次呢?每转化一次,后者末位都会多一个0,所以最多32次,后者变为0
}
};
LeetCode 372. 超级次方
/*
快速幂,可以用logb的时间复杂度求出a^b%p的值
a^(b0b1b2...bk)%p==a^(b0b1..b(k-1)*10+bk)%p==a^(b0b1..b(k-1)*10)*a^(bk)%p
*/
class Solution {
public:
const int p = 1337;
int qmi(int a, int b) {//快速幂模板
a %= p;
int res = 1;
while (b) {
if (b & 1) res = res * a % p;
a = a * a % p;
b >>= 1;
}
return res;
}
int superPow(int a, vector<int>& b) {
if (b.empty()) return 1;
int k = b.back();
b.pop_back();
return qmi(superPow(a, b), 10) * qmi(a, k) % p;
}
};
LeetCode 374. 猜数字大小
class Solution {
public:
int guessNumber(int n) {
int l = 1, r = n;
while (l < r) {
int mid = (long long)l + r >> 1;
if (guess(mid) <= 0) r = mid;
else l = mid + 1;
}
return r;
}
};
LeetCode 376. 摆动序列
class Solution {
public:
/*
使用cur_type来标记上一个元素的状态,如果cur_type > 0,说明上一个元素的状态是上升,否则是下降,初始值为0。res标记当前答案是多少,初始值为1。我们从前往后遍历数组,把当前数字和上一个元素进行比较:
如果当前元素大于上一个元素,并且上一个元素的状态是下降,那么说明我们找到了一个山谷(极小值),那么更新答案和变化状态。
如果当前元素小于上一个元素,并且上一个元素的状态是上升,那么说明我们找到了一个山顶(极大值),那么更新答案和变化状态。
如果当前元素等于上一个元素,那么我们什么都不用做。
*/
int wiggleMaxLength(vector<int>& nums) {
int n = nums.size(),res = 1,cur_type = 0;
if(n < 2) return n;
for(int i = 1 ; i < n ; i ++)
{
if(nums[i] > nums[i - 1] && cur_type <= 0)
{
cur_type = 1;
res ++;
}else if(nums[i] < nums[i - 1] && cur_type >= 0)
{
cur_type = -1;
res ++;
}
}
return res;
}
};
class Solution {
public:
int wiggleMaxLength(vector<int>& nums) {
nums.erase(unique(nums.begin(), nums.end()), nums.end());
if (nums.size() <= 2) return nums.size();
int res = 2;
for (int i = 1; i + 1 < nums.size(); i ++ ) {
int a = nums[i - 1], b = nums[i], c = nums[i + 1];
if (b > a && b > c || b < a && b < c) res ++ ;
}
return res;
}
};
LeetCode 377. 组合总和 Ⅳ
/*
(动态规划) O(n⋅target)
完全背包的变形,涉及顺序
设f(i)表示组成和为i的方案数。
根据方案最后一个数是谁划分,假设最后一个数是nums[k],则对应的方案数量是f(i-nums[k])
初始时,f(0)=1 其余为 0。
转移时,先枚举i 然后枚举每个数字nums[j],如果 i≥nums[j],令 f(i)=f(i)+f(i−nums[j])
最终答案为 f(target)
注意此题和完全背包的区别,背包问题的状态表示是 f(i,j) 表示前i个物体,组成的重量为j,经过优化变成了一维。如果此题用完全背包解答,则会将顺序不同的序列算作相同的组合。
此题在求解过程中整数会出现溢出,但由于答案在 32 位整数范围内,所以我们只需要在计算过程中模 INT_MAX 即可避免溢出)
进阶
如果数组中存在负数,则有可能有无穷多个答案。
可以添加负数个数的限制。
*/
class Solution {
public:
int combinationSum4(vector<int>& nums, int m) {
int n = nums.size();
vector<long long> f(m + 1);
f[0] = 1;
for (int j = 0; j <= m; j ++ )
for (auto v: nums)
if (j >= v)
f[j] = (f[j] + f[j - v]) % INT_MAX;
return f[m];
}
};
LeetCode 383. 赎金信
class Solution {
public:
bool canConstruct(string a, string b) {
unordered_map<char, int> hash;
for (auto c: b) hash[c] ++ ;
for (auto c: a)
if (!hash[c]) return false;
else hash[c] -- ;
return true;
}
};
class Solution {
public:
bool canConstruct(string a, string b) {
int h[26] = { 0 };
for (auto c: b) h[c - 'a']++;
for (auto c: a)
if (!h[c - 'a']) return false;
else h[c - 'a'] -- ;
return true;
}
};
LeetCode 385. 迷你语法分析器
class Solution {
public:
NestedInteger deserialize(string s) {
int u = 0;
return dfs(s, u);
}
NestedInteger dfs(string& s, int& u) {
NestedInteger res;
if (s[u] == '[') // 遇到内部节点
{
u++; // 跳过当前左括号
while (s[u] != ']') // 如果当前字符不是]就不断使用dfs递归遍历
res.add(dfs(s, u));
u++; // 跳过右括号
if (u < s.size() && s[u] == ',') // 如果当前字符是,那么跳过都好
u++; // 跳过逗号
}
else // 当前是数字
{
int k = u;
while (k < s.size() && s[k] != ',' && s[k] != ']')
k++;
res.setInteger(stoi(s.substr(u, k - u)));
if (k < s.size() && s[k] == ',')
k++; // 跳过逗号
u = k;
}
return res;
}
};
LeetCode 387. 字符串中的第一个唯一字符
class Solution {
public:
//两次遍历
int firstUniqChar(string s) {
unordered_map<char, int> hash;
for (auto c: s) hash[c] ++ ;
for (int i = 0; i < s.size(); i ++ )
if (hash[s[i]] == 1)
return i;
return -1;
}
};
class Solution {
public:
/*
直接使用 int arr[26]的数组作为字母的哈希表
字母与’a’的差值作为数组索引速度更快
*/
int h[26] = { 0 };
int firstUniqChar(string s) {
for (int i = 0; i < s.size(); i++) {
h[s[i] - 'a']++;
}
//遍历字符串 查找哈希中出现的次数
for (int i = 0; i < s.size(); i++) {
if (h[s[i] - 'a'] == 1) return i;
}
return -1;
}
};
LeetCode 388. 文件的最长绝对路径
class Solution {
public:
/*
相当于给定一棵树,求最长路径,用栈维护路径
*/
int lengthLongestPath(string input) {
stack<int> stk;
int res = 0;
for (int i = 0, sum = 0; i < input.size(); i ++ ) {
int k = 0;//当前节点在第几层取决于首字母前面有几个制表符
while (i < input.size() && input[i] == '\t') i ++ , k ++ ;//注意\t\n是一个字符
while (stk.size() > k) sum -= stk.top(), stk.pop();
//k表示第0层,第1层...如果当前k==1,表示栈中应该只有第0层的一个元素才对,所以==时停止循环
int j = i;//此时i指向文件或文件名的首字母,从i开始求出长度
while (j < input.size() && input[j] != '\n') j ++ ;
int len = j - i;//当前点长度//此时j指向换行符\n。
stk.push(len), sum += len;//stk存的各个元素表示当前这条路径各个文件(名)的长度,sum表示总长度
if (input.substr(i, len).find('.') != -1)//如果当前节点是文件的话,判断是否需要更新答案
res = max(res, sum + (int)stk.size() - 1);//后面的是加\
i = j;//此时i更新之后指向换行符\n。循环中+1之后要么指向制表符\t,要么指向文件或文件名的首字母
}
return res;
}
};
LeetCode 389. 找不同
class Solution {
public:
char findTheDifference(string s, string t) {
int x = 0;//异或,x作为基数与两个字符串中的所有字符异或,最后得到的就是要找的字符,然后以char形式输出
for (auto c: s) x ^= c;
for (auto c: t) x ^= c;
return x;
}
};
LeetCode 392. 判断子序列
class Solution {
public:
bool isSubsequence(string s, string t) {
int k = 0;
for (auto c: t)
if (k < s.size() && c == s[k])
k ++ ;
return k == s.size();
}
};
LeetCode 393. UTF-8 编码验证
class Solution {
public:
int get(int x, int k) {//取得数x二进制表示中倒数第k位的数(k从0开始)
return x >> k & 1;
}
bool validUtf8(vector<int>& data) {
for (int i = 0; i < data.size(); i ++ ) {
if (get(data[i], 7)==0) continue;//开头为0,后面随意,表示一字节
//此时说明开头Wie1,要数一下开头有多少个1
int k = 0;//k表示连续的1的个数
while (k <= 4 && get(data[i], 7 - k)) k ++ ;
if (k == 1 || k > 4) return false;//如果开头为1,且只有一个1或者大于四个1,矛盾
for (int j = 0; j < k - 1; j ++ ) {//判断接下来k-1个数的首位是否为10
int t = i + 1 + j;
if (t >= data.size()) return false;//如果后面不足k-1个数,越界
if (get(data[t], 7)==0 || get(data[t], 6)==1) return false;//首位必须为10
}
i += k - 1;
}
return true;
}
};
LeetCode 394. 字符串解码
class Solution {
public:
/*
(DFS) O(k^n)
用递归的思想来解决这个问题, 当遇到一个括号时,就将括号内的字符串截取出来作为一个新的子问题递归求解,比如对于s = "3[a2[c]]",我们可以将a2[c]]作为一个新问题来求解,并将该子问题求解的结果加到上一层的答案中去。
*/
string decodeString(string s) {
int u = 0;
return dfs(s, u);
}
string dfs(string& s, int& u) {
string res;
while (u < s.size() && s[u] != ']') {
if (s[u] >= 'a' && s[u] <= 'z' || s[u] >= 'A' && s[u] <= 'Z') res += s[u ++ ];
else if (s[u] >= '0' && s[u] <= '9') {
int k = u;
while (s[k] >= '0' && s[k] <= '9') k ++ ;
int x = stoi(s.substr(u, k - u));
u = k + 1;
string y = dfs(s, u);
u ++ ; // 过滤掉右括号
while (x -- ) res += y;
}
}
return res;
}
};
class Solution {
public:
/*
(用栈模拟递归) O(k^n)
如果递归层数太多则上面的算法有可能会造成栈溢出,我们可以用栈来模拟上述的递归过程,每当遇到一个括号序列时,说明我们要递归进入下一层,那么我们就把该序列重复的次数num和在该序列前已经计算好的答案字cur分别压入栈中,当把这个括号序列处理完后,我们从两个栈的栈顶分别取出来之前的num和cur来计算出新的答案cur。
*/
string decodeString(string s) {
stack<int> nums;
stack<string> strs;
string cur;
int num = 0;
for (int i = 0; i < s.size(); i ++ ) {
if (s[i] == '[') {
nums.push(num);
strs.push(cur);
num = 0;
cur = "";
} else if (s[i] == ']') {
int t = nums.top();
nums.pop();
string tmp = cur;
cur = strs.top();
strs.pop();
while(t -- ) cur += tmp;
} else if (s[i] >= '0' && s[i] <= '9') {
num = num * 10 + s[i] - '0';
} else {
cur += s[i];
}
}
return cur;
}
};
LeetCode 396. 旋转函数
class Solution {
public:
/*
(找规律) O(n)
刚开始F(0)是A[0]到A[n-1]各自与0到n-1相乘再相加,然后数组 A 顺时针旋转1个位置代表数组右移1位,也相当于0到n-1左移一位,此时F(1)是A[0]到A[n-1]各自与1...n-1,0相乘再相加
首先求出数组的和 sum,以及 F[0] 的值 cur
每次从 F[0] 到 F[1],相当于 cur += sum - n * A[n - 1]。从 F[1] 再到 F[2],相当于 cur += sum - n * A[n - 2]
以此类推,找到过程中 res 的最大值
*/
int maxRotateFunction(vector<int>& A) {
typedef long long LL;
LL sum = 0, cur = 0;
for (auto c: A) sum += c;
int n = A.size();
for (int i = 0; i < n; i ++ ) cur += i * A[i];
LL res = cur;
for (int i = n - 1; i >=1; i -- ) {
cur += sum - (LL)n * A[i];
res = max(res, cur);
}
return res;
}
};