目录
1--扑克牌中的顺子(61)
主要思路:
五个数是顺子的充要条件:① 最大值 - 最小值 < 5(大小王除外);② 没有出现重复的值(大小王除外);
判断是否出现重复的值可以借助 set 容器;
#include <iostream>
#include <vector>
#include <set>
class Solution {
public:
bool isStraight(std::vector<int>& nums) {
int max = -1, min = 14; // 初始化最大值和最小值
for(int num : nums){
if (num == 0) continue; // 跳过大小王
if(S.find(num) != S.end()) return false;
else S.insert(num);
// 更新最大值和最小值
max = std::max(max, num);
min = std::min(min, num);
if ((max - min) >= 5) return false;
}
return true;
}
private:
std::set<int> S;
};
int main(int argc, char *argv[]){
Solution S1;
std::vector<int> test = {1, 2, 3, 4, 5};
bool res = S1.isStraight(test);
if(res) std::cout << "true" << std::endl;
else std::cout << "false" << std::endl;
return 0;
}
2--圆圈中最后剩下的数字(62)
主要思路:
直观思路是借助于环形队列或者是环形链表,但需要遍历每一个数,会出现超时的现象;
// 超时
#include <iostream>
#include <queue>
class Solution {
public:
int lastRemaining(int n, int m) {
std::queue<int> q;
for(int i = 0; i < n; i++) q.push(i);
while(q.size() != 1){
for(int j = 1; j < m; j++){
int tmp = q.front();
q.pop();
q.push(tmp);
}
q.pop();
}
return q.front();
}
};
int main(int argc, char *argv[]){
Solution S1;
int n = 10, m = 17;
int res = S1.lastRemaining(n, m);
std::cout << res << " ";
return 0;
}
本题是经典的约瑟夫环问题,可通过动态规划解决;
需要抓住的核心思想是,f(n, m) 和 f(n-1, m) 实质上是同一个数,只不过这个数在 n 序列 和 n - 1 序列中对应的编号不一样,因此需要求出两个编号的对应关系;
// 超时
#include <iostream>
class Solution {
public:
int lastRemaining(int n, int m) {
int dp = 0; // dp[1] = 0;
for(int i = 2; i <= n; i++){
dp = (dp + m) % i; // f[i] = (f[i-1] + m) % i;
}
return dp;
}
};
int main(int argc, char *argv[]){
Solution S1;
int n = 10, m = 17;
int res = S1.lastRemaining(n, m);
std::cout << res << " ";
return 0;
}
3--股票的最大利润(63)
主要思路:
本题只能一次买入和买出,因此直观地只需要计算最大利润,即卖出的价格高,买入的价格低,且买入要在卖出之前,可以采用双指针来遍历,也可以采用动态规划来计算;
定义 dp[i] 表示第 i 天的最大利润,则 dp[i] 与当天卖出得到的利润,以及前一天 dp[i-1]的最大利润有关 (前i - 1天已完成买入和卖出的操作);
#include <iostream>
#include <vector>
class Solution {
public:
int maxProfit(std::vector<int>& prices) {
if(prices.size() <= 1) return 0;
int dp = 0; // 初始化 dp 为 0, 表示第一天完成买入和卖出,利润为0
int buy = prices[0]; // 初始化买入的时间
for(int i = 1; i < prices.size(); i++){
// 计算在第 i 天卖出的利润
int sell = prices[i];
int profit = sell - buy;
// 更新最大利润
if (profit > dp) dp = profit;
// 更新买入的时间
if(prices[i] < buy) buy = prices[i];
}
return dp;
}
};
int main(int argc, char *argv[]){
Solution S1;
std::vector<int> test = {7, 1, 5, 3, 6, 4};
int res = S1.maxProfit(test);
std::cout << res << " ";
return 0;
}
4--求1+2+...+n(64)
主要思路:
利用逻辑运算符的短路性质来确定递归出口;
#include <iostream>
class Solution {
public:
int sumNums(int n) {
// 利用逻辑运算符的短路性质来确定递归出口
// 当 n <= 0 时,逻辑运算符将 n += sumNums(n - 1) 短路,从而结束递归
bool x = (n > 1) && (n += sumNums(n - 1));
return n;;
}
};
int main(int argc, char *argv[]){
Solution S1;
int test = 3;
int res = S1.sumNums(test);
std::cout << res << std::endl;
return 0;
}
5--不用加减乘除做加法(65)
主要思路:
将整数 a 和 b 的和,拆分为 a 和 b 的无进位加法结果与进位结果的和;
当进位结果为0时,结束加法;
#include <iostream>
class Solution {
public:
int add(int a, int b) {
while(b != 0){
int c = ((a & b) << 1); // 进位
a = a ^ b; // 无进位和
b = c; // 更新b,相当于不断执行 (无进位和 + 进位)
}
return a;
}
};
int main(int argc, char *argv[]){
int a = 1, b = 1;
Solution S1;
int res = S1.add(a, b);
std::cout << res << std::endl;
return 0;
}
6--构建乘积数组(66)
主要思路:
对于位置 i 的元素,使用 L 存储其左边元素([0, i - 1])的乘积,使用 R 存储其右边元素的乘积([i + 1, len - 1]);
最后将 L 和 R 进行乘积并返回即可;
#include <iostream>
#include <vector>
class Solution {
public:
std::vector<int> constructArr(std::vector<int>& a) {
if(a.size() <= 1) return a;
std::vector<int> L = {1};
for(int i = 1; i < a.size(); i++){
L.push_back(L[i - 1] * a[i - 1]); // 存储左边的乘积
}
int R = 1;
for(int j = a.size() - 2; j >= 0; j--){
R = R * a[j+1]; // 存储右边的乘积
L[j] = L[j] * R; // 更新最终的结果
}
return L;
}
};
int main(){
std::vector<int> test = {1, 2, 3, 4, 5};
Solution S1;
std::vector<int> res = S1.constructArr(test);
for(int num : res){
std::cout << num << " ";
}
return 0;
}
7--把字符串转换成整数(67)
主要思路:
主要难点在于确定边界条件,防止越界;
#include <iostream>
#include <string>
#define INT_MAX 2147483647
#define INT_MIN -2147483648
class Solution {
public:
int strToInt(std::string str) {
if(str.length() <= 0) return 0;
// 去除前面的空格
int i = 0;
while(i < str.length() && str[i] == ' ') i++;
// 判断第一个字符
int sign = 1;
int res = 0;
if(str[i] == '-'){
sign = -1; // 负数
i++;
}
else if(str[i] == '+'){
sign = 1; // 正数
i++;
}
else if(str[i] < '0' || str[i] > '9') return 0; // 非数
for(; i < str.length(); i++){
if(str[i] < '0' || str[i] > '9') break;
if(res > (INT_MAX / 10) || res == (INT_MAX / 10) && str[i] > '7'){
return sign == 1 ? INT_MAX : INT_MIN;
}
int tmp = (str[i] - '0');
res = res * 10 + tmp;
}
return sign * res;
}
};
int main(){
std::string test = " -42";
Solution S1;
int res = S1.strToInt(test);
std::cout << res << std::endl;
return 0;
}
8--二叉搜索树的最近公共祖先(68-I)
主要思路:
题目提供的是二叉搜索树,即左子树的值比根节点小,右子树的值比根节点大;
假设 p 和 q 分别在某个根节点的左右子树,则其最近公共祖先为当前根节点;
假设 p 和 q 处在某个根节点的同一颗子树上,需要遍历下一层来判断,直到 p 和 q 不满足在同一颗子树上;
#include <iostream>
#include <vector>
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){
// p 和 q 都处在右子树
if(p->val > root->val && q->val > root->val) root = root->right;
// p 和 q 都处在左子树
else if(p->val < root->val && q->val < root->val) root = root->left;
// 结束遍历
else break;
}
return root;
}
};
int main(){
// root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8
TreeNode *Node1 = new TreeNode(6);
TreeNode *Node2 = new TreeNode(2);
TreeNode *Node3 = new TreeNode(8);
TreeNode *Node4 = new TreeNode(0);
TreeNode *Node5 = new TreeNode(4);
TreeNode *Node6 = new TreeNode(7);
TreeNode *Node7 = new TreeNode(9);
TreeNode *Node8 = new TreeNode(3);
TreeNode *Node9 = new TreeNode(5);
Node1->left = Node2;
Node1->right = Node3;
Node2->left = Node4;
Node2->right = Node5;
Node3->left = Node6;
Node3->right = Node7;
Node5->left = Node8;
Node5->right = Node9;
Solution S1;
TreeNode *res = S1.lowestCommonAncestor(Node1, Node2, Node3);
std::cout << res->val << std::endl;
return 0;
}
9--二叉搜索树的最近公共祖先(68-II)
主要思路:
与上题不同,本题提供的是普通二叉树;核心思路是使用深度递归搜索节点 p 和 节点 q,返回其对应的节点;当节点 p 和 节点 q 分属某个根节点的左右子树时,这个根节点是其最近祖先(因为回溯是自底向上,首先遇到的肯定是最近祖先);
需要注意的是,当在一颗子树找到某一个节点时,是直接返回该节点,无需在同一个子树搜索另一个节点;因为只需要回溯判断上一层,看看另一个节点是否在另一颗子树上即可;如果另一个节点在另一颗子树,则上一层的根节点是最近祖先;如果另一个节点不在另一颗字树上,则另一个节点肯定在刚刚直接返回节点的那颗子树上(即没有继续搜索的那一颗),此时返回节点必定是最近祖先(即节点和最近祖先是同一个节点);(写得有点乱,细想一下就理解了);
#include <iostream>
#include <vector>
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->val == p->val || root->val == q->val) return root;
TreeNode* left = lowestCommonAncestor(root->left, p, q); // 从左子树找节点
TreeNode* right = lowestCommonAncestor(root->right, p, q); // 从右子树节点
if(left != NULL && right != NULL){ // p和q分别在root的左右子树上
return root;
}
else if(left != NULL && right == NULL){ // p和q不在root的右子树上
return left; // 返回找到的节点位置
}
else if(left == NULL && right != NULL){ // p和q不在root的左子树上
return right; // 返回找到的节点位置
}
else return NULL;
}
};
int main(){
// root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
TreeNode *Node1 = new TreeNode(3);
TreeNode *Node2 = new TreeNode(5);
TreeNode *Node3 = new TreeNode(1);
TreeNode *Node4 = new TreeNode(6);
TreeNode *Node5 = new TreeNode(2);
TreeNode *Node6 = new TreeNode(0);
TreeNode *Node7 = new TreeNode(8);
TreeNode *Node8 = new TreeNode(7);
TreeNode *Node9 = new TreeNode(4);
Node1->left = Node2;
Node1->right = Node3;
Node2->left = Node4;
Node2->right = Node5;
Node3->left = Node6;
Node3->right = Node7;
Node5->left = Node8;
Node5->right = Node9;
Solution S1;
TreeNode *res = S1.lowestCommonAncestor(Node1, Node2, Node3);
std::cout << res->val << std::endl;
return 0;
}
剑1 end in 2023.08.07. (~v~)