0. PTA得分截图

1. 本周学习总结

1.1 总结栈和队列内容

栈的总结

1.栈(stack)是一种只能在一端进行插入或删除操作的线性表。
2.表中允许进行插入、删除操作的一端称为栈顶(top),表的另一端称为栈底(bottom)。
3.栈顶的当前位置是动态的,栈顶的当前位置由一个被称为栈顶指针的位置指示器来指示。
4.当栈中没有数据元素时称为空栈。
5.栈的插入操作通常称为进栈或入栈(push),栈的删除操作通常称为出栈或退栈(pop)。
6.栈的主要特点是后进先出(Last In First Out,LIFO), 即后进栈的元素先出栈。每次进栈的数据元素都放在原来栈顶元素之前,成为新的栈顶元素,每次出栈的数据元素都是当前栈顶元素。栈也可以称为后进先出表。

7.栈抽象数据类型的定义:


ADT Stack
{
数据对象:
    D = { ai | 1≤i≤n,n≥0,ai 为ElemType类型} //ElemType 是自定义类型标识符
数据关系:
    R = { < ai ,ai +1 > | ai 、ai+1∈ D,i = 1,…,n - 1}
基本运算:
    InitStack(&s) : 初始化栈,构造一个空栈s。
    DestroyStack(&s) : 销毁栈,释放栈s占用的存储空间。
    StackEmpty(s) : 判断栈是否为空,若栈s为空,则返回真; 否则返回假。
    Push(&s,e) :进栈,将元素e插人到栈s中作为栈顶元素。
    Pop(&s, &e) : 出栈,从栈s中删除栈顶元素,并将其值赋给e。
    GetTop(s, &e) : 取栈顶元素,返回当前的栈顶元素,并将其值赋给e。
}


8.栈的顺序存储结构及操作

  • 假设栈的元素个数最大不超过正整数MaxSize,所有的元素都具有同一数据类型,即ElemType,可用下列方式来声明顺序栈的类型SqStack:

typedef struct{ 
ElemType data[MaxSize]; //存放栈中的数据元素
int top;//栈顶指针,即存放栈顶元素在data数组中的下标
} SqStack;//顺序栈类型


  • 顺序栈中的4个关键点:

    • 栈空的条件: s->top== -1
    • 栈满的条件: s-> top== MaxSize- 1(data数组的最大下标)
    • 元素e的进栈操作:先将栈顶指针top增1,然后将元素e放在栈顶指针处
    • 出栈操作:先将栈顶指针top处的元素取出放在e中,然后将栈顶指针减1
  • 1)初始化栈initStack(&s)

    • 该运算创建一个空栈, 由s指向它。实际上就是分配一个顺序栈空间, 并将栈顶指针设置为-1。算法如下:

void InitStack(SqStack*& s)
{
	s = (SqStack*)malloc(sizeof(SqStack));//分配一个顺序栈空间,首地址存放在s中
	s->top = -1;//栈顶指针置为-1
}


  • 2)销毁栈DestroyStack(&s)
    • 该运算释放顺序栈s占用的存储空间。算法如下:

void DestroyStack(SqStack*& s)
{
	free(s);
}


  • 3)判断栈是否为空StackEmpty(s)
    • 该运算实际上用于判断条件s->top == -1是否成立。算法如下:

bool StackEmpty(SqStack * s)
{
	return(s->top == -1);
}


  • 4)进栈Push(&s, e)
    • 该运算的执行过程是, 在栈不满的条件下先将栈顶指针增1, 然后在该位置上插入元素e, 并返回真; 否则返回假。算法如下:

bool Push(SqStack*& s, ElemType e)
{
	if (s->top == MaxSize - 1)//栈满的情况,即栈上溢出
		return false;
	s->top++;//栈顶指针增1
	s->data[s->top] = e;//元素e放在栈顶指针处
	return true;
}


  • 5)出栈Pop(&s, &e)
    • 该运算的执行过程是,在栈不为空的条件下先将栈项元素赋给e, 然后将栈顶指针减1, 并返回真; 否则返回假。算法如下:

bool Pop(SqStack*& s, ElemType& e)
{
	if (s->top == -1)//栈为空的情况,即栈下溢出
		return false;
	e = s->data[s->top];//取栈项元素
	s->top--;//栈顶指针减1
	return true;
}


  • 6)取栈顶元素GetTop(s, &e)
    • 该运算在栈不为空的条件下将栈顶元素赋给e并返回真; 否则返回假。算法如下:

bool GetTop(SqStack* s, ElemType& e)
{
	if (s->top == -1)//栈为空的情况,即栈下溢出
		return false;
	e = s->data[s->top];//取栈顶元素
	return true;
}


PS:以上6个函数的时间复杂度都是O(1)

9.共享栈

  • 用一个数组来实现两个栈,称为共享栈(sharestack)。

  • 在设计共享栈时,由于一个数组(大小为MaxSize)有两个端点,两个栈有两个栈底,让一个栈的栈底为数组的始端,即下标为0处,另一个栈的栈底为数组的末端,即下标为MaxSize-1,这样在两个栈中进栈元素时栈顶向中间伸展。

  • 共享栈的4个要素:

    • 栈空条件:栈1空为topl-1;栈2空为top2MaxSize
    • 栈满条件: topl==top2-1
    • 元素x进栈操作:进栈1操作为topl++;data[top1]=x;进栈2操作为top2--;data[top2]=x;
    • 出栈x操作:出栈1操作为x=data[topl];top1--;出栈2操作为x=data[top2];top2++
  • data数组表示共享栈的存储空间, top1和top2分别为两个栈的栈顶指针, 所以共享栈通过data, topl和top2来标识, 将它们设计为一个结构体类型:


typedef struct
{
	ElemType data[MaxSize];//存放共享栈中的元素
	int topl,top2;//两个栈的栈顶指针
}DStack;//共享栈的类型


10.栈的链式存储结构及操作

  • 采用链式存储结构的栈称为链栈(linked stack),链表有多种,一般采用带头结点的单链表来实现链栈。
  • 链栈的优点是不存在栈满上溢出的情况。规定栈的所有操作都是在单链表的表头进行的(因为给定链栈后,已知头结点地址,在其后面插人一个新结点和删除首结点都十分方便,对应算法的时间复杂度均为0(1))。
  • 如图所示为头结点指针为s的链栈,首结点是栈顶结点,尾结点是栈底结点。栈中元素自栈底到栈顶依次是a1 ,a2 ,…,an
  • 链栈中结点类型LinkStNode的声明如下:

typedef struct linknode
{
	ElemType data;//数据域
	struct linknode* next;//指针域
} LinkStNode;//链栈结点类型


  • 在以s为头结点指针的链栈(简称链栈s)中重要的4个要素:

    • 栈空的条件: s->next== NULL
    • 栈满的条件:由于只有内存溢出时才出现栈满,通常不考虑这样的情况,所以在链栈中可以看成不存在栈满
    • 元素e的进栈操作:新建一个结点存放元素e(由p指向它),将结点p插入到头结点之后
    • 出栈操作:取出首结点的data值并将其删除
  • 1)初始化栈initStack(&s)

    • 该运算创建一个空链栈s, 实际上是创建链栈的头结点, 并将其next域置为NULL。算法如下:

void InitStack(LinkStNode*& s)
{
	s = (LinkStNode*)malloc(sizeof(LinkStNode));
	s->next = NULL;
}
//时间复杂度为0(1)。


  • 2)销毁栈DestroyStack(&s)
    • 该运算释放链栈s占用的全部结点空间,和单链表的销毁算法完全相同。算法如下:

void DestroyStack(LinkStNode*& s)
{
	LinkStNode* pre = s, * p = s->next;//pre 指向头结点,p指向首结点
	while (p != NULL)//循环到p为空
	{
		free(pre);//释放pre结点
		pre == p;//pre、p 同步后移
		p = pre->next;
	}

	free(pre);//此时pre指向尾结点,释放其空间
}
//本算法的时间复杂度为O(n), 其中n为链栈中的数据结点个数。


  • 3)判断栈是否为空StackEmpty(s)
    • 该运算判断s->next = NULL的条件是否成立。算法如下:

bool StackEmpty(LinkStNode * s)
{
	return(s->next == NULL);
}
//本算法的时间复杂度为0(1)。


  • 4)进栈Push(&s, e)
    • 该运算新建一个结点, 用于存放元素e(由p指向它), 然后将其插入到头结点之后作为新的首结点。算法如下:

void Push(LinkStNode*& s, ElemType e)
{
	LinkStNode* p;
	p = (LinkStNode*)malloc(sizeof(LinkStNode));//新建结点p
	p->data = e;//存放元素e
	p->next = s->next;//将p结点插人作为首结点
	S->next = p;
}
//本算法的时间复杂度为0(1)。


  • 5)出栈Pop(&s, &e)
    • 该运算在栈不为空的条件下提取首结点的数据域赋给引用型参数e, 然后将其删除。算法如下:

bool Pop(LinkStNode*& s, ElemType& e)
{
	LinkStNode* p;
	if (s->next == NULL)//栈空的情况
		return false;//返回假
	p = s->next;//p指向首结点
	e = p->data;//提取首结点值
	s->next = p - > next;//删除首结点
	free(p);//释放被删结点的存储空间
	return true;//返回真
}
//本算法的时间复杂度为0(1)。


  • 6)取栈顶元素GetTop(s, &e)
    • 该运算在栈不为空的条件下提取首结点的数据域赋给引用型参数e。算法如下:

bool GetTop(LinkStNode* s, ElemType& e)
{
	if (s->next == NULL)//栈空的情况
		return false;//返回假
	e = s->next->data;//提取首结点值
	return true;//返回真
}
//和出栈运算相比, 本算法只是没有改变栈顶结点, 其时间复杂度为0(1)。


11.stack头文件

  • stack头文件只能用在C++中
  • 定义stack 对象的示例代码如下:
    • stack < int > s1;
    • stack < string > s2;
  • stack 的基本操作有:
    • s.push(x);//入栈
    • s.pop();//出栈,注意,出栈操作只是删除栈顶元素,并不返回该元素。
    • s.top();//访问栈顶元素
    • s.empty();//判断栈空,当栈空时,返回true。
    • s.size();//访问栈中元素个数

12.栈的应用
1>简单表达式

  • 中缀表达式转后缀表达式
  • 后缀表达式求值
    2>求解迷宫问题
    3>数制转换问题

队列的总结

1.队列(queue)简称队,它也是一种操作受限的线性表,其限制为仅允许在表的一端进行插入操作,而在表的另一端进行删除操作。
2.把进行插入的一端称为队尾(rear),把进行删除的一端称为队头或队首(front)。

3.向队列中插入新元素称为进队或入队(enqueue),新元素进队后就成为新的队尾元素;从队列中删除元素称为出队或离队(dequeue),元素出队后,其直接后继元素就成为队首元素。
4.由于队列的插入和删除操作分别是在各自的一端进行的,每个元素必然按照进入的次序出队,所以又把队列称为先进先出表(First In First Out,FIFO)
5.队列抽象数据类型的定义如下:


ADT Queue
{
数据对象:
    D = { ai | 1≤i≤n,n≥0,ai为ElemType类型 } //ElemType 是自定义类型标识符
数据关系:
    R = { < ai ,ai +1 > |ai,ai + 1∈D,i = 1,,n - 1}
基本运算:
    InitQueue(&q) : 初始化队列,构造个空队列 q。
    DestroyQueue(&q) : 销毁队列,释放队列q占用的存储空间。
    QueueEmpty(q) : 判断队列是否为空,若队列q为空,则返回真; 否则返回假。
    enQueue(&q,e) :进队列,将元素e进队作为队尾元素。
    deQueue(&q, &e) : 出队列,从队列q中出队一个元素,并将其值赋给e。
}


6.队列的顺序存储结构及操作

  • 假设队列中元素个数最多不超过整数MaxSize, 所有的元素都具有ElemType数据类型,则顺序队类型SqQueue声明如下:

typedef struct
{
	ElemType data[MaxSize];//存放队中元素
	int front, rear;//队头和队尾指针
}SqQueue;//顺序队类型


  • 队列到顺序队的映射过程如图所示,并且约定在顺序队中队头指针front指向当前队列中队头元素的前一个位置,队尾指针rear指向当前队列中队尾元素的位置,采用队列指针q的方式建立和使用顺序队。

  • 对于q所指的顺序队,初始时设置q->rear=q->front=-1,4个要素:

    • 队空的条件: q-> front==q-> rear。
    • 队满的条件: q-> rear== MaxSize-l(data数组的最大下标)。
    • 元素e的进队操作:先将rear增1,然后将元素e放在data数组的rear位置。
    • 出队操作:先将front增1,然后取出data数组中front位置的元素。
  • 1)初始化队列InitQueue(&q)

    • 构造一个空队列q, 将front和rear指针均设置成初始状态, 即-1值。算法如下:

void InitQueue(SqQueue * &q)
{
	q = (SqQueue*)malloc(sizeof(SqQueue));
	q->front = q->rear = -1;
}


  • 2)销毁队列DestroyQueue(&q)
    • 释放队列q占用的存储空间。算法如下:

void DestroyQueue(SqQueue*& q)
{
	free(q);
}


  • 3)判断队列是否为空QueueEmpty(q)
    • 若队列q为空, 返回真; 否则返回假。算法如下:

bool QueueEmpty(SqQueue* q)
{
	return(q->front == q->rear);
}


  • 4)进队列enQueue(&q,e)
    • 在队列q不满的条件下先将队尾指针rear增1,然后将元素e插入到该位置。算法如下:

bool enQueue(SqQueue*& q, ElemType e)
{
	if (q->rear = = MaxSize - 1)//队满上溢出
		return false;//返回假
	q->rear++;//队尾增1
	q->data[q->rear] = e;//位置插人元素e
	return true;//返回真
}


  • 5)出队列deQueue(&q, &e)
    • 在队列q不空的条件下先将队头指针front增1, 并将该位置的元素值赋给e。算法如下:

bool deQueue(SqQueue*& q, ElemType& e)
{
	if (q->front == q->rear)//队空下溢出
		return false;
	q->front++;
	e = q->data[q->front];
	return true;

}


  • Ps:上述5个基本运算算法的时间复杂度均为0(1)。

7.环形队列

  • 在顺序队操作中,元素进队时队尾指针rear增1,元素出队时队头指针front增1,当队满的条件(即rear== MaxSize- 1)成立时,表示此时队满(上溢出)了,不能再进队元素。实际上,当rear== MaxSize-1成立时,队列中可能还有空位置,这种因为队满条件设置不合理导致队满条件成立而队列中仍然有空位置的情况称为假溢出(falseoverflow)。

  • 可以看出,在出现假溢出时队尾指针rear指向data数组的最大下标,而另外一端还有若干个空位置。

  • 解决的方法是把data数组的前端和后端连接起来,形成一个环形数组,即把存储队列元素的数组从逻辑上看成一个环,称为环形队列或者循环队列(circularqueue)。

  • 环形队列首尾相连后,当队尾指针rear= MaxSize-1后,再前进一个位置就到达0,于是就可以使用另一端的空位置存放队列元素了。

  • 实际上存储器中的地址总是连续编号的,为此采用数学上的求余运算(%)来实现:

    • 队头指针front循环增1: front= (front+ 1) % MaxSize
    • 队尾指针rear循环增1: rear= (rear+ 1) % MaxSize

  • 环形队列结构体定义如下:

typedef struct
{
	ElemType data[MaxSize];
	int front;//队头指针
	int count;//队列中的元素个数
} QuType;//环形队列类型


  • 1)初始化队列InitQueue(&q)
    • 构造一个空队列q, 将front和rear指针均设置成初始状态, 即0值。算法如下:

void InitQueue(SqQueue*& q)
{
	q = (SqQueue*)malloc(sizeof(SqQueue));
	q->front = q->rear = 0;
}


  • 2)销毁队列DestroyQueue(&q)
    • 释放队列q占用的存储空间。算法如下:

void DestroyQueue(SqQueue*& q)
{
	free(q);
}


  • 3)判断队列是否为空QueueEmpty(q)
    • 若队列为空返回真;否则返回假。算法如下:

bool QueueEmpty(SqQueue* q)
{
	return(q->front == q->rear);
}


  • 4)进队列enQueue(&q, e)
    • 在队列不满的条件下先将队尾指针rear循环增1, 然后将元素插人到该位置。算法如下:

bool enQueue(SqQueue*& q, ElemType e)
{
	if ((q->rear + 1) % MaxSize == q->front)//队满上溢出
		return false;
	q->rear = (q->rear + 1) % MaxSize;
	q->data[q->rear] = e;
	return true;
}


  • 5)出队列deQueue(&q, &e)
    • 在队列q不空的条件下将队首指针front循环增1, 取出该位置的元素并赋给e。算法如下:

bool deQueue(SqQueue*& q, ElemType& e)
{
	if (q->front == q->rear)//队空下溢出
		return false;
	q->front = (q->front + 1) % MaxSize;
	e = q->data[q->front];
	return true;
}


  • Ps:上述5个基本运算算法的时间复杂度均为O(1)。

8.队列的链式存储结构及操作

  • 采用链式存储结构的队列称为链队(linkedqueue)。链表有多种,一般采用单链表来实现链队。
  • 链队中数据结点的类型DataNode声明如下:

typedef struct qnode
{
	ElemType data;//存放元素
	struct qnode头next;//下一个结点指针
} DataNode;//链队数据结点的类型


  • 链队头结点(或链队结点)的类型LinkQuNode声明如下:

typedef struct
{
	DataNode* front;//指向队首结点
	DataNode* rear;//指向队尾结点
} LinkQuNode;//链队结点的类型


  • 在以q为链队结点指针的链队(简称链队q)中4个要素:

    • 队空的条件: q-> rear== NULL(也可以为q-> front==NULL)。
    • 队满的条件:不考虑。
    • 元素e的进队操作:新建一个结点存放元素e(由p指向它),将结点p插入作为尾结点。
    • 出队操作:取出队首结点的data值并将其删除。
  • 1)初始化队列InitQueue(&q)

    • 构造一个空队,即创建一个链队结点, 其front和rear域均置为NULL。算法如下:

void InitQueue(LinkQuNode*& q)
{
	q = (LinkQuNode*)malloc(sizeof(LinkQuNode));
	q->front = q->rear = NULL;
}
//本算法的时间复杂度为0(1)。


  • 2)销毁队列DestroyQueue(&q)
    • 释放链队占用的全部存储空间,包括链队结点和所有数据结点的存储空间。算法如下:

void DestroyQueue(LinkQuNode*& q)
{
	DataNode* pre = q->front, * p;//pre 指向队首结点
	if (pre != NULL)
	{
		p = pre->next;//p指向结点pre的后继结点
		while (p != NULL)//p不空循环
		{
			free(pre);//释放pre结点
			pre = p; p = p->next;//pre、p同步后移
		}

		free(pre);//释放最后一个数据结点		
	}
	free(q);//释放链队结点
}
//本算法的时间复杂度为O(n), 其中n为链队中数据结点的个数。


  • 3)判断队列是否为空QueueEmpty(q)
    • 若链队为空, 返回真; 否则返回假。算法如下:

bool QueueEmpty(LinkQuNode* q)
{
	return(q->rear == NULL);
}
//本算法的时间复杂度为0(1)。


  • 4)进队列enQueue(&q,e)
    • 创建一个新结点用于存放元素e(由p指向它)。若原队列为空,则将链队结点的两个域均指向结点p,否则将结点p链接到单链表的末尾,并让链队结点的rear域指向它。算法如下:

void enQueue(LinkQuNode*& q, ElemType e)
{
	DataNode* p;
	p = (DataNode*)malloc(sizeof (DataNode));//创建新结点
	p->data = e;
	p->next = NULL;
	if (q->rear == NULL)//若链队为空,则新结点既是队首结点又是队尾结点
		q->front = q->rear = p;
	else//若链队不空
	{
		q->rear->next = p;//将结点p链到队尾,并将rear指向它
		q->rear = p;
	}
}
//本算法的时间复杂度为0(1)。


  • 5)出队列deQueue(&q ,&e )
    • 若原队列为空,则下溢出返回假;若原队列不空,则将首结点的data域值赋给e,并删除之,若原队列只有一个结点,则需将链队结点的两个域均置为NULL,表示队列已为空。算法如下:

bool deQueue(LinkQuNode*& q, ElemType& e)
{
	DataNode* t;
	if (q->rear == NULL)//原来队列为空
		return false;
	t = q->front;//t指向首结点
	if (q->front == q->rear)//原来队列中只有一个数据结点时
		q->front = q->rear = NULL;
	else//原来队列中有两个或两个以上结点时
		q->front = q->front->next;
	e = t->data;
	free(t);
	return true;
}
//本算法的时间复杂度为0(1)。


9.queue头文件

  • queue头文件只能用在C++中
  • 定义queue 对象的示例代码如下:
    • queue < int > q1;
    • queue < double > q2;
  • queue 的基本操作有:
    • q.push(x);//入队,将x接到队列的末端。
    • q.pop();//出队,弹出队列的第一个元素,注意,并不会返回被弹出元素的值。
    • q.front();//访问队首元素,即最早被压入队列的元素。
    • q.back();//访问队尾元素,即最后被压入队列的元素。
    • q.empty();//判断队空,当队列空时,返回true。
    • q.size();//访问队中元素个数。

10.双端队列

  • 双端队列(deque,doubleendedqueue)是指两端都可以进行进队和出队操作的队列。
  • 将队列的两端分别称为前端和后端,两端都可以进队和出队。

11.队列的应用
1>求解报数问题
2>求解迷宫问题
3>银行排队问题

1.2 谈谈你对栈和队列的认识及学习体会

  • 学习栈和队列时,概念很好理解,实际操作时一般不用自己建栈和队列的结构体,直接用stack头文件和queue头文件,非常地方便
  • 但是对于做题来说还是比较难,尤其是在学习循环队列时的一些内容不太能理解,就比如说取余那一部分,还有有时候队空队满的一些条件,总感觉一直在变化o(╥﹏╥)o
  • 在中缀表达式和后缀表达式那块思路可以理解,但是代码还是有待商榷
  • 在符号配对部分思路换了好几种,结果最后还是用把所有可能性排出来分类讨论的方法,感觉自己并没有多用到栈的内容,运用还是不熟练
  • 老师上课讲到的map函数用来做符号匹配比较方便,附我看到的比较好理解的一份资料:STL——map函数_C/C++
  • 在迷宫的题中感觉也是迷迷糊糊的,不太能理解,感觉就是好像有点思路,但继续挖下去,就发现走不过去了,很纠结
  • 银行排队问题,上课看同学的演示和讲解感觉听明白思路了,但是真的到自己写时还是一头雾水
  • 在平时写代码一般直接用两个头文件,对于自己写函数还是不熟悉

2. PTA实验作业

2.1 题目1:7-4 符号配对 (20分)

2.1.1 代码截图










2.1.2 本题PTA提交列表说明

  • 编译错误:这道题做得时间比较久,刚开始不知道怎么才能输入一大堆的代码,又要怎么才能在规定的地方结束输入,所以用了一些C语言中的东西,导致了编译错误
  • 段错误:存入数组中造成了死循环,输入无法结束
  • 答案错误:开始写的是while(1)时怎么怎么,后来又改成了while不是点不是0,但是前面的遇到换行符也会结束,导致卡在上面无法出来
  • 编译错误:在这个代码中,不能使用gets函数,否则就会编译错误,直接用cin就可以了
  • 答案正确:一开始我的思路是把所有的括号都入栈,//用《和》来代替也入栈,然后将其再进入一个数组中去,把数组左右进行相互比对,如果匹配就略过,不匹配就再进行左或右的下一个或上一个的比较,如果还是不匹配的就输出NO和这个符号,但是后来发现这个有弊端,数组也容易越界,就放弃了这个想法;再然后我的思路是把每个符号都赋一个值,如果左括号和右括号可以相加等于9,则匹配,否则,也是再进行下一个的比较,但是发现比较乱,我把自己给绕进去了;所以最后我就用了思路最好理解,但是代码很多的思路,直接把所有的可能性直接摆出来,然后终于这道题结束了

2.2 题目2:7-7 银行业务队列简单模拟 (25分)

2.2.1 代码截图





2.2.2 本题PTA提交列表说明

  • 编译错误:return 0后面忘记加分号了…
  • 部分正确:其它测试点显示的是格式错误,不过我估计我答对了的测试点2最小N也是凑巧的,不知道为什么,输出来的数是连续3个凑一堆,然后一个空格,不过顺序倒是对的,后来发现,奇数队列本来应该每出2个,偶数队列出1个,在while循环中,我在一次循环中只输出了一个空格,所以格式是错误的
  • 部分正确:将奇数队列的2次输出放入一个for循环中后,测试点0对了,不过其他还是格式错误
  • 多种错误:发现如果先不直接输出人的编号,而是先把排列中输出的编号先全部放入同一个队列中,到最后再输出比较好,因为开始时我已经找到了总共循环的次数,所以不用担心会一直循环下去,这样即使放入原来的队列也可以,不用再设一个队列了
  • 答案正确:果然,换一种思路答案就正确啦!

3. 阅读代码

3.1 验证栈序列

  • 题目


  • 代码

class Solution {
public:
    bool validateStackSequences(vector<int>& pushed, vector<int>& popped) {
        stack<int> s;
        for (int i = 0, j = 0; i < pushed.size(); ++i) {
            s.push(pushed[i]);
            while (!s.empty() && s.top() == popped[j]) {
                s.pop();
                ++j;
            }
        }
        return s.empty();
    }
};


3.1.1 该题的设计思路

  • 初始时,左侧粉色部分为题目输入的pushed数组,左侧的蓝色矩形为栈s,右侧的蓝色矩形为题目输入的pop数组,也就是题目所给的出栈顺序

  • pushed数组内数字依次入栈,直到找到栈顶元素与pop数组的第一个相符

  • 将栈s栈的栈顶元素出栈,pop数组移动到下一位置


  • 相应的,其它数字以此类推

  • 当栈s内为空时,说明全部符合,题目所给出栈顺序可以达到

  • Ps:本题的时间复杂度和空间复杂度都为O(n)

3.1.2 该题的伪代码


新建立int型栈s

for (i = 0 to i < pushed栈的大小, ++i) 
{
    将pushed栈中元素压入s中

    while (当s栈不为空 且 s的栈顶元素等于pop的栈顶元素时,说明符合条件) 
    {
        s的栈顶元素出栈,匹配下一个
    }
}

最后判断栈s是否为空,如果为空,说明全部符合,否则,题目要求的出栈顺序不符合


3.1.3 运行结果


3.1.4 该题目解题优势及难点

  • 优势:这道题读起来很容易理解,主要考察的就是关于栈的基本功,记住先进后出,后进先出即可
  • 难点:刚开始读到这道题的时候,我先想的是把所有可能的出栈顺序都推出来,然后进行匹配,不过这样显然是很麻烦而且不必要的,然后我就想到了将pushed中的元素在入栈的过程中与pop中数字匹配,pop数组可以放在一个队列里,新建立的栈和队列的队头元素比较,如果匹配成功就出栈,队头也出队,然后我就在众多的题解中找到了一种比较符合我思路的代码,不过他是直接用的pop数组,比我的简单了一点,不过我很高兴我的思路基本上是没问题的

3.2 根据身高重建队列

  • 题目

  • 代码

class Solution {
public:
    vector<vector<int>> reconstructQueue(vector<vector<int>>& people) {
        // 排序
        sort(people.begin(), people.end(),
                [](const vector<int>& lhs, const vector<int>& rhs)
                 {return lhs[0] == rhs[0] ? lhs[1] <= rhs[1] : lhs[0] > rhs[0];});
        int len = people.size();
        list<vector<int>> tmp;
        // 循环插入
        for(int i = 0; i < len; ++i){
            auto pos = tmp.begin();
            advance(pos, people[i][1]);
            tmp.insert(pos, people[i]);
        }
        // 重建vector返回
        return vector<vector<int>>(tmp.begin(), tmp.end());
    }
};


3.2.1 该题的设计思路


先排序:
[7,0], [7,1], [6,1], [5,0], [5,2], [4,4]
        
再一个一个插入:
[7,0]
[7,0], [7,1]
[7,0], [6,1], [7,1]
[5,0], [7,0], [6,1], [7,1]
[5,0], [7,0], [5,2], [6,1], [7,1]
[5,0], [7,0], [5,2], [6,1], [4,4], [7,1]


  • 当队列中所有人的 (h,k) 都是相同的高度 h,只有 k 不同时,解决方案就是将每个人在队列的索引 index = k,即使不是所有人都是同一高度,这个策略也是可行的。因为个子矮的人相对于个子高的人是 “看不见” 的,所以可以先安排个子高的人


  • 先安排身高为 7 的人,将它放置在与 k 值相等的索引上;再安排身高为 6 的人,同样的将它放置在与 k 值相等的索引上








  • PS:本题的时间复杂度为O(n^2),空间复杂度为O(n)

3.2.2 该题的伪代码


先将people按照身高降序排序,且将相同身高按k升序排序,否则插入位置会越界

循环地读取people,根据people[i][1]也就是k,插入list,需要使用advance()找到应插入位置

将完成所有插入操作的list重建为vector返回


3.2.3 运行结果

3.2.4 该题目解题优势及难点

  • 优势:可以运用贪心算法求解,重构队列,虽然没太弄明白…

  • 难点:这道题的思路是先排身高更高的,防止后排入人员影响先排入人员位置,因为每次排入新人员[h,k]时,已处于队列的人身高都>=h,所以新排入位置就是people[k],所以先将最高的人按照 k 值升序排序,然后将它们放置到输出队列中与 k 值相等的索引位置上;按降序取下一个高度,同样按 k 值对该身高的人升序排序,然后逐个插入到输出队列中与 k 值相等的索引位置上,直到完成为止

附:阅读代码相关资料

posted on 2020-03-22 17:10    阅读(299)  评论(0编辑  收藏  举报

/*
*/