一.链表
1.反转链表
双指针,使用临时变量储存前驱节点的next。依次反转,返回后继节点curr.
ListNode* reverseList(ListNode* head) {
if(!head)return NULL;
ListNode* pre;
ListNode* curr;
curr = NULL;
pre = head;
while(pre){
ListNode* tem = pre->next;
pre->next = curr;
curr = pre;
pre = tem;
}
return curr;
}
反转链表||
反转从位置 m 到 n 的链表。请使用一趟扫描完成反转。
说明:
1 ≤ m ≤ n ≤ 链表长度。
示例:
输入: 1->2->3->4->5->NULL, m = 2, n = 4
输出: 1->4->3->2->5->NULL
ListNode* reverseBetween(ListNode* head, int m, int n) {
ListNode* pre = head;
ListNode* curr = NULL;
int changenum = n-m+1;
//1.移动m-1个位置,到达反转的开始节点,并储存到Lt
while(pre&&--m){
curr=pre;
pre=pre->next;
}
ListNode* Lt = pre;
//2.开始反转。反转结束后new_head为反转后链表的头节点,pre为后一个节点
ListNode* new_head = NULL;
while(pre&&changenum){
ListNode* tem = pre->next;
pre->next = new_head;
new_head = pre;
pre = tem;
changenum--;
}
Lt->next=pre;//反转后的尾结点与后一个节点相连
//3.判断是否从第一个节点开始反转
if(curr){//不是从第一个节点开始,头结点还是head
curr->next=new_head;
return head;
}
else{//从第一个节点开始,头结点是new_head
return new_head;
}
}
2.链表相交节点
采用两个链表各自的指针各走一步,走到本链表的尾结点时跳到另外一个链表的头结点,两个指针最终会在交汇点出相遇。总的步数不超过两个链表的节点之和。注意排除空链表的情况。
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
int length1 = computeLength(headA);
int length2 = computeLength(headB);
int step = length1+length2;
ListNode*head1=headA;
ListNode*head2=headB;
if(!head1||!head2)return NULL;
while(step){
if(head1==NULL){
head1=headB;
}
if(head2==NULL){
head2=headA;
}
if(head1==head2){
return head1;
}
head1=head1->next;
head2=head2->next;
step--;
}
return NULL;
}
int computeLength(ListNode* head){
int length = 0;
while(head){
length++;
head = head->next;
}
return length;
}
简单写法
ListNode *p=headA;
ListNode *q=headB;
while(p!=q){
p==NULL?p=headB:p=p->next;
q==NULL?q=headA:q=q->next;
}
return(p);
3.链表求环
141、142
思路1:使用set查找
bool hasCycle(ListNode *head) {
std::set<ListNode*> node_set;
while(head){
if(node_set.find(head)!=node_set.end()){//在set中找到了head
return head;
}
node_set.insert(head);//将节点插入set
head = head->next;
}
return NULL;
}
思路2:快慢指针,可以确定环的起点
快指针每次走两步,慢指针每次走一步。
ListNode *detectCycle(ListNode *head) {
if(!head)return NULL;
ListNode* fast = head;
ListNode* slow = head;
ListNode* meet;
while(fast&&slow){
fast = fast->next;
slow = slow->next;
if(fast == NULL)return NULL;
fast = fast->next;
if(fast == slow){
meet = fast;//记录下相交点
break;
}
}
while(meet&&head){
if(meet==head){
return head;
}
meet=meet->next;
head=head->next;
}
return NULL;
}
4.链表划分(86)
使用两个临时头节点分别存储小于x和大于等于x的节点。
ListNode* partition(ListNode* head, int x) {
ListNode less(0);
ListNode more(0);
ListNode* less_ptr = &less;//注意指针与变量之间是引用关系
ListNode* more_ptr = &more;
while(head){
if(head->val < x){
less_ptr->next = head;
less_ptr = head;
}
if(head->val >= x){
more_ptr->next = head;
more_ptr = head;
}
head = head->next;
}
less_ptr->next = more.next;
more_ptr->next = NULL;//不要忘记最后添加Null
return less.next;
}
5.排序链表合并(21)
同样使用临时头结点及相应的临时指针,遍历两个链表,将较小的量添加到临时节点之后,指针依次后移。
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
ListNode tem_node(0);
ListNode* tem_ptr = &tem_node;
ListNode* node1 = l1;
ListNode* node2 = l2;
while(node1||node2){
if(node2&&node1){
if(node1->val >= node2->val){
tem_ptr->next = node2;
tem_ptr = node2;
node2 = node2->next;
}
else {
tem_ptr->next = node1;
tem_ptr = node1;
node1 = node1->next;
}
}
else{
if(node1 == nullptr &&node2 != nullptr){
tem_ptr->next = node2;
tem_ptr = node2;
node2 = node2->next;
}
else if(node1 != nullptr &&node2 == nullptr){
tem_ptr->next = node1;
tem_ptr = node1;
node1 = node1->next;
}
}
}
tem_ptr->next = nullptr;
return tem_node.next;
}
代码二:最后哪个有剩余之间将其剩余段链表加到临时指针后就行
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
ListNode tem_node(0);
ListNode* tem_ptr = &tem_node;
ListNode* node1 = l1;
ListNode* node2 = l2;
while(node1&&node2){
if(node1->val >= node2->val){
tem_ptr->next = node2;
tem_ptr = node2;
node2 = node2->next;
}
else {
tem_ptr->next = node1;
tem_ptr = node1;
node1 = node1->next;
}
}
if(node1){
tem_ptr->next = node1;
}
if(node2){
tem_ptr->next = node2;
}
return tem_node.next;
}
6.合并k个升序链表(23)
注意列表形式的数据的解析处理
注意使用sort函数,比较非int类型的数据,需要自己写compare函数传入。
临时头结点来重新连接加点
用于C++中,对给定区间所有元素进行排序。
头文件是algorithm 时间复杂度为n*log2(n)
Sort函数使用模板: Sort(start,end,排序方法)
用到了快速排序,但不仅仅只用了快速排序,还结合了插入排序和堆排序。
vector、deque,适用sort算法。
使用vector将所有链表节点都push进入,然后按链表val值排序,最后将排序后的重新写成链表。
static bool com(const ListNode *a, const ListNode *b){
return a->val < b->val;//从小到大排序
}
ListNode* mergeKLists(vector<ListNode*>& lists) {
vector<ListNode*> vc;
for(int i=0;i<lists.size();i++){
ListNode* head = lists[i];
while(head){
vc.emplace_back(head);
head=head->next;
}
}
if(vc.empty()){
return nullptr;
}
std::sort(vc.begin(),vc.end(),com);//传入com函数
ListNode new_head(0);
ListNode* new_ptr = &new_head;
for(int i=0;i<vc.size();i++){
new_ptr->next=vc[i];
new_ptr=vc[i];
}
new_ptr->next=nullptr;
return new_head.next;
}
7.旋转链表(61)
旋转数k<len时情况正常,k=len时,旋转后的链表和原链表相同,k>len时,计算m=k%len,情况和旋转数k=m时是相同的。
设定两个指针,指向旋转节点的首节点的前一个节点和链表尾结点。找到这两个节点问题就可以解决。
void length(ListNode* head, int &len, ListNode* &node){
while(head){
node = head;
len++;
head = head -> next;
}
}
ListNode* rotateRight(ListNode* head, int k) {
ListNode* node = head;
ListNode* node1 = head;
int len = 0;
length(head, len, node);
if(len == 0) return nullptr;
int m = k % len;
if(m==0)return head;
else{
for(int i=1;i<len-m;i++){
node1 = node1->next;
}
ListNode* new_head = node1->next;
node1->next = nullptr;
node -> next = head;
return new_head;
}
}
8.汇总区间(228)
//拼接形成字符串的函数
void strcom(int begin, int end, string &str){
if(begin==end){
str = to_string(end);
}
else{
str = to_string(begin) + "->" + to_string(end);
}
}
vector<string> summaryRanges(vector<int>& nums) {
vector<string> res;
if(nums.size()==0)return res;
int begin = nums[0];
int end = nums[0];
string str = "";
for(int i=1;i<nums.size();i++){
if(nums[i]==end+1){
end = nums[i];
continue;
}
strcom(begin,end,str);
res.push_back(str);
begin = nums[i];
end = nums[i];
}
//末尾最后一组添加进result
strcom(begin,end,str);
res.push_back(str);
return res;
}
9.k个一组,翻转链表(25)
使用双指针的方法写翻转子链表的函数,返回变量为翻转后的链表的头结点和尾结点。
pair<ListNode*,ListNode*>reverse(ListNode* start, ListNode* end){
if(start==end){
return {start,end};
}
ListNode* cur = start;
ListNode* pre = start->next;
start->next = end->next;//先将start指向end之后的元素
ListNode* r = end->next;
while(pre!=r){//注意end->next要提前保存,end->next会随之改变
ListNode* tem = pre->next;
pre->next = cur;
cur = pre;
pre = tem;
}
return {cur,start};
}
ListNode* reverseKGroup(ListNode* head, int k) {
if(!head||!head->next)return head;
ListNode pre(0);
ListNode* prep = ⪯
prep->next = head;
ListNode* start, * end, * nex;
int len = 0;
while(head){
len++;
head = head->next;
}
for(int i=0;i<len/k;i++){
start = prep->next;
end = start;
for(int j=1;j<k;j++){
end = end->next;
}
nex = end->next;
pair<ListNode*,ListNode*>A = reverse(start,end);
prep->next = A.first;
A.second->next = nex;
prep = A.second;
}
return pre.next;
}
10.删除倒数第n个节点(19)
思路1:先遍历得出链表长度,然后再遍历到删除节点的前驱结点
思路2:建立一个前驱空洞结点,然后使用快慢指针,慢指针指向空洞结点,快指针指向head结点。先让快指针移动n步,然后再同时移动快慢指针,知道快指针为null。此时慢指针指向删除节点的前驱结点。
ListNode* removeNthFromEnd(ListNode* head, int n) {
//先建立一个虚拟头结点
ListNode new_head(0);
ListNode* ptr = &new_head;
ptr -> next = head;
ListNode* curr = ptr;
ListNode* pre = head;
//先让pre走n步,
for(int i=0;i<n;i++){
pre = pre -> next;
}
//同时移动pre和curr,当pre到null时候,pre和curr中间刚好相隔n个节点,curr刚好就是倒数第n个的前驱结点
while(pre){
pre = pre -> next;
curr = curr -> next;
}
ListNode* tem = curr -> next -> next;
curr -> next = tem;
return new_head.next;
}
二、栈、队列
1.用队列实现栈
栈的主要操作:push,pop,top,empty()
队列的主要操作有:push,pop,front,empty(),back
主要区别在于栈是先进后出,队列是先进先出。所以主要是push操作时要注意。要让后进来的元素处于队列的头部。使用两个队列来实现。
queue<int>tem;
/** Initialize your data structure here. */
MyStack() {
}
/** Push element x onto stack. */
void push(int x) {
tem.push(x);
while(!q.empty()){
tem.push(q.front());
q.pop();
}
while(!tem.empty()){
q.push(tem.front());
tem.pop();
}
}
/** Removes the element on top of the stack and returns that element. */
int pop() {
int tem = q.front();
q.pop();
return tem;
}
/** Get the top element. */
int top() {
return q.front();
}
/** Returns whether the stack is empty. */
bool empty() {
return q.empty();
}
private:
queue<int>q;
用栈实现队列
使用两个栈来实现
MyQueue() {
}
/** Push element x to the back of queue. */
stack<int>tem;
void push(int x) {
while(!data.empty()){
tem.push(data.top());
data.pop();
}
data.push(x);
while(!tem.empty()){
data.push(tem.top());
tem.pop();
}
}
/** Removes the element from in front of queue and returns that element. */
int pop() {
int x = data.top();
data.pop();
return x;
}
/** Get the front element. */
int peek() {
return data.top();
}
/** Returns whether the queue is empty. */
bool empty() {
return data.empty();
}
private:
stack<int> data;
2.最小栈(155)
利用一个栈专门记录当前状态对应的最小值。
public:
/** initialize your data structure here. */
MinStack() {
}
void push(int x) {
if(min_data.empty()){
//min_ = x;
min_data.push(x);
}
else {
//min_ = min_ > x ? x : min_;
min_data.push(min(x,min_data.top()));
}
data.push(x);
//min_data.push(min_);
}
void pop() {
data.pop();
min_data.pop();
//min_ = min_data.top();
}
int top() {
return data.top();
}
int getMin() {
return min_data.top();
}
private:
stack<int>data;
stack<int>min_data;
3.合法的栈序列
bool check_is_valid_order(std::queue<int>&order){
int n = order.size();
stack<int> s;
for(int i=1;i<=n;i++){
s.push(i);
while(!s.empty()&&s.top()==order.front()){
s.pop();
order.pop();
}
}
if(!s.empty()){
return false;
}
return true;
}
4.下一个更大元素(496)
这一类问题统一使用单调栈来解决,单调栈的使得每次入栈之后栈内元素都是保持递增或者递减。单调栈的模板代码:注意从后往前遍历数组
for(int i=len2-1;i>=0;i--){
while(!s.empty()&&s.top()<=nums2[i]){
s.pop();//剔除小于当前位的元素
}
vc[i] = s.empty() ? -1 : s.top();//vc即为num2数组每一位下一个最大值的数组
s.push(nums2[i]);
}
这一题结合map图,先不管num1,先得到num2的每一位对应的下一个最大值,在利用map查找num1中每一位对应的下一位最大值。
vector<int> nextGreaterElement(vector<int>& nums1, vector<int>& nums2) {
int len1 = nums1.size();
int len2 = nums2.size();
vector<int> res(nums1.size(),-1);
vector<int> vc(nums2.size());
stack<int>s;
map<int,int> map1;
//使用单调栈
for(int i=len2-1;i>=0;i--){
while(!s.empty()&&s.top()<=nums2[i]){
s.pop();
}
vc[i] = s.empty() ? -1 : s.top();
map1[nums2[i]]=vc[i];
s.push(nums2[i]);
}
for(int j=0;j<len1;j++){
res[j]=map1[nums1[j]];
}
return res;
}
5.下一个更大元素II
使用取余操作%模拟扩展数组的操作。还是使用单调栈完成。
vector<int> nextGreaterElements(vector<int>& nums) {
int n = nums.size();
vector<int>res(n);
stack<int>s;
for(int i=2*n-1;i>=0;i--){
while(!s.empty()&&s.top()<=nums[i%n]){
s.pop();
}
res[i%n] = s.empty() ? -1 : s.top();
s.push(nums[i%n]);
}
return res;
}
6.用栈操作构建数组(1441)
使用变量index表示数组下标,利用for遍历数数。
注意index不要越界。
vector<string> buildArray(vector<int>& target, int n) {
vector<string>res;
int len = target.size();
stack<int>s;
int index = 0;
for(int i=1;i<=n;i++){
if(index<len){
res.push_back("Push");
s.push(i);
}
if(index<len && s.top()!=target[index]){//两个条件还不能互换位置,需要先判断index不越界,否则内存错误。
res.push_back("Pop");
s.pop();
}
else{
index++;
}
}
return res;
}
7.有效括号(20)
使用栈来存储左括号,当遍历到右括号时,从栈的头部取出左括号看是否匹配。注意在此之前要判断栈里是否有元素。最后返回栈是否为空。
bool isValid(string s) {
stack<char> stack;
for(int i=0;i<s.length();i++){
char c = s[i];
if(c=='('||c=='['||c=='{'){
stack.push(c);
}
else{
if(stack.empty())return false;
char b = stack.top();
stack.pop();
if(s[i]==')' && b!='(')return false;
if(s[i]==']' && b!='[')return false;
if(s[i]=='}' && b!='{')return false;
}
}
return stack.empty();
}
8.实现循环队列(622)
使用vector来实现循环队列,注意实现循环操作使用取余(%)来实现。
使用head和tail两个指针记录队列的头和尾,在出队的时候实际不用删除容器内的元素,只需要移动相应的头指针就行。
class MyCircularQueue {
public:
MyCircularQueue(int k) {
data.resize(k);
size = k;
head = tail = -1;
}
bool enQueue(int value) {
//case1:队列为空
if(isEmpty()){
head = tail = 0;
data[tail] = value;
return true;
}
//case2:队列满了
else if(isFull()){
return false;
}
//case3:队列不为空,但还没满
else{
tail = (tail+1) % size;
data[tail] = value;
return true;
}
}
bool deQueue() {
//case1:队列为空
if(isEmpty()){
return false;
}
//case2:队列只有一个元素
else if(tail==head){
head = tail = -1;
}
//case3:队列有多个元素,移动head
else{
head = (head+1) % size;
}
return true;
}
int Front() {
if(isEmpty()){
return -1;
}
else{
return data[head];
}
}
int Rear() {
if(isEmpty()){
return -1;
}
else{
return data[tail];
}
}
bool isEmpty() {
if(head==-1){
return true;
}
return false;
}
bool isFull() {
if((tail+1)%size==head){
return true;
}
return false;
}
private:
vector<int> data;
int size;
int head;
int tail;
};
9.实现双端循环队列(641)
class MyCircularDeque {
private:
vector<int> data;
int size;
int head;
int tail;
public:
/** Initialize your data structure here. Set the size of the deque to be k. */
MyCircularDeque(int k) {
data.resize(k);
size = k;
head = tail = -1;
}
/** Adds an item at the front of Deque. Return true if the operation is successful. */
bool insertFront(int value) {
//case1:队列为空
if(isEmpty()){
head = tail = 0;
data[head] = value;
return true;
}
//case2:队列为满
else if(isFull()){
return false;
}
//case3:队列中head==0
else if(head==0){
head = size - 1;
data[head] = value;
return true;
}
//case4:head != 0
else{
head -= 1;
data[head] = value;
return true;
}
}
/** Adds an item at the rear of Deque. Return true if the operation is successful. */
bool insertLast(int value) {
//case1:队列为空
if(isEmpty()){
head = tail = 0;
data[tail] = value;
return true;
}
//case2:队列为满
else if(isFull()){
return false;
}
//case3:队列不为满
else{
tail = (tail+1) % size;
data[tail] = value;
return true;
}
}
/** Deletes an item from the front of Deque. Return true if the operation is successful. */
bool deleteFront() {
//case1:队列为空
if(isEmpty()){
return false;
}
//case2:队列只有一个元素
else if(head==tail&&head!=-1){
head = tail = -1;
return true;
}
//case3:队列有多个元素
else{
head = (head+1) % size;
return true;
}
}
/** Deletes an item from the rear of Deque. Return true if the operation is successful. */
bool deleteLast() {
//case1:队列为空
if(isEmpty()){
return false;
}
//case2:队列只有一个元素
else if(head==tail&&head!=-1){
head = tail = -1;
return true;
}
//case3:tail位于0
else if(tail==0){
tail = size - 1;
return true;
}
//case4:有多个元素
else{
tail = tail - 1;
return true;
}
}
/** Get the front item from the deque. */
int getFront() {
if(isEmpty()){
return -1;
}
return data[head];
}
/** Get the last item from the deque. */
int getRear() {
if(isEmpty()){
return -1;
}
return data[tail];
}
/** Checks whether the circular deque is empty or not. */
bool isEmpty() {
if(tail==-1||head==-1){
return true;
}
return false;
}
/** Checks whether the circular deque is full or not. */
bool isFull() {
if((tail+1)%size==head){
return true;
}
return false;
}
};
三、堆
获取最大值:O(1)
删除最大值:O(logn)
添加元素:O(logn)
堆的基本操作:size(),empty(),top(),pop(),push()
1.第k大的数(215)
利用小顶堆维护k个元素的堆
当堆元素少于k个时直接入堆
当元素数量大于K个时,比较输入的元素与堆顶元素,若大于堆顶元素,则弹出堆顶元素,输入元素入堆,进行自动排序。
建立堆使用优先队列
priority_queue<int> pq;//最大值优先队列
priority_queue<int,vector<int>,greater<int> > pq2;//最小值优先队列
std::priority_queue<int,std::vector<int>,std::greater<int>> Q;
int findKthLargest(vector<int>& nums, int k) {
std::priority_queue<int,std::vector<int>,std::greater<int>> Q;
for(int i=0;i<nums.size();i++){
if(Q.size()<k){
Q.push(nums[i]);
}
else if(nums[i]>Q.top()){
Q.pop();
Q.push(nums[i]);
}
}
return Q.top();
}