输入问题
如果一直空格输出 没告诉你输出多少个 则
int n;
cin>>n;
cin.ignore();//这里输入一般是1 abc dd如果没有这个
//n接收了1 然后后面的s1会接收 abc dd就会多个空格 cin.ignore();就是忽略这个空格
string s1,tmp;
getline(cin,s1);//这里不遇到回车不结束
//所以一定要字符串分割
/**字符分割/
string b, c;
stringstream d(s1);//直接通过空格分割
d >> b;
d >> c;
int num;
while(cin>>num)
data.push_back(num);
碰到相同字母组成 对字母进行排序 然后用哈希表存储
碰到求和或者数组序列 用滑动窗口双指针
数组求和(带去重)
void cersion(vector<int>&nums,vector<int>now,int target,int index)
{
if(target == 0)
{
res.push_back(now);
return;
}
if(index == nums.size())
{
return;
}
int last = INT_MIN;
for(int i = index ;i<nums.size();i++)
{
if(nums[i]<=target)
{
if(last == nums[i])
continue;
last = nums[i];
now.push_back(nums[i]);
target-=nums[i];
cersion(nums,now,target,i+1);
target += nums[i];
now.pop_back();
}
}
}
vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
vector<int> now;
sort(candidates.begin(),candidates.end());
cersion(candidates,now,target,0);
return res;
}
剑指 Offer 03. 数组中重复的数字
做法
数组中找重复数字我们第一个能想到的就是一一遍历 然后拿hash表记录下来
只要有一个键的值超过了1 那就直接输出
我的办法也是 可开辟一个vectorcan每个数字值就是他的位置 然后出现一次的就把值标位false 下次再遇见can[i] == false这个值的时候就直接输出
更好的办法
核心思想
一个重要的点是题目中数一定都比总数量n小 所以如果没有重复的数字 在0-n这些坑里面应该放着0-n数字对齐 也就是0号坑里面一定是0 所以我们就在每次遍历的时候把这些数字放回去 假设num[i] = x 那说明num[i]这个数字应该属于x这个坑 我们就不把他和num[x]
(也就是num[num[i]])做交换 那在交换的如果这个数字是第二次出现的 在他交换的时候肯定已经放着相同的数字了
从头开始遍历每一个数
例如
0 1 2 3 4 坑
2 2 4 1 2 萝卜
第一次找2 将2和4交换 放回2坑
0 1 2 3 4 坑
4 2 2 1 2 萝卜
接下去就换下一个数 2 将他放到2坑的时候 发现那里已经是2了 就说明他重复了
实现代码
bool swap(int &a,int &b)
{
if(a == a)
return true;
int temp = a;
a= b;
b = temp;
return false;
}
int findRepeatNumber(vector<int>& nums) {
for(int i =0;i<nums.size();i++)
{
if(nums[i] != i)
{
if(swap(nums[i],nums[nums[i]]))
return nums[i];
}
}
return -1;
}
剑指 Offer 05. 替换空格
请实现一个函数,把字符串 s 中的每个空格替换成"%20"。
示例 1:
输入:s = “We are happy.”
输出:“We%20are%20happy.”
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/ti-huan-kong-ge-lcof
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
做法
第一时间想的就是遍历一遍 用另外一个string存字符 碰到’ '就依次push_back(‘%’) push_back(‘2’) push_back(‘0’)
更好的办法
貌似还没有
剑指 Offer 09. 用两个栈实现队列
用两个栈实现一个队列。队列的声明如下,请实现它的两个函数 appendTail 和 deleteHead ,分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead 操作返回 -1 )
示例 1:
输入:
[“CQueue”,“appendTail”,“deleteHead”,“deleteHead”]
[[],[3],[],[]]
输出:[null,null,3,-1]
示例 2:
输入:
[“CQueue”,“deleteHead”,“appendTail”,“appendTail”,“deleteHead”,“deleteHead”]
[[],[],[5],[2],[],[]]
输出:[null,-1,null,null,5,2]
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/yong-liang-ge-zhan-shi-xian-dui-lie-lcof
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
方法
一个栈用来添加数据 一个栈用来删除数据 如果删除栈空了 就把添加栈的而所有数据放过来
核心思想 目的就是要能删到栈底的元素
因为栈是删除栈顶元素 把一个栈的元素放到另一个栈里 原来栈顶元素就会变成栈底元素了 原栈底元素就在栈顶 这样就跟队一样了
代码
class CQueue {
stack<int> del,add;
public:
CQueue() {
while(!del.empty())
{
del.pop();
}
while(!add.empty())
{
add.pop();
}
}
void appendTail(int value) {//插入元素
add.push(value);
}
int deleteHead() {//删除元素
int out;
if(del.empty())
{
if(add.empty())
{
return -1;
}
while(!add.empty())
{
del.push(add.top());
add.pop();
}
out = del.top();
del.pop();
}
else
{
out = del.top();
del.pop();
}
return out;
}
};
/*
栈的特点是先进后出
队的特点是先进先出 队尾插入 队头删除
是两个栈反向链接吗?
队本来是怎么实现的
链表:
剑指 Offer 10- I. 斐波那契数列
写一个函数,输入 n ,求斐波那契(Fibonacci)数列的第 n 项(即 F(N))。斐波那契数列的定义如下:
F(0) = 0, F(1) = 1
F(N) = F(N - 1) + F(N - 2), 其中 N > 1.
斐波那契数列由 0 和 1 开始,之后的斐波那契数就是由之前的两数相加而得出。
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
示例 1:
输入:n = 2
输出:1
示例 2:
输入:n = 5
输出:5
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/fei-bo-na-qi-shu-lie-lcof
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
做法
正常就是从i =0 和1的时候是0和1
很明显的递归终止条件 直接递归 return f(n-1)+f(n-2);
这个时候会发现时间是超时的 因为有很多重复的运算
所以就需要用个东西记一下过去的东西就行了 实际上只需要记两项 然后加起来就行了
代码
/*迭代*/
int fib(int n) {
int mod = 1e9+7;
if(n == 0||n == 1)
{
return n;
}
int first=0;
int second=1;
int third=0;
for(int i=2;i<=n;i++){
third=(first+second)%mod;
first=second;
second=third;
}
return third%mod;
}
剑指 Offer 15. 二进制中1的个数
n = n & (n - 1);
//例如 1011
1011 1010
1010
一
1010 1001
1000
二
1000 0111
0000
三
剑指 Offer 21. 调整数组顺序使奇数位于偶数前面
输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇数位于数组的前半部分,所有偶数位于数组的后半部分。
示例:
输入:nums = [1,2,3,4]
输出:[1,3,2,4]
注:[3,1,2,4] 也是正确的答案之一。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/diao-zheng-shu-zu-shun-xu-shi-qi-shu-wei-yu-ou-shu-qian-mian-lcof
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
双指针
一个是偶数的位置 一个是奇数的位置
void swap(int &a,int &b){
int temp = a;
a = b;
b = temp;
}
vector<int> exchange(vector<int>& nums)
{
int dou = nums.size()-1;
for(int i = 0;i<nums.size();i++)
{
if(dou == i)
return nums;
if(nums[i]%2 == 0)
{
swap(nums[i],nums[dou]);
dou--;
i--;
}
}
return nums;
}
快慢指针 快指针一直走找奇数位置 快指针会经过所有奇数的位置 快指针不会管慢指针指的是哪
void swap(int &a,int &b){
int temp = a;
a = b;
b = temp;
}
vector<int> exchange(vector<int>& nums)
{
int fast = 0;int low = 0;
while(fast<nums.size())
{
if(nums[fast]%2 == 1)
{
swap(nums[fast],nums[low]);
low++;
}
fast++;
}
return nums;
}
剑指 Offer 22. 链表中倒数第k个节点
输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。
例如,一个链表有 6 个节点,从头节点开始,它们的值依次是 1、2、3、4、5、6。这个链表的倒数第 3 个节点是值为 4 的节点。
示例:
给定一个链表: 1->2->3->4->5, 和 k = 2.
返回链表 4->5.
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/lian-biao-zhong-dao-shu-di-kge-jie-dian-lcof
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
做法
快慢指针
先让快指针走k步 k走到链表尾部的时候 慢指针就走到了倒数第k个位置了
ListNode* getKthFromEnd(ListNode* head, int k)
{
ListNode* fast = head;
ListNode *low = head;
while(k)
{
k--;
fast = fast->next;
}
while(fast)
{
fast = fast->next;
low = low->next;
}
return low;
}
如果碰到二叉树 要用z字型输出
要么是到奇数行用 先用一个vector存储 然后再从尾部输出出来 就是实际上就是存两次
还有一种就是用双向队列 从头和从尾插入
等差数列求和公式
a1×n+(n×(n-1))/2×d
关于不同字母 数字的
一个是要用unordered_map除了可以记下是否有 一个是记下他上次出现的位置 方便直接左边界直接放到他的右边一格
关于回文数
第一个是怎么判断回文数
就是判断从l = 0 和r = size-1;
也可以用栈的先进后出性质 进行对比
不断地靠拢判断
那么第二个点就是用动态规划来算 因为回文数去掉头尾还是回文数 所以我们可以把他优化成子问题 但是他有一个要注意点的点是 他如果是用dp[i][j]
状态转移方程是dp[l][r] = dp[l+1][r-1]&&(nums[l] == nums[r])
l是左边 r是右边 但是这个时候因为r一定要l大 所以就是在右上三角进行 那么如果按正常的l是行r是列 就会发现你从上到下一行一行更新的 l+1了 此时你还没有下一行的数据 所以我们可以一列一列更新 也就是竖着更新就可以解决这个问题
解决这个方法还可以从后面往前面遍历 也就是l为
for(int l = n-1;l>=0;l--)
{
for(int r = l+1;r<n;r++)
{
}
}
图
大概率就是bfs dfs
树
如果在牛客碰到树
大概率就是struct tree{
int val ;int left;int right;
}tree[];
基本不可能建树
二叉树重建
中序遍历如果知道了根节点 他的左边是左子树的节点 右边是右子树的节点 也就是可以通过中序遍历知道左右子树的节点数量
前序遍历就能知道 第一个一定是根节点 如果有左节点 那就后面跟的一定是左根节点
后序遍历就一样了
关于数字求和 什么两数之和 三数之和
一般就是要么就哈希表存数字 方便寻找 要么是双指针进行查找
最大公约数 最小公倍数
任意n和n-1都是互质 也就是n和n-1的公因数只有1 而最大公倍数一定是n*(n-1)
如何球最大公因式
辗转相除法
我们求72和86
那么我们假设有一个数x可以整除72和86
那么可以把86分为 72 + 14
因为x可以整除72 所以 14也能被整除
那么我们就变成求14和72最大公因式
同上 72 = 14 + 58
58和14
58 = 14 + 44
14和44
44 = 30 + 14
30和14
30 = 14 + 16
14和16
16 = 14+ 2
14和2
14 = 2+12
。。。。
最后就是0和2
int gcd(int a, int b) {
int t;
while(b!=0) {
t=a%b;//就是能减就一直减
a=b;
b=t;
}
return a;
}
求最小公倍数
假设知道a和b的最大公因数x 那么最小公倍数是 a*b/x
实现乘法
long mul(long a, long k) {
long ans = 0;
while (k > 0) {
if ((k & 1) == 1) ans += a;
k >>= 1;
a += a;
}
return ans;
}
实现除法
实现除二
a>>1;
或者
(left + right+1)>>1就是对left和right 除2
+1是为了避免死循环 如果最后left = 0 right = 1 就会进去死循环
相同前缀字母
就可以用字典树
多组字符串组合进行全排列时
data是需要被排列的
核心思想比如 第一个数组 man 和woman 一定是在重新组成的字符串的第一个位置 也就是pos == n-1的地方 然后for循环就是让每一个第一个数组里面的元素都组成一个递归 都呆一次第一的位置 bfs吧
void bfs(vector<vector<string>> &data,int pos,vector<string> &ans,string tmp){
if(pos == -1){
int t = tmp.size();
tmp = tmp.substr(0,t-1);
ans.push_back(tmp);
return;
}
for(int i=0;i<data[pos].size();i++){
bfs(data,pos-1,ans,data[pos][i]+"-"+tmp);
}
}
旋转数组问题
最核心的就是拿中间的值和最右边那个值做对比 可以知道哪一边是有序的
求解一个数组中数字的各种组合的和
例如[1,2,3,4]
就是求
1 2
1 3
1 4
2 3
2 4
3 4
1 2 3
1 2 4
2 3 4
1 2 3 4
//sum是数列中所有数字的和 scores[i]存储的是数组中每一个数字
int[] dp = new int[20001];//保持这个值是否可以被求和到
// 求解背包问题
dp[0] = 1;//0是可以得到的
dp[sum] = 1;//所有数字的和自然可以得到
for(int i = 0; i < n; i++){//遍历数组中每一个数字
dp[scores[i]] = 1;//只有这一个数字 可以得到这个值[scores[i]]
for(int j = 0; j <= sum; j++){//从0开始试 直到最大的值sum
if(dp[j] == 1 && j - scores[i] >= 0)
dp[j - scores[i]] = 1;
//dp[j] == 1表示这个数是可以得到的 例如sum-scores[i-1]也就是上一个循环中的值 然后再让这个值-现在这个scores[i]肯定也是存在的 意思就是除了scores[i]和scores[i-1]其他数组中的值都加了
}
}
最后dp中所有的是1的就是可以得到的值了
例如[1,2,3] sum = 6
dp数组最初为
第一次执行就是dp[scores[i]] = 1; 和循环里面的 j == sum的时候dp[sum] == 1
所以执行dp[j - scores[i]] = 1; 也就是dp[sum - scores[i]] = 1 也就是dp[6-1] = 1
后面就是
另一种方法循环递归
void dfs(vector<int> nums,vector<int>now,int index)
{
if();
for()
}
链表
可以用a[1] = 2;这样表示链表 也就是1号的下一个是2号
判断有没有环
快慢指针:快指针走两步 慢指针走一步 如果他们倆相遇了 就说明有环
用哈希存起来
并查集 如果两个人祖先是相同的 则说明是一个环 因为
你只有可能在接34的时候 才会发现祖宗相同
拓扑排序 如果有元素不在拓扑排序中就说明有环
丑数问题
他的核心就是这些丑数都是由他给出的那些质数互乘组成
例如这些数 [1,2,4,7,8,13,14,16,19,26,28,32]
就是由[2,7,13,19]和1组成 例如4就是2*2
那么我们想要知道这些数 方法很多
第一种就是从2开始(因为0不行 而1本来就是)不断地尝试+1 让这个数字不断的/数组中的这些质数 如果能除尽那就说明吗是丑数 但是这个时间太长了
第二种开始我们就换个思路 既然这些都是数组中质数组成 那么我们就让这些数不断的互乘 我们在第二种中是用小顶堆 然后先将1放进小顶堆 然后取出小顶堆堆顶的数字 乘数组中所有的质数 然后再放回小顶堆 还要用个unordered_map来去重 这种方法时间也很久 但是不超时 主要是整理小顶堆要时间
第三种就是n指针每一个指针都代表质数数组中的一个数 而生成的丑数中每个人都可以和质数数组中的每个数组乘一次 然后我们要找出最小的那个放进丑数数组中
具体看别人的题解
检查是否是字母或者数字的函数
char a = 'c';
if(isalnum(a));
将大写变成小写
如果不是大写字母就不会有变化 返回原来的值 例如’1’就返回’1’
char a = 'A';
a = tolower(a);
两个相同数字问题
相同数字之间异或操作是0 也就是4^4 = 0
而零异或任何数字都是那个数字 0^8 = 8
我们就就可以知道 2^2^3^4^9^3^4 = 9
因为其他相同的就会被抵消掉
二进制中 数字转字符串 字符串转数字
自己实现的
/*将数字6转换成字符串011*/
string i_s(int nums)
{
string res = "";
while (nums)
{
res = res + (char)('0' + nums % 2);
nums = nums >> 1;
}
return res;
}
/*二进制的字符串例如"011"(6) 转换成数字6 也就是二进制011*/
int s_i(string nums)
{
int res = 0;
for (int i = nums.size() - 1; i >= 0; i--)
{
//if (i != 0)
res = res << 1;
if (nums[i] == '1')
res = res + 1;
}
return res;
}
内置方法
atoi()字符串转数字
这个是直接转成数字 例如
string a = "123";
int b = atoi(a.c_str());
而上面转化就是转化成二进制代表的数字 0,2,7,3,6
to_string()数字转字符串
也就是直接转 6转成"6"
求共同祖先
方法
快慢指针 滑动窗口(也就是双指针 左右指针) 动态规划 bfs dfs 字典树 排序 unordered_map