刷Leetcode算法题目日志(C++)

刷Leetcode算法题目日志


前言

最近刷些算法题目,思来想去还是觉得以博客的形式来记笔记挺不错。所以这篇博客就是我今后一两个月时间刷代码题的日志,内容会很随意。 用的OJ是Leetcode

刚开始使用Leetcode遇到的问题

leetcode不需要有cin和cout,也不需要有main函数,它唯一有的就是一个叫做**Solution的类**,类中有一个函数,返回值和参数都给你写好了。但是代码怎么本地运行是个问题。
有下面两种方法:

1、手动添加头文件、main函数、输入样例等
2、VS Code刷力扣(LeetCode)题目
以及VScode无法登陆LeetCode的解决办法

————————————————————————————————————————————————————————————
                                                                                               正篇开始

有关C++代码函数方面的知识点

遇到的都会补充在这里

C++常用容器(转载)

string容器

string容器
本质:
	·string是C++风格的字符串,而string本质上是一个类
string和char*的区别:
	·char*是一个指针 
	·string是一个类,类内 不封装了char*,管理这个字符串,是一个char*型的容器。
特点:
	string类内部封装了很多成员方法
	例如:查找find,拷贝copy,删除delete,替换replace, 插入insert
	string 管理char* 所分配的内存,不用担心复制越界和取值越界等,由类内部进行负责

构造函数: 
string();					//创建一个空的字符串,例如:string str;
string(const char* s);		//使用字符串初始化
string(const string& str);	//使用一个string对象初始化里另一个string对象
string(int n, char c);		//使用n个字符c初始化

string赋值操作:
string & operator=(const char* s);		//char*类型字符串 赋值给当前的字符串 
string & operator=(const string &s);	//把字符串s赋值给当前的字符串	
string & operator=(char c);				//把字符赋值给当前的字符串
string & assign(const char* s);			//把字符串s赋值给当前的字符串
string & assign(const char* s, int n);	//把字符串s的前n个字符赋值给当前的字符串
string & assign(const string &s);		//把字符串s赋值给当前的字符串
string & assign(int n, char c);			//用n个字符c赋值给当前的字符串

tring字符串拼接:
string & operator+=(const char* str);		//重载+=操作符 
string & operator+=(char c);				//重载+=操作符 
string & operator+=(const string & str);	//重载+=操作符 
string & append(const char* s);				//把字符串s连接到当前字符串结尾
string & append(const char* s, int n);		//把字符串s连接到当前字符串结尾
string & append(const string & s);			//把字符串s连接到当前字符串结尾
string & append(const string & s, int pos, int n);//把字符串s中从pos开始的n个字符连接到当前字符串结尾 
string & push_back(char c)                //只能添加字符
string查找和替换:
int find(const string & str, int pos = 0)const;		//查找str第一次出现的位置,从pos开始查找
int find(const char* s, int pos = 0)const;			//查找s第一次出现的位置,从pos开始查找
int find(const char* s, int pos, int n)const;		//从pos查找s的前n个字符第一次位置
int find(const char c, int pos = 0)const;			//查找字符c第一次出现的位置,从pos开始查找
int rfind(const string & str, int pos = npos)const;	//查找str最后一次出现的位置,从pos开始查找
int rfind(const char* s, int pos = npos)const;		//查找s最后一次出现的位置,从pos开始查找
int rfind(const char* s, int pos, int n)const;		//从pos查找s的前n个字符最后一次位置
int rfind(const char c, int pos = 0)const;			//查找字符c最后一次出现的位置,从pos开始查找
string & replace(int pos, int n, const string & str);	//替换从pos开始n个字符为字符串str
string & replace(int pos, int n, const char* s);	//替换从pos开始n个字符为字符串s

string字符串比较:
int compare(const string & s) const;	//与字符串s比较,相等则返回 0 
int compare(const char* s) const;		//与字符串s比较,相等则返回 0 

string字符存取:
char & operator[](int n);				//通过[]方式获取字符
char & at(int n);						//通过at方式获取字符

string插入和删除:
string & insert(int pos, const char*s);				//在pos处插入字符串 
string & insert(int pos, const string & str);		//在pos处插入字符串
string & insert(int pos, int n ,char c);			//在pos处插入n个c字符
string & erase(int pos, int n = npos);				//删除pos开始的n个字符

string子串:
string substr(int pos = 0; int n = npos) const;		//返回有pos开始的n个字符组成的字符串

string的长度获取
int length()          //返回string类型的长度
int size()

vector容器

vector容器:
vector构造函数:
vector<T> v;					//采用模板实现类实现,默认构造函数
vector(v.begin(), v.end());		//将v[begin(), end())区间中的元素拷贝给本身
vector(n, elem);				//构造函数将n个elem拷贝给本身
vector(const vector & vec);		//拷贝构造函数

vector赋值操作:
vector & operator=(const vector &vec);		//重载等号操作符
assign(beg,end);		//将[beg,end)区间中的数据拷贝赋值给本身
assign(n,ele);			//将n个elem拷贝赋值给本身

vector容量和大小:
empty();				//判断容器是否为空;
capacity();				//容器的容量
size();					//返回容器中的个数
resize(int num);		//重新指定容器的长度为num,若容器变长,则以默认值0填充新位置
						//如果容器变短,则末尾超出容器长度的元素被删除
resize(int num, elem);	//重新指定容器的长度为num,若容器变长,则以elem值填充新位置
						//如果容器变短,则末尾超出容器长度的元素被删除

vector插入和删除:
push_back(ele);					//尾部插入元素ele
pop_back();						//删除最后一个元素
insert(const_iterator pos, ele);//迭代器指向位置pos插入元素ele
insert(const_iterator pos, int n,ele);	//迭代器指向位置pos插入n个元素ele
erase(const_iterator pos);		//删除迭代器指向的元素
erase(const_iterator start, const_iterator end)	//删除迭代器从start到end之间的元素
clear();						//删除容器中所有的元素

vector数据存取:
at(int idx);		//返回索引idx所指的数据
operator[];			//返回索引idx所指的数据
front();			//返回容器中第一个数据
back();				//返回容器中最后一个数据元素

vector互换容器:
swap(vec);					//将vec与本身的元素互换
vector<int>(vec).swap(vec)	//收缩内存

vector预留空间:
reserve(int len);		//容器预留len个元素长度,预留位置不初始化,元素不可访问



获取vector容器最后一个元素
//方法一: 
return vec.at(vec.size()-1);
 
//方法二: 
return vec.back();

后面两种通过迭代器
//方法三: 
return *(vec.end()-1);  //注意:end指向末尾元素的下一个元素。
 
//方法四: 
return *(vec.rbegin());

deque容器

deque容器:
	·双端队列。可以对头端和尾部进行插入删除操作
deque与vector的区别:
	·vector对于头部插入删除效率低,数据量越大,效率越低
	·deque相对而言,对头部的插入删除速度比vector快
	·vector访问元素的速度会比deque快,这和两者的内部实现有关

deque内部工作原理:
	deque内部有个中控器,维护每段缓冲区中的内容,缓冲区中存放真实数据
	中控器维护的是每个缓冲区的地址,使得使用deque时像一片连续的内存空间

·deque容器的迭代器也是支持随机访问的

deque构造函数:
deque<T> deqT;					//采用模板实现类实现,默认构造函数
deque(deq.begin(), deq.end());	//将deq[begin(), end())区间中的元素拷贝给本身
deque(n, elem);					//构造函数将n个elem拷贝给本身
deque(const deque & deq);		//拷贝构造函数

deque赋值操作:
deque & operator=(const deque &deq);		//重载等号操作符
assign(beg,end);		//将[beg,end)区间中的数据拷贝赋值给本身
assign(n,ele);			//将n个elem拷贝赋值给本身

deque和大小:			//deque是没有容量(capacity)的概念的 
empty();				//判断容器是否为空;
size();					//返回容器中的个数
resize(int num);		//重新指定容器的长度为num,若容器变长,则以默认值0填充新位置
						//如果容器变短,则末尾超出容器长度的元素被删除
resize(int num, elem);	//重新指定容器的长度为num,若容器变长,则以elem值填充新位置
						//如果容器变短,则末尾超出容器长度的元素被删除

deque插入和删除:			//pos的位置是迭代器,eg: deque.begin(),deque.end()
push_back(ele);				//尾部插入元素ele
push_front(ele);			//头部插入元素ele	
pop_back();					//删除最后一个元素
pop_front();				//删除第一个元素
insert(pos, ele);			//在pos位置插入一个ele元素的拷贝,返回新数据的位置 
insert(pos, n, ele);		//在pos位置插入n个ele数据,无返回值 
insert(pos, begin, end);	//在pos位置插入[begin,end)区间的数据,无返回值 
clear();					//删除容器中所有的元素
erase(begin, end)			//删除[begin,end)区间的数据,返回下一个数据的位置 
erase(pos);					// 删除pos未知的数据,返回下一个数据的位置 

deque数据存取:
at(int idx);		//返回索引idx所指的数据
operator[];			//返回索引idx所指的数据
front();			//返回容器中第一个数据
back();				//返回容器中最后一个数据元素

deque排序
#include <algorithm> 
sort(iterator begin, iterator end)	//对begin到end区间内的元素进行排序
	//对于支持随机访问的迭代器的容器,都可以利用sort算法直接对其进行排序

stack、queue容器

stack容器:
概念:stack是一种先进后出(First in Last out, FILO)的数据结构,它只有一个出口

常用接口:
构造函数:
stack<T> stk;				//stack采用模板实现,stack对象默认构造形式
stack(const stack &stk);	//拷贝构造函数

赋值操作:
stack & operator=(const stack &stk); 	//重载=操作符

数据存取:
push(elem);					//向栈顶添加元素
pop(); 						//从栈顶移除第一个元素
top();						//返回栈顶元素
empty();					//判断堆栈是否为空
size();						//返回栈的大小

queue容器:
概念:Queue是一种先进先出(First in First out, FIFO)的数据结构,它有两个出口 

常用接口:
构造函数:
queue<T> que;				//queue采用模板类实现,queue对象的默认构造形式
queue(const queue &que);	//拷贝构造函数

赋值操作:
queue & operator=(const queue &que);	//重载=操作符

数据存取:
push(elem);					//往队尾添加元素
pop();						//从对头移除第一个元素
back();						//返回最后一个元素
front();					//返回第一个元素
empty();					//判断队列是否为空
size();						//返回队列大小 

list容器

list容器
list构造函数
list<T> lst;				//list采用模板类实现对象的默认构造形式
list(begin,end);			//构造函数将[begin,end)区间中的元素拷贝给本身
list(n,elem);				//构造函数将n个elem拷贝给本身
list(const list &lst);		//拷贝构造函数

list的赋值和交换:
assign(begin, end);					//将[begin, end)区间中的数据拷贝赋值给本身
assign(n, ele);						//将n个elem拷贝赋值给本身
list& operator=(const list &lst);	//重载等号操作符 
swap(lst);							//将lst与本身元素互换

list大小操作:
size();				//返回容器中元素的个数
empty();			//判断容器是否为空
resize(num);		//重新指定容器的长度为num,若容器变长,则以默认值填充新位置
					//如果容器变短,则末尾长处容器长度的元素被删除
resize(num ,elem)	//重新指定容器的长度为num,若容器变长,则elem填充新位置
					//如果容器变短,则末尾长处容器长度的元素被删除 

不可以随机访问???

list插入和删除:
push_back(elem);		//在容器尾部添加一个元素
pop_back();				//删除容器中最后一个元素
push_front(elem);		//在容器开头插入一个元素 
pop_front();			//从容器开头移除第一个元素		
insert(pos, elem);		//在pos位置插入elem元素的拷贝,返回新的数据位置
insert(pos, n, elem);	//在pos位置插入n个elem元素的拷贝,无返回值
insert(pos, begin, end);//在pos位置插入[begin, end)区间的数据,无返回值
clear();				//移除容器中的所有数据
erase(begin, end);		//删除[begin, end)区间的数据,返回下一个数据的位置
erase(pos);				//删除pos位置的数据,返回下一个数据的位置
remove(elem);			//删除容器中所有的与elem值匹配的元素 

list数据存取,排序,反转:
front();				//返回第一个元素
back();					//返回最后一个元素
reverse();				//反转链表
sort();					//链表排序
*/ 

set/multiset容器

set/multiset容器:
set基本概念:
	所有元素都会在插入时自动排序
本质:
	set/multiset属于关联式容器,底层结构是用二叉树实现。
set和multiset区别:
	set不允许容器中有重复元素
	multiset允许容器中有重复元素
	
set构造和赋值:
set<T> st;						//默认构造函数
set(const set &st);				//拷贝构造函数

set & operator=(const set &st);	//重载等号操作符 

size();			//返回容器中元素的数目
empty();		//判断容器是否为空
swap(st);		//交换两个容器 

set插入和删除:
insert(elem);			//在容器中插入元素
clear();				//清除所有元素
erase(pos);				//删除pos迭代器所指的元素,返回下一个元素的迭代器
erase(begin, end);		//删除区间[begin, end)的所有元素,返回下一个元素的迭代器
erase(ele);				//删除容器中值为ele的元素

set查找和统计:
find(key);		//查找key是否存在,若存在,返回该键的元素的迭代器;若不存在,返回,set.end();
count(key);		//统计key的元素个数

pair对组:
pair<type,type> p (value1, value2);
pair<type,type> p = make_pair(value1, value2);

set容器排序:
自定义的数据类型,都要指定排序规则
仿函数

unordered_set容器

C++ 11 为 STL 标准库增添了 4 种无序(哈希)容器
unordered_set 容器,可直译为“无序 set 容器”,即 unordered_set 容器和 set 容器很像,唯一的区别就在于 set 容器会自行对存储的数据进行排序,而 unordered_set 容器不会。

总的来说,unordered_set 容器具有以下几个特性:
1、不再以键值对的形式存储数据,而是直接存储数据的值;
2、容器内部存储的各个元素的值都互不相等,且不能被修改。
3、不会对内部存储的数据进行排序

另外,实现 unordered_set 容器的模板类定义在<unordered_set>头文件,并位于 std 命名空间中。
#include <unordered_set>
using namespace std;

unordered_set<string> uset;    //利用默认的构造函数创建unordered_set容器

unordered_set<string> uset{ "a","b", "c" };    //在创建 unordered_set 容器的同时,可以完成初始化操作

unordered_set<string> uset2(uset);            //可以调用 unordered_set 模板中提供的复制(拷贝)构造函数,将现有 unordered_set 容器中存储的元素全部用于为新建 unordered_set 容器初始化。
                            

C++ unordered_set容器的成员方法
unordered_set 类模板成员方法

map/multimap容器

map/multimap容器:
map基本概念:
	map中所有元素都是pair
	pair中第一个元素为key(键值),起到索引作用,第二个元素为value(实值)
	所有元素都会根据元素的键值排序
本质:
	map/multimap属于关联式容器,底层结构是二叉树实现
优点:
	可以根据key快速找到value值
map和multimap的区别:
	map不容许容器中有重复key值元素
	multimap允许容器中有重复的key值元素

map构造函数和赋值:
//map中所有元素都是成对出现,插入数据的时候要使用对组
map<T1, T2> mp;					//map默认构造函数
map(const map &mp);				//拷贝构造函数
map& operator=(const map &mp);	//重载等号操作符

map的大小和交换:
size();				//返回容器中元素的数目
empty();			//判断容器是否为空
swap(mp);			//交换两个集合容器

map插入和删除:

map插入元素的四种方法:
方法一:pair
	map<int, string> mp;
	mp.insert(pair<int,string>(1,"aaaaa"));
方法二:make_pair
	map<int, string> mp;
	mp.insert(make_pair<int,string>(2,"bbbbb"));
方法三:value_type
	map<int, string> mp;
	mp.insert(map<int, string>::value_type(3,"ccccc"));
方法四:[]
	map<int, string> mp;
	mp[4] = "ddddd";

四种方法异同:
前三种方法当出现重复键时,编译器会报错,而第四种方法,当键重复时,会覆盖掉之前的键值对。


clear();			//清除所有元素
erase(pos);			//删除pos迭代器所指的元素,返回下一个元素
erase(begin,end);	//删除区间[begin, end)的所有元素,返回下一个元素的迭代器
erase(key);			//删除容器中值为key的元素

map查找和统计:
find(key);			//查找key是否存在,若存在,则返回该键的元素的迭代器;若不存在,返回set.end()
cout(key);			//统计key元素的个数

map容器排序:
map容器默认排序规则为:按照key值进行 从小到大排序
利用仿函数,改变排序规则

unorder_map容器

algorithm库

c++中的algorithm库,包含了所有vector、list、set、map容器的一些函数,如查找,替换、排序、计数等常用的功能

常用的函数:
1max()min()abs()比较数字
这个在math头文件也可以,浮点型的绝对值要用math的fabs

#include <iostream>
using namespace std;
int main()
{
    int a, b;
    cin >> a >> b;
    cout << max(a, b) << endl;
    cout << min(a, b) << endl;
    cout << abs(a - b) << endl;
    return 0;
}

2*max_element()*min_element()比较容器(数组、字符串等)
参数是两个指针或迭代器表示范围,返回一个地址
注意,min_element和max_element要加星号*

3swap()交换值
这个一般不用algorithm库也可以实现swap函数,但还是比较常用

4reverse()翻转容器
reverse()函数可以将一个容器直接翻转,例如数组、动态数组和字符串等
reverse(str.begin(), str.end());

5、快速排序——sort函数
sort(sortv.begin(), sortv.end());
把sort函数改成降序:
sort(v.begin(), v.end(), cmp); //名称任意
bool cmp(int a, int b)
{
    return a > b;
}

————————————————————————————————————————————————————————————
                                                                                              分隔符

做题遇到的C++小知识点

note1:length()和size()的区别

· c++中,length()只是用来获取字符串的长度。
· c++中,在获取字符串长度时,size()函数与length()函数作用相同。
除此之外,size()函数还可以获取vector类型的长度。

note2:NULL和nullptr的区别

NULL和nullptr的区别:
因为C++里面有重载而C没有,所以NULL在c++里容易引起二义性,
NULL 在 C++ 中被定义为 0,而 nullptr 是一个空指针常量类型,但并没有实际的类型名称。

总结
在 C++ 中表示指针的地方,使用 nullptr 表示空指针。尽量不使用 NULL 和 __null

https://blog.csdn.net/nyist_zxp/article/details/118458563

note3:c++结构体几种初始化方法

1、通过冒号初始化

结构体名(传入参数): 成员变量1(参数1),成员变量2(参数2{}
struct Entry{
    Keytype _key;
    Valuetype _value;
    Entry(Keytype key=NULLKEY,Valuetype value=0): _key(key),_value(value){}
};


int main(){
    Entry a;
    Entry b(2,3);
    cout<< a._key << a._value <<endl;
    cout<< b._key << b._value <<endl;

    return 0;
}

2、初始化列表

 Entry c={2,3};
 cout<< c._key << c._value <<endl;

如果某个结构成员未被初始化,则所有跟在它后面的成员都需要保留为未初始化
不能在结构体声明中初始化结构体成员

3、构造函数初始化
虽然初始化列表易于使用,但它有两个缺点:

1、如果有某个成员未被初始化,那么在这种情况下,跟随在该成员后面的成员都不能初始化。
2、如果结构体包括任何诸如字符串之类的对象,那么在许多编译器上它都将无法运行。

struct Employee
{
    string name;    // 员工姓名
    int vacationDays,    // 允许的年假
    daysUsed;    //已使用的年假天数
    Employee (string n ="",int d = 0)    // 构造函数
    {
        name = n;
        vacationDays = 10;
        daysUsed = d;
    }
};

在建立结构体数组时,如果只写了带参数的构造函数将会出现数组无法初始化的错误,因此要写一个无参数的构造函数
下面是一个比较安全的带构造的结构体示例

Copy
struct node{
    int data;
    string str;
    char x;
    //注意构造函数最后这里没有分号哦!
  node() :x(), str(), data(){} //无参数的构造函数数组初始化时调用
  node(int a, string b, char c) :data(a), str(b), x(c){}//有参构造
}N[10];

也就是第一种冒号初始化

一道有关结构体链表的题目

https://leetcode-cn.com/problems/add-two-numbers/

————————————————————————————————————————————————————————————
                                                                                              分隔符

算法笔记

快速乘(俄罗斯农民乘法)

算法简述:

考虑 A 和 B 两数相乘的时候我们如何利用加法和位运算来模拟,其实就是将 B 二进制展开,如果 B 的二进制表示下第 ii 位为 1,那么这一位对最后结果的贡献就是 A(1<<i)A∗(1<<i) ,即 A<<iA<<i。我们遍历 B 二进制展开下的每一位,将所有贡献累加起来就是最后的答案,这个方法也被称作「俄罗斯农民乘法」。这个方法经常被用于两数相乘取模的场景,如果两数相乘已经超过数据范围,但取模后不会超过,我们就可以利用这个方法来拆位取模计算贡献,保证每次运算都在数据范围内
下面给出这个算法的 C++ 实现:

int quickMulti(int A, int B) {
    int ans = 0;
    for ( ; B; B >>= 1) {
        if (B & 1) {
            ans += A;
        }
        A <<= 1;
    }
    return ans;
}

leetcode题目链接

https://leetcode-cn.com/problems/qiu-12n-lcof/solution/qiu-12n-by-leetcode-solution/
https://leetcode-cn.com/problems/recursive-mulitply-lcci/

俄罗斯农民乘法详解

斐波那契数列

斐波那契数列不难,但是很经典。下面来总结下斐波那契数列的几种算法:
1、暴力递归

    int fib(int n) {
        if(n==0)
            return 0;
        else if(n==1)
            return 1;
        else
            return fib(n-1)+fib(n-2);
    }

2、递归加记忆化

 int fib(int n,map<int,int> &m){
      if(n<2)
         return n;
      if(m.count(n))
         return m[n];
      m[n]=fib(n-1,m)+fib(n-2,m);
      return m[n];
    }

3、动态规划DP,即递推,然后使用「滚动数组思想」把空间复杂度优化成O(1)

int fib(int n) {
        if (n < 2) {
            return n;
        }
        int p = 0, q = 0, r = 1;
        for (int i = 2; i <= n; ++i) {
            p = q; 
            q = r; 
            r = p + q;
        }
        return r;
    }

4、矩阵快速幂、通项公式
链接

C++基于范围的for循环

以下是基于范围的 for 循环的一般格式:

for (dataType rangeVariable : array)
    statement;
现在来仔细看一看该格式的各个部分:
· dataType:是范围变量的数据类型。它必须与数组元素的数据类型一样,或者是数组元素可以自动转换过来的类型。
· rangeVariable:是范围变量的名称。该变量将在循环迭代期间接收不同数组元素的值。在第一次循环迭代期间,它接收的是第一个元素的值;在第二次循环迭代期间,它接收的是第二个元素的值;以此类推。
· array:是要让该循环进行处理的数组的名称。该循环将对数组中的每个元素迭代一次。
· statement:是在每次循环迭代期间要执行的语句。要在循环中执行更多的语句,则可以使用一组大括号来包围多个语句。

可以使用基于范围的 for 循环来显示 numbers 数组的内容

for(int val : numbers)
{
    cout << "The next value is ";
    cout << val << endl;
}


如果需要的话,可以使用 auto 关键字指定范围变量的数据类型,而不必手动指定,示例如下:
for (auto val : numbers)
{
    cout << "The next value is ";
    cout << val << endl;
}


使用基于范围的 for 循环来修改数组
当基于范围的 for 循环执行时,其范围变量将仅包含一个数组元素的副本。因此,不能
使用基于范围的 for 循环来修改数组的内容,除非将范围变量声明为一个引用。引用变
量是其他值的一个别名,任何对于引用变量的修改都将实际作用于别名所代表的值。

要将范围变量声明为引用变量,可以在循环头的范围变量名称前面添加一个 & 符号。

 for (int &val : numbers)
    {
        cout << "Enter an integer value: ";
        cin >> val;
    }

http://c.biancheng.net/view/1416.html
https://zhuanlan.zhihu.com/p/351615111

二分查找

刷题的时候刷到了一道有关二分查找的题目,顺便写了下二分查找的模板:

int binary_search(vector<int>& nums, int target) {
        int n=nums.size();
        if(n==0){
           return -1;
        }
        if(n==1){
            if(nums[0]==target)
               return 0;
            else 
               return -1;
        }
        int l=0,r=n-1;
        while (l<=r){
            int mid=(r+l)/2;
            if(target==nums[mid])
               return mid;
            if(target<nums[mid]){
                  r=mid-1;
               }
               else
                  l=mid+1;         
        }
        return -1;
    }      

那道题目的链接:
搜索旋转排序数组

C++位运算

1 位逻辑运算符:

常用的位运算符

  & (位   “与”)  and
  ^  (位   “异或”)
  |   (位    “或”)   or
  ~  (位   “取反”)

2 移位运算符:
<<(左移)
>>(右移)

位逻辑运算

& 运算

&运算通常用于二进制取位操作,例如一个数 
&1的结果就是取二进制的最末位。
&3就是取二进制的后两位
&7就是取二进制的后三位
&0xf就是取二进制的后四位

| 运算

| 运算通常用于二进制特定位上的无条件赋值,例如一个数|1的结果就是把二进制最末位强行变为1
如果需要把二进制最末位变成0,对这个数 |1之后再减一就可以了

^ 运算———不同则为1,相同则为0

^运算通常用于对二进制的特定一位进行取反操作,`^运算的逆运算是它本身,也就是说两次异或同一个数最后结果不变,即(a^b)^b=a`;
^运算还可以实现两个值的交换而不需要中间变量,例如:

先看加减法中交换实现
void swap(long int &a,long int &b)

{

     a = a+b;

     b = a-b;

     a = a-b;

}

void swap(long int &a,long int &b)

{

    a = a^b;

    b = a^b;

    a = a^b;


}
所以 ^运算可以理解成类似加法(+)记忆 , 1+1 =01+0 =10+1 =10+0 =0//因为机器码是二进制,1+1=2%2 =0,其实不然

’<<’ 和 '>>'

a<<b 表示把a转为二进制后左移b位(在后面添加 b个0
<<相似,a>>b表示二进制右移b位(去掉末b位),相当于a除以2的b次方(取整)
>>代替除法运算可以使程序的效率大大提高

位移运算运用的例子

1.合并数据

缩短数据:int a =4; int b=2;  可以将数据 a,b 保存于一个变量 int c中,在此int 类型为32位

a=0x0000 0004; / /十六进制

b=0x0000 0002;

int c = a<<16;//左移操作-将a数据向左移动16位=0x0004 0000

 c |=b;  // (|)操作,一个为1 则为1,所以高16位不变,低16位值为 b值,即c = 0x0004 0002;完成数据的合并

2.解析数据

上面c = 0x0004 0002;

读取高位:int a1 = c>>16; / / 右移16位,消除低位数据,读取高位数据 a1 = 0x0000 0004

读取低位:int a2 = c&0xFFFF; //(&)操作,2个都为1 则为1,所以0xFFFF 即 0X0000 FFFF, 所以高位全为0,低位的 1不变,0还是0,a2=0x0000 0002,读取低位成功

读取低位2int a2 = c<<16; 消除高位,低位存入高位,a2=0x0002 0000;

    a2 = a2>>16;高位存入低位,消除低位; a2 = 0x0000 0002;

在这里插入图片描述
在计算机中,负数以其正值的补码形式表达。
转载于

leetcode题目405. 数字转换为十六进制数

做题中常用的排序算法总结

1、选择排序

算法思想:在未排序的序列中找到最小(最大)元素与待排序列的首个元素交换位置;然后重复此操作知道排序完成
时间复杂度 O(n^2)
void selection_sort(T arr[], int len) {
        for (int i = 0; i < len - 1; i++) {
                int min = i;
                for (int j = i + 1; j < len; j++)
                        if (arr[j] < arr[min])
                                min = j;
                swap(arr[i], arr[min]);     //algorithm库的交换函数
        }
}

2、冒泡排序

算法思想:依次比较相邻的两个元素,一轮比较结束后 最大元素(最小元素)会浮到待排序列的末尾,重复上述此操作知道序列排序完成。
时间复杂度:O(n^2void bubble_sort(T arr[], int len) {
        int i, j;
        for (i = 0; i < len - 1; i++)
                for (j = 0; j < len - 1 - i; j++)
                        if (arr[j] > arr[j + 1])
                                swap(arr[j], arr[j + 1]);
}

3、插入排序

算法思想:对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。
时间复杂度:O(n^2void insertion_sort(int arr[],int len){
        for(int i=1;i<len;i++){
                int key=arr[i];
                int j=i-1;
                while((j>=0) && (key<arr[j])){
                        arr[j+1]=arr[j];
                        j--;
                }
                arr[j+1]=key;
        }
}

4、快速排序

算法思想:快速排序使用分治法(Divide and conquer)策略来把一个串行(list)分为两个子串行(sub-lists)。
快速排序又是一种分而治之思想在排序算法上的典型应用。本质上来看,快速排序应该算是在冒泡排序基础上的递归分治法。

算法步骤:
1、从数列中挑出一个元素,称为 "基准"(pivot);

2、重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;

3、递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序;

时间复杂度:
快速排序的最坏运行情况是 O(),比如说顺序数列的快排。但它的平摊期望时间是 O(nlogn),且 O(nlogn) 记号中隐含的
常数因子很小,比复杂度稳定等于 O(nlogn) 的归并排序要小很多。所以,对绝大多数顺序性较弱的随机数列而言,快速排序
总是优于归并排序。
——————————————————————————————————————————————————————————————————————————————————————————————————————
实现1int Paritition1(int A[], int low, int high) {
   int pivot = A[low];
   while (low < high) {
     while (low < high && A[high] >= pivot) {
       --high;
     }
     A[low] = A[high];
     while (low < high && A[low] <= pivot) {
       ++low;
     }
     A[high] = A[low];
   }
   A[low] = pivot;
   return low;
 }
 
void QuickSort(int A[], int low, int high) //快排母函数
 {
   if (low < high) {
     int pivot = Paritition1(A, low, high);
     QuickSort(A, low, pivot - 1);
     QuickSort(A, pivot + 1, high);
   }
 }

——————————————————————————————————————————————————————————————————————————————————————————————————
实现2void sort(int a[], int low, int hight) {
        int i, j, index;
        if (low > hight) {
            return;
        }
        i = low;
        j = hight;
        index = a[i]; // 用子表的第一个记录做基准
        while (i < j) { // 从表的两端交替向中间扫描
            while (i < j && a[j] >= index)
                j--;
            if (i < j)
                a[i++] = a[j];// 用比基准小的记录替换低位记录
            while (i < j && a[i] < index)
                i++;
            if (i < j) // 用比基准大的记录替换高位记录
                a[j--] = a[i];
        }
        a[i] = index;// 将基准数值替换回 a[i]
        sort(a, low, i - 1); // 对低子表进行递归排序
        sort(a, i + 1, hight); // 对高子表进行递归排序
 
    }

先总结这四种吧,前三种比较简单且常用,快排比较经典

计数排序

计数排序的核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数

当输入的元素是 n 个 0 到 k 之间的整数时,它的运行时间是 Θ(n + k)。计数排序不是比较排序,排序的速度快于任何比较排序算法。

由于用来计数的数组C的长度取决于待排序数组中数据的范围(等于待排序数组的最大值与最小值的差加上1),这使得计数排序对于数据范围很大的数组,需要大量时间和内存。

 算法的步骤如下:

1)找出待排序的数组中最大和最小的元素
2)统计数组中每个值为i的元素出现的次数,存入数组C的第i项
3)对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加)
4)反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1

动图演示:
动图演示
leetcode题目:

https://leetcode-cn.com/problems/how-many-numbers-are-smaller-than-the-current-number/

计数排序代码:
来自官方题解

class Solution {
public:
    vector<int> smallerNumbersThanCurrent(vector<int>& nums) {
        vector<int> cnt(101, 0);
        int n = nums.size();
        for (int i = 0; i < n; i++) {
            cnt[nums[i]]++;
        }
        for (int i = 1; i <= 100; i++) {
            cnt[i] += cnt[i - 1];
        }
        vector<int> ret;
        for (int i = 0; i < n; i++) {
            ret.push_back(nums[i] == 0 ? 0: cnt[nums[i]-1]);
        }
        return ret;
    }
};

滑动窗口(无重复字符的最长子串)

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        int n=s.size();
        unordered_set<char> m;
        int rk=-1,ans=0;    //右指针的位置,初始位置为-1
        for(int i=0;i<n;i++){
            if(i!=0){
                m.erase(s[i-1]);  //移除一个字符
            }
            while(!m.count(s[rk+1])&&rk+1<n){  //当这个字符不重复且字符没到头
                m.insert(s[rk+1]);          //添加字符到集合中
                rk++;                       //右指针向右移动
            }
            
            ans=max(ans,rk-i+1);           //每次都更新最大长度
        }
        return ans;

    }
};

题目链接:
无重复字符的最长子串
——————————————————————————————————————————————————————

双指针

合并两个有序数组(逆向双指针)

class Solution {
public:
    void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
        int a=m-1,b=n-1;
        while(a>=0&&b>=0){
            if(nums1[a]>=nums2[b]){
                nums1[a+b+1]=nums1[a];
                --a;
            }
            else{
                nums1[a+b+1]=nums2[b];
                --b;
            }

        }
        while(b>=0){
            nums1[b]=nums2[b];
            --b;
        }
    }
};

题目链接:
第88题 合并两个有序数组

盛最多水的容器(双指针)

class Solution {
public:
    int maxArea(vector<int>& height) {
        int n=height.size()-1;
        int m=0;
        int ans=0;
        while(m<n){
            int a=min(height[m],height[n])*(n-m);
            ans=ans>a ? ans:a;
            if(height[m]<height[n])
                ++m;
            else
                --n;
        }
        return ans;
    }
};

第11题 盛最多水的容器

三数之和(排序+双指针)

算法流程:
算法流程

所看到题解里最简单的写法

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        int n=nums.size();
        vector<vector<int>> ans;
        if(n<3){
            return ans;
        }

        sort(nums.begin(),nums.end());

        for(int i=0;i<n;i++){
            if(nums[i]>0)
                break;
            if(i>0 && nums[i]==nums[i-1])
                continue;
            int l=i+1,r=n-1;
            while(l<r){
                int p=nums[i]+nums[l]+nums[r];
                if(p==0){
                    ans.push_back({nums[i],nums[l],nums[r]});
                    ++l;
                    --r;  
                    while(l<r&&nums[l]==nums[l-1])
                        ++l;
                    while(l<r&&nums[r]==nums[r+1])
                        --r;
                       
                }
                else if(p>0)
                    --r;
                else
                    ++l;

            }
        }
    return ans;

    }
};
这道题的关键是避免重复,另外注意数组越界问题

第15题 三数之和

删除链表中倒数第n个结点(前后指针)

在这里插入图片描述

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 {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode* dummy=new ListNode(0,head);         //头结点指针
        ListNode* first=head; 
        ListNode* second=dummy;
        for(int i=0;i<n;i++){
            first=first->next;
        }
        while(first!=NULL){
            second=second->next;
            first=first->next;
        }

        second->next=second->next->next;
        ListNode* ans=dummy->next;
        return ans;
    }
};

第19题 删除链表的倒数第n个结点

dp动态规划

第5题 最长回文子串

第53题 最大子数组和

f(i)代表第i个数结尾的 连续子数组的最大和,
状态转移方程为 f(i) = max( f(i-1) + nums[i] ,nums[i])

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int pre=0;
        int maxA=nums[0];
        for(auto i:nums){
            pre=max(pre+i,i);
            maxA=max(pre,maxA);
        }
        return maxA;
    }
};

回溯法

八皇后问题

class Solution {
public:
    int col[14];            		//用来标记某行某列被放置了一个皇后,坐标为行数,数组值为列数
    vector<vector<string>> ans;
    vector<string> s;       		//在dfs函数中用来存放string数组时使用
    bool check(int r,int n){		//检查下一个放置的是否满足不同列,以及不在对角线上
        for(int i=0;i<n;i++){
        if(col[i]==r||abs(r-col[i])==abs(n-i))
        return false;
        }
        return true;

    }
    void Dfs(int k,int n){        //递归回溯函数
    if(k==n){						//当k==n时说明一种放置结果已经出现,就进行回溯
        for (int i=0;i<n;i++){      //存取这种情况到ans二维数组中
            
            string row = string(n,'.');
            row[col[i]-1]='Q';
            s.push_back(row);
        }
        ans.push_back(s);
        s.clear();
        return;
    }
    else
    {
        for(int i=1;i<=n;i++){          //遍历看哪种放置满足约束函数
            if(check(i,k)){
                col[k]=i;
                Dfs(k+1,n);             //进入下一行
            }
        }

    }


    }
    vector<vector<string>> solveNQueens(int n) {
        Dfs(0,n);
        return ans;
        
    }
};

第17题 电话号码的字母组合

这个代码与上面的八皇后问题结构一样,很类似。

class Solution {
public:
    vector<string> phoneboard={"","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
    string tmp;
    vector<string> ans;
    void def(int n,string digits){
        if(n==digits.size()){
            ans.push_back(tmp);
            return;
        }
        else{
            int num=digits[n]-'0';
            for(int i=0;i<phoneboard[num].size();i++){
                tmp.push_back(phoneboard[num][i]);
                def(n+1,digits);
                tmp.pop_back();
            }
        }

    }
    vector<string> letterCombinations(string digits) {
        if(digits.size()==0)
            return ans;
        def(0,digits);
        return ans;

    }
};

第39题 组合总和

排序+回溯
class Solution {
    void def(vector<vector<int>>& ans,vector<int>& temp,int sum,int j,vector<int>& candidates,int target ){
        if(sum==target){
            ans.push_back(temp);
            return;
        }
        for(int i=j;i<candidates.size();i++){        //从自身坐标开始防止重复
            if(sum+candidates[i]<=target){
                temp.push_back(candidates[i]);
                def(ans,temp,sum+candidates[i],i,candidates,target);
                temp.pop_back();
            }
            else{
                return;
            }
        }
    }
public:
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        sort(candidates.begin(),candidates.end());
        vector<vector<int>> ans;
        vector<int> temp;
        def(ans,temp,0,0,candidates,target);
        return ans;
    }
};

第46题 全排列

法一、交换两个数

class Solution {
    void def(vector<vector<int>>& ans,vector<int>& temp,int begin,int end){
        if(begin==end){
            ans.push_back(temp);
        }
        else{
            for(int i=begin;i<=end;i++){
                swap(temp[begin],temp[i]);            
                def(ans,temp,begin+1,end);
                swap(temp[begin],temp[i]);
            }
        }
    }
public:
    vector<vector<int>> permute(vector<int>& nums) {
        vector<vector<int>> ans;
        def(ans,nums,0,nums.size()-1);
        return ans;
    }
};

法二、标记数组

class Solution {
    int sign[7]={0};
    void def(vector<vector<int>>& ans,vector<int>& temp,vector<int> nums,int begin,int end){   //begin 添加的第几个数字 ,end数字总数
        if(begin==end){
            ans.push_back(temp);
            return;
        }
        else{
            for(int i=0;i<end;i++){
                if(sign[i]==0){
                    temp.push_back(nums[i]);
                    sign[i]=1;           
                    def(ans,temp,nums,begin+1,end);
                    temp.pop_back();
                    sign[i]=0;
                }
            }
        }
    }
public:
    vector<vector<int>> permute(vector<int>& nums) {
        vector<vector<int>> ans;
        vector<int> temp;
        def(ans,temp,nums,0,nums.size());
        return ans;
    }
};

第78题 子集
方法一、递归回溯法


class Solution {
    vector<vector<int>> ans;
    vector<int> temp;

    void def(int cur,vector<int> nums,int n){
        if(cur==n){
            ans.push_back(temp);
            return;
        }
        else{
            temp.push_back(nums[cur]);
            def(cur+1,nums,n);
            temp.pop_back();
            def(cur+1,nums,n);
        }

    }
public:
    vector<vector<int>> subsets(vector<int>& nums) {
        int n=nums.size();
        def(0,nums,n);
        return ans;
    }
};

方法二、迭代法加位运算
详细见官方题解

class Solution {
public:
    vector<int> t;
    vector<vector<int>> ans;

    vector<vector<int>> subsets(vector<int>& nums) {
        int n = nums.size();
        for (int mask = 0; mask < (1 << n); ++mask) {
            t.clear();
            for (int i = 0; i < n; ++i) {
                if (mask & (1 << i)) {
                    t.push_back(nums[i]);
                }
            }
            ans.push_back(t);
        }
        return ans;
    }
};

作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/subsets/solution/zi-ji-by-leetcode-solution/
来源:力扣(LeetCode)

合并两个有序数组

1、递归法
不得不说这个方法真是妙!

class Solution {
public:
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        if (l1 == nullptr) {
            return l2;
        } else if (l2 == nullptr) {
            return l1;
        } else if (l1->val < l2->val) {
            l1->next = mergeTwoLists(l1->next, l2);
            return l1;
        } else {
            l2->next = mergeTwoLists(l1, l2->next);
            return l2;
        }
    }
};

作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/merge-two-sorted-lists/solution/he-bing-liang-ge-you-xu-lian-biao-by-leetcode-solu/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

2、迭代法
自己手写版

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 {
public:
    ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
        ListNode* p=list1,*q=list2;
        ListNode* head,*tail;

        if(p==NULL)
            return q;
        if(q==NULL)
            return p;
        if(p->val<q->val){
            head=tail=p;
            p=p->next;
        }
        else{
            head=tail=q;
            q=q->next;
        }

        while (p&&q)
        {
            if(p->val<q->val){
                tail->next=p;
                p=p->next;
                tail=tail->next;
            }
            else{
                tail->next=q;
                q=q->next;
                tail=tail->next;
            }
            
        }
        if(p){
            tail->next=p;
        }
        if(q){
            tail->next=q;
        }
        return head;
        
    }
};

官方题解版和我写的不同之处在于:官方题解巧妙的借了一个空结点,避免了 两个
链表开头结点的单独比较,最后返回了这个空结点的next。学到了

下一个排列

STL提供求下一个排列组合的函数:next_permutation(下一个字典序)
有下列两种形式:

1、next_permutation (String begin, String end)
2、next_permutation (String begin, String end,Compare comp)

返回值:
如果没有下一个排列组合,返回 false ,否则返回true
每执行一次就会把新的排列放回到原来的空间里

复杂度O(n)

上一个排列组合的函数:prev_permutation

leetcode题目:
下一个排列

class Solution {
public:
    void nextPermutation(vector<int>& nums) {
        int len=nums.size();
        int sma=-1,lag;
        for(int i=len-2;i>=0;i--){
            if(nums[i]<nums[i+1]){
                sma=i;
                break;
            }
        }
        if(sma>=0){
            for(int i=len-1;i>sma;i--){
            if(nums[sma]<nums[i]){
                lag=i;
                break;
            }
        }

        swap(nums[sma],nums[lag]);
        }
        reverse(nums.begin()+sma+1,nums.end());

    }
};

                                                                                        结束符

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值