手撕字节跳动面试时出现过的算法题
1.买股票的最佳时机
leetcode 原题链接:买股票的最佳时机
题目:
121. 买卖股票的最佳时机
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
如果你最多只允许完成一笔交易(即买入和卖出一支股票一次),设计一个算法来计算你所能获取的最大利润。
代码:
class Solution {
public:
int maxProfit(vector<int>& prices)
{
int minData=INT_MAX;
int maxSub=0;
for(int i=0;i<prices.size();i++)
{
if(min(minData,prices[i])!=minData)
{
minData=prices[i];
}else if(prices[i]-minData>maxSub)
{
maxSub=prices[i]-minData;
}
}
return maxSub;
}
};
2.跳跃游戏
leetcode 原题链接:跳跃游戏
难度级别:中等
题目:
给定一个非负整数数组,你最初位于数组的第一个位置。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
判断你是否能够到达最后一个位置。
代码:
class Solution {
public:
bool canJump(vector<int>& nums)
{
int maxIndex=0;
for(int i=0;i<nums.size();i++)
{
if(i>maxIndex) return false;
maxIndex=max(maxIndex,i+nums[i]);
if(maxIndex>=nums.size()-1) return true;
}
return true;
}
};
3.场景题:发红包
题目:
让你设计一个微信发红包的api,你会怎么设计,不能有人领到的红包里面没钱,红包数值精确到分。
思路:
也就是说,加入现在有5个人,分10元红包
1.当第一个人分红包时,由于其他四个人不能没有,也就是第一个人最多抢 10-40.01元红包,至少是 0.01元 因此就是在【0.01,10-40.01】之间随机产生一个数 ,第二个,第三个,第四个在剩下的里面再分就可以了
2.最后一个人时直接拿剩下来的就可以了
代码:
class Solution {
private:
double getRandom(int upperLimit)//生成1--sum之间的随机值
{
// random()%sum 代表【0,sum)
// (int)random()%sum 代表【0,sum-1】
return 1 + (int)rand() % upperLimit;//代表【1,sum】
}
public:
vector<double> redEnvelopes(int money, int k)
{
vector<double> ret_v;
double sum = money * 100; //由于需要精确到分
//如何生成1 --sum-count*1 的随机值
for (int i = 1;i <= k;i++)
{
int upperLimit = (int)(sum - (k - i) * 1);
double value = getRandom(upperLimit);
if (i < k) ret_v.push_back(value/100);
else ret_v.push_back(sum/100);
//下一个的sum的时候
sum = sum - value;
}
return ret_v;
}
};
Solution s;
vector<double> ret_v=s.redEnvelopes(20,5);
double sum = 0;
for (double tt:ret_v)
{
sum = sum + tt;
cout << "tt="<<tt<< "sum="<< sum << endl;
}
4.合并排序的数组
leetcode 原题链接:合并排序的数组
难度级别:简单
题目:
给定两个排序后的数组 A 和 B,其中 A 的末端有足够的缓冲空间容纳 B。 编写一个方法,将 B 合并入 A 并排序。
初始化 A 和 B 的元素数量分别为 m 和 n。
代码:
class Solution {
public:
void merge(vector<int>& A, int m, vector<int>& B, int n)
{
int i=m-1,j=n-1;
while(i>=0&&j>=0)
{
int index=i+j+1;
if(A[i]>B[j])
{
A[index]=A[i];
i--;
} else
{
A[index]=B[j];
j--;
}
}
while(j>=0)
{
A[j]=B[j];
j--;
}
}
};
5.剪绳子
leetcode 原题链接:剑指 Offer 14- I. 剪绳子
难度级别:中等
题目:
给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]…k[m-1] 。请问k[0]*k[1]*...*k[m-1]
可能的最大乘积是多少?例如,当绳子的长度是 8 时,我们把它剪成长度分别为 2、3、3 的三段,此时得到的最大乘积是 18
代码:
class Solution {
public:
int cuttingRope(int n)
{
if(n==2||n==3) return n-1;
//最大乘积dp[n] 长度为n的绳子最大乘积
//状态转移方程 dp[n]=max(dp[i]*dp[n-i],max) for i in range(1,n-1)
//初始化dp[0]=1
//dp[1]=1;
//dp[2]=
vector<int> dp(n+1,0);
for(int i=1;i<=n;i++)
{
dp[i]=i;
for(int j=0;j<i;j++)
{
dp[i]=max(dp[j]*dp[i-j],dp[i]);
}
// cout<<"现在的dp"<<i<<"="<<dp[i]<<endl;
}
return dp[n];
}
};
题目:
给定一个有序数组,请算出平方后的结果可能的个数。
解答1,快排后比较
解答2双指针
解答2代码
int findDiff(vector<int> nums) {
int res = 0;
int left = 0;
int right = nums.size() - 1;
while(left <=right) {//这里为毛是left<right 一定要注意
res++;
if(abs(nums[left])> abs(nums[right])) {
while(left <right&&abs(nums[left])==abs(nums[left+1]))
{
left++;
}
left++;
}
else if(abs(nums[left])< abs(nums[right])){
while(left < right&&abs(nums[right])==abs(nums[right-1]))
{
right--;
}
right--;
}else if(abs(nums[left])==abs(nums[right]))
{
// int leftIndex=left;
// int rightIndex=right;
while(left <right&&abs(nums[left])==abs(nums[left+1]))
{
left++;
}
left++;
while(left < right&&abs(nums[right])==abs(nums[right-1]))
{
right--;
}
right--;
}
}
cout<<res;
return res;
}
7.找出不重复的元素个数
题目:
一个数据先递增再递减,找出数组不重复的个数。不能使用额外空间,复杂度o(n)
例如【1,2,3,3,3,4,5,6,5,5,2,1】就是五个不同的元素
【1,10,15,19,9,8,4】就是7个不同的元素
思路:
方法1:首先用快排进行1排序,然后中不同
方法二:找到最大的位置然后,从最大的位置向两边移动(双指针)
谁大谁移动 左右指针相等 那么同时移动
代码
class Solution {
private:
int _maximumSwap(vector<int> &numbers)
{
//首先找到最大值
int maxVaule=INT_MIN;
int maxIndex=0;
for(int i=0;i<numbers.size();i++)
{
if(max(maxVaule,numbers[i])<=numbers[i])
// if(numbers[i]>=maxVaule)
{
// cout<<"maxVaule="<<maxVaule<<"numbers[i]"<<numbers[i]<<endl;
// cout<<"当前索引值index="<<i<<"当前值是"<<numbers[i]<<endl;
maxVaule=numbers[i];
maxIndex=i;
}
}
cout<<"maxIndex="<<maxIndex<<endl;
int left=maxIndex;
int right=maxIndex+1;
int ret=0;
while(left>=0&&right<=numbers.size()-1)
{
ret++;
if(numbers[left]>numbers[right])
{
while(left>=1&&numbers[left]==numbers[left-1])
{
left--;
}
left--;
}else if(numbers[left]<numbers[right])
{
while(right<=numbers.size()-2&&numbers[right]==numbers[right+1])
{
right++;
}
right++;
}else if(numbers[left]==numbers[right])
{
while(left>=1&&numbers[left]==numbers[left-1])
{
left--;
}
left--;
while(right<=numbers.size()-2&&numbers[right]==numbers[right+1])
{
right++;
}
right++;
}
}
while(left>=0)
{
ret++;
while(left>=1&&numbers[left]==numbers[left-1])
{
left--;
}
left--;
}
while(right<=numbers.size()-1)
{
ret++;
while(right<=numbers.size()-2&&numbers[right]==numbers[right+1])
{
right++;
}
right++;
}
return ret;
}
public:
int diffNumberSum((vector<int> &numbers)
{
// vector<int> numbers{1,10,15,19,9,8,4};
// vector<int> numbers{1,2,3,3,3,4,5,5,5,4,3,2};
//首先排序法警方判定有多少个不同的数
sort(numbers.begin(),numbers.end());
if(numbers.size()==0) return 0;
int diffS=0;
for(int i=0;i<numbers.size();i++)
{
if(i>0&&numbers[i]==numbers[i-1]) continue;
diffS++;
}
cout<<"diffS1="<<diffS<<endl;
int diffs2=_maximumSwap(numbers);
cout<<"diffs2="<<diffs2<<endl;
return diffS;
}
};
8.找范围
题目:
高考成绩2000万数据,分数0-750,如何快速知道你的排名,如何知道任一分数排名?
思路:
利用桶排序。
将分数分成 0 - 150, 151 - 300, 301 - 450, 451 - 600, 601 - 750 共五个区间(每个区间内还可以再分),将 2000 万分数据按照成绩分到对应的成绩区间中。这样就可以快速查到对应分数的排名了。
9.测时间
题目:
两根香,一根烧完1小时,如何测量15分钟
思路:
先将一根香的一端点燃,另一根香的两端全部点燃。当第二根香全部烧完时,此时已经过了半个小时。再将第一根香的另一端也点燃,那么此时第一根香剩下部分烧完的时间就是 15 min。
10.链表相交
leetcode 原题链接:面试题 02.07. 链表相交
难度级别:简单
题目:
给定两个(单向)链表,判定它们是否相交并返回交点。请注意相交的定义基于节点的引用,而不是基于节点的值。换句话说,如果一个链表的第 k 个节点与另一个链表的第 j 个节点是同一节点(引用完全相同),则这两个链表相交。
思路:
方法一:快慢指针
方法二:我走过的路也是你走过的路,最终我们会相遇
方法一代码:
方法二代码:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB)
{
ListNode *pA=headA;
ListNode *pB=headB;
while(pA!=pB)
{
pA= pA? pA->next:headB;
pB= pB? pB->next:headA;
}
return pA;
}
};
11.求平方根
题目:
(这道题目跟 leetcode 上的不一样,leetcode 上只要求整数,这个还有精度)
写一个函数,求平方根,函数参数为目标数字和精度,测试案例 fn(4.1,0.001) fn(501.1,0.001) fn(0.045,0.001)
思路:
我们先看看leetcode中的题目
69. x 的平方根
实现 int sqrt(int x) 函数。
计算并返回 x 的平方根,其中 x 是非负整数。
由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去。
说明: 8 的平方根是 2.82842…,
由于返回类型是整数,小数部分将被舍去。
leetcode题解:
class Solution {
public:
int mySqrt(int x) {
// for(long long i=0;i<=x;i++)
// {
// if(i*i<=x&&(i+1)*(i+1)>x)
// {
// return i;
// }
// }
// return 0;//这行完全不可能执行的,加这行只是为了让程序跑起来
// 采用二分法
int left=0;
int right=x;
int ans=0;
while(left<=right)
{
long long mind=(left+right)/2;
if(mind*mind<=x)
{
ans=mind;
left=mind+1;
}else
{
right=mind-1;
}
}
return ans;//压根不会执行
}
};
然后这题是有浮点数的信息。
待补上
12.场景题:互相关注表设计
题目:
场景题:需求:谁关注了我,我关注了谁,谁与我互相关注。表该如何设计,索引怎么建。查询语句怎么写
思路:
个人见解,表可以有:id、粉丝id、被追随者id 三个字段,索引可以建两个,分别是粉丝 id 和被追随者 id。
由于我个人对于数据库这块不是很了解,因此不能给出好的见解
13.找 K 个最小值
题目:
10亿个数字,取最小的100个数
思路:
如果是几百或者几千的数字,还能用堆排给搞一下。但是 10 亿个恐怕是不行的。这边给出几种方法,是否可行还得大家自己判断,甚至可以互相叠加使用:
- (1) 单纯的堆排
- (2) 先通过 hash 去除重复元素,再通过堆排
- (3) 先桶排序,每个桶里再进行堆排序
- (4) 维持一个长度为 100 的数组,遍历 10 亿个数据,比较并加入到数组中
14.找出重复元素
题目:
1亿个正整数,范围是0-42亿。求出现次数是2的数字,空间复杂度
思路:
这题很像是剑指offer,是以每个字节为对象来进行实验的。
待定…
15.二叉树层次遍历
leetcode 原题链接:102. 二叉树的层序遍历
难度级别:中等
题目:
给你一个二叉树,请你返回其按 层序遍历 得到的节点值。 (即逐层地,从左到右访问所有节点)。
思路:
借助队列即可(queue)
代码:
/**
* 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 {
private:
vector<vector<int>>ret_vv;
public:
vector<vector<int>> levelOrder(TreeNode* root)
{
if(root==NULL) return ret_vv;
queue<TreeNode* >qu;
qu.push(root);
while(!qu.empty())
{
int n=qu.size();
vector<int> temp_v;
for(int i=0;i<n;i++)
{
TreeNode* front=qu.front();
temp_v.push_back(front->val);
qu.pop();
if(front->left)
{
qu.push(front->left);
}
if(front->right)
{
qu.push(front->right);
}
}
ret_vv.push_back(temp_v);
}
return ret_vv;
}
};
16.蛇形遍历二叉树
leetcode 原题链接:103. 二叉树的锯齿形层次遍历
难度级别:中等
题目:
给定一个二叉树,返回其节点值的锯齿形层次遍历。(即先从左往右,再从右往左进行下一层遍历,以此类推,层与层之间交替进行)。
思路:
这题就是层序遍历多加了一个判断条件 比如说 奇数层和偶数层 ,偶数层需要reverse(temp_v.begin(),temp_v.end())
代码:
/**
* 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 {
private:
vector<vector<int>> ret_vv;
public:
vector<vector<int>> zigzagLevelOrder(TreeNode* root)
{
if(root==NULL) return ret_vv;
queue<TreeNode* > qu;
qu.push(root);
int deep=1;
while(!qu.empty())
{
int n=qu.size();
vector<int> temp_v;
for(int i=0;i<n;i++)
{
TreeNode* front=qu.front();
temp_v.push_back(front->val);
qu.pop();
if(front->left)
{
qu.push(front->left);
}
if(front->right)
{
qu.push(front->right);
}
}
if(deep%2!=0)
{
ret_vv.push_back(temp_v);
}else
{
reverse(temp_v.begin(),temp_v.end());
ret_vv.push_back(temp_v);
}
deep++;
}
return ret_vv;
}
};
17.链表求和
leetcode 原题链接:面试题 02.05. 链表求和
难度级别:中等
题目:
给定两个用链表表示的整数,每个节点包含一个数位。
这些数位是反向存放的,也就是个位排在链表首部。
编写函数对这两个整数求和,并用链表形式返回结果。
示例:
输入:(7 -> 1 -> 6) + (5 -> 9 -> 2),即617 + 295
输出:2 -> 1 -> 9,即912
思路:
设置一个进位的标志位即可
代码
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2)
{
int ok=0;
ListNode* newHead=new ListNode(0);
// newHead->next=NULL;//默认指针的next=null;
ListNode* pre=newHead;
while(l1!=NULL&&l2!=NULL)
{
int currentSum=l1->val+l2->val+ok;
ListNode*tempNode=new ListNode(currentSum%10);
ok=currentSum/10;//进位的值(下一次循环时候的进位)
pre->next=tempNode;
pre=tempNode;
//为下一次循环做出准备
l1=l1->next;
l2=l2->next;
}
cout<<"ok="<<ok<<endl;
while(l1!=NULL)
{
ListNode*tempNode=new ListNode((l1->val+ok)%10);
ok=(l1->val+ok)/10;
pre->next=tempNode;
pre=tempNode;
l1=l1->next;
}
while(l2!=NULL)
{
ListNode*tempNode=new ListNode((l2->val+ok)%10);
ok=(l2->val+ok)/10;
pre->next=tempNode;
pre=tempNode;
l2=l2->next;
}
if(ok==1)
{
pre->next=new ListNode(1);
}
ListNode* ret=newHead->next;
//free(newHead);//防止内存泄漏
return ret;
}
};
优化
但是了,我们发现我很多代码都是重复使用因此我们将 while(l1!=NULL&&l2!=NULL)换成 while(l1!=NULL||l2!=NULL)
代码如下
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2)
{
int ok=0;
ListNode* newHead=new ListNode(0);
// newHead->next=NULL;//默认指针的next=null;
ListNode* pre=newHead;
while(l1!=NULL||l2!=NULL)
{
int value1=l1?l1->val:0;
int value2=l2?l2->val:0;
int currentSum=value1+value2+ok;
ListNode*tempNode=new ListNode(currentSum%10);
ok=currentSum/10;//进位的值(下一次循环时候的进位)
pre->next=tempNode;
pre=tempNode;
//为下一次循环做出准备
if(l1)
{
l1=l1->next;
}
if(l2)
{
l2=l2->next;
}
}
if(ok==1)
{
pre->next=new ListNode(1);
}
ListNode* ret=newHead->next;
//free(newHead);//防止内存泄漏
return ret;
}
};
提问
我们发现我们每次都是创建新的链表,我们还不是也可以在原有数组上面修改了?
**进阶:**假设这些数位是正向存放的,请再做一遍。
示例:
输入:(6 -> 1 -> 7) + (2 -> 9 -> 5),即617 + 295
输出:9 -> 1 -> 2,即912
思路因此我们只需要逆序链表就可以了
代码:
略
18.生成随机数
题目:
给定一个 0-4 随机数生成器 如何生成 0-6 随机数
思路:
首先0-4生成器里面有5个数,也就是0,1,2,3,4 我们将 0,1,标记为二进制的0 。3,4标记为二进制的1 生成2市 重新生成
因此由random4生成 等概率的0,1 代码是
int random01()
{
int ret=2;
while(ret==2)
{
ret=random4();
}
return ret<2?0:1;
}
现在问题可以转换为由random01() 如何生成rangdom6()
我们发现生成 0,1,2,3,4,5,6 这些函数是可以用 3位二进制数来表示 0,1,2,3,4,5,6,7 生成7时
因此由rangdom01()生成random6() 的代码是
int random()
{
int ret=0;
while(ret==7)
{
for(int i=0;i<3;i++)
{
ret=ret<<1+random01();
}
}
return ret;
}
综上所述 由random4()生成random6()的完整代码:
int random01()
{
int ret=2;
while(ret==2)
{
ret=random4();
}
return ret<2?0:1;
}
int random()
{
int ret=0;
while(ret==7)
{
for(int i=0;i<3;i++)
{
ret=ret<<1+random01();
}
}
return ret;
}
19.二叉树的最近公共祖先
leetcode 原题链接:剑指 Offer 68 - II. 二叉树的最近公共祖先
难度级别:中等
题目:
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
例如,给定如下二叉树: root = [3,5,1,6,2,0,8,null,null,7,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&&right==NULL) return left;
if(left==NULL&&right!=NULL) return right;
if(left==NULL&&right==NULL) return NULL;
return root;
}
};
20.二叉树中的最大路径和
leetcode 原题链接:124. 二叉树中的最大路径和
难度级别:困难
题目:
给定一个非空二叉树,返回其最大路径和。
本题中,路径被定义为一条从树中任意节点出发,达到任意节点的序列。该路径至少包含一个节点,且不一定经过根节点。
思路:
其实是归并思想,由下到上进行寻找maxVale 每次遍历当前节点时,我们需要注意的是 maxValue 与 当前的maxValue = max(root->val+leftMax+rightMax,root->val+leftMax,root->val+rightMax) 由于是从下到上的 可以抛弃部分 因此 leftMax rightMax 需要max(0,…)
代码:
/**
* 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
{
private:
int maxSum=INT_MIN;
int _maxPathSum(TreeNode* &root)
{
if(root==nullptr) return 0;
int leftMax=max(0,_maxPathSum(root->left)); // max() 就排除了 当子节点有负值时。直接抛弃
int rightMax=max(0,_maxPathSum(root->right)); // max() 就排除了 当子节点有负值时。直接抛弃
int lmr=root->val+leftMax+rightMax;
int lmormr=root->val+max(leftMax,rightMax);//这个是与上层相连时的返回值
maxSum=max(maxSum,max(lmormr,lmr));//当比较大小时
// cout<<"maxSum="<<maxSum<<endl;
return lmormr;//因为这个是要与上层相连的
}
public:
int maxPathSum(TreeNode* root)
{
_maxPathSum(root);
return maxSum;
}
};
21.快速排序
思路:
没啥好说的,认真背吧
//从小到大
void quickSort(vector<int> nums,int left,int right)
{
if(left>right) return;
int i=left;
int j=right;
int temp=nums[left];
while(i<j)
{
while(i<j&&nums[j]>=temp)
{
j--;
}
while(i<j&&nums[i]<=temp)
{
i++;
}
if(i<j)
{
swap(nums[i],nums[j]);
}
}
swap(nums[left],nums[i]);
quickSort( nums,left,i-1);
quickSort( nums,i+1,right);
}
22.二叉树的前序遍历非递归
leetcode 原题链接:144. 二叉树的前序遍历
难度级别:中等
题目:
给定一个二叉树,返回它的 前序 遍历。
思路:
1.递归
2.非递归
当然面试题一面都是斜非递归
非递归写法
class Solution {
private:
vector<int> ret_v;
public:
vector<int> preorderTraversal(TreeNode* root)
{
stack<TreeNode* >s;
if(root==nullptr)
{
return ret_v;
}
s.push(root);
while(!s.empty())
{
TreeNode* top=s.top();
ret_v.push_back(top->val);
s.pop();
if(top->right)
{
s.push(top->right);
}
if(top->left)
{
s.push(top->left);
}
}
return ret_v;
}
};
扩展 :非递归写法后序遍历
先序是 根 左 右==》根右左 ==》左右根
class Solution {
private:
vector<int> ret_v;
public:
vector<int> postorderTraversal(TreeNode* root)
{
if(root==nullptr)
{
return ret_v;
}
stack<TreeNode*> s;
s.push(root);
while(!s.empty())
{
TreeNode * top=s.top();
ret_v.push_back(top->val);
s.pop();
if(top->left)
{
s.push(top->left);
}
if(top->right)
{
s.push(top->right);
}
}
reverse(ret_v.begin(),ret_v.end());
return ret_v;
}
};
二叉树的非递归中序遍历
class Solution {
private:
vector<int > ret_v;
public:
vector<int> inorderTraversal(TreeNode* root)
{
stack<TreeNode *> s;
while(s.empty()!=true||root!=nullptr)
{
//cout<<"进来了"<<endl;
while(root!=nullptr)
{
s.push(root);
root=root->left;
}
TreeNode *top=s.top();
cout<<top->val<<endl;
ret_v.push_back(top->val);
s.pop();
root=top->right;
}
return ret_v;
}
};
24.最长连续递增数列
leetcode 原题链接:128. 最长连续序列
难度级别:困难
题目:
给定一个未排序的整数数组,找出最长连续序列的长度。
要求算法的时间复杂度为 O(n)。
思路:
我们先用一个unordered_set 来存数组元素,然后我们开始遍历数组 在unordered_set 是不是没有出现nums[i]-1 这个元素,出现过 就表示可以跳过了(保证时间复杂度是o(n)),没出现就代表nums[i]可能是某一个连续序列的头部 进入循环体中, 进行寻找该元素后面的元素是否出现过 nums[i],nums[i]+1,nums[i]+2,nums[i]+3, 一旦不存在就跳出
代码:
class Solution {
public:
int longestConsecutive(vector<int>& nums)
{
unordered_set<int> s;
for(int i=0;i<nums.size();i++)
{
s.insert(nums[i]);
}
int maxLenSeries=0;
for(int i=0;i<nums.size();i++)
{
int tempMaxS=0;
int currentValue=nums[i];
if(s.count(currentValue-1)==0)//证明这是开始,之前没出现过头文件
{
while(s.count(currentValue)>0)
{
currentValue++;
tempMaxS++;
}
maxLenSeries=max(maxLenSeries,tempMaxS) ;
}
}
return maxLenSeries;
}
};
25.(智力题)海盗分金币
题目:
有5个海盗,获得了100枚金币,于是他们要商量一个方法来分配金币。商议方式如下:
- (1) 由5个海盗轮流提出分配方案。
- (2) 如果超过半数海盗(包括提出者)同意该方案,则按照该方案分配。
- (3) 如果同意该方案的人数(包括提出者)小于等于半数,则提出者要被扔到海里喂鱼,剩下的海盗继续商议分配。
- (4) 海盗们都是绝对理性的,以自己尽可能多获得金币为目的。但是在收益相等的情况下,会倾向把提出者扔到海里。
问:第一个海盗应该提出怎样的分配方案,才能保证自己既不被扔到海里,又能使自己利益最大化?
这道题可以看这个链接,
26.接雨水
leetcode 原题链接:42. 接雨水
难度级别:困难
题目:
给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Dd1JwT17-1607527819316)( https://uploader.shimo.im/f/1susWMEI5AUVmY7j.png!thumbnail)]
上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。
思路:
1.单调栈
2. max(0, min(dpLeftMaxLength[i],dpRightMaxLength[i])-height[i] ) 这是每个坑里面可以有的水量
方法二代码
class Solution {
public:
int trap(vector<int>& height)
{
if(height.size()==0) return 0;
vector<int> dpLeft(height.size(),0);//dp[i] 代表的是 索引为i的左边的最大值
dpLeft[0]=0;
for(int i=1;i<height.size();i++)
{
dpLeft[i]=max(dpLeft[i-1],height[i-1]);
// cout<<"dpLeft[i]="<<dpLeft[i]<<endl;
}
vector<int> dpRight(height.size(),0);
dpRight[height.size()-1]=0;
for(int j=height.size()-2;j>=0;j--)
{
dpRight[j]=max(dpRight[j+1],height[j+1]);
// cout<<"dpRight[i]="<<dpRight[j]<<endl;
}
int ret=0;
for(int i=0;i<height.size();i++)
{
ret=ret+max(0,min(dpLeft[i],dpRight[i])-height[i]);
}
return ret;
}
};
27. ip 判断
题目:
有一个IP地址库,假设有几十万条ip,如何判断某个ip地址是否在这个库中
思路:
我的思路是先将ip地址进行分块存取 采用hash表存
然后读入某个ip找到相对应的hash小块中,在小快中去寻找。
28. 二叉树的路径和
题目:
给定一棵二叉树,求根节点到叶子节点的最小路径和。leetcode 差不多的题目 求根到所有节点的和
思路:
方法一:一种归并的思想 从下到上 求最小值 递归函数表示求当前节点,与当前节点下方的最小值
方法二:从下到上 递归函数代表从当前节点,与当前节点上方的和,当遍历到叶子节点时,比较每个叶子节点之上的最小值即可。
代码:
方法一:从下到上(归并的思想)
class Solution {
public:
int sumMinSeries(TreeNode* root)
{
if(root==NULL) reeturn 0;
int lefMin=sumMinSeries(root->left);
int rightMin=sumMinSeries(root->right);
return lefMin<rightMin? root->val+lefMin:root->rightMin;
}
};
方法二:从上到小
class Solution
{
private:
int minSum=INT_MAX;
void sumMinSeries(TreeNode* root,int preValueSum)
{
if(root==NULL) minSum=min(minSum,value);
int currentValueSum=preValueSum+root->val;
sumMinSeries(root->left,currentValueSum);
sumMinSeries(root->right,currentValueSum);
}
public:
int Main(TreeNode* root)
{
int preValueSum=0;
sumMinSeries(root,preValueSum);
return minSum;
}
};
29. 反转链表 + 每 k 个反转链表
这题其实有两题,我们分开来做,不过第二题显然要用到第一题的知识。
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)
{
ListNode*m1=NULL;
ListNode *m2=head;
if(m2==NULL) return m2;
ListNode *m3=head->next;
while(m2!=NULL)
{
m2->next=m1;
m1=m2;
m2=m3;
if(m3!=NULL) m3=m3->next;
}
return m1;
}
};
leetcode 原题链接:25. K 个一组翻转链表
难度级别:困难
题目:
给你一个链表,每 k 个节点一组进行翻转,请你返回翻转后的链表。
k 是一个正整数,它的值小于或等于链表的长度。
如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。
思路:
有四个指针, pre currentHead nextpre nextHead (这个含义有点欠妥)
记住一点 pre currentHead 可以确定 nextpre nextHead ,因此将pre currentHead 作为进入循环体的已知条件
代码:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
private:
ListNode* reverseList(ListNode* head)
{
ListNode*m1=NULL;
ListNode *m2=head;
if(m2==NULL) return m2;
ListNode *m3=head->next;
while(m2!=NULL)
{
m2->next=m1;
m1=m2;
m2=m3;
if(m3!=NULL) m3=m3->next;
}
return m1;
}
public:
ListNode* reverseKGroup(ListNode* head, int k)
{
ListNode*newHead=new ListNode(0);
newHead->next=head;
ListNode* pre=newHead;
ListNode* currentHead=pre->next;
ListNode* nextPre=nullptr;
ListNode* nextHead=nullptr;
while(head!=nullptr)
{
for(int i=1;head!=nullptr&&i<k;i++)
{
head=head->next;
}
if(head==nullptr) return newHead->next;
nextPre=head;
nextHead=head->next;//防止断链
head->next=nullptr;
reverseList(currentHead);
//将链表中的断链进行拼接
pre->next=nextPre;
currentHead->next=nextHead;
//准备下一次的循环 pre currentHead
pre=currentHead;
currentHead=nextHead;
head=currentHead;
}
return newHead->next;;
}
};
30. 场景题:求中位数
题目:
2g内存,要求一个10g文件的中位数
思路:
其实就是外村排序。。。
首先将 10g 的文件分成 10 * 1g,然后读入两个 g 的内存,排序后输出,然后不断进行这样的操作。
- 螺旋矩阵
leetcode 原题链接:59. 螺旋矩阵 II
难度级别:中等
题目:
给定一个正整数 n,生成一个包含 1 到 n^2 所有元素,且元素按顺时针顺序螺旋排列的正方形矩阵。
思路:
这题有两种方法
方法一:递归写法
方法二: while 写法
我个人比较喜欢将第一个元素单独拎出来,后面循环体中判断的是我们需要到达的下一个元素,并且设置元素的信息
class Solution
{
private:
vector<vector<int>> dxy{{0,1},{1,0},{0,-1},{-1,0}};//右下左上
bool isArea(vector<vector<int> > maze,int current_i,int current_j)
{
if(current_i<0||current_i>=maze.size()||current_j<0||current_j>=maze[0].size()) return false;
return true;
}
public:
vector<vector<int>> generateMatrix(int n)
{
//深度优先遍历法
//直接法
vector<vector<int> > maze (n,vector<int>(n,0));
vector<vector<bool> > used (n,vector<bool>(n,false));
if(n<=0) return maze;
//我比较喜欢单独先拎出来
int starti=0;
int startj=0;
int fangxiang=0;
if(isArea(maze,starti,startj))
{
maze[starti][startj]=1;
used[starti][startj]=true;
}
for(int i=2;i<=n*n;i++)//这个是填充多少次 我们充当里面每次的value数值
{
int temp_i=starti+dxy[fangxiang][0];
int temp_j=startj+dxy[fangxiang][1];
if(isArea(maze,temp_i,temp_j)==true&&used[temp_i][temp_j]==false)
{
;
}else
{
fangxiang=(fangxiang+1)%4;
temp_i=starti+dxy[fangxiang][0];
temp_j=startj+dxy[fangxiang][1];
}
// cout<<"temp_i="<<temp_i<<"temp_j="<<temp_j<<endl;
used[temp_i][temp_j]=true;
maze[temp_i][temp_j]=i;
starti=temp_i;
startj=temp_j;
}
return maze;
}
};
32. 数据结构 heap
题目:
讲一讲 heap 是什么?手写一个 heap
思路:
建立一个大跟堆
heap 即堆,根据性质可以分为大根堆和小根堆,存储形式是一棵完全二叉树,因此使用数组来保存。如果是大根堆,那么父节点大于等于子节点,根节点是最大的。如果是小根堆,那么父节点小于等于子节点,根节点是最小的。
大根堆写法,并且排序数组 测试程序是否正确可以用 leetcode排序链接
class Solution
{
void heapSort(vector<int>& nums,int index,int length)
{
int maxIndex=index;
int leftIndex=2*maxIndex+1;
int RightIndex=2*maxIndex+2;
if(leftIndex<length&&nums[maxIndex]<nums[leftIndex])
{
maxIndex=leftIndex;
}
if(RightIndex<length&& nums[maxIndex]<nums[RightIndex])
{
maxIndex=RightIndex;
}
if(maxIndex!=index)
{
swap(nums[maxIndex],nums[index]);
heapSort(nums, maxIndex, length);
}
}
//大根堆写法
public:
vector<int> sortArray(vector<int>& nums)
{
int n=nums.size();
for(int length=n/2-1;length>=0;length--)
{
heapSort(nums, length, n);
}
for(int i=n-1;i>=0;i--)
{
swap(nums[i],nums[0]);
heapSort(nums, 0, i);
}
return nums;
}
};
- 中文数字转阿拉伯数字
问题:
给定一个中文字符串,将其转换成对应的阿拉伯数字
思路:
待定
34. 重建二叉树
leetcode 原题链接:剑指 Offer 07. 重建二叉树
难度级别:中等
题目:
输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。
代码:
/**
* 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
{
private:
TreeNode* _buildTree(vector<int>&preorder,int preLeft,int preRight,unordered_map<int ,int>&map,int inLeft,int inRight)
{
if(preLeft>preRight||inLeft>inRight) return NULL;
TreeNode* root=new TreeNode(preorder[preLeft]);
int rootIndex=map[preorder[preLeft]];
root->left=_buildTree(preorder,preLeft+1,preLeft+rootIndex-inLeft,map,inLeft,rootIndex-1);
root->right=_buildTree(preorder,preLeft+rootIndex-inLeft+1,preRight,map,rootIndex+1,inRight);
return root;
}
public:
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder)
{
int preLeft=0;
int preRight=preorder.size()-1;
int inLeft=0;
int inRight=inorder.size()-1;
unordered_map<int ,int> map;//inorder【i】-->i
for(int i=0;i<inorder.size();i++)
{
map[inorder[i]]=i;
}
return _buildTree(preorder,preLeft,preRight,map,inLeft,inRight);
}
};
35. 路径总和
leetcode 原题链接:112. 路径总和
难度级别:简单
题目:
给定一个二叉树和一个目标和,判断该树中是否存在根节点到叶子节点的路径,这条路径上所有节点值相加等于目标和。
说明: 叶子节点是指没有子节点的节点。
代码:
/**
* 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:
bool hasPathSum(TreeNode* root, int sum)
{
if(root==NULL) return false;
sum=sum-root->val;
if(root->left==NULL&&root->right==NULL&&sum==0)
{
return true;
}
return hasPathSum(root->left,sum)||hasPathSum(root->right,sum);
}
};
36. 路径总和-2
leetcode 原题链接:113. 路径总和 2
难度级别:中等
题目:
给定一个二叉树和一个目标和,找到所有从根节点到叶子节点路径总和等于给定目标和的路径。
说明: 叶子节点是指没有子节点的节点。
思路:
其实就是将35题中每次递归一次,就用一个temp加上root->val, 每次找到符合的路径后,将temp加入到ret_vv中。
代码:
/**
* 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 {
private:
vector<vector<int>> ret_vv;
void _pathSum(TreeNode* root, int sum,vector<int> temp)
{
if(root==NULL) return ;
sum=sum-root->val;
temp.push_back(root->val);
if(root->left==NULL&&root->right==NULL&&sum==0)
{
ret_vv.push_back(temp);
}
_pathSum( root->left, sum, temp);
_pathSum( root->right, sum, temp);
}
public:
vector<vector<int>> pathSum(TreeNode* root, int sum)
{
vector<int> temp;
_pathSum( root, sum, temp);
return ret_vv;
}
};
37. 单例模式
题目:
手写单例模式
思路:
//标准的懒汉式 (会出现线程安全问题)
class singleton
{
public:
static singleton * CreateInstance()
{
if (Instance == nullptr)
{
Instance = new singleton();
return Instance;
}
return Instance;
}
private:
singleton()
{
}
static singleton * Instance;
};
//防止野指针
singleton* singleton::Instance = nullptr;
//改进的懒汉式
class singleton
{
public:
static singleton * CreateInstance()
{
static singleton Instance;
return &Instance;
}
//如果防止用户用delete可以这样写
//static singleton & CreateInstance()
//{
//static singleton Instance;
//return Instance;
//}
private:
singleton()
{
}
};
//为了实现线程安全(双重锁)
class singleton
{
public:
static singleton * CreateInstance()
{
if (Instance == nullptr)//判断是否是第一次调用
{
m.lock();
if (Instance == nullptr)//线程安全。
{
Instance = new singleton();
return Instance;
}
m.unlock();
}
return Instance;
}
private:
singleton()
{
}
static singleton * Instance;
};
//标准的饥饿式
class singleton
{
public:
static singleton * CreateInstance()
{
return Instance;
}
private:
singleton()
{
}
static singleton * Instance;
singleton(singleton& obj) {} //外界不能使用拷贝构造了
singleton &operator=(singleton& obj) {} //外界不能使用重载运算符运算了
};
//防止野指针
singleton* singleton::Instance = new singleton();
38. 合并区间
leetcode 原题链接:56. 合并区间
难度级别:中等
题目:
给出一个区间的集合,请合并所有重叠的区间。
思路:
首先写仿函数 将vector<vector> 进行排序 然后比较 i与i-1的 vector 比较 temp[i-1][1]>=temp[i][0] 就进行合并
代码
class Solution {
private:
struct cmp
{
bool operator()(vector<int > a,vector<int >b)
{
return a[0]<b[0];
}
};
public:
vector<vector<int>> merge(vector<vector<int>>& intervals)
{
sort(intervals.begin(),intervals.end());
vector<vector<int>> ret_vv;
for(int i=0;i<intervals.size();i++)
{
int start=intervals[i][0];
int end=intervals[i][1];
if(!ret_vv.empty()&& ret_vv.back()[1]>=start)
{
// vector<int> temp=ret_vv.back();
// temp[1]=max(ret_vv.back()[1],end);
ret_vv.back()[1]= max(ret_vv.back()[1],end);
}else
{
ret_vv.push_back({start,end});
}
}
return ret_vv;
}
};
- 翻转字符串中的单词
leetcode 原题链接:151. 翻转字符串里的单词
难度级别:中等
题目:
给定一个字符串,逐个翻转字符串中的每个单词。(注意单词间可能有多个空格但是输出只有一个空格)
输入: " hello world! "
输出: “world! hello”
方法一:使用栈
注意第一次放入栈(去多个空)的时候 空the空sky空is空blue 前面多了一个空,后面出栈判断时会发现更加简单了 当发现top==’ ',就说明有个temp字符串。直接结果ret=ret+" "+temp; 会发现结果多了一个“ ” 因此结果是str.erase(str.begin());
class Solution {
public:
string reverseWords(string s)
{
stack<char> st;
bool isWord=false;
for(int i=0;i<s.size();i++)
{
if(isWord==false&&s[i]!=' ')
{
st.push(' ');
st.push(s[i]);
isWord=true;
}else if(isWord==true&&s[i]!=' ')
{
st.push(s[i]);
}else if(s[i]==' ') isWord=false ;//啥都不做
}
string str="";
string temp="";
while(!st.empty())
{
char top=st.top();
st.pop();
if(top==' ')
{
//cout<<"进来了"<<"temp="<<temp<<endl;
cout<<"str="<<str<<endl;
str=str+" "+temp;
temp.clear();
}else
{
temp=top+temp;
}
}
str.erase(str.begin());
return str;
}
};
方法二:双指针:
class Solution {
public:
string reverseWords(string s)
{
//采用双指针法
string str="";
int n=s.size();
int i=n-1,j=n-1;
while(i>=0&&j>=0)
{
while(i>=0&&s[i]==' ') i--;
j=i;
while(j>=0&&s[j]!=' ') j--;
if(i>=0&&j>=-1)
{
string temp=s.substr(j+1,i-j);
// cout<<"temp="<<temp<<endl;;
str=str+temp+" ";
// cout<<"str="<<str<<endl;
}
i=j;
}
str.erase(str.end()-1);
return str;
}
};
方法三: 使用 istringstream
class Solution {
public:
string reverseWords(string s)
{
stack<string> stk;
string res, temp;
istringstream ss(s); // ss与数输入的字符串s绑定
while (ss >> temp) // 以空格为分隔符将其分割
{
stk.push(temp);
stk.push(" ");
}
if(!stk.empty())
stk.pop(); // 弹出最后压入的那个多余空格
while (! stk.empty())// 单词翻转后的新串
{
res += stk.top();
stk.pop();
}
return res;
}
};
40. 和为s的连续正整数序列
leetcode 原题链接:剑指 Offer 57 - II. 和为s的连续正数序列
难度级别:简单
题目:
输入一个正整数 target ,输出所有和为 target 的连续正整数序列(至少含有两个数)。
序列内的数字由小到大排列,不同序列按照首个数字从小到大排列。
思路:
采用滑动窗口
class Solution {
public:
vector<vector<int>> findContinuousSequence(int target)
{
vector<vector<int> > ret_vv;
int left=1;
int right=2;
int sum=1;
while(left<=target)
{
if(sum<target)
{
sum=sum+right;
right++;
}else if(sum>target)
{
sum=sum-left;
left++;
}else if(sum==target)
{
if(right-left>=2)
{
vector<int> temp_v;
for(int i=left;i<right;i++)
{
temp_v.push_back(i);
}
ret_vv.push_back(temp_v);
}
sum=sum-left;
left++;
}
}
return ret_vv;
}
};
优化:
但是呢,我发现是可以优化的,left<=target/2时,后面就不需要判断right-left>=2 因为只要满足sum==target 时,right-left>=2一定成立 想想例子就可以了
class Solution {
public:
vector<vector<int>> findContinuousSequence(int target)
{
vector<vector<int> > ret_vv;
int left=1;
int right=2;
int sum=1;
while(left<=target/2)
{
if(sum<target)
{
sum=sum+right;
right++;
}else if(sum>target)
{
sum=sum-left;
left++;
}else if(sum==target)
{
vector<int> temp_v;
for(int i=left;i<right;i++)
{
temp_v.push_back(i);
}
ret_vv.push_back(temp_v);
sum=sum-left;
left++;
}
}
return ret_vv;
}
};
- 二分查找
题目:
介绍下二分查找,如果我想在链表中使用二分查找,怎么做比较好?
思路:
二分查找就是将查找有序的范围不断均分,将查找的值和中间值进行比较,从而缩小范围。
待定:
- 归并排序
题目:
介绍一下归并排序,时间复杂度多少?
归并排序是稳定的排序 时间复杂度是nlongn 空间复杂度是n
思路:
归并排序其实就是首先将大的范围分成多个小部分,先保证整个小部分有序,然后再将各个小部分合并成较大的有序部分,直到整个部分都有序。时间复杂度为 O(nlogn)。
我想写写代码
归并排序代码思路是从下到上,递归的含义是 求一个数组 左边界和有边界的排序(leetcode数组排序链接)
代码:
class Solution {
private:
void Merge(vector<int> &arr, int start, int end,int mid, vector<int> &temparr)
{
int i_start = start;
int i_end = mid;
int j_start = mid + 1;
int j_end = end;
int length = 0;
//合并两个有序序列(为什么就有序了) 思考!!!
while (i_start<= i_end&& j_start<= j_end)//两个有序数组合并后可能会有个对于的
{
if (arr[i_start]< arr[j_start])
{
temparr[length] = arr[i_start];
length++;
i_start++;
}
else
{
temparr[length] = arr[j_start];
length++;
j_start++;
}
}
while (i_start<= i_end)//这是判断的
{
temparr[length] = arr[i_start];
length++;
i_start++;
}
while (j_start <= j_end)
{
temparr[length] = arr[j_start];
length++;
j_start++;
}
//辅助空间数据覆盖到原空间
for (int i=0;i<length;i++)
{
arr[start+i]=temparr[i];
}
}
void MergeSort(vector<int> &arr, int start, int end, vector<int> &temparr)
{
if (start>=end)
{
return;
}
//划分到最后是每一个里面只有一个数据
int mid = (start + end) / 2;
MergeSort(arr,start,mid, temparr);
MergeSort(arr, mid+1, end, temparr);
Merge(arr, start, end, mid, temparr);
}
public:
vector<int> sortArray(vector<int>& nums)
{
int left=0;
int right=nums.size()-1;
vector<int> temparr(nums.size(),0);
MergeSort(nums, left, right,temparr);
return nums;
}
};
43. LRU 算法
leetcode 原题链接:146. LRU缓存机制
难度级别:中等
题目:
运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制。它应该支持以下操作: 获取数据 get 和 写入数据 put 。
获取数据 get(key) - 如果关键字 (key) 存在于缓存中,则获取关键字的值(总是正数),否则返回 -1。
写入数据 put(key, value) - 如果关键字已经存在,则变更其数据值;如果关键字不存在,则插入该组「关键字/值」。当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。
思路:
“背多分”法
代码:
class LRUCache {
public:
LRUCache(int capacity) {
capacity_ = capacity;
}
int get(int key) {
const auto it = m_.find(key);
// If key does not exist
if (it == m_.cend()) return -1;
// Move this key to the front of the cache
cache_.splice(cache_.begin(), cache_, it->second);
return it->second->second;
}
void put(int key, int value) {
const auto it = m_.find(key);
// Key already exists
if (it != m_.cend()) {
// Update the value
it->second->second = value;
// Move this entry to the front of the cache
cache_.splice(cache_.begin(), cache_, it->second);//it->second代表着什么?
return;
}
// Reached the capacity, remove the oldest entry
if (cache_.size() == capacity_) {
const auto& node = cache_.back();
m_.erase(node.first);//这里应该是删除 m_与cache_中最后一个元素的连线
cache_.pop_back();//然后删除cache_中最后一个元素
}
//程序从这里看起
// Insert the entry to the front of the cache and update mapping.
cache_.emplace_front(key, value); //push_front()
m_[key] = cache_.begin();
}
private:
//先从push开始看起(假设没有元素), 然后看push时,元素怎么办?catch满了怎么办, 最后看get
int capacity_;
list<pair<int, int>> cache_;
unordered_map<int, list<pair<int, int>>::iterator> m_;
};
- 数据库连接池
题目:
数据库连接池怎么设计?
思路:
我真的不会数据库啊 待定
45. 版本数字比较
题目:
版本数字比较,比如"1.10.0"版本比"1.8.1"版本新,不允许使用 split 等函数
思路:
双指针解法 注意每次left是字母开始的地方,right是“.”(数字结束)的地方
每次判断后需要准备下一次循环条件 我们向string中加入一个辅助字符“.” 保证每个连续数字字符都可以弹出
代码:
class Solution {
private:
public:
int compareVersion(string version1, string version2)
{
version1=version1+'.';
version2=version2+'.';
int one_left=0;
int one_right=0;
int two_left=0;
int two_right=0;
while(one_left<version1.size()&&two_left<version2.size())
{
while(version1[one_right]!='.')
{
one_right++;
}
while(version2[two_right]!='.')
{
two_right++;
}
//现在one_right two_right都在点这里
string temp1=version1.substr(one_left,one_right-one_left);
string temp2=version2.substr(two_left,two_right-two_left);
// cout<<"**********"<<endl;
// cout<<"temp1="<<temp1<<" temp2="<<temp2<<endl;
// cout<<"stoi(temp1)="<<stoi(temp1)<<" stoi(temp2)="<<stoi(temp2)<<endl;
//cout<<"**********"<<endl;
if(stoi(temp1)>stoi(temp2)) return 1;
else if(stoi(temp1)<stoi(temp2)) return -1;
//两者相等时继续比较
//准备下一次循环的东西
one_left=one_right+1;
two_left=two_right+1;
one_right++;
two_right++;
}
while(one_left<version1.size())
{
while(version1[one_right]!='.')
{
one_right++;
}
string temp1=version1.substr(one_left,one_right-one_left);
//cout<<"temp1="<<temp1<<endl;
if(stoi(temp1)>0) return 1;
//准备下一次循环的东西
one_left=one_right+1;
one_right++;
}
while(two_left<version2.size())
{
while(version2[two_right]!='.')
{
two_right++;
}
string temp2=version2.substr(two_left,two_right-two_left);
//cout<<"temp2="<<temp2<<endl;
if(stoi(temp2)>0) return -1;
//准备下一次循环的东西
two_left=two_right+1;
two_right++;
}
return 0;
}
};
优化
可以看到,代码中重复工作,while(one_left<version1.size()&&two_left<version2.size())改为while(one_left<version1.size()||two_left<version2.size()) 可以实现代码的复用
class Solution {
private:
public:
int compareVersion(string version1, string version2)
{
version1=version1+'.';
version2=version2+'.';
int one_left=0;
int one_right=0;
int two_left=0;
int two_right=0;
while(one_left<version1.size()||two_left<version2.size())
{
while(one_left<version1.size()&&version1[one_right]!='.')
{
one_right++;
}
while(two_left<version2.size()&&version2[two_right]!='.')
{
two_right++;
}
//现在one_right two_right都在点这里
string temp1=one_left<version1.size() ?version1.substr(one_left,one_right-one_left):"0";
string temp2=two_left<version2.size() ?version2.substr(two_left,two_right-two_left):"0";
// cout<<"**********"<<endl;
//cout<<"temp1="<<temp1<<" temp2="<<temp2<<endl;
// cout<<"stoi(temp1)="<<stoi(temp1)<<" stoi(temp2)="<<stoi(temp2)<<endl;
//cout<<"**********"<<endl;
if(stoi(temp1)>stoi(temp2)) return 1;
else if(stoi(temp1)<stoi(temp2)) return -1;
//两者相等时继续比较
//准备下一次循环的东西
one_left=one_right+1;
two_left=two_right+1;
one_right++;
two_right++;
}
return 0;
}
};
46. 文件拷贝
题目:
某一个大文件被拆成了 N 个小文件,每个小文件编号从 0 至 N-1,相应大小分别记为 S(i)。给定磁盘空间为 C ,试实现一个函数从 N 个文件中连续选出若干个文件拷贝到磁盘中,使得磁盘剩余空间最小。
思路:
这一题不就是 给定一个正数数组,找到其中一个最接近target的连续序列
双指针,注意循环结束条件是 while(right<=nums.size()) 为啥有“=” 因为right指向的是当前元素和的下一个元素
代码:
class Solution {
public:
vector<int> SumClosest(vector<int>& nums, int target)
{
int left=0;//所求和的开始位置
int right=0; //所求和的end位置的下一个位置
int sum=0;
int maxSum=0;
vector<int> ret_v;
while(left<nums.size()&& right<=nums.size()) //left<nums.size()防止输入target是负数 right<=nums.size() = 是因为有可能最后一次才是最接近的 比如 【1 2 3】 6 最后一次必须算上
{
if(sum<target)
{
//这里符合条件,需要比较
if(sum>maxSum)
{
maxSum=sum;
ret_v.clear();
for(int i=left;i<right;i++)
{
ret_v.push_back(nums[i]);
}
}
sum=right!=nums.size()?sum+nums[right]:sum+0;//注意最后一次比较 防止出界
right++;
}else if(sum>target)
{
sum-=nums[left];
left++;
}else if(sum==target)
{
ret_v.clear();
for(int i=left;i<right;i++)
{
ret_v.push_back(nums[i]);
}
break;
}
}
// cout<<"ret_v.size()="<<ret_v.size()<<endl;
// for(int i=0;i<ret_v.size();i++)
// cout<<ret_v[i]<<" ";
return ret_v;
}
};
47. 场景题:redis 高并发
问题:
redis 设置高并发抢单一的东西,如何避免高并发对一个键进行访问?
思路:
服务器问题我不懂啊 待定
48. 最大栈
leetcode 原题链接:716.最大栈
难度级别:简单
在看这一题时,leetcode中有一题最小栈 leetcode155链接
题目:
思路:
借用两个栈:
写法一:入单调栈时 如果x 将x=min(x,s2.top()) 入栈2(s2)出栈时pop()即可
写法二:入单调栈时,如果x>s2.top() 不入栈2(s2) 出栈时 比较s1和s2的栈顶是否一样,一样单调栈s2才弹出
最小栈代码一
class MinStack {
public:
/** initialize your data structure here. */
MinStack() {
}
void push(int x) {
s1.push(x);
if(!s2.empty())
{
int topMax=s2.top();
if(x<topMax) s2.push(x);
else if(x>=topMax)
{
s2.push(topMax);
}
}else
s2.push(x);
}
void pop()
{
s1.pop();
s2.pop();
}
int top()
{
return s1.empty()?-1:s1.top();
}
int getMin() {
return s2.empty()?-1:s2.top();
}
private:
stack<int> s1;//常规操作
stack<int> s2;//单调栈(不严格)
};
/**
* Your MinStack object will be instantiated and called as such:
* MinStack* obj = new MinStack();
* obj->push(x);
* obj->pop();
* int param_3 = obj->top();
* int param_4 = obj->getMin();
*/
最小栈代码二
class MinStack {
public:
/** initialize your data structure here. */
MinStack() {
}
void push(int x) {
s1.push(x);
if(!s2.empty())
{
int topMax=s2.top();
if(x<=topMax) s2.push(x);
}else
{
s2.push(x);
}
}
void pop()
{
int top=s1.top();
s1.pop();
if(top==s2.top()) s2.pop();
}
int top()
{
return s1.empty()?-1:s1.top();
}
int getMin() {
return s2.empty()?-1:s2.top();
}
private:
stack<int> s1;//常规操作
stack<int> s2;//单调栈(不严格)
};
/**
* Your MinStack object will be instantiated and called as such:
* MinStack* obj = new MinStack();
* obj->push(x);
* obj->pop();
* int param_3 = obj->top();
* int param_4 = obj->getMin();
*/
现在我们看看最大栈
题目:
最大栈代码:
class MaxStack {
public:
/** initialize your data structure here. */
MaxStack() {}
void push(int x) {
s1.push(x);
if(s2.empty())
{
s2.push(x);
return;
}
int topMax=s2.top();
if(x>=topMax)
{
s2.push(x);
}
}
int pop()
{
if(s1.empty) return -1;
int ret=s1.top();
s1.pop();
if(ret==s2.top())
{
s2.pop();
}
return ret;
}
int peekMax() {
return s2.empty()? -1:s2.top();
}
int popMax()
{
if(s2.empty()) return -1;
int topMax=s2.top();
for(int i=0;i<s1.size();i++)
{
if(s1.top()<topMax)
{
s1.pop();
}else
{
s1.pop();//移除一个后 退出
break;
}
}
return topMax;
}
private:
stack<int> s1;//常规栈的操作
stack<int> s2;//单调栈(不严格)
};
我们发现 最大栈中多了一个popMax这个操作时间复杂度是o(n)
如何进行优化了? 待定
49. 场景题:分布式生成数据
题目:
分布式多个机器生成id,如何保证不重复
思路:
服务器真不会
- B+树和红黑树(未完成)
题目:
B+树和红黑树, 红黑树和一般的平衡二叉树,增、删、改、查的过程和效率、时间复杂度
思路:
红黑树是针对线索二叉树的优化 时间复杂度
黑树是每个节点都带有颜色属性的二叉查找树,颜色或红色或黑色。 [3] 在二叉查找树强制一般要求以外,对于任何有效的红黑树我们增加了如下的额外要求:
性质1. 节点是红色或黑色
性质2. 根节点是黑色
性质3.所有叶子都是黑色
性质4. 每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点)
性质5… 从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。
51. 找出和为 k 的数对
leetcode 原题链接:剑指 Offer 57. 和为s的两个数字
难度级别:简单
题目:
输入一个递增排序的数组和一个数字s,在数组中查找两个数,使得它们的和正好是s。如果有多对数字的和等于s,则输出任意一对即可。
方法一借助散列表 时间复杂度是o(n) 空间复杂度是o(n)
方法二****双指针 时间复杂度是o(longn) 空间复杂度是o(1)
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target)
{
vector<int> ret_v;
unordered_set<int> st;
for(int i=0;i<nums.size();i++)
{
st.insert(nums[i]);
}
for(int i=0;i<nums.size();i++)
{
int restoreNumber=target-nums[i];
if(restoreNumber!=nums[i]&&st.count(restoreNumber)>0)
{
ret_v.push_back(nums[i]);
ret_v.push_back(restoreNumber);
break;
}
}
return ret_v;
}
};
方法二:双指针
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target)
{
vector<int> ret_v;
// 双指针,时间复杂度是o(log(n))
int left=0;
int right=nums.size()-1;
while(left<right)
{
int sum=nums[left]+nums[right];
if(sum==target)
{
ret_v.push_back(nums[left]);
ret_v.push_back(nums[right]);
break;
}else if(sum>target)
{
right--;
}
else if(sum<target)
{
left++;
}
}
return ret_v;
}
};
52. 二叉树各层节点数
题目:
返回二叉树各层节点数,使用递归和非递归方法,并说明时间复杂度和空间复杂度
我觉得写这题时我们需要先看看
leetcode102 二叉树的层序遍历
题目:
二叉树的层序遍历节点有两种方法:
方法一: 采用队列,迭代就可以实现了
**方法二:**递归实现 递归过程中,递归采用的方式时修改了先序遍历,我们需要加上一个index ,相同的index层的节点放到同一个 vector 中即可。
102层序遍历代码方法一:迭代
/**
* 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 {
private:
vector<vector<int>>ret_vv;
public:
vector<vector<int>> levelOrder(TreeNode* root)
{
if(root==NULL) return ret_vv;
queue<TreeNode* >qu;
qu.push(root);
while(!qu.empty())
{
int n=qu.size();
vector<int> temp_v;
for(int i=0;i<n;i++)
{
TreeNode* front=qu.front();
temp_v.push_back(front->val);
qu.pop();
if(front->left)
{
qu.push(front->left);
}
if(front->right)
{
qu.push(front->right);
}
}
ret_vv.push_back(temp_v);
}
return ret_vv;
}
};
102层序遍历代码方法一:递归
/**
* 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 {
private:
vector<vector<int>> ret_vv;
void preorder(TreeNode* root,int index)
{
if(root==NULL) return ;
if(index>(int)ret_vv.size()-1)
{
// cout<<"进来了"<<endl;
ret_vv.push_back({root->val});
}else
{
ret_vv[index].push_back(root->val);
}
preorder(root->left, index+1);
preorder(root->right, index+1);
}
public:
vector<vector<int>> levelOrder(TreeNode* root)
{
//cout<<ret_vv.size()<<endl;
int index=0;
preorder( root,index);
return ret_vv;
}
};
有了上述的经验,我们再来看看 ,二叉树各层节点数
方法也有两种 :
方法一:迭代
方法二:递归
二叉树各层节点数方法一:迭代:队列
/**
* 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 {
private:
vector<int>ret_v;
public:
vector<int> levelOrder(TreeNode* root)
{
if(root==NULL) return ret_v;
queue<TreeNode* >qu;
qu.push(root);
//ret_v.push_back(n); 放在这里可能更好理解
while(!qu.empty())
{
int n=qu.size();
for(int i=0;i<n;i++)
{
TreeNode* front=qu.front();
qu.pop();
if(front->left)
{
qu.push(front->left);
}
if(front->right)
{
qu.push(front->right);
}
}
ret_v.push_back(n);
}
return ret_v;
}
};
二叉树各层节点数方法一:递归:
/**
* 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 {
private:
vector<int> ret_v;
void preorder(TreeNode* root,int index)
{
if(root==NULL) return ;
if(index>(int)ret_v.size()-1)
{
// cout<<"进来了"<<endl;
ret_v.push_back(1);
}else
{
ret_v[index]+=1;
}
preorder(root->left, index+1);
preorder(root->right, index+1);
}
public:
vector<int>>levelOrder(TreeNode* root)
{
//cout<<ret_vv.size()<<endl;
int index=0;
preorder(root,index);
return ret_v;
}
};
53. 输出下一个更大的数字
题目:
做这一题时,我想做一个
修改版的的输出下一个更大的数字1题目
496. 下一个更大元素 I修改版
给定数组nums1 。找到 nums1中每个元素的下一个比其大的值。
示例 1:
输入: nums = [1,4,5,2]
输出: [4,5,-1,-1]
思路:
单调栈
代码:
class Solution {
public:
vector<int> nextGreaterElement( vector<int>& nums)
{
//使用单调栈 栈顶是大的 栈底是小的
vector<int> nexMax(nums.size(),-1);
stack<int> s;
//unordered_map<int,int> map;
for(int i=0;i<nums.size();i++)
{
while(!s.empty()&&nums[i]>nums[s.top()])
{
int topIndex=s.top();
nexMax[topIndex]=nums[i];
s.pop();
}
s.push(i);
}
//下面这段可以不要,因为刚开始nexMax默认初始化是-1
while(!s.empty())
{
int topIndex=s.top();
nexMax[topIndex]=-1;
s.pop();
}
return nexMax;
}
};
然后 输出下一个更大的数字,我们发现其实就是多了一个循环遍历;
思路:
单调栈,注意题目中有循环的含义,我们在将nums数组,赋值一份,然后拼接起来进行遍历,如果弹出的i<题目中的nums.size() 就代表找到了下一个最大值
代码:
class Solution {
public:
vector<int> nextGreaterElements(vector<int>& nums)
{
// nums.insert(nums.end(),nums,nums.begin());
int n=nums.size();
vector<int> temp=nums;
nums.insert(nums.end(),nums.begin(),nums.end());
vector<int> ret_v(n,-1);
stack<int> s;
for(int i=0;i<nums.size();i++)
{
while(!s.empty()&&nums[i]>nums[s.top()])
{
int topIndex=s.top();
if(topIndex<n)
{
ret_v[topIndex]=nums[i];
}
s.pop();
}
s.push(i);
}
// 当然下面一个 while(!s.empty()) 可以去掉,因为vector<int> ret_v(n,-1); 默认初始值是-1
while(!s.empty())
{
int topIndex=s.top();
if(topIndex<n)
{
ret_v[topIndex]=-1;
}
s.pop();
}
return ret_v;
}
};
54. 输出字符串数组?(题目看不懂)
题目:
(这是原博的描述)
算法:输入List ,删除当中形如”1_”的,返回原来的List (2)
思路:
题目不详,待定 。
- 缺失的第一个正数
leetcode 原题链接:41. 缺失的第一个正数
难度级别:困难
题目:
思路:
最好的思路如果nums.size()=n 那么缺失的第一个正数一定在1-n+1 之间
因此我们的思路就是很简单了 将所有的原数组的 数值只要在【1,n】都放在原有的位置上面 比如【1,3,2,4】》【1,2,3,4】 再如【0,1,2,5,6】》【1,2,0,6,5】 然后在进行搜素 发现i+1!=nums[i] 就返回i+1 如果b遍历完了都找不到 就返回n+1
class Solution {
public:
int firstMissingPositive(vector<int>& nums)
{
//从1-n的数应该在他所在的位置
//例如【1,4,3,2,5】 ==》【1,2,3,4,5】 都是在自己的位置就可以了
int i=0;
while(i<nums.size())
{
int val=nums[i];
while(val>=1&&val<=nums.size()&&i+1!=val&&nums[val-1]!=val)//val是当前的值 nums[val-1]是我们目标应该交换的值
{
//cout<<"nums[i]="<<nums[i]<<"nums[val-1]="<<nums[val-1]<<endl;
swap(nums[i],nums[val-1]);
val=nums[i];//这样写的话,这行别忘记了
}
i++;
}
for(int i=0;i<nums.size();i++)
{
cout<<nums[i]<<endl;
}
for(int i=0;i<nums.size();i++)
{
//cout<<"nums[i]="<<nums[i]<<" i="<<i<<endl;
if(i+1!=nums[i]) return i+1;
}
return i+1;
}
};
56. 场景题:抛硬币问题
题目:
一个硬币,正面概率0.7,反面概率0.3,现在有一瓶水,怎么掷能让两个人公平的喝到水
思路:
只要保证两个人喝水的概率一直即可。将硬币抛两次,那么出现情况的概率如下:
情况 | 正(第一次) | 反(第一次) |
---|---|---|
正(第二次) | 0.49 | 0.21 |
反(第二次) | 0.21 | 0.09 |
那么显然,只要让两个一正一反的情况出现时让他们分别喝水,否则重抛即可。
57. 两个栈实现队列
leetcode 原题链接:剑指 Offer 09. 用两个栈实现队列
难度级别:简单
题目:
用两个栈实现一个队列。队列的声明如下,请实现它的两个函数 appendTail 和 deleteHead ,分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead 操作返回 -1 )
class CQueue
{
private:
stack<int> s1;
stack<int> s2;
public:
CQueue() {
}
void appendTail(int value)
{
s1.push(value);
}
int deleteHead()
{
while(!s1.empty())
{
int top=s1.top();
s1.pop();
s2.push(top);
}
int ret=-1;
if(!s2.empty())
{
ret=s2.top();
s2.pop();
}
while(!s2.empty())
{
int top=s2.top();
s2.pop();
s1.push(top);
}
return ret;
}
};
/**
* Your CQueue object will be instantiated and called as such:
* CQueue* obj = new CQueue();
* obj->appendTail(value);
* int param_2 = obj->deleteHead();
*/
58. 二叉树右视图
leetcode上面有原题 leetcode199二叉树右视图
题目:
思路:
这就是二叉树的层序遍历,将每一层的最后一个节点(back)提取,放到返回数组里面就可以了。
代码:
/**
* 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:
vector<int> rightSideView(TreeNode* root)
{
vector<int> ret_v;
if(root==NULL) return ret_v;
queue<TreeNode*> qu;
qu.push(root);
while(!qu.empty())
{
int n=qu.size();
vector<int > temp;
for(int i=0;i<n;i++)
{
TreeNode* front=qu.front();
temp.push_back(front->val);
qu.pop();
if(front->left) qu.push(front->left);
if(front->right) qu.push(front->right);
}
ret_v.push_back(temp.back());
}
return ret_v;
}
};
59. 概率题:扑克牌
题目:
54张扑克牌,平均分成3份,大小王在一份的概率
思路:
方法一:
分成3份 总的分法 M=(C54,18)(C36,18)(C18,18) 大小王在同一份N=(C3,1)(C52,16)(C36,18)*(C18,18) P=N /M=17/53 注:逗号后面为上标,前面为下标。
方法二:
把题目解释一下就成最简单的了:一副扑克54张,先把大王拿出来,剩下53张牌,然后分成17,18,18 3份,求小王在17那一份的概率。随意大王在哪一堆,求小王也在那个堆中的概率,因还剩下53张牌,因此概率为17/53
60. 有序有重复数组查找范围
leetcode 原题链接:34. 在排序数组中查找元素的第一个和最后一个位置
难度级别:中等
题目:
思路:
显然先用
1.二分法找到左边第一个的数,leftPosition
2.二分法找到右边第一个的数,rightPosition
细节:
1.数组为空时,直接返回{-1,-1},
2. left=mind 这句话 就代表着 循环体中 mind=(left+right+1)/2(不然会造成死循环) ,
2.我们还需判断 nums[left]==target nums[right]==target 来说明是否存在这样的值。因此我们必须防止left 和right越界
a.当存在target值时,返回 left和right都可以
b.找不到时,返回值 很有讲究
找不到左边界时 循环中mind=(left+right)/2 返回left ,不能返回right(越下界)
例如【2,3,5】 1 循环结束后 left=0,right=-1(越界);
找不到右边界时 循环中mind=(left+right+1)/2 返回right
【2,3,5】6 循环结束后 left=3(越上界),right=2;
综上所述
代码:
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target)
{
if(nums.size()==0) return {-1,-1};
int left=0;
int right=nums.size()-1;
vector<int> ret_v;
//寻找左边第一个出现的target的值
while(left<right)
{
int mind=(left+right)/2;
if(nums[mind]<target)//说明mind位置以及左边的位置不可能出现目标值
{
left=mind+1;
}else if(nums[mind]>target)//说明mind位置以及右边的位置不可能出现目标值
{
right=mind-1;
}else if(nums[mind]==target)//由于我们需要找到左边第一个出现target的位置,所以
{
right=mind;
}
}
int leftBind=nums[left]==target?left:-1;
left=0;
right=nums.size()-1;
while(left<right)
{
int mind=(left+right+1)/2;
if(nums[mind]<target)//说明mind位置以及左边的位置不可能出现目标值
{
left=mind+1;
}else if(nums[mind]>target)//说明mind位置以及右边的位置不可能出现目标值
{
right=mind-1;
}else if(nums[mind]==target)//由于我们需要找到右边第一个出现target的位置,所以
{
left=mind;
}
}
int rightBind=nums[right]==target?right:-1;
return {leftBind,rightBind};
}
};
这题我还想看另外一个题目,leetcode153旋转数组最小值
思路:
我们发现最小值一定存在于数组中的 这样就没有放回left还是right的讨论了,最终循环结束时 left=right 返回哪个都是一样的,时间复杂度是log(n);
代码:
class Solution {
public:
int findMin(vector<int>& nums)
{
int left=0;
int right=nums.size()-1;
while(left<right)
{
int mind=(left+right)/2;
if(nums[mind]>nums[right])
{
left=mind+1;
}else if(nums[mind]<nums[right])
{
right=mind;//mind 可能当前数组是最小值
}else if(nums[mind]==nums[right])
{
right=mind;//mind 可能当前数组是最小值
}
}
return nums[left];
}
};
61. 三数之和
leetcode 原题链接:15. 三数之和
难度级别:中等
题目:
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。
注意:答案中不可以包含重复的三元组。
思路:
先排序 nlongn
取一个数出来后 然后双指针 一直移动 n2
所以总的时间复杂度是n2
代码:
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums)
{
vector<vector<int>> ret_vv;
sort(nums.begin(),nums.end());
for(int i=0;i<nums.size();i++)
{
if(i>0&&nums[i]==nums[i-1]) continue;
int left=i+1;
int right=nums.size()-1;
while(left<right)
{
int sum=nums[i]+nums[left]+nums[right];
if(sum<0)
{
left++;
}else if(sum>0)
{
right--;
}
else if(sum==0)
{
ret_vv.push_back({nums[i],nums[left],nums[right]});
while(left<right&&nums[left]==nums[left+1])
{
left++;
}
left++;
while(left<right&&nums[right]==nums[right-1])
{
right--;
}
right--;
}
}
}
return ret_vv;
}
};
这一题和 611. 有效三角形的个数 很像
题目:
思路:
我们需要直到 当三条边 小 中 大 (resort原数组) 只需保证 小+中>大 就能保证能够构成三角形
先固定最大的边,然后双指针进行移动 一个在左边,一个在右边 ,找到出现的 nums[left]+nums[right]>nums[i] 时 我们可以固定 得到构成的三角形时 nums[left]—nums[right-1] ,nums[right],nums[i] 个数 right-left 此时 right–;接着循环
代码:
class Solution {
public:
int triangleNumber(vector<int>& nums)
{
/**
* 双指针
* 1 排序,固定最长边
* 2 若left满足两边之和大于第三边,固定i,right,left右移增大三角形较小的两边和,则left到right - 1共right - left个满足三角形条件
* 3 将right左移,两边和减小,判断三角形大于第三边的最小值
* 4 若两边和不满足三角形条件,则左移,增大三角形较小的两边和
**/
sort(nums.begin(), nums.end());
int len = nums.size();
int res = 0;
for(int i = len - 1; i >= 0; i --){
int left = 0, right = i - 1;
while(left < right){
if(nums[left] + nums[right] > nums[i]){
res += right - left;
right--;
}else{
left++;
}
}
}
return res;
}
};
62. 扫码登录
题目:
扫码登录是如何实现的?
思路:
答案来源于博客
主要分成四个步骤:
- (1) 访问网站,网站服务器为当前会话生成一个全局唯一的 ID
- (2) 手机相关 APP 扫码,提示当前账号是否登录
- (3) app 将当前登录的账号信息和扫描得到的全局唯一的 ID 提交到服务器
- (4) 服务器将该 ID 和账号信息绑定,并通知网站登录该账号
63. 思考题:赛马
题目:
参考链接
思路:
这里面涉及到假设问题。
64. 思考题:抛硬币
题目:
两个人轮流抛硬币,先抛到正面的赢,问先抛的人赢的概率
思路:
题目没有额外说明,那么我们认为甲乙投硬币每次正反的概率都是 0.5。
- 三个线程循环打印ABC
题目:
三个线程循环打印ABC
思路:
其实考的就是多线程间通信的方法。
#include<iostream>
#include<thread>
#include<mutex>
#include<string>
using namespace std;
mutex m;
string ss = "ABC";
int i = 0;
void printOne()
{
while (true)
{
m.lock();
cout << "线程1打印输出" << ss[i] << endl;
i = (i+1) % 3;
m.unlock();
}
}
void printTwo()
{
while (true)
{
m.lock();
cout << "线程2打印输出" << ss[i] << endl;
i = (i + 1) % 3;
m.unlock();
}
}
void printThree()
{
while (true)
{
m.lock();
cout << "线程3打印输出" << ss[i] << endl;
i = (i + 1) % 3;
m.unlock();
}
}
int main()
{
thread one(printOne);
thread two(printTwo);
thread three(printThree);
one.join();
two.join();
three.join();
return 0;
}
66. 数组中第K大的数
leetcode 原题链接:215. 数组中的第K个最大元素
难度级别:中等
题目:
在未排序的数组中找到第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。
思路:
快排和二分混合使用
肯定能找到这个数字
class Solution {
private:
int quicksort(vector<int>& nums,int left,int right)//从小到大排序
{
// if(left>right) 普通的快排需要这样 但是这里不需要
// {
// return -1;
// }
int i=left;
int j=right;
int targetNumber=nums[i];
while(i<j)//从小到大进行排序
{
while(i<j&&nums[j]>=targetNumber)
{
j--;
}
while(i<j&&nums[i]<=targetNumber)
{
i++;
}
//此时的双指针i,j所指的数都不满足条件
if(i<j)
{
swap(nums[i],nums[j]);
}
}
swap(nums[left],nums[i]);
return i;
}
public:
int findKthLargest(vector<int>& nums, int k)
{
int left=0;
int right=nums.size()-1;
int seq=-1;
while(seq!=nums.size()-k)
{
seq=quicksort(nums,left,right);
if(seq>nums.size()-k)
{
right=seq-1;
}
else if(seq<nums.size()-k)
{
left=seq+1;
}
}
return nums[seq];
}
};
67. top k
题目:
返回一个数组中前 k 个大的或者小的数字
思路:
使用堆排序 代码,略
68. 狼和羊的故事
题目:
一个农场中有狼和羊,现在要将他们用围栏分开,问最少需要多少围栏。
思路:
最小割算法
最小割最大流定理
待定
69. 二叉排序树找第k大的元素
题目:
利用二叉排序树的性质找第k大的元素
方法一(略):中序遍历二叉排序树得到的就是从小到大,然后返回nums[nums.size()-k]
方法二: 我们知道左中右 中序遍历是倒数第 nums[nums.size()-k]个数,
那么右中左 这样的中序遍历就是 nums[k] 就是所求
方法二代码
/**
* 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 {
private:
int ret=0;
private:
void dfs(TreeNode* root, int &k)
{
if(root==NULL) return ;
dfs(root->right,k);
k--;
if(k==0)
{
ret=root->val;
}
dfs(root->left,k);
}
public:
int kthLargest(TreeNode* root, int k)
{
dfs( root, k);
return ret;
}
};
- 链表求和
leetcode 原题链接:面试题 02.05. 链表求和
难度级别:中等
题目:
给定两个用链表表示的整数,每个节点包含一个数位。
这些数位是反向存放的,也就是个位排在链表首部。
编写函数对这两个整数求和,并用链表形式返回结果。
示例:
输入:(7 -> 1 -> 6) + (5 -> 9 -> 2),即617 + 295
输出:2 -> 1 -> 9,即912
代码:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2)
{
ListNode *dummy=new ListNode(0);
ListNode *tail=dummy;
int ok=0;
while(l1!=NULL||l2!=NULL)
{
int leftValue=l1!=NULL?l1->val:0;
int rightValue=l2!=NULL?l2->val:0;
int sum=leftValue+rightValue+ok;
ListNode *temp=new ListNode(sum%10);
tail->next=temp;
tail=temp;
ok=sum/10;
l1=l1!=NULL?l1->next:NULL;
l2=l2!=NULL?l2->next:NULL;
}
if(ok==1)
{
tail->next=new ListNode(1);
}
// vector<vector<double>> ret_vv;
// ret_vv.push_back({1,2,3,5,5.2,6.3,8.9,7.5});
return dummy->next;
}
};
进阶:
假设这些数位是正向存放的,请再做一遍。就是先链表翻转一次就可以了
71. 穿墙术(未解决)
题目:
可以看博客
思路:
待定
72. 场景题:定时任务
题目:
系统中有几万个任务需要在各自的特定时刻触发执行,怎么做?
思路:
这是服务器方向吧?待定
73. 二叉树的最近公共祖先 + 搜索二维矩阵 II + 最长不含重复字符的子字符串
这里有三题。
(1) 二叉树的最近公共祖先(同题 19)
leetcode 原题链接:236. 二叉树的最近公共祖先
难度级别:中等
题目:
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
思路:
一种归并的思路,先深入递归,然后回溯,我的理解是每次都是从下到上进行的找最近节点
代码:
/**
* 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&&right==NULL) return left;
if(left==NULL&&right!=NULL) return right;
if(left==NULL&&right==NULL) return NULL;
return root;
}
};
(2) 搜索二维矩阵 II
leetcode 原题链接:240. 搜索二维矩阵 II
难度级别:中等
题目:
编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target。该矩阵具有以下特性:
- 每行的元素从左到右升序排列。
- 每列的元素从上到下升序排列。
思路:
就是以右上角为开始点然后寻找target
num[i][j]>target 往左移动
num[i][j]<target 往右移动
num[i][j]==target 找到了,终止循环,返回true
代码:
class Solution {
public:
bool searchMatrix(vector<vector<int>>& matrix, int target)
{
int i=0;
int j=matrix[0].size()-1;
while(i<=matrix.size()-1&&j>=0)
{
if(matrix[i][j]<target)
{
i++;
}else if(matrix[i][j]>target)
{
j--;
}else if(matrix[i][j]==target)
{
return true;
}
}
return false;
}
};
(3) 最长不含重复字符的子字符串
leetcode 原题链接:剑指 Offer 48. 最长不含重复字符的子字符串
难度级别:中等
题目:
请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子字符串的长度。
class Solution {
public:
int lengthOfLongestSubstring(string s)
{
unordered_map<char, int> hash;
int res = 0;
//j在前面跑
for (int i=0,j=0;j<s.size();j++)
{
hash[s[j]]++;
while (hash[s[j]]>1)//有相同的元素 需要将i指针一直移动
{
hash[s[i]] --;
i++;
}
res = max(res, j - i + 1);
}
return res;
}
};
73. 场景题:微博关注列表
题目:
一个很复杂的场景,大概是微博用户有个关注列表,以及知道各种大V的动态,设计数据结构啥的,到最后在用户这边显示一个更新微博列表
思路:
可以看博客
待定
74. 场景题:游戏中的 debuff
题目:
游戏里,一个玩家可能吃到很多种debuff,每个debuff有一定持续时间。给你一个定时器,怎么设计数据结构,来维护玩家的debuff状态?
思路:
我的思路:
先用小根堆 存放的buff的定时器,然后 用一个散列表存放buff名称 --node节点的迭代器,注意迭代器是指向二叉树每个节点的
75. 场景题:红黑树,B+树,跳表应用场景
题目:
红黑树,B+树,跳表应用场景
思路:
红黑树 - HashMap;
B+ 树 - Mysql inoodb 索引结构;
跳表 - redis 的数据结构 zset
76. “马”在棋盘上的概率
leetcode 原题链接:688. “马”在棋盘上的概率
难度级别:中等
题目:
已知一个 NxN 的国际象棋棋盘,棋盘的行号和列号都是从 0 开始。即最左上角的格子记为 (0, 0),最右下角的记为 (N-1, N-1)。
现有一个 “马”(也译作 “骑士”)位于 (r, c) ,并打算进行 K 次移动。
如下图所示,国际象棋的 “马” 每一步先沿水平或垂直方向移动 2 个格子,然后向与之相垂直的方向再移动 1 个格子,共有 8 个可选的位置。
现在 “马” 每一步都从可选的位置(包括棋盘外部的)中独立随机地选择一个进行移动,直到移动了 K 次或跳到了棋盘外面。
求移动结束后,“马” 仍留在棋盘上的概率。
思路:
方法一:
dfs :万能的递归,(先拎出来,然后判断),基本思路是一直递归到符合index>=K 时 count++; 然后递归完成后 cout/pow(8,K) 超时了
方法二:
动态规划 这里也有两种讨论一种是从前到后,另外一个是从后往前看
方法一超时代码:
class Solution {
private:
vector<vector<int> > dxy{{-2,1},{-1,2},{1,2},{2,1},
{2,-1},{1,-2},{-1,-2},{-2,-1}};
private:
bool isArea(int &N,int temp_i,int temp_j)
{
if(temp_i<0||temp_j<0||temp_i>=N||temp_j>=N) return false;
return true;
}
void dfs(int &N,int start_i,int start_j,int index,int &k,double &count )
{
if(index>=k)
{
count++;
return;
}
for(int i=0;i<dxy.size();i++)
{
int temp_i=start_i+dxy[i][0];
int temp_j=start_j+dxy[i][1];
if(isArea(N,temp_i,temp_j))
{
dfs(N,temp_i,temp_j,index+1, k,count );
}
}
}
public:
double knightProbability(int N, int K, int r, int c)
{
//首先想到了是万能的dfs
double count=0;
int start_i=r;
int start_j=c;
int index=0;
if(isArea(N,start_i,start_j))
{
dfs(N,start_i,start_j,index, K,count );
}
return count/pow(8,K);
}
};
方法二:动态规划 (从前往后看)
class Solution {
private:
vector<vector<int> > dxy{{-2,1},{-1,2},{1,2},{2,1},
{2,-1},{1,-2},{-1,-2},{-2,-1}};
bool isArea(int &N,int temp_i,int temp_j)
{
if(temp_i<0||temp_j<0||temp_i>=N||temp_j>=N) return false;
return true;
}
public:
double knightProbability(int N, int K, int r, int c)
{
//动态规划
if(!isArea( N, r, c)) return 0;
vector<vector<double>> dp1(N,vector<double>(N,0));
dp1[r][c]=1;
for(int m=0;m<K;m++) //因为我们求的动态规划后的第K次
{
vector<vector<double>> dp2(N,vector<double>(N,0));//放在这是准备使用翻滚数据来节约空间
for(int i=0;i<N;i++)//这是代表着行
{
for(int j=0;j<N;j++)//这是代表者列
{
for(int n=0;n<dxy.size();n++)//这是跳跃的的可能步数
{
int temp_i=i+dxy[n][0];
int temp_j=j+dxy[n][1];
if(isArea(N,temp_i,temp_j))
{
dp2[temp_i][temp_j]=dp1[i][j]+dp2[temp_i][temp_j];
}
}
}
}
swap(dp1,dp2);
}
double ret_d=0;
for(int i=0;i<N;i++)//这是代表着行
{
for(int j=0;j<N;j++)//这是代表者列
{
ret_d+=dp1[i][j];
}
}
return ret_d/pow(8,K);
}
};
77. 场景题:大文件中的 id 计算
题目:
一个5T的文件,里面全是id,1-10^9 ,如何计算不同id的个数
思路:
首先将 将id分成若干份。然后将每一个数字放在应该放的 集合中,如果存在就舍弃改数字,遍历每一个集合,统计每个集合不同的数字。
(我们采用外部排序的方法,将 5T 的文件进行排序,然后分块遍历,计算不同 id 的个数。)
78. 求中位数
题目:
一个有序数组,从随即一位截断,把前段放在后边,如 4 5 6 7 1 2 3求中位数
思路:
这题就是找到这个"半"有序数组的最小值位置,然后看看中位数应该出现的位置,
1.中位数在前边
2.中位数在前边第一个数和后边最后一个数的平均值
3.中位数在后面
代码:
测试d
class Solution {
public:
double findMedianSortedArrays(vector<int>& nums1)
{
//nums1=【4 5 6 7 1 2 3】求中位数
//首先用二分法,找到nums1的最小值位置
if(nums1.size()==0) return 0;
int left=0;
int right=nums1.size()-1;
while(left<right)//肯定能够找到最小值数 left =right 就是这个数组中的最小值的位置元素
{
int mind=(left+right)/2;
if(nums1[mind]<nums1[right])
{
right=mind;
}else if(nums1[mind]==nums1[right])
{
right=mind;
}else if(nums1[mind]>nums1[right])
{
left=mind+1;
}
}
int miniIndex=left;
int ans=nums1.size()%2==0?0:1;
if(ans==1)
{
cout<<"原来数组是基数"<<"最小值的索引是="<<miniIndex<< endl;
if(miniIndex>(nums1.size()-1)/2)
{
int trueIndex=(nums1.size()+1)/2-(nums1.size()-miniIndex)-1;//在前面位置
cout<<"trueIndex="<<trueIndex<<endl;
return nums1[trueIndex] ;
}else if(miniIndex<=(nums1.size()-1)/2){//在后面位置
int trueIndex=miniIndex+(nums1.size()+1)/2-1;
return nums1[trueIndex];
}
}else if(ans==0)
{
cout<<"miniIndex="<<miniIndex<<endl;
if(miniIndex==nums1.size()/2)
{
return (nums1[0]+nums1[nums1.size()-1])/2;
}else if(miniIndex<nums1.size()/2)//在右边
{
int trueindex1=miniIndex+nums1.size()/2-1;
int trueindex2=trueindex1+1;
cout<<"trueindex1="<<trueindex1<< " trueindex2="<<trueindex2<<endl;
return ((double)nums1[trueindex1]+nums1[trueindex2])/2;
}else if(miniIndex>nums1.size()/2)//在左边
{
//右边的数字有多少个
int numberRight =nums1.size()-miniIndex;
int trueindex1=nums1.size()/2-numberRight-1;
int trueindex2=trueindex1+1;
return ((double)nums1[trueindex1]+nums1[trueindex2])/2;
}
}
return 0;
}
};
79. 输出第 n 个字符
题目:
一个形如 123456789101112…… 的字符串,输入一个 n(很大很大),输出字符串第 n 个字符
思路:
待定
80. 公交车超载
题目:
一辆公交车,有 m 站,最多坐 n 人,输入一路上票的信息(即上车下车站),输出会不会超载。
例如:
输入:
7 11 [7, 2 ,-2, -1, 4, 3, 5]
输出:
true
方法一:动态规划
方法二:一直加
方法一:动态规划
代码:
void solution(int m,int n,vector<int> arr)
{
vector<int> dp(m,0);
dp[0]=arr[0];
if(dp[0]>n) return true;
for(int i=1;i<m;i++)
{
dp[i]=dp[i-1]+arr[i];
if(dp[i]>n) return true;
}
return false;
}
**优化:**我们发现其实只要存 我们上一次人数就可以了,空间可以优化成o(1)
代码:
void solution(int m,int n,vector<int> arr)
{
int pre=0;
pre=arr[0];
if(pre>n) return true;
for(int i=1;i<m;i++)
{
int current=pre+arr[i];
if(current>n) return true;
pre=current;
}
return false;
}
方法二:一直加
代码
void solution(int m,int n,vector<int> arr)
{
int count=0;
for(int i=0;i<m;i++)
{
count+=arr[i];
if(count>n) return true;
}
return false;
}
81. 全排列
题目:
leetcode46全排列
思路:
这题就是需要借助 used 并且借助index 这个代表能够凑成的个数,只不过刚好h
代码:
class Solution {
private:
vector<vector<int>> ret_vv;
void dfs(vector<int>& nums,vector<bool>& used,vector<int>& temp,int index)
{
if(index>=nums.size())
{
ret_vv.push_back(temp);
return;
}
for(int i=0;i<nums.size();i++)
{
if(used[i]==true) continue;
temp.push_back(nums[i]);
used[i]=true;
dfs(nums,used,temp,index+1);
temp.pop_back();
used[i]=false;
}
}
public:
vector<vector<int>> permute(vector<int>& nums)
{
vector<bool> used(nums.size(),false);
vector<int > temp;
int index=0;
dfs(nums,used,temp,index);
return ret_vv;
}
};
82. 场景题:台球入洞
题目:
坐标系中有一个球桌,四个角坐标:(0,0), (0,4), (2,4), (2,0),一颗球在 (1,1),请问从哪些角度可以射入洞内(可无限次碰撞)?
思路:
待定,如果无限次碰撞那么就是无数个角度来回碰撞
83. 完全二叉树的节点个数
题目:
求完全二叉树的节点个数,小于O(n),并分析复杂度
写代码吗?
非递归用队列,将每一层节点数进行计数就可以了
递归的话 改改先序遍历 每次发现root!=null时就开始cout++;
代码略
84. 链表实现一个栈
题目:
链表实现一个栈
思路:
85. 加油站 + 柠檬水找零
(1) 加油站
leetcode 原题链接:134. 加油站
难度级别:中等
思路:
首先判断只要总加油站gasSum的和严格小于costSum的和,必不能全部走一周
否则是存在可以从某一加油站出发,走一圈的,注意一个技巧,就是一次count=count+gas[i]-cost[i] 这种方法是以当前位置预测下一个位置。假如count<0 那么start=i+1,count=0; 继续循环 ,肯定会找到存在的点
代码:
class Solution {
public:
int canCompleteCircuit(vector<int>& gas, vector<int>& cost)
{
int gasSum=0;
int costSum=0;
for(int i=0;i<gas.size();i++)
{
gasSum+=gas[i];
costSum+=cost[i];
}
if(costSum>gasSum)
{
return -1;
}
int count=0;
int start=0;
int i=0;
while(i<gas.size())//看是不是可以到达了下一个位置
{
count=count+gas[i]-cost[i];
if(count<0)
{
start=i+1;
count=0;
}
i++;
}
return start;
}
};
2) 柠檬水找零
leetcode 原题链接:860. 柠檬水找零
难度级别:简单
题目:
在柠檬水摊上,每一杯柠檬水的售价为 5 美元。
顾客排队购买你的产品,(按账单 bills 支付的顺序)一次购买一杯。
每位顾客只买一杯柠檬水,然后向你付 5 美元、10 美元或 20 美元。你必须给每个顾客正确找零,也就是说净交易是每位顾客向你支付 5 美元。
注意,一开始你手头没有任何零钱。
如果你能给每位顾客正确找零,返回 true ,否则返回 false 。
86. 堆
题目:
手写jdk中的优先级队列 PriorityQueue(最小堆)
思路:
直接看源码把,略
87. 圆圈剩下的数字(约瑟夫环)
leetcode 原题链接:剑指 Offer 62. 圆圈中最后剩下的数字
难度级别:简单
题目:
0,1,n-1这n个数字排成一个圆圈,从数字0开始,每次从这个圆圈里删除第m个数字。求出这个圆圈里剩下的最后一个数字。
例如,0、1、2、3、4这5个数字组成一个圆圈,从数字0开始每次删除第3个数字,则删除的前4个数字依次是2、0、4、1,因此最后剩下的数字是3。
思路:
代码:
class Solution {
public:
int lastRemaining(int n, int m) {
int pos = 0; // 最终活下来那个人的初始位置
for(int i = 2; i <= n; i++){
pos = (pos + m) % i; // 每次循环右移
}
return pos;
}
};
88. 和为K的子数组
leetcode 原题链接:560. 和为K的子数组
难度级别:中等
题目:
给定一个整数数组和一个整数 k,你需要找到该数组中和为 k 的连续的子数组的个数。
思路:
方法一
暴力解法,外层循环 i,j 内存【i,j】,然后一直加如果um=k,显然count++;时间复杂度是o(n3),leetcode超时
方法二:
也就是将暴力解法进行了修改, 我们只需要外层循环i,以这个作为边界i,内存循环j作为边界j,每次判断[i,i],[i,i+1],[i,i+2]…[i,j] 是否sum==k,是的话count++;
方法三:
前缀和参考链接
代码:
**方法一:**代码略
方法二:
class Solution {
public:
int subarraySum(vector<int>& nums, int k)
{
int count=0;
for(int i=0;i<nums.size();i++)
{
int sum=0;
for(int j=i;j<nums.size();j++)
{
sum=sum+nums[j];
if(sum==k)
{
count++;
}
}
}
return count;
}
};
方法三:
class Solution {
public:
int subarraySum(vector<int>& nums, int k)
{
unordered_map<int,int> map;//前缀和集合
map[0]=1;//这个是保证nums[0]+nums[1]+..nums[]==k 这种情况 这个很关键
int Sum=0;
int count=0;
for(int i=0;i<nums.size();i++)
{
Sum=Sum+nums[i];
if(map.count(Sum-k)>0)// pre-k是代表着
{
count=count+map[Sum-k];//前面有多少和可以相加 就加多少个
}
map[Sum]++;//刚开始map不存在 Sum时,默认map[sum]=0;
}
return count;
}
};
89. 设计模式
题目:
说说你知道的设计模式,说说项目里用到的设计模式,说说策略模式,设计一个下棋的场景问如何结合设计模式使用,设计模式什么时候继承,什么时候委托?
思路:
待定
90. 代码输出
题目:
以下代码题输出什么?
Map<Short, String> map = new HashMap<>();
for(short i = 0; i <100; i++) {
map.put(i, String.valueOf(i));
map.remove(i-1);
}
System.out.println(map.size());
上面是java代码 好像是 map.remove(i-1) 中i-1类型是int,但是map中key值类型是short类型,所以不会进行删除。所以java环境下面输出100
下面是转换成的c++代码
unordered_map<short, string> map;
for (short i = 0; i < 100; i++) {
map[i] = to_string(i);
map.erase(i);
}
cout<<map.size()<<endl;
这个c++代码,进过测试是可以删除的,所以c++环境环境下面输出1