1000 0000
这是人为规定的,-1的二进制是1111 1111
。想一下为什么是这两个形式,因为-127=-128+1是1000 0001
,-126是-128+21000 0010
,那么-1就是-128+127=1111 1111
。logN
的时间复杂度
lower_bound()
和set.lower_bound()
和map.lower_bound()
查找有序序列中第一个大于等于目标值的元素的第一个元素的迭代器。
返回值==set.begin()
说明所有元素均 ≥ \geq ≥目标值返回值==set.end()
说明所有元素均 < < <目标值set.lower_bound()
效率比lower_bound(set.begin(),set.end()
效率高!i=~i+1
,因为计算机底层是用补码来存储数据。(原码和补码相互转换是除了符号位以外,按位取反,然后+1,那么计算机中的一个数的相反数,即所有位按位取反,然后+1即可)#include
#include
using namespace std;
struct Node {
int key, val;
Node *pre, *next;
Node() : pre(NULL), next(NULL) {
}
Node(int k, int v) : key(k), val(v) {
}
};
class DoubleList {
public:
//构造
DoubleList() {
head = new Node(0, 0);
tail = new Node(0, 0);
head->next = tail;
tail->pre = head;
size = 0;
}
//在链表尾部添加元素,时间复杂度O(1)
void addLast(Node *x) {
tail->pre->next = x;
x->pre = tail->pre;
x->next = tail;
tail->pre = x;
size++;
}
//删除链表中的x节点(该节点一定存在)节点指针(地址)由哈希表给出
void remove(Node *x) {
x->pre->next = x->next;
x->next->pre = x->pre;
//free(x); 此处不能free,因为挪动节点到开头需要用到此函数
size--;
}
//删除链表中的第一个节点,并返回该节点,时间O(1)
Node *removeFirst() {
if (head->next == tail)
return NULL;
Node *first = head->next;
head->next = first->next;
head->next->pre = head;
size--;
return first;
}
int getSize() {
return size;
}
//按照LRU顺序,展示所有键值,用于测试
void show(){
Node*p=tail;
while(p->pre!=head){
cout<<p->pre->key<<":"<<p->pre->val<<" ";
p=p->pre;
}
}
private:
//头尾虚节点
Node *head, *tail;
//链表元素个数
int size;
};
class LRUCache {
public:
//构造:提供最大容量
LRUCache(int capacity) {
this->capacity = capacity;
}
//获取某键的缓存值
int get(int key) {
//没有该键的缓存值
if (Map.find(key) == Map.end()) {
return -1;
}
//将该键提升为最近使用的键
makeRencent(key);
return Map[key]->val;
}
//存放某键的缓存值
void put(int key, int val) {
//缓存键已存在,则更新键值
if (Map.find(key) != Map.end()) {
deleteKey(key);
addRencent(key, val);
return;
}
//如果已满,则淘汰最近最少使用的缓存
if (cache.getSize() == capacity) {
removeLeastRecently();
}
//添加为最近使用的元素
addRencent(key, val);
}
//测试用,按LRU顺序展示缓存中所有内容
void show(){
cache.show();
cout<<endl;
}
//将某个key提升为最近使用的
void makeRencent(int key) {
Node *x = Map[key];
cache.remove(x);
cache.addLast(x);
}
//添加最近使用的元素
void addRencent(int key, int val) {
Node *x = new Node(key, val);
Map[key] = x;
cache.addLast(x);
}
//删除某个key
void deleteKey(int key) {
Node *x = Map[key];
cache.remove(x);
Map.erase(key);
free(x);
}
/*删除最久未使用元素*/
void removeLeastRecently() {
Node *target = cache.removeFirst();
int key = target->val; //这就是为什么双向链表中节点要存储key,是为了删除的时候,反向查找在map中的位置并删除
Map.erase(key);
free(target);
}
private:
//哈希表
map<int, Node *> Map;
DoubleList cache;
int capacity;
};
int main() {
LRUCache lru(4);
while(1){
string op;
cin>>op;
if(op=="get"){
int key;
cin>>key;
int res=lru.get(key);
if(res==-1){
cout<<"Not exist!"<<endl;
continue;
}
cout<<key<<":"<<res<<endl;
lru.show();
}else if(op=="put"){
int key,val;
cin>>key>>val;
lru.put(key,val);
lru.show();
}else{
cout<<"wrong command!"<<endl;
continue;
}
}
}
动态连通性
#include
using namespace std;
#define MAXN 10
int fa[MAXN+1];
void init(){
for(int i=1;i<=MAXN;i++)
fa[i]=i;
}
//返回当前节点的根节点,并进行路径压缩
int find(int x){
if(fa[x]==x)
return x;
fa[x]=find(fa[x]);
return fa[x];
}
//合并两个节点,注意在同一棵树上的问题
void merge(int a,int b){
fa[find(a)]=find(b);
}
按秩合并
优化版:#include
using namespace std;
const int MAXN = 7;
int fa[MAXN+1];
int rank_[MAXN + 1];
void init(){
for(int i=1;i<=MAXN;i++){
fa[i]=i;
rank_[i]=1;
}
}
int find(int x){
if(x==fa[x]){
rank_[x]=1;
return x;
}
fa[x]=find(fa[x]);
rank_[x]=2;
return fa[x];
}
void merge(int a,int b){
int ap=find(a);
int bp=find(b);
//秩(高度)低的,合并到秩高的树上
if(rank_[ap] > rank_[bp]){
fa[bp]=ap;
}else{
fa[ap]=bp;
}
//如果两棵树高度相同,那么合并后,合并的目标树高度要加一(排除同一棵树的情形)
if(rank_[ap] == rank_[bp] && ap != bp){
rank_[bp]++;
}
}
class Solution {
public:
bool isValidBST(TreeNode* root) {
return checkBST(root,LONG_LONG_MIN,LONG_LONG_MAX);
}
bool checkBST(TreeNode*root,long long low,long long high){
if(!root)
return true;
if(root->val<=low || root->val>=high)
return false;
return checkBST(root->left,low,root->val)
&& checkBST(root->right,root->val,high);
}
};
class Solution {
public:
//树的递归中,以当前节点为思考对象,最佳
bool isValidBST(TreeNode* root) {
return checkBST(root)[0];
}
//三个返回值:1.是否是BST 2.最小值 3. 最大值
vector<long long>checkBST(TreeNode*root){
if(!root)
return {
1,LONG_LONG_MAX,LONG_LONG_MIN};
vector<long long>l_res=checkBST(root->left);
vector<long long>r_res=checkBST(root->right);
if(l_res[0] && r_res[0] && root->val>l_res[2] && root->val<r_res[1]){
long long l_min=l_res[1];
long long r_max=r_res[2];
return {
1,l_min!=LONG_LONG_MAX?l_min:root->val,r_max!=LONG_LONG_MIN?r_max:root->val};
}
return {
0,0,0};
}
};
左子树序列化+根节点val字符串+右子树序列化
,因此要使用后序遍历//用**后序遍历**的方式,实现二叉树**前序的序列化**
string serializeTree(TreeNode*root){
//终止条件
if(!root)
return "";
//逻辑
string ans=intToStr(root->val);
string lStr=serializeTree(root->left);
string rStr=serializeTree(root->right);
if(lStr!=""){
ans+=','+lStr;
}
if(rStr!=""){
ans+=','+rStr;
}
return ans;
}
string intToStr(int n){
string s;
stringstream ss;
ss<<n;
ss>>s;
ss.clear();
return s;
}
今天对补码有了全新的认识
其中1000 0000
是规定的最小值,然后简单+1就是-127,可以一直简单+1到127
补码的好处就是让如1-2的运算,变成硬件最简单操作的相加运算
1-2=0000 0001
+1111 1110
=1111 1111
=-1
C++中如何获取int型的最大值和最小值
climits
中有INT_MAX
,INT_MIN
1000 0000...0000
,补码规定的负数的最小值, − 2 31 − 1 -2^{31}-1 −231−1~a>>1
即为0111 1111...1111
, 2 31 2^{31} 231反转链表这道题目
,迭代和递归方法都非常精妙,其中博主labuladong对递归的思考方式令我印象深刻,受益匪浅。#include
#include
typedef long long ll;
using namespace std;
vector<int> state; //11001中1表示消耗了数字
int len; //合法平方数个数
void init() {
for (ll i = 0; i * i <= 9876543210l; i++) {
ll tmp = i * i;
int st = 0;
if (tmp == 0) {
st = 1;
state.push_back(st);
continue;
}
bool flag = true;
while (tmp) {
int t = tmp % 10;
tmp /= 10;
if (st & (1 << t)) {
//存在1冲突
flag = false;
break;
} else
st |= (1 << t);
}
if (flag) {
state.push_back(st);
}
}
len = state.size();
}
//判断当前剩余数字能否实现num
bool check(int st, int numSt) {
return !(st & numSt);
}
ll ans = 0;
//在下标k~len-1范围内,找第一个合法的平方数
void f(int k, int st) {
//st 0表示可用
if(st==(1<<10)-1){
//0~9都是1
ans++;
return;
}
for (int i = k; i < len; i++) {
if (check(st, state[i])) {
f(i + 1, st | state[i]);
}
}
}
int main() {
//将范围内的平方数都找出来,将其消耗的数字的信息,存入state中
init();
//递归
f(0, 0);
cout << ans;
return 0;
}
今天写蓝桥杯国赛真题遇到2020十一届C++B组H题答疑这一题
看到一个对前缀和、树状数组、线段树的总结,写的不错
最长递增子序列
这道经典的算法题,用动态规划解法的时间复杂度是 O ( n 2 ) O(n^2) O(n2)
lower_bound()
可以实现)0x3f3f3f3f
的好处和1e9+7
一样,量级在10^9
,一般数字都小于它,可以用于无穷大。并且两个0x3f3f3f3f
相加,不会超出4字节32位int的表示范围。此外,用memset
进行初始化的时候,可以直接写成memset(arr,0x3f,sizeof arr)
,非常方便搭积木
这一题
不是所有都适合动态规划,比如蓝桥杯国赛搭积木这一题,记忆化递归是最优方案
dp[i]
需要与前i-1
项分别发生作用,那么就失去了动态规划的作用*(*arr+100)
这样才行arr
自身是二维指针11011
状态,其实是0000...0011011
共32位数据规模往往决定着该题用什么算法
注意,树状数组的下标,一定要从1开始,否则lowbit操作和树状数组的逻辑不匹配
一个非负数,取二进制最低位的1和后边的0,即lowbit操作
~a +1
=-a
Docker
蓝桥杯遇到题目不会写,想一下是否是考察以下知识点
减少时间复杂度的方法
p=(a+b+c)/2; S=sqrt(p(p-a)(p-b)(p-c) )
i
和arr[i]
lower_bound
和upper_bound
找到一个结果时,要想得到其下标,只需要用迭代器减去arr.begin()
即可lower_bound
和upper_bound
是基于二分的,也就意味着序列需要是从小到大排序好的class Solution {
public:
/** @param head The linked list's head.
Note that the head is guaranteed to be not null, so it contains at least one node. */
ListNode*myList;
Solution(ListNode* head) {
myList=head;
srand((unsigned int)time(NULL));
}
/** Returns a random node's value. */
int getRandom() {
int ans=myList->val; //初始选中第一个节点
ListNode*node=myList->next;
int i=2;
while(node){
if(rand()%i==0){
//1/i的概率选中当前的节点
ans=node->val; //覆盖掉之前的选择
}
i++;
node=node->next;
}
return ans;
}
};
//反向洗牌
for (int i = n - 1; i >= 0; --i) {
swap(shuffled[i], shuffled[rand() % (i + 1)]);
}
//正向洗牌:
for (int i = 0; i < n; ++i) {
int pos = rand() % (n - i);
swap(shuffled[i], shuffled[i+pos]);
}
int findLargest3Power(){
int x=3;
while(1){
int next=3*x;
//发生溢出时返回x
if(next/3!=x){
return x;
}
x=next;
}
}
//判断a能是否能被开方
bool canBeExtract(int a){
int res=sqrt(a);
return a==b*b;
}
C++判断一个浮点数小数部分是否为0
fmod(double , double)
fmod(a,1)==0
今日教训:写题目一定要草稿纸勾勾画画,不要想当然。
今天遇到一道很有意思的题目
写算法三大注意事项
算数基本定理:任何一个大于1的自然数 N,如果N不为质数,那么N可以唯一分解成有限个质数的乘积
质数的定义:在大于1的自然数中,除了1和它本身以外,不再有其他因数的自然数。(只能被1和自身整除的大于1的自然数)
自然数肯定包括0,否则应该叫正数才对。
拓展欧几里得算法
int extGcd(int a, int b, int &x, int &y) {
int gcd = a;
if (!b) {
x = 1, y = 0;
} else {
gcd = extGcd(b, a % b, y, x);
y -= (a / b) * x;
}
return gcd;
}
写一道算法题,开始写代码之前要做的事
逻辑运算中,今天遇到一个大坑
return !(st1 & st2) && !(st1 & st3) && !(st2 & st3)
return !(st1 & st2 & st3);
使用递归输出一个十进制数的二进制形式
void printBinary(int num) {
if(!num)
return;
printBinary(num>>1);
cout<<(num&1);
}
scanf
输入数字之后,如果要输入别的类型(如字符,字符串)一定要吸收换行符!(getchar())t<<=1;t+=c
;即可cstdio
头文件,还是要写,只有iostream
的话,printf
和scanf
有可能会报错&
的优先级比+
低!!
1e9+7
是不错的选择
有向图floyd
void floyd() {
for (int i = 0; i < N; i++) //src
for (int j = 0; j < N; j++) //des
for (int k = 0; k < N; k++) {
//mid
if (i == j || i == k || j == k)
continue;
if (dist[i][j] > dist[i][k] + dist[k][j])
dist[i][j] = dist[i][k] + dist[k][j];
}
}
无向图floyd
for(int i=0;i<n;i++)
for(int j=i+1;j<n;j++)
for(int k=0;k<n;k++) //基于无向图的优化
if(dist[i][k]+dist[k][j]<dist[i][j]){
dist[i][j]=dist[i][k]+dist[k][j];
dist[j][i]=dist[i][j];
}
hold持有
和unhold不持有
两个dp数组会比buy
和sell
更好,因为对一支股票每天只有两种状态,持有或者不持有。class Solution {
public:
int findTargetSumWays(vector<int>& nums, int target) {
int arr[2001];
fill(arr,arr+2001,0);
arr[nums[0]+1000]++;
arr[-nums[0]+1000]++;
// 记录已经能表示的target
for(int i=1;i<nums.size();i++){
int N=nums[i];
int temp[2001];
fill(temp,temp+2001,0);
for(int j=0;j<2001;j++){
if(arr[j]>0){
temp[j-N]+=arr[j];
temp[j+N]+=arr[j];
}
}
memcpy(arr,temp,2001*4);
}
return arr[target+1000];
}
};
dp[i][j]
和之前的dp的关系。其次就是,多状态问题(如股票买卖),最好将多种状态分开dp。同时,总结结果时也从必须根据dp的定义来返回。buy[i][j]
表示,0~i
天进行一共进行j
次买入,能够获取的最大利润sell[i][j]
表示,0~i
天进行一共j
次卖出,能够获取的最大利润这里的一共j次的一共非常重要,意味着第i天可以进行买\卖,也可以不买\不卖
class Solution {
public:
int maxProfit(int k, vector<int>& prices) {
int d=prices.size();
//buy[i][j]表示,0~i天进行一共进行j次买入,能够获取的最大利润
vector<vector<int> >buy(d+1,vector<int>(k+1,INT_MIN));
//sell[i][j]表示,0~i天进行一共j次卖出,能够获取的最大利润
vector<vector<int> >sell(d+1,vector<int>(k+1,0));
for(int i=1;i<d+1;i++){
int price=prices[i-1];
for(int j=1;j<=k;j++){
//两种情况,在第i天买入股票,或者不买
buy[i][j]=max(buy[i-1][j],sell[i-1][j-1]-price);
//两种情况,在第i天卖出股票,或者不卖
sell[i][j]=max(sell[i-1][j],buy[i][j]+price);
}
}
return sell[d][k];
}
};
dp一般有两种描述
class Solution {
public:
bool isMatch(string s, string p) {
int len1 = p.length(), len2 = s.length();
vector<vector<bool> > dp(len1 + 1, vector<bool>(len2 + 1, false));
dp[0][0] = true;
for (int i = 1; i < len1 + 1; i++)
for (int j = 0; j < len2 + 1; j++) {
//第一列特殊处理
if (j == 0) {
dp[i][j] = (p[i - 1] == '*' && dp[i - 2][j]);
continue;
}
//p串末尾是*
if (p[i - 1] == '*') {
//情形1:*表示0个前边的元素
//清醒2:*表示n(n>0)个前边的元素
dp[i][j] = dp[i - 2][j] || ((p[i - 2] == s[j - 1] || p[i - 2] == '.') && dp[i][j - 1]);
continue;
}
//p串末尾不是*
dp[i][j] = (p[i - 1] == s[j - 1] || p[i - 1] == '.') && dp[i - 1][j - 1];
}
return dp[len1][len2];
}
};
class Solution {
public:
//统计字符串中0和1的个数
void count(string &str,int &count0,int &count1){
for(int i=0;i<str.length();i++){
if(str[i]=='0')
count0++;
else
count1++;
}
}
int findMaxForm(vector<string>& strs, int m, int n) {
int len=strs.size();
//dp[i][j]表示,有i个0和j个1的情况下最多容纳几个子集
vector<vector<int> >dp(m+1,vector<int>(n+1,0));
//每个字符串是压缩空间前的横坐标
for(int k=0;k<strs.size();k++){
string &str=strs[k];
int count0=0,count1=0;
count(str,count0,count1);
//01背包,后向遍历,防止重复使用本轮的字符串
for(int i=m;i>=count0;i--){
//i
for(int j=n;j>=count1;j--){
//分两种情况,一种选中这个字符串,一种不选中这个字符串
dp[i][j]=max(dp[i][j],1+dp[i-count0][j-count1]);//第一个dp[i][j]是上一轮的结果
}
}
}
return dp[m][n];
}
};
普通01背包
普通01背包压缩空间
多维费用01背包
多维费用01背包压缩空间后
使用lower_bound解决问题!(找到第一个大于等于key的数,返回其迭代器)
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
vector<int>res;
for(int i=0;i<nums.size();i++){
int key=nums[i];
if(res.empty()){
res.push_back(key);
continue;
}
if(key>res[res.size()-1])
res.push_back(key);
else{
*lower_bound(res.begin(),res.end(),key)=key;
}
}
return res.size();
}
};
class Solution {
public:
int numDecodings(string s) {
//dp[i]表示:0~i位置一共有多少种编码方法
int len=s.length();
vector<int>dp(len+1,0); //s的下标对应dp的1、2、3...
//首个是0就无解
if(s[0]=='0')
return 0;
//排除长度为1的情况
if(len==1)
return 1;
//开始dp
dp[0]=1,dp[1]=1;
stringstream ss;
int t;
//i是dp的下标
for(int i=2;i<len+1;i++){
//s的下标
int index=i-1;
//独立算
if(s[index]!='0')
dp[i]+=dp[i-1];
//和前边合并算
if(s[index-1]!='0'){
ss<<s.substr(index-1,2);
ss>>t;
ss.clear();
if(t>=10 && t<=26)
dp[i]+=dp[i-2];
}
if(dp[i]==0)
break;
}
return dp[len];
}
};
写代码时,养成习惯:先理清思路,接着搭好框架(注释),最后再编码
重新写爬楼梯leetcode70
这一题,想都没想直接用递归,结果测试用例给了个44,超出时间限制。
2^n
,1s最多能容纳的n的规模约为27。多个选项中选多个的题目,计算合法方案数。这种题目可以利用全排列来写
int arr[]={0,0,0,1,1,1,};
消除尾一:x=x&(x+1)
10个数的全排列,时间复杂度为10!
最大公共子串
和最大公共子序列
在二维动态规划时,是不一样的
if(s1[i-1]==s2[i-1]) dp[i][j]=dp[i-1][j-1]+1;
背包问题的精髓在于:在递归或动态规划时,假设背包中已经装了xx的情况下,看剩下的容量怎么装。
(K-i+K-j)%K
(i以外需要凑的余数+j以外需要凑的余数)对K取余
int convert(int i,int j,int k){
int a=i-1,b=j-1,c=k-1;
return a*row*column+b*column+c;
}
递归写法
long long fastPow(long long base,int pow){
if(pow==0)
return 1;
if(pow==1)
return base;
if(pow%2==1){
return base*fastPow(base*base,pow/2);
}
return fastPow(base*base,pow/2);
}
非递归位运算写法
long long fastPow(long long base,int pow){
long long res=1;
while(pow){
//处理奇数
if(pow&1){
res*=base;
}
base*=base;
pow>>=1;
}
return res;
}
0~n
来逐个解决,每次选取数组中一个值时,对背包剩下的容量进行调整(一般是减少,也可能增加),装入背包后,剩余容量用已有的dp[j]来解决是背包问题能动态规划的核心原因。动态问题一定要搞清楚对谁进行0~n的划分(如果coins问题是对金额进行划分),也有可能是多个变量进行划分(二维、三维dp,但也可压缩。
01背包问题为什么要倒序遍历(压缩的)dp数组
01背包问题,动态规划的思路是:物品一个一个尝试,容量一点一点尝试,每个物品分类讨论的标准是:选与不选
safeReturn函数来处理第一行第一列的特殊情况
:class Solution
{
public:
int safeReturn(vector<vector<int> >&dp,int x,int y)
{
if(x<0 || y<0)
return 0;
return dp[x][y];
}
int longestCommonSubsequence(string text1, string text2)
{
int len1=text1.size(),len2=text2.size();
//dp[i][j]表示,text1前i个字符和text2前j个字符的LCS数
vector<vector<int> >dp(len1,vector<int>(len2,0));
for(int i=0; i<len1; i++)
for(int j=0; j<len2; j++)
{
if(text1[i]==text2[j])
{
dp[i][j]+=1;
dp[i][j]+=safeReturn(dp,i-1,j-1);
}
else
{
dp[i][j]=max(safeReturn(dp,i-1,j),safeReturn(dp,i,j-1));
}
}
return dp[len1-1][len2-1];
}
};
dp[i]
记录的可以是中间结果,也可以是最终结果。如果记录的是中间结果,最后不能忘了遍历并返回最大值或最小值
。next_permutation
或者自定义DFS+回溯
的递归函数实现;选中
和不选中
两种方法。void permutation(vector<int>&nums,int k){
//在下一轮进行判断
if(k==nums.size()){
ans.push_back(nums);
return;
}
set<int>record;
for(int i=k;i<nums.size();i++){
int target=nums[i];
if(record.find(target)!=record.end()){
continue;
}
record.insert(target);
swap(nums[i],nums[k]);
permutation(nums,k+1);
swap(nums[i],nums[k]);
}
递归+回溯
实现,其递归函数逻辑如下//实现排列数,n个数全排列
void f(int k){
if(k==n+1){
生成一种排列;
return;
}
for(int i=k;i<=n;i++){
swap(nums[i], nums[k]);
f(k+1);
swap(nums[i], nums[k]);
}
}
//实现组合数,从n个数种挑选m个
void f(int k){
if(已选中的个数==m){
生成一种组合;
return;
}
if(k==n+1){
return;
}
选中第k个数;
f(k+1);
不选第k个数;
f(k+2);
}
// 题目描述:在一个未排序的数组中,找到第 k 大的数字
class Solution {
public:
int findKthLargest(vector<int> &nums, int k) {
int l = 0, r = nums.size() - 1, target = r - k + 1;
while (1) {
int p = fix(nums, l, r);
if (p == target)
break;
if (target < p) {
r = p - 1;
} else
l = p + 1;
}
return nums[target];
}
//找到l位置元素的最终index,并返回
int fix(vector<int> &nums, int l, int r) {
int i=l,j=r;
while(1){
while(i<=r && nums[i]<=nums[l])
i++;
if(i==r+1){
swap(nums[l],nums[r]);
return r;
}
while(j>=l && nums[j]>=nums[l])
j--;
if(j==l-1){
return l;
}
if(i>j)
break;
swap(nums[i],nums[j]);
}
swap(nums[l],nums[j]);
return j;
}
};
int main() {
vector<int> vec = {
10, 5, 6, 0, 0, 1, 2};
cout << Solution().findKthLargest(vec,3)<<endl;
return 0;
}
class Solution {
public:
int mySqrt(int x) {
long long a=x;
while(a*a>x){
a=(a+x/a)/2;
}
return a;
}
};
int chars[128]
。int*a=NULL;
判断a是否为NULL/nullptr
可以用if(!a)
表示a是NULLclass Solution {
public:
int candy(vector<int>& ratings) {
int size=ratings.size();
if(size<2)
return size;
vector<int>num(size,1);
for(int i=1;i<size;i++){
if(ratings[i]>ratings[i-1])
num[i]=num[i-1]+1;
}
for(int i=size-1;i>0;i--){
if(ratings[i]<ratings[i-1])
num[i-1]=max(num[i-1],num[i]+1);
}
return accumulate(num.begin(),num.end(),0);
}
};
注意右边是闭区间
中数是否能被n整除,因为大于n1/2的数不可能被n整除a=set()
Integer.parseInt()
class Tuple<A,B>{
public final A a;
public final B b;
public Tuple(A a,B b){
this.a=a;
this.b=b;
}
}
动态规划的思想
三角形最小路径和
一题中,记录三角形中对应每个节点的最小路径和,最终可获得最后一行的最小路径和。java中截取字符串是消耗性能的,因此一般采取传递索引的方式来表示子串。
java中的==
对于基本数据类型byte,short,int,long,float,double,char,boolean
,是比较其值
对于引用数据类型,比较其内存地址
用“==”运算符,该运算符表示指向字符串的引用是否相同,比如: String a=“abc”;String b=“abc”,
那么a==b将返回true。这是因为在java中字符串的值是不可改变的,相同的字符串在内存中只会存
一份,所以a和b指向的是同一个对象;再比如:String a=new String(“abc”); String b=new String(“abc”);
那么a==b将返回false,因为a和b指向不同的对象。
String a="abc",b="abc";
String c=new String("abc"),d=new String("abc");
System.out.println(a==b);
System.out.println(c==d);
//输出结果
//true
//false
java中的StringBuilder的reverse()
方法,是原地逆置(在自身上逆置)
Java官方推荐使用Deque替代Stack使用,Deque堆栈操作方法:push()、pop()、peek()。
Deque<String>stack=new ArrayDeque<>();
java中将数组转化为ArrayList的方法,注意不能使用基本数据类型
Integer[]arr={
1,2,3};
List<Integer> lst=new ArrayList<>(Arrays.asList(arr));
//或者
List<String>l=new ArrayList<>(Arrays.asList("aaaa","aaa"));
List wordDict
SetwordDictHashSet=new HashSet<>(wordDict)
int[]arr_1=Arrays.copyOfRange(arr,0,arr.length-1);
int
类型数据有时可能会出现中间结果溢出,用long
即可int[][]dp=new int[m+1][n+1];
<<
和>>
,能够大大加快运算for(int i=0;i就对了
p<=r
的原因是,r下标的元素并未处理,需要p来处理完成后才覆盖所有元素public void quickSort(int[]arr,int left,int right){
if(left>=right)
return;
int l=left,r=right;
int p=l;
int pivot=arr[left];
while(p<=r){
if(arr[p]>pivot){
int temp=arr[p];
arr[p]=arr[r];
arr[r]=temp;
r--;
}
else if(arr[p]<pivot){
int temp=arr[p];
arr[p]=arr[l];
arr[l]=temp;
l++;
p++; //此处注意需要p++
}
else
p++;
}
System.out.println("l="+l+",r="+r);
quickSort(arr,left,l-1);
quickSort(arr,r+1,right);
}
写法二
#include
void swap(int a[], int i, int j)
{
int t = a[i];
a[i] = a[j];
a[j] = t;
}
int partition(int a[], int p, int r)
{
int i = p;
int j = r + 1;
int x = a[p];
while(1){
while(i<r && a[++i]<x); //如果i位置元素小于基准,就一直往右,目的是使i位置>=基准
while(a[--j]>x); //如果j的位置大于x,就一直往左,目的是使j的位置<=基准
if(i>=j) break; //如果i>=j,跳出循环
swap(a,i,j); //否则,交换i,j位置上的元素
}
swap(a,p,j);
return j;
}
void quicksort(int a[], int p, int r)
{
if(p<r){
int q = partition(a,p,r); //确定一个元素的最终位置
quicksort(a,p,q-1);
quicksort(a,q+1,r);
}
}
其中标志l左侧的都小于基准,标指r右侧的都大于基准,故递归时使用范围[left,l-1]和[r+1,right]
Scanner
先输入一个数字,再输入一行,会报错
BigInteger
来求最大公因数GreatestCommonFactor(GCF)BigInteger two=BigInteger.valueOf(2);
BigInteger a=two.pow(20).subtract(BigInteger.ONE);
BigInteger b=two.pow(19);
BigInteger gcd=a.gcd(b);
public long quickPower(long base,long power){
long result=1;
while(power>0){
if(power%2==0){
power/=2;
base*=base;
}
else{
power/=2;
result*=base; //都在这一步乘到结果中了
base*=base;
}
}
return result;
}
public static long normalPower(long base,long power){
long result=1;
while(power>0){
if((power & 1)==1 ) result*=base; //这里与运算相当于判断二进制形式下最后一位是否是1,即是否是奇数
power>>=1;
base*=base;
}
return result;
}
&与运算
:二进制形式下,只有相同位置上都为1,才为1,否则为0union
类型中,赋值时,各成员只能有一个被赋值,即各成分互斥。union
类型只能赋值一个成员,具有互斥性union{
int cls;
char position[10];
}category;
(left+right)/2
产生溢出,那么可以写成left+(right-left)/2
void mergeSort(int arr[],int left,int right){
//递归终止条件
if(left==right)
return;
//把左边和右边排序好
int mid=(left+right)/2;
mergeSort(arr,left,mid);
mergeSort(arr,mid+1,right);
//然后将两个顺序序列合并
int temp[right-left+1];
int l=left,r=mid+1;
int i=0;
while(l<=mid && r<=right){
if(arr[l]<arr[r]){
temp[i++]=arr[l++];
}
else
temp[i++]=arr[r++];
}
while(l<=mid)
temp[i++]=arr[l++];
while(r<=right)
temp[i++]=arr[r++];
for(int j=left,i=0;j<=right;j++)
arr[j]=temp[i++];
}
/*a个字母A,b个字母B,c个字母C,能组成多少种不同的长度位固定n的字符串*/
/*
* 这种DFS的题目,其需要求出的是DFS能走要正确终点的路径数*/
#include
int DFS(int a,int b,int c,int n){
//这是我的思路,有某个字母数量还有才能递归下去
//给出两种终止条件
if(n==0)
return 1;
int res=0;
if(a>0)
res+=DFS(a-1,b,c,n-1);
if(b>0)
res+=DFS(a,b-1,c,n-1);
if(c>0)
res+=DFS(a,b,c-1,n-1);
return res;
}
int cal(int a, int b, int c, int n) {
//这是答案的思路:先递归下来,如果字母数量由0变成-1,那就剪枝return 0
//终止条件
if (a < 0 || b < 0 || c < 0)
return 0;
if (n == 0)
return 1;
//递归
return cal(a - 1, b, c, n - 1) + cal(a, b - 1, c, n - 1) + cal(a, b, c - 1, n - 1);
//这里的返回值代表的是,选择A作为当前位置字母的话后续共有多少种方案+选择B...+选择C...
//cal(a-1...)说明该方案选择A作为当前位置的字母,其返回值代表的是当前位置选择了A这种方案一共有多少种不同方法走到底,若下一步中判断出了a-1是<0的,那么返回值是0,相当于对这个方案做了剪枝
}
int main() {
printf("%d\n",DFS(1,2,3,3));
printf("%d",cal(1,2,3,3));
return 0;
}
//递归简约版
int gcd(int a,int b){
if(b==0)return a;
return gcd(b,a%b);
}
//二分法模板
int binarySearch(int arr[],int n,int key){
int l=0,r=n-1;
while(l<=r){
//注意这里必须是等号
int mid=(l+r)/2;
if(arr[mid]==key)
return mid;
if(arr[mid]<key){
l=mid+1;
}else{
r=mid-1;
}
}
}
next_permutation(指针1,指针2)
注意指针2是开区间∣A∪B∪C∣=∣A∣+∣B∣+∣C∣−∣A∩B∣−∣A∩C∣−∣B∩C∣+∣A∩B∩C∣
int arr[3]={0};
或int arr[3]={-1};
不能使用这种方法为数组初始化赋值
bool arr[3]={true};
&
是按位与
+1
,-1
,&
x=0011011
,那么x&(x+1)
=0011000
a/b==c&&a%b==0;
避免数学上和程序上的不一致//递归实现全排列
#include
#include
using namespace std;
int arr[]={
1,1,3};
void show(){
for(auto a:arr)
printf("%d ",a);
putchar('\n');
}
//k代表的是固定第i个位置的数
void full_permunation(int k){
if(k==3){
show();
return;
}
for(int i=k;i<3;i++){
//这个条件加上了,能实现去重的全排列
if(i!=k && arr[i]==arr[k])
continue;
swap(arr[i],arr[k]);
full_permunation(k+1);
swap(arr[i],arr[k]);
}
}
int main(){
full_permunation(0);
return 0;
}
next_permutation()
的优点在于可以自定义剪枝(剪枝时务忘回溯),优化效率13!=6,227,020,800
,执行13个数的全排列大概需要1分钟左右sqrt(a)*sqrt(a)==a
max(dp[i-1][j-1],dp[i][j-1],dp[i-1][j])
#include
#include
#include
using namespace std;
int main(){
char s1[100];
char s2[100];
int dp[101][101];
gets(s1);
gets(s2);
int len1=strlen(s1);
int len2=strlen(s2);
//初始化
fill(dp[0],dp[0]+101*101,0);
for(int i=1;i<len1+1;i++)
for(int j=1;j<len2+1;j++){
//计算三个值,取最大值
int t=dp[i-1][j-1];
if(s1[i]==s2[j])
t+=1;
dp[i][j]=max(max(t,dp[i][j-1]),dp[i-1][j-1]);
}
printf("%d",dp[len1][len2]);
return 0;
}
//LCS最长公共子串的dp 一维数组写法
#include
#include
#include
using namespace std;
int main(){
char s1[100];
char s2[100];
int dp[101];
int temp[101];
gets(s1);
gets(s2);
int len1=strlen(s1);
int len2=strlen(s2);
//初始化dp数组
fill(dp,dp+101,0);
fill(temp,temp+101,0);
for(int i=0;i<len1;i++)
for(int j=0;j<len2;j++){
int t=dp[j];
if(s1[i]==s2[j]) {
t+=1;
}
temp[j+1]=max(max(dp[j+1],t),temp[j]);
for(int k=0;k<len2;k++)
dp[k+1]=temp[k+1];
}
printf("%d",dp[len2]);
return 0;
}
回溯法
//深刻理解什么叫不考虑得到牌的先后顺序
//即第一张、第二章拿到A 和 第一张、第三张拿到A是一样的
//也就是说,重要的是A的张数
#include
#include
using namespace std;
int cards[13];
int ans=0;
int chosen=0; //记录已经选的牌的数量
//k表示选下标为k的牌的张数
void func(int k){
//终止条件
if(chosen==13){
ans++;
return;
}
else if(chosen>13){
//剪枝
return;
}
if(k==13){
//选完了还没选够13张牌
return;
}
for(int i=0;i<=4;i++){
chosen+=i;
cards[k]-=i;
func(k+1);
chosen-=i;
}
}
int main(){
fill(cards,cards+13,4);
func(0);
printf("%d",ans);
return 0;
}
非回溯法
#include
#include
using namespace std;
int cards[13];
int ans=0;
void f(int k,int c){
//终止条件
if(k>13 || c>13) //这里k不能=13是因为每次都是下一轮判断前面的选牌总数
return;
if(c==13){
ans++;
return;
}
//递归主体
for(int i=0;i<=4;i++){
f(k+1,c+i);
}
}
int main(){
fill(cards,cards+13,4);
f(0,0);
printf("%d",ans);
return 0;
}
abc
和cab
是同一个手链,只是旋转了一下abc
->abcabc
cab
是abcabc
的子串,那么abc
和cab
是旋转关系
s1.find(s2)!=string::npos
表示s2是s1的子串abcabc
和翻转cab
的子串关系‘在二维数组的两维之间来回切换
cur=0;
cur=1-cur;
对于结果需要对某个大数取余的题目,中间结果也需要去余,防止溢出
#define MOD 1000000007
,注意没有;
生成最小生成树的两个经典算法
克鲁斯卡尔算法Kruskal
Prim普利姆算法
本质是动态规划,使用三个数组
每一轮实现一个顶点数为1,2,3…n的最小生成树(动态规划)
具体方法
//实现一个并查集
#include
#include
using namespace std;
struct UFNode {
UFNode *parent;
UFNode() : parent(NULL) {
}
};
/**
* 目的是找到p所指向的节点的父节点
* 此外做了优化,将访问路径上的节点全部指向祖宗节点,这样下次查找祖宗节点时更快
* @param p
* @return
*/
UFNode *find(UFNode *p) {
set<UFNode *> path;
while (p->parent != NULL) {
path.insert(p); //这里放入的是p指向的节点的地址,而不是p的地址
p = p->parent;
}
//将路径上所有的节点,父指针都指向祖宗节点
for (set<UFNode *>::iterator it = path.begin(); it != path.end(); it++) {
(*it)->parent = p;
}
return p;
}
void merge(UFNode *p1, UFNode *p2) {
if(find(p1)==find(p2))
return;
//将p2的祖宗节点变成p1的祖宗节点的父节点
find(p1)->parent = find(p2);
}
int a=1,b=2;
int*p=&a;
vectorvec;
vec.push_back(p);
p=&b;
printf("%d",*vec[0]); //这里的结果是1
https://www.bilibili.com/video/BV18k4y1m7Ar?from=search&seid=15693083662541831658
#include
/**
* 如果主串中存在子串,则返回第一次匹配时,第一个字符的下标
* @param s 主串
* @param sub 子串
* @return 如果不存在,则返回-1
*/
int KMP(char s[],int len1,char sub[],int len2){
//创建next数组
int next[len2];
next[0]=0;
int i=0,j=1;
while(j<len2){
if(sub[j]==sub[i]){
next[j]=i+1; //i表示,再j位置之前的串中,其从0~i位置和末尾向匹配
i++; //当然我们从最大的可能性开始查找,直至i为0
j++;
}
else{
if(i==0){
next[j]=0;
j++;
}
else{
i=next[i-1];
}
}
}
i=0,j=0;
while(j<len2 && i<len1){
printf("i=%d,j=%d\n",i,j);
if(s[i]==sub[j]){
if(j==len2-1)
return i-len2+1;
i++;
j++;
}
else{
if(j==0){
i++;
}
else
j=next[j-1];
}
}
return -1;
}
int main(){
char s1[]="abxabcabcaby";
char s2[]="abcaby";
int ans=KMP(s1,12,s2,6);
printf("%d",ans);
return 0;
}
C/C++中怎样使用64位整数?
64位整数的类型为:long long
使用cin读的操作为:cin >> x;
使用cout写的操作为:cout << x;
使用scanf读的操作为:scanf("%l64d", &x);
使用printf写的操作为:printf("%l64d", x);
%10000
对一万取余&1
相当于取二进制最低位数字,一般用于判断奇偶数#include
#include
const int maxn=1000; //表长
int prime[maxn],pNum=0; //记录素数
bool p[maxn]={
false};
int N; //求N以内的素数
void f(int n){
for(int i=2;i<=n;i++){
if(!p[i]) //如果没有被筛掉,说明它是素数
prime[pNum++]=i;
for(int j=0;j<pNum;j++){
if(i*prime[j]>n)
break;
p[i*prime[j]]=true;
if(i%prime[j]==0)
break;
}
}
}
int main(){
memset(p,0,sizeof(p));
scanf("%d",&N);
f(N);
for(int i=0;i<pNum;i++){
printf("%d ",prime[i]);
}
return 0;
}
set
进行排序,自定义比较函数cmp(T p1, T p2)
,如果按照优先级从低到高,则返回值return 优先级低的<优先级高的
,其背后的原理时,sort将调换参数位置分别调用两次cmp
函数,如果一次返回true,一次返回false,则认定其一个对象小另一个大,若两次都返回false,则认定两个对象相等。
vector<pair<int, int> > time_and_shopID;
bool cmp(pair<int, int> p1, pair<int, int> p2) {
return p1.first < p2.first;
}
self
,调用的时候直接写函数名即可isalnum
第i项
和前i-1项
(或第i-1项
)的关系
^
在单个位上相同为0,不同为1
的性质使之
reduce
函数的作用是将序列所有元素以一定的规则累积起来
recude(lambda x,y:x+y,[1,2,3,4])
是对序列进行求和