❤ 作者主页:Java技术一点通的博客
❀ 个人介绍:大家好,我是Java技术一点通!( ̄▽ ̄)~*
❀ 微信公众号:Java技术一点通
记得点赞、收藏、评论⭐️⭐️⭐️
认真学习! ! !
窗口对应的数据结构为 双端队列 ,本题使用 单调队列 即可解决本问题。遍历数组时,每轮保证单调队列 d e q u e deque deque :
算法流程:
deque.front() <= i - k
,说明在插入新的元素时队头不能继续留在队列中,则将队首元素出队;class Solution {
public:
vector maxSlidingWindow(vector& nums, int k) {
vector res;
deque q;
for (int i = 0; i < nums.size(); i ++ ) {
while (q.size() && q.front() <= i - k) q.pop_front();
while (q.size() && nums[q.back()] <= nums[i]) q.pop_back();
q.push_back(i);
if (i >= k - 1) res.push_back(nums[q.front()]);
}
return res;
}
};
根据题意,此 5 张牌是顺子的 条件 如下:
max - min < 5
;算法流程:
class Solution {
public:
bool isStraight(vector& nums) {
int k = 0;
sort(nums.begin(), nums.end());
for (int i = 0; i < 4; i ++ ) {
if (nums[i] == 0) k ++;
else if (nums[i] == nums[i + 1]) return false;
}
return nums[4] - nums[k] < 5;
}
};
本题是著名的约瑟夫环问题,用数学解法。
假设有一圈数字 [ 0 , 1 , 2 , 3 , 4 ] , m = 3 [0, 1, 2, 3, 4],m = 3 [0,1,2,3,4],m=3
我们每次将删除元素的后一个元素放在最前面方便计数:
尝试反推:
如何从最后剩下的元素的索引 0 0 0 反推至第一轮该元素的索引呢?
第四轮反推,补上 m m m 个位置,然后模上当时的数组大小 2,位置是 (0 + 3) % 2 = 1
。
第三轮反推,补上 m m m 个位置,然后模上当时的数组大小 3,位置是 (1 + 3) % 3 = 1
。
第二轮反推,补上 m m m 个位置,然后模上当时的数组大小 4,位置是 (1 + 3) % 4 = 0
。
第一轮反推,补上 m m m 个位置,然后模上当时的数组大小 5,位置是 (0 + 3) % 5 = 3
。
所以最后剩下的数字的下标就是 3
。因为数组是从 0 0 0 开始的,所以最终的答案就是 3
。
反推的公式: (当前index + m) % 上一轮剩余数字的个数
。
class Solution {
public:
int lastRemaining(int n, int m) {
int res = 0;
for (int i = 2; i <= n; i ++ ) {
res = (res + m) % i;
}
return res;
}
};
遍历数组:
我们只需要在最低点的时候买入股票就最好了,因此只要用一个变量 c o s t cost cost 来记录最低价格,我们就可以假设股票是在这一天购买的。那么在第 i i i 天卖出股票能得到的利润是 p r i c e s [ i ] − c o s t prices[i] - cost prices[i]−cost。
class Solution {
public:
int maxProfit(vector& prices) {
int res = 0, cost = INT_MAX;
for (int price : prices) {
cost = min(cost, price);
res = max(res, price - cost);
}
return res;
}
};
解题思路: 递归
最直接的方法就是用递归, s u m ( n ) = n + s u m ( n − 1 ) sum(n) =n + sum(n - 1) sum(n)=n+sum(n−1),但是要注意终止条件,由于求的是 1 + 2 + . . . + n 1 + 2 + ... + n 1+2+...+n的和,所以需要在 n = 0 n = 0 n=0 的时候跳出递归,由于题目要求不能使用 i f , w h i l e if, while if,while 等分支进行判断,可以考虑利用 && 短路运算来终止条件。
class Solution {
public:
int sumNums(int n) {
int res = n;
n > 0 && (res += sumNums(n - 1));
return res;
}
};
解题思路:
本题考察对位运算的灵活使用,即使用 位运算 实现加法。
通过观察发现,无进位和 与 异或运算 规律相同,进位 和 与运算 规律相同(并需左移一位)。因此,无进位和 n n n 与进位 c c c 的计算公式如下:
{ n = a ⊕ b 非进位和:异或运算 c = a & b < < 1 进位:与运算 + 左移一位 \begin{cases} n=a⊕b & 非进位和:异或运算\\ c=a\&b<<1 & 进位:与运算 + 左移一位 \end{cases} {n=a⊕bc=a&b<<1非进位和:异或运算进位:与运算+左移一位
(和 s s s )=(非进位和 n n n )+(进位 c c c )。即可将 s = a + b s = a + b s=a+b 转化为: s = a + b ⇒ s = n + c s=a+b⇒s=n+c s=a+b⇒s=n+c
若数字 a a a 和 b b b 中有负数,则变成了减法,如何处理?
在计算机系统中,数值一律用 补码 来表示和存储。补码的优势: 加法、减法可以统一处理(CPU只有加法器)。因此,以上方法 同时适用于正数和负数的加法 。
class Solution {
public:
int add(int a, int b) {
while (b) {
int sum = a ^ b;
int carry = (unsigned int)(a & b) << 1; // C++中负数不支持左移位,因为结果是不定的
a = sum;
b = carry;
}
return a;
}
};
解题思路:
本题的难点在于 不能使用除法 ,即需要 只用乘法 生成数组 B B B 。根据题目对 B [ i ] B[i] B[i] 的定义,可列表格,如下图所示。
根据表格的主对角线(全为 1 ),可将表格分为 上三角 和 下三角 两部分。分别迭代计算下三角和上三角两部分的乘积,即可 不使用除法 就获得结果。
算法流程:
class Solution {
public:
vector constructArr(vector& a) {
int n = a.size();
if (n == 0) return {};
vector b(n, 1);
b[0] = 1;
int tmp = 1;
for (int i = 1; i < n; i ++ ) {
b[i] = b[i - 1] * a[i - 1];
}
for (int i = n - 2; i >= 0; i -- ) {
tmp *= a[i + 1];
b[i] *= tmp;
}
return b;
}
};
算法流程:
class Solution {
public:
int strToInt(string str) {
int k = 0;
while (k < str.size() && str[k] == ' ') k ++;
if (k == str.size()) return 0;
int minus = 1;
if (str[k] == '-') minus = -1, k ++;
else if (str[k] == '+') k ++;
long long res = 0;
while (k < str.size() && str[k] >= '0' && str[k] <= '9') {
res = res * 10 + str[k] - '0';
k ++;
if (res > INT_MAX) break;
}
res *= minus;
if (res > INT_MAX) res = INT_MAX;
if (res < INT_MIN) res = INT_MIN;
return res;
}
};
解题思路:
祖先的定义: 若节点 p p p 在节点 r o o t root root 的左 / 右子树中, 或者 p = r o o t p = root p=root,则称 r o o t root root 是 p p p 的祖先。
最近公共祖先的定义: 设节点 r o o t root root 为节点 p , q p,q p,q 的某公共祖先,若其左子节点 r o o t − > l e f t root->left root−>left 和右子节点 r o o t − > r i g h t root->right root−>right 都不是 p , q p,q p,q 的公共祖先,则称 r o o t root root 是 “最近的公共祖先” 。
根据以上定义,若 r o o t root root 是 p , q p,q p,q 的 最近公共祖先 ,则只可能为以下情况之一:
算法流程:
/**
* 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) {
while (root != NULL) {
if (root->val < p->val && root->val < q->val)
root = root->right;
else if (root->val > p->val && root->val > q->val)
root = root->left;
else break;
}
return root;
}
};
考虑通过递归对二叉树进行先序遍历,当遇到节点 p p p 或 q q q 时返回。从底至顶回溯,当节点 p , q p, q p,q 在节点 r o o t root root 的异侧时,节点 r o o t root root 即为最近公共祖先,则向上返回 r o o t root root 。
算法流程:
观察发现, 情况 1. 可合并至 3. 和 4. 内。
/**
* 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 (root == NULL || root == p || root == q) return root;
TreeNode *left = lowestCommonAncestor(root->left, p, q);
TreeNode *right = lowestCommonAncestor(root->right, p, q);
if (left == NULL) return right;
if (right == NULL) return left;
return root;
}
};
算法流程:
class Solution {
public:
int get_sum(pair p) {
int s = 0;
while (p.first) {
s += p.first % 10;
p.first /= 10;
}
while (p.second) {
s += p.second % 10;
p.second /= 10;
}
return s;
}
int movingCount(int m, int n, int k) {
if (!m || !n) return 0;
queue> q;
vector> st(m, vector(n, false));
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
int res = 0;
q.push({0, 0});
while (q.size()) {
auto t = q.front();
q.pop();
if (st[t.first][t.second] || get_sum(t) > k) continue;
res ++;
st[t.first][t.second] = true;
for (int i = 0; i < 4; i ++ ) {
int x = t.first + dx[i], y = t.second + dy[i];
if (x >= 0 && x < m && y >= 0 && y < n) q.push({x, y});
}
}
return res;
}
};
解题思路:
对于普通队列,入队 push_back() 和出队 pop_front() 的时间复杂度均为 O ( 1 ) O(1) O(1) ;本题难点为实现查找最大值 max_value() 的 O ( 1 ) O(1) O(1) 时间复杂度。
假设队列中存储 N N N 个元素,从中获取最大值需要遍历队列,时间复杂度为 O ( N ) O(N) O(N) ,单从算法上无优化空间。
考虑利用 数据结构 来实现,即经常使用的 “空间换时间” 。考虑构建一个递减列表来保存队列 所有递减的元素 ,递减链表随着入队和出队操作实时更新,这样队列最大元素就始终对应递减列表的首元素,实现了获取最大值 O ( 1 ) O(1) O(1) 时间复杂度。
为了实现此递减列表,需要使用 双向队列 ,假设队列已经有若干元素:
push_back()
时: 若入队一个比队列某些元素更大的数字 x x x ,则为了保持此列表递减,需要将双向队列 尾部所有小于 x x x 的元素 弹出。pop_front()
时: 若出队的元素是最大元素,则 双向队列 需要同时 将首元素出队 ,以保持队列和双向队列的元素一致性。使用双向队列原因:维护递减列表需要元素队首弹出、队尾插入、队尾弹出操作皆为 O ( 1 ) O(1) O(1) 时间复杂度。
算法流程:
queue
,双向队列 deque
;deque
为空,则返回 − 1 -1 −1 ;deque
首元素;queue
为空,则直接返回 − 1 -1 −1 ;设计双向队列为 单调不增 的原因:若队列
queue
中存在两个 值相同的最大元素 ,此时queue
和deque
同时弹出一个最大元素,而queue
中还有一个此最大元素;即采用单调递减将导致两队列中的元素不一致。
class MaxQueue {
public:
queue que;
deque deq;
MaxQueue() {
}
int max_value() {
return deq.empty() ? -1 : deq.front();
}
void push_back(int value) {
que.push(value);
while (!deq.empty() && deq.back() < value)
deq.pop_back();
deq.push_back(value);
}
int pop_front() {
if (que.empty()) return -1;
int val = que.front();
if (val == deq.front())
deq.pop_front();
que.pop();
return val;
}
};
/**
* Your MaxQueue object will be instantiated and called as such:
* MaxQueue* obj = new MaxQueue();
* int param_1 = obj->max_value();
* obj->push_back(value);
* int param_3 = obj->pop_front();
*/
创作不易,如果有帮助到你,请给文章点个赞和收藏,让更多的人看到!!!
关注博主不迷路,内容持续更新中。