408 数据结构编程题

线性表相关

【2009年真题】

在这里插入图片描述

【解析】

快慢指针思想:

  • 定义两个指针,快指针 f a s t fast fast, 慢指针 l o w low low
  • f a s t fast fast 先向前移动 k k k 个位置,然后 l o w low low f a s t fast fast 再一起向前移动 .
  • f a s t fast fast 到达链表尾部,返回 l o w low low
struct ListNode{
	int val;
	ListNode *next;
	ListNode(int x):val(x),next(nullptr){}
};

ListNode* helper(ListNode *head, int k){
	ListNode *fast = head;
	ListNode *slow = head;
	while(fast!=nullptr){
		fast = fast->next;
		if(!k) low = low->next;
		else k--;
	}
	return slow;
}

int Solution(ListNode *head, int k){
	ListNode *tmp = helper(head, k);
	if(tmp)
	{
		cout << tmp->val;
		return 1;
	}
	else return 0;
}

【2010年真题】

在这里插入图片描述
【解析】

局部翻转的思想:

假设前 p p p 个元素构成子序列 a a a,那么题目说描述问题既是把数组 a b ab ab 翻转得到数组 b a ba ba,于是 b a = ( a − 1 b − 1 ) − 1 ba=(a^{-1}b^{-1})^{-1} ba=(a1b1)1

void helper(vector<int> &nums, int from, int to){
	int tmp;
	for(int i=0;i<(to-from+1)/2;i++)
	{
		tmp = nums[from+i];
		nums[from+i] = nums[to-i];
		nums[to-i] = tmp;
	}
}

void Solution(vector<int> &nums,int p){
	int n = nums.size();
	helper(nums, 0, p-1);
	helper(nums, p-1, n);
	helper(nums, 0, n-1);
}

复杂度分析: 三个 helper 函数的时间复杂度为 O ( p / 2 ) , O ( ( n − p ) / 2 ) , O ( n / 2 ) O(p/2),O((n-p)/2),O(n/2) O(p/2)O((np)/2),O(n/2),故所设计算法的时间复杂度为 O ( n ) O(n) O(n);空间复杂度为 O ( 1 ) O(1) O(1)

【2011真题】

在这里插入图片描述

【解析】

分别求出序列 A 和 B 的中位数,设为 a 和 b,求序列 A 和 B 的中位数过程如下:

  • 若 a = b,则 a 或 b 即为所求中位数,算法结束
  • 若 a < b,则舍弃序列 A 中较小的一半,同时舍弃序列 B 中较大的一半,要求舍弃的长度相等
  • 若 a>b,则舍弃序列 A 中较大的一半,同时舍弃序列 B 中较小的一半,要求舍弃的长度相等
  • 在保留的两个升序序列中,重复过程 上面三个步骤,直到两个序列中只含一个元素时为止,较小者即为所求的中位数
int Solutoin(vector<int> &A,vector<int> &B){
	int n = A.size();
	int s1 = 0, d1=n-1, s2 = 1, d2 = n-1;
	int m1, m2;
	while(s1!=d1||s2!=d2)
	{
		m1 = (s1+d1)/2;
		m2 = (s2+d2)/2;
		if(A[m1]==B[m2]) return A[m1];
		if(A[m1]<B[m2])
		{
			if((s1+d1)%2==0)
			{
				s1 = m1;
				d2 = m2;
			}
			else
			{
				s1 = m1+1;
				d2 = m2;
			}
		}
		else
		{
			if((s1+d1)%2==0)
			{
				d1 = m1;
				s2 = m2;
			}
			else
			{
				d1 = m1+1;
				s2 = m2;
			}
		}
	}
	return A[s1]<B[s2]?A[s1]:B[s2];
}

复杂度分析: 时间复杂度为 O ( l o g 2 n ) O(log_2n) O(log2n),空间复杂度为 O ( 1 ) O(1) O(1)

我能想到的算法:

先求得两个序列的总长度,然后对应一半计算出中位数的下标(注意是向上取整),然后对两个升序列取两指针游走比较舍去中位数之前的元素。这样下来,时间复杂度是 O ( n + m ) O(n+m) O(n+m),空间复杂度为 O ( 1 ) O(1) O(1)

【2012真题】

在这里插入图片描述

【解析】

算法的思路很简单,就是:

  • 初始化 ha = headA, hb = headB,开始遍历。
  • 若A链短,ha 会先到达链表尾,当 ha 到达末尾时,重置 ha 为 headB;同样的,当 hb 到达末尾时,重置 hb 为 headA;当 ha 与 hb 相遇时,必然就是两个链表的交点

为什么这么做可以做呢?

因为这样的一个遍历过程,对 ha 而言,走过的路程即为 a+c+b,对 hb 而言,即为 b+c+a,显然 a+c+b = b+c+a,这就是该算法的核心原理。

在这里插入图片描述

ListNode* getIntersectionNode(ListNode* srt1, ListNode* str2){
	ListNode *first1 = str1->next, *first2 = str2->next;
	ListNode *p = first1, *q = fisrt2;
	while(p->data!=q->data){
		p = p==nullptr?first2:p->next;
		q = q==nullptr?first1:q->next;
	}
	return p;
}

复杂度分析: 时间复杂度为 O ( m + n ) O(m+n) O(m+n)

【2013真题】

在这里插入图片描述

【解析】
在这里插入图片描述

第一步是一个非常重要的子模块,如何查找一个数组中出现次数最多的元素,就采用此办法

int Solution(vector<int> nums>{
	int c = nums[0], n = nums.size(), cnt = 1;
	for(int i=1;i<n;i++)
	{
		if(nums[i]==c) cnt++;
		else
		{	
			if(cnt) cnt--;
			else
			{
				c = A[i];
				cnt = 1;
			}
		}
	}
	if(cnt)
	{
		cnt  = count(nums.begin(), nums.end(), c);
		if(cnt>n/2) return c;
		else return -1;
	}
}

复杂度分析: 时间复杂度为 O ( n ) O(n) O(n),空间复杂度为 O ( 1 ) O(1) O(1)

我能想到的算法:

计数排序,时间复杂度为 O ( n ) O(n) O(n),空间复杂度为 O ( n ) O(n) O(n)

【2015 年真题】

在这里插入图片描述
【解析】

打表,以空间换时间

ListNode *Solution(ListNode *head, int n){
	vectot<int> flag(n, 0);
	ListNode *p = head, *t;
	int tmp;
	while(p->next!=nullptr)
	{
		tmp = p->next->data > 0 ? p->next->data:-p->next->data;
		if(flag[tmp])
		{
			t = p->next;
			p->next = t->next;
			delete t;
			t = nullptr;
		}
		else 
		{
			p = p->next;
			flag[tmp] = 1;
		}
	}
	return head;	
}

复杂度分析: 时间复杂度为 O ( m ) O(m) O(m),空间复杂度为 O ( n ) O(n) O(n)

【2018真题】

在这里插入图片描述
【解析】

取一个标志数组,记录数组中已经出现的正整数,由此设计的算法时间复杂度为 O ( n ) O(n) O(n),空间复杂度为 O ( n ) O(n) O(n)

int Solution(vector<int> nums){
	int n = nums.size();
	vector<int> flag(n, 0);
	for(auto num: nums)
		if(num>0 && num<=n) flag[num-1] = 1;
	int i;
	for(i=0;i<n;i++)
		if(!flag[i]) break;
	return i+1;
}

【2019年真题】

在这里插入图片描述
【解析】

思路是这样的:把链表的后半段逆序,于是就可以从前半段的正序部分和后半段的逆序部分构造出 L ′ L' L

在实际操作中,

  • 我们需要先利用快慢指针找到链表的中间结点(慢指针走一步,快指针走两步)
  • 然后对后半段链表原地逆序
  • 按要求交叉合并前半段和后半段
NODE* reverseList(NODE* head){
	NODE* pre = nullptr;
	NODE* cur = head;
	NODE* tmp = nullptr;
	while(cur)
	{
		tmp = cur->next;
		cur->next = pre;
		pre = cur;
		cur = tmp;
	}
	return pre;
} 

NODE* middleNode(NODE* head){
	NODE *p = head, *q = head;
	while(q!=nullptr&&q->next!=nullptr)
	{
		p = p->next;
		q = q->next->next;
	}
	return p;
}


NODE* Solution(NODE* head){
	NODE* second_half = middle(head);
	second_half = reverList(second->half);
	NODE *ptr1 = head->next, *ptr2 = second_half;
	while(ptr2)
	{
		second_half = ptr2->next;
		ptr2->next = ptr1->next;
		ptr1->next = ptr2;
		ptr1 = ptr2->next;
		ptr2 = second_half;
		
	}
	return head;	 
}

本题中出现的两个子函数模块是十分重要的,请加强重视

复杂度分析: 时间复杂度为 O ( n ) O(n) O(n),空间复杂度为 O ( 1 ) O(1) O(1) 满足题目要求

【2020年真题】
在这里插入图片描述
【解析】

在这里插入图片描述
在这里插入图片描述

【习题】

在这里插入图片描述
【解析】快慢指针思想,慢指针走一步,快指针走两步

ListNode* middleNode(ListNode* head) {
	ListNode *p = head, *q = head;
    while(q!=nullptr && q->next!=nullptr)
    {
    	p = p->next;
        q = q->next->next;
    }
    return p;
}

【习题】

在这里插入图片描述
【解析】

ListNode* reverseList(ListNode* head){
	ListNode* pre = nullptr;
	ListNode* cur = head;
	ListNode* tmp = nullptr;
	while(cur)
	{
		tmp = cur->next;
		cur->next = pre;
		pre = cur;
		cur = tmp;
	}
	return pre;
} 

【习题】
在这里插入图片描述
【解析】

void solution(ListNode* &head){
	ListNode* pre = nullptr;
	ListNode* cur =  head ->next;
	ListNode* tmp  = nullptr;
	while(cur!=nullptr)
	{
		tmp = cur->next;
		cur->next = pre;
		pre = cur;
		cur = tmp;
	}
	head->next = pre;
}

【习题】
在这里插入图片描述
【解析】

数组和单链表的处理是一样的,这里给出一种迭代的解法,时间复杂度是 O ( m + n ) O(m+n) O(m+n),空间复杂度是 O ( 1 ) O(1) O(1)(如果是数组的话,空间复杂度为 O ( m + n ) O(m+n) O(m+n)

ListNode* mergeTwoLists(ListNode* l1, ListNode* l2){
	ListNode* dummyhead = new ListNode(-1);
	ListNode* p = dummyhead;
	while(l1!=nullptr&&l2!=nullptr)
	{
		if(l1->val<l2->val)
		{
			p->next = l1;
			l1 = l1->next;
		}
		else
		{
			p->next = l2;
			l2 = l2->next;
		}
		p = p->next;
	}
	p->next = l1?l1:l2;
	return dummyhead->next;
} 

【习题】
在这里插入图片描述
【解法】
在这里插入图片描述

ListNode* difference(ListNode *A, ListNode *B){
	ListNode *p = A->next;
	ListNode *q = B->next;
	ListNode *pre = A;
	while(p!=nullptr && q!=nullptr)
	{
		if(p->val<q->val)
		{
			pre =p;
			p = p->next;	
		}
		else if(p->val>q->val) q = q->next;
		else 
		{
			pre->next = p->next;
			ListNode *tmp = p;
			p = p->next;
			delete tmp;
			tmp = nullptr;
		}
	}
	return A;
} 

【习题】
在这里插入图片描述
【解析】

void swap(Sqlist& L, int i, int j){
	int tmp;
	tmp = L.data[i];
	L.data[i] = L.data[j];
	L.data[j] = tmp;
}

void reverse(Sqlist& L){
	for(int i=0,j=L.length-1;i<j;i++,j--)
		swap(L, i, j);	
}

【习题】
在这里插入图片描述
【解析】

void deleteij(Sqlist& L, int i, int j){
	for(int k=j+1;k<L.length-1;k++)
		L.data[i++] = L.data[k];
	L.length -= (j-i+1);
}

【习题】
在这里插入图片描述
【解析】

在这里插入图片描述

void solution(Sqlist &L){
	int i = 0, j = L.length-1;
	int tmp  = L.data[i];
	while(i<j)
	{
		while(i<j && L.data[j]>tmp) j--;
		if(i<j)
		{
			L.data[i] = L.data[j];
			i++;
		} 
		while(i<j && L.data[i]<tmp) i++;
		if(i<j)
		{
			L.data[j] = L.data[i];
			j--; 
		}
	}
	L.data[i] = tmp;	
}

【习题】
在这里插入图片描述
【解析】

void solution(ListNode* &head){
	ListNode* p = head->next;
	ListNode* q = nullptr;
	while(p>next!=nullptr)
	{
		if(p->val==p->next->val)
		{
			q = p->next;
			p->next = q->next;
			delete q;
			q = nullptr;
		}
		else p = p->next ;
	}
}

【注】如果链表无序的话,用打表的方式更好

【习题】
在这里插入图片描述
【解析】

void solution(ListNode* &head){
	ListNode* pre = head;
	ListNode* p = heda->next;
	ListNode* tmp = p;
	ListNode* tmp_pre = pre;
	while(p!=nullptr)
	{
		if(p->val<tmp->val)
		{
			tmp = p;
			tmp_pre = pre;
		}
		pre = p;
		p = p->next;
	}
	tmp_pre->next = tmp->next;
	delete tmp;
	tmp = nullptr;
}

【习题】
在这里插入图片描述
【解析】

ListNode* solution(ListNode* &A){
	ListNode *p, *q, *r 
	ListNode* B= new ListNode(-1) ;
	r = B;
	p = A;
	while(p->next!=nullptr) 
	{
		if(p->next->data%2==0)
		{
			q = p->next;
			p->next = p->next->next;
			q->next = nullptr;
			r->next = q;
			r= q;
		}
		else p = p->next;
	}
	return B;
}

【习题】
在这里插入图片描述
【解析】

数组找最小值不是难题,一个循环加一个辅助变量就可以找到最小的元素了。但是这道题有一个限制的地方就在于没有两个变量可用。于是, i i i 就担负起了一个变量两个任务的责任,我们取 i i i 为一个两位的数字,其十位数字表示循环控制变量,个位数字表示当前位置的元素值

int solution(vector<int> A, int N){
	int i = A[0];
	while(i/10<=N-1)
	{
		if(i%10>A[i/10])
		{
			i -= i%10;
			i += A[i/10];
		}
		i += 10;
	}
	return i%10;
}

【习题】
在这里插入图片描述
【解析】

和反转链表一样的,先反转后打印,当然如果直接用递归反转链表可以不用拆分为两步

void solution(ListNode* head){
	if(head!=nullptr)
	{
		solution(head->next);
		cout << head->val << " "; 
	}
}

【习题】
在这里插入图片描述
【解析】

一趟遍历,最坏情况下(从大到小)比较次数为 2 ( n − 1 ) 2(n-1) 2(n1) 次,最好情况下(从小到大)比较次数为 n − 1 n-1 n1 次,平均一下 2 ( n − 1 ) + ( n − 1 ) 2 = 3 n 2 − 3 2 < 3 n 2 \frac{2(n-1)+(n-1)}{2}=\frac{3n}{2}-\frac{3}{2}<\frac{3n}{2} 22(n1)+(n1)=23n23<23n,所以就一趟遍历,没那么多事

【习题】
在这里插入图片描述
【解析】

int solution(vector<char>& A, vector<char>& B){
	int i = 0, n = A.size(), m = B.size();
	while(i<n && i<m)
	{
		if(A[i]==B[i]) i++;
		else break;
	}
	if(i>=n && i>=m) return 0;
	else if((i>=n && i<m) || (A[i]<B[i])) return -1;
	return 1;
}

【习题】
在这里插入图片描述
【解析】

一句话解决的事,剑指offer - 链表中倒数第k个节点,快慢指针,直接看代码

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* getKthFromEnd(ListNode* head, int k) {
        ListNode* fast = head;
        ListNode* low = head;
        while(fast!=NULL)
        {
            fast = fast->next;
            if(k==0) low = low->next;
            else k--;
        }
        return low;
    }
};

【习题】
在这里插入图片描述
【解析】
在这里插入图片描述

void reverse(vector<int>& R, int l, int r){
	int tmp;
	for(int i=1,j=r;i<j;i++,j--)
	{
		tmp = R[i];
		R[i] = R[j];
		R[j] = tmp;
	}
}

void solution(vector<int>& R, int p){
	int n = R.size();
	if(p<=0 || p>=n) cout << "ERROR" << endl;
	else
	{
		reverse(R, 0, p-1);
		reverse(R, p, n-1);
		reverse(R, 0, n-1);
	}
}

【习题】
在这里插入图片描述
【解析】
在这里插入图片描述

int solution(vector<int>& A){
	int c = A[0], n = A.size(), cnt = 1;
	for(int i=1;i<n;i++)
	{
		if(A[i]==c) cnt ++;
		else
		{
			if(cnt) cnt--;
			else
			{
				c = A[i];
				cnt = 1;
			}
		}
	}
	if(cnt) 
	{
		cnt = 0;
		for(int i=0;i<n;i++) 
			if(A[i]==c) cnt++;
	}
	if(cnt>n/2) return c;
	else return -1;
}

【关于候选主元素的选取再补充一点】

我们假定某个元素是主元素,当下一个元素和它相等时,我们就让计数变量加一,不等就减一,当计数变量变为 0 的时候,我们就需要重新选取我们的假定主元素重复上面的过程

栈和队列相关

【习题】有效括号匹配

【解析】

bool Solution(string s){
	unordered_map<char, char> pairs {{'(', ')'}, {'[', ']'}, {'{', '}'}, {'?', '?'}};
	unordered_set<char> brackets {'(', ')', '[', ']', '{', '}'};
	vector<char> st;
	st.push_back('?');
	
	for(char i: s)
	{
		if(brackets.count(i))
		{
			if(pairs.count(i)) st.push_back(i);
			else if(pairs[st.back()]==i) st.pop_back(); 
			else return false;	
		}
	} 
	return st.size()==1;
}

【习题】计算括号的深度

【解析】

维护一个栈 s,从左至右遍历括号字符串中的每一个字符:

  • 如果当前字符是 (,就把 ( 压入栈中,此时这个 ( 的嵌套深度为栈的高度;
  • 如果当前字符是 ),此时这个 ) 的嵌套深度为栈的高度,随后再从栈中弹出一个 (

例如.

在这里插入图片描述

【习题】
在这里插入图片描述
【解析】

1)两条规则:I 的个数和 O 的个数相等:从给定序列的开始到序列中的任一位置,I 的个数要大于或等于 O 的个数
2)能,如
输入 ABC,IOIOIO,输出为 ABC
输入 BAC,IIOOIO,输出为 ABC
3)

int judge(char ch[]) {
	int i = 0, o = 0;
	for(auto c: ch)
	{
		if(c=='\0') break;
		else
		{
			if(c=='I') i++;
			else o++;
			if(o>i) return 0;
		}
	}
	if(i!=o) return 0;
	return 1;
}

【习题】
在这里插入图片描述
【解析】

知道尾指针后,实现元素入队,则直接在链表尾插入就行。而实现出队,而需要根据尾指针找到头结点和开始节点,然后进行删除。要注意的是,尾指针应始终指向终端结点,并且当删除结点后队列为空时,必须特殊处理。

void Enque(ListNode* &rear, int x){
	ListNode* s = new ListNode(x);
	s->next = rear->next;
	rear->next = s;
	rear = s; 
}

bool Deque(ListNode* &rear, int &x){
	ListNode* s = nullptr;
	if(rear->next==rear) return false;
	else
	{
		s = rear->next->next;
		rear->next->next = s->next;
		x = s->val;
		if(s==rear) rear = rear->next;
		delete s;
		s = nullptr;
		return true;
	}
}

【习题】
在这里插入图片描述
【解析】
约定 f r o n t front front 指向队头元素的前一位置, r e a r rear rear 指向队尾元素,定义满足 f r o n t = = r e a r front == rear front==rear 时队空。从队尾删元素时, r e a r rear rear 向着下标减小的方向走,从队头插入元素时, f r o n t front front 向着下标减小的方向走。因此,当满足 r e a r = ( f r o n t − 1 + m a x s i z e ) % m a x s i z e rear=(front-1+maxsize)\%maxsize rear=(front1+maxsize)%maxsize 时队满。

typedef struct
{
	int nums[maxsize];
	int front, rear;
}cycque
 

bool Deque(cycque &q, int &x){ // 队尾删除 
	if(q.front==q.rear) return false;
	x = q.nums[q.rear];
	q.rear = (rear-1+maxsize)%masize;
	return true;
}

bool Enque(cycque &q, int x){ // 队头插入
	if(q.rear==(q.front-1+maxsize)%maxsize) return 0;
	q.nums[q.front] = x;
	q.front = (q.front-1+maxsize)%maxsize;
	return true;
}

注意,算法中频繁使用了 f r o n t = ( f r o n t − 1 + m a x s i z e ) % m a x s i z e front=(front-1+maxsize)\%maxsize front=(front1+maxsize)%maxsize 之类的操作,如果把这一语句放到循环中,那么 f r o n t front front 指针将沿着 m a x s i z e − 1 , m a x s i z e − 2 , ⋯   , 2 , 1 , 0 , m a x s i z e − 1 , m a x s i z e − 2 , ⋯ maxsize-1,maxsize-2,\cdots,2, 1, 0, maxsize-1, maxsize-2,\cdots maxsize1,maxsize2,,2,1,0,maxsize1,maxsize2, 的无限循环走下去,刚好和 f r o n t = ( f r o n t + 1 ) % m a x s i z e front=(front+1)\%maxsize front=(front+1)%maxsize 实现的效果相反

【习题】
在这里插入图片描述
在这里插入图片描述
【解析】

typedef struct
{
	int nums[maxsize];
	int front, rear;
	int tag
}cycque
 
void Init(cycque &q){
	q.front = q.rear = 0;
	q.tag = 0;
}

bool isEmpty(cycque q){
	if(q.front==q.rear&&!tag) return true;
	return false;
}

bool isFull(cycque q){
	if(q.front==q.rear&&tag) return true;
	return false;
}

bool Enque(cycque &q, int x){
	if(isFull(q)) return false;
	q.rear = (q.rear+1)%maxsize;
	q.nums[q.rear] = x;
	q.tag = 1;
	return true;
}

bool Deque(cycque &q, int &x){
	if(isEmpty(q)) return false;
	q.front = (q.front+1)%maxsize;
	x = q.nums[front];
	q.tag = 0;
	return true;
}

注意,对于 t a g tag tag 的赋值。初始的时候一定是 0,插入成功后置为 1,删除成功后置为 0,因为之后再插入操作之后队列才有可能满,只有在删除操作后,队列才有可能空

【习题】
在这里插入图片描述
【解析】

int Solution(int n){
	int res = 0, tmp = 0;
	int st[maxsize], top = -1;
	while(n)
	{
		tmp = n%2;
		n /= 2;
		st[++top] = tmp;
	}
	while(top!=-1)
	{
		tmp = st[top];
		top--;
		res = res*10+tmp;
	}
	return res;
}

【习题】
在这里插入图片描述
【解析】

// 递归
float Solution(flaot A, float p, float e){
	if(fabs(p*p-A)<e) return p;
	else return Solution(A, (p+A/p)/2, e);
}

// 非递归
float Solution(flaot A, float p, float e){
	while(fabs(p*p-A)>=e) p = (p+A/p)/2;
	return p;
}

【习题】
在这里插入图片描述
【解析】 S o l u t i o n ( s t r , k , n ) Solution(str, k, n) Solution(str,k,n) s t r [ k ] − s t r [ n ] str[k]-str[n] str[k]str[n] 的全排列。那么,如果 S o l u i o t n ( s t r , k − 1 , n ) Soluiotn(str, k-1, n) Soluiotn(str,k1,n) 可求的话,对于 s t r [ k ] str[k] str[k],就可取 s t r [ 0 ] − s t r [ k ] str[0]-str[k] str[0]str[k] 中的任意值,再组合 S o l u t i o n ( s t r , k − 1 , n ) Solution(str, k-1, n) Solution(str,k1,n),就得到 S o l u t i o n ( s t r , k , n ) Solution(str, k, n) Solution(str,k,n)

void Solution(vector<char> &str, int k, int n){
	char tmp;
	if(k==0)
		for(int i=0;i<n-1;i++) cout << str[i];
	else
	{
		for(int i=0;i<=k;i++)
		{
			tmp = str[k];
			str[k] = str[i];
			str[i] = tmp;
			Solution(str, k-1, n);
			tmp = str[i];
			str[i] = str[k];
			str[k] = tmp;
		}
	}
} 

树相关

【2014真题】

在这里插入图片描述
【解析】

利用层遍历记录每个结点当前所处的层,如果遇到叶结点就计算带权路径长度

int Solution(TreeNode *root){
	if(!root) return 0;
	
	int wpl = 0, deep = 0;
	TreeNode *lastnode, *newlastnode;
	deque<TreeeNode*> q;
	q.push_back(root);
	while(!q.empty())
	{
		TreeNode *tmp = q.front();
		q.pop_front();
		if(!tmp->left && !tmp->right) wpl += deep*tmp->weight;
		if(tmp->left)
		{
			q.push_back(tmp->left);
			newlastnode = tmp->left;
		}
		if(tmp->right)
		{
			q.push_back(tmp->right);
			newlastnode = tmp->right;
		}
		if(tmp = lastnode)
		{
			lastnode = newlastnode;
			deep += 1;
		}
	}
	return wpl;
}

除开本题所讨论的内容,有一个很关键的部分就是如何在层遍历中求结点所处的层次

【2017真题】

在这里插入图片描述
【解析】

添加括号时注意根结点和叶子结点时不填括号的

void helper(BTree *root, int deep){
	if(!root) return;
	else if(!root->left && !root->right) cout << root->data;
	else
	{
		if(deep>1) cout << "(";
		helper(root->left, deep+1);
		cout << root->data;
		helper(root->right, deep+1);
		if(deep>1) cout << ")";
	}
}

void Solution(BTree *root){
	helper(root, 1);
}

【习题】
在这里插入图片描述
【解析】声明一点,把类型 char 改为 类型 int,出题的是脑子吃多了弄个啥的字符类型

TreeNode* CreateBTree(vextor<int> pre, vector<int> in, int l1, int r1, int l2, int r2){
	TreeNode* root = new TreeNode(-1);
	int i;
	
	if(l1>r1) return nullptr;
	root->left = root->right = nullptr;
	for(i=l2;i<=r2;i++)
		if(in[i]=pre[l1]) break;
	root->val = in[i];
	root->left = CreateBTree(pre, in, l1+1, l1+i-l2, l2, i-1);
	root->right = CreateBTree(pre, in, l1+i-l2+1, r1, i+1, r2);
	return root;
}

【习题】
在这里插入图片描述
【解析】

int Solution(TreeNode* root) {
	int n_left, n_right;
	if(!root) return 0;
	n_left = Solution(root-left);
	n_right = Solution(root->right);
	return n_left+n_right+1;
}

【习题】
在这里插入图片描述
【解析】

int Solution(TreeNode* root) {
	int n_left, n_right;
	if(!root) return 0;
	if(!root->left && !root->right) return 1;
	n_left = Solution(root->left);
	n_right = Solution(root->right);
	return n_left+n_right;
}

【习题】
在这里插入图片描述
【解析】

先给个图,看明白题干在说啥玩意的
在这里插入图片描述
可以明确的一点,不管哪种遍历方式叶子结点的相对顺序都不会改变,所以我们就只需要在先序遍历中判断叶子结点,再单独处理就万事大吉

void Solution(TreeNode* root, TreeNode* &head, TreeNode* &tail){
	if(!root)
	{
		if(!root->left && !root->right)
		{
			if(!head) // 第一个叶子结点 
			{
				head = root;
				tail = root; 
			}
			else 
			{
				tail->right = root;
				tail = root;
			}
		}
		Soluiton(root->left, head, tail);
		Solution(root->right, head, tail);
	}
}

【习题】
在这里插入图片描述
【解析】

struct TreeNode{
	int val;
	TreeNode* left;
	TreeNode* right;
	TreeNode* parent;
	TreeNode(int x) : val(x), left(nullptr), right(nullptr), parent(nullptr) {}
}; 

void FindParent(TreeNode* cur, TreeNode* par){
	if(cur)
	{
		cur->parent = par;
		par = cur;
		FindParent(cur->left, par);
		FindParent(cur->right, par);
	}
	
}

void PrintPath(TreeNode* cur){
	while(cur)
	{
		cout << cur->val << " " << endl;
		cur = cur->parent; 
	}
}

void PrintAllPath(TreeNode* root){
	if(root)
	{
		PrintPath(root);
		PrintAllPath(root->left);
		PrintAllPath(root->right);
		
	}
}

【习题】
在这里插入图片描述
【解析】
已知先序遍历的结果,则序列的第一个元素即为根节点,将除去第一个元素后的序列分成前后长度相等的两半,前一半为左子树上的结点,后一半为右子树上的结点。只需要将根节点移动到整个序列的末尾,然后分别递归地去处理两个子序列就行

void Solution(vector<int> pre, int l1, int r1, vector<int> &post, int l2, int r2){
	if(l1<=r1) 
	{
		post[r2] = pre[l1]; // 将 pre 的第一个元素放在 post 的最后
		Solution(pre, l1+1, (l1+1+r1)/2, post, l2, (l2+r2-1)/2); // 处理 pre 中的前一半子序列,存在 post 的前一半中 
		Solution(pre, (l1+1+r1)/2, r1, post, (l2+r2-1)/2+1, r2-1); // 处理 pre 中的后一半子序列,存在 post 的后一半中 
	}
}

【习题】
在这里插入图片描述
【解析】

int layer = 0;
int Solution(TreeNode* root, int x){
	if(root)
	{
		if(root->val==x) return layer;
		layer++;
		Solution(root->left, x);
		Solution(root->right, x);
		layer--;
	}
}

这里对 layer-- 解释一下:如果按照层次遍历的话,当然就没有这个破事。但是上面代码中采用的是先序遍历的方式,所以我们看下面这图展示指针在遍历整个树时的行走路线

在这里插入图片描述
很清楚地可以看到,在访问当前结点的左子树的时候指针向下移动层数 + 1 +1 +1,当访问完当前结点右子树所有结点后,指针向上移动层数自然就应该 − 1 -1 1

【习题】
在这里插入图片描述
【解析】

void Solution(TreeNode* root){
	if(root)
	{
		visit(root);
		Solution(root->left);
		visit(root);
		Solution(root->right);
	}
}

【习题】
在这里插入图片描述
【解析】
1)沿着结点 t 的右子树一直往下走,直到遇到右指针为右线索的结点为止,该节点即为所求结点

TBTNode* inLast(TBTNode* t){
	TBTNode* p = t;
	while(p && !p->rtag) p = p->right;
	return p;
} 

2)若结点 t 有左线索,则其左线索所指结点即为其中序的前驱结点;反之,则其左子树的最后一个结点为其中序的前驱结点

TBTNode* inPrior(TBTNode* t){
	TBTNode* p = t->left;
	if(p && !p->left) p =p->left;
	return p;
}

在这里插入图片描述

TBTNode* treNext(TBTNode* root){
	TBTNode* p = t;
	if(!t->ltag) p = t->left;
	else if(!t->rtag) p = t->right;
	else
	{
		while(p && p->rtag) p = p->right;
		if(p) p = p->right;
	}
	return p;
}

【习题】
在这里插入图片描述
【解析】

stack<int> st;
void PrintAllPath(TreeNode* root){
	int tmp;
	if(root)
	{
		st.push(root->val);
		if(!root->left && !root->right)
		{
			while(!st.empty())
			{
				tmp = st.top();
				st.pop();
				count << tmp;
			}
		}
		PrintAllPath(root->left);
		PrintAllPath(root->right);
		st.pop();
	}
}

图相关

【2014真题】

在这里插入图片描述
在这里插入图片描述
【解析】

(1)题中给出的是一个简单的网络拓扑图,可以抽象为无向图

(2)链式存储结构如下图所示
在这里插入图片描述
对应代码定义为

struct LinkNode{
	unsigned int ID, IP;
};

struct NetNode{
	unsigned int Prefix, Mask;
};

struct ArcNode{
	int flag;
	LinkNode Lnode;
	NetNode Nnode;
	unsigned int Metric;
	ArcNode *next;
};

struct Head{
	unsigned int RounterID;
	ArcNode *link;
	Head *next;
};

在这里插入图片描述
(3)计算结果 如下表所示

在这里插入图片描述

【习题】

在这里插入图片描述
【解析】在不带权的无向图中,BFS 体现了从图中某个顶点开始,以由近向远层层扩展的方式遍历图中结点的过程,故只需要在 BFS 中返回最后访问的一个顶点即可

vector<int> visited(maxsize, 0);

int Solution(AGraph* G, int v){
	ArcNode *p;
	deque<int> dq;
	int tmp;
	
	Visit(v);
	visited[v] = 1;
	dq.push_back(v);
	while(!dq.empty())
	{
		tmp = dq.front();
		dp.pop_front();
		p = G->adjlist[tmp].firstarc;
		while(p)
		{
			if(!visited[p->adjvex])
			{
				Visit(p->adjvex);
				visited[p->adjvex] = 1;
				dq.push_back(p->adjvex);
			}
			p = p->next;
		}
	}
	
	return tmp;
}

【习题】

在这里插入图片描述
【解析】要求 ∣ E ∣ = ∣ V ∣ − 1 |E|=|V|-1 E=V1 ,题中还应该假设无向图的顶点数和边数是未知的

void helper_DFS(AGraph* G, int v, int &vcnt, int &ecnt){
	ArcNode* p;
	visited[v] = 1;
	Visit(v);
	vnt++;
	p = G->adjlist[v].first;
	while(p)
	{
		ecnt++;
		if(!visited[p->adjvex])
		{
			Solution(G, p->adjvex, vcnt, ecnt);
			p = p->next;
		}
	}
} 

int Solution(AGraph* G){
	int vnt = 0, ecnt = 0;
	vector<int> visited(G->n, 0);
	helper_DFS(G, 0, vcnt, ecnt);
	if((vcnt==G->n)&& (ecnt/2==(G->n-1)) return 1; // 注意前面代码在算边的时候有重复计数
	else return 0;
}

【习题】

在这里插入图片描述
【分析】

int Solution(AGraph* G, int i, int j){
	vector<int> visited(G->n, 0);
	DFS(G, i);
	if(visited[j]) return 1;
	else return 0;
} 

【习题】
在这里插入图片描述
【解析】

bool hasCyc(AGraph* g, int v, vector<bool> visited){
	bool flag;
	ArcNode* p;
	visited[v] = true;
	for(p=g->adjList[v].fist;p!=nullptr;p=p->next)
	{
		if(visited[p->adjV]==true) return true;
		else flag = hasCyc(g, p->adjV,visited);
		if(flag) return true;
		visited[p->adjV] = false;
		return false;
	}
}

【习题】
在这里插入图片描述
【解析】

void Solution(<vector<vector<int>> &g1, AGraph g2){
	ArcNode* p = nulptr;
	memset(g1, 0, sizeof(g1));
	for(int i=0;i<g1.szie();i++)
	{
		p = g2.adjList[i].fisrt;
		while(p)
		{
			g1[i][p->adjvex] = 1;
			p = p->next;
		}
	}
}

【习题】
在这里插入图片描述
【解析】

int Solution(AGraph* g, int k){
	ArcNode* p = nullptr;
	int sum;
	for(int i=0;i<g->n;i++)
	{
		p = g->adjList[i].first;
		while(p)
		{
			if(p->adjvex==k)
			{
				sum++;
				break;
			}
			p = p->next ;
		}
	}
	return sum;
}

【习题】
在这里插入图片描述
【解析】

void DFS(AGraph* g, int v){
	ArcNode* p;
	vector<int> st;
	vector<int> visited(maxsize);
	Visit(v);
	visited[v] = 1;
	st.push_back(v);
	while(st)
	{
		int k = st.back();
		p = g->adjList[k].fisrt;
		while(p && visited[v->adjvex]) p = p->next;
		if(!p) st.pop_back();
		else
		{
			Visit(p->adjvex);
			visited[p->adjvex] = 1;
			st.push_back(p->adjvex);
		}
	}	
}

【习题】
在这里插入图片描述
在这里插入图片描述
【解析】最小生成树算法,因为有些道路已经存在了,所以只在此基础上继续建立,这里采用克鲁斯卡尔算法

int getRoot(int a, vector<int> set){ // 找并查集中根节点的函数
	while(a!=set[a]) a = set[a];
	return a;
}

int LowCost(vector<Road> road){
	int a, b;
	int mmin = 0;
	for(int i=0;i<N;i++)
		set[i] = i; // 初始化并查集,各村庄是孤立的,因此自己就是根节点
	for(int i=0;i<M;i++) 
	{
		if(road[i].is) // 将已经有路相连的村庄合并为一个集合 
		{
			a = getRoot(road[i].a, set);
			b = getRoot(road[i].b, set);
			set[a] = b;
		}
	}
	// 对 road 中的 M 条 道路按照花费进行排序 
	sort(road, M);
	// 从 road 中逐个调出应修的道路 
	for(int i=0;i<M;i++)
	{
		a=getRoot(road[i].a, set);
		b=getRoot(road[i].b, set);
		if(!road[i].is && a!=b) // 当 a、b 不属于一个集合,并且 a、b 间没有道路时,将 a、b 并入一个集合,并记录下修建 a、b 间道路所需的花费 
		{
			set[a] = b;
			mmin += road[i].cost;
		}
	}
	return mmin;
}

【习题】
在这里插入图片描述
【解析】

bool BFS(AGraph* g, int v, int u){
	ArcNode* p = nullptr;
	vector<int> qu;
	vector<int> visited(maxsize);
	qu.push_back(v);
	visited[v] = 1;
	while(visited.size())
	{
		int tmp = qu.pop(0);
		if(tmp==u) return true;
		p = G->adjList[tmp].first;
		while(p)
		{
			if(!visited[p->adjvex])
			{
				qu.push_back(p->adjvex);
				visited[p->adjvex] = 1;
			}
			p = p->next;
		}
	}
	return false;
}

时间复杂度为 O ( n + e ) \mathcal O(n+e) O(n+e)

【习题】
在这里插入图片描述
【解析】

vector<int> visited(maxsize);
int sum = 0;

void DFS(AGraph* g, int v){
	ArcNode* p = g->adjList[v].first;
	visited[v] = 1;
	sum++;
	
	while(p)
	{
		if(!visited[p->adjvex]) DFS(g, p->adjvex);
		p = p->next;
	}
}

void PrintAllRoot(AGraph* g){
	for(int i=0;i<g->n;i++)
	{
		sum = 0;
		memset(visited, 0, sizeof(visited));
		DFS(g, i);
		if(sum==g->n) cout << i << endl;
	}
}

排序相关

【2016年真题】

在这里插入图片描述

【解析】

其实题意翻译过来就是,把最小的 n / 2 n/2 n/2 个元素归为 A 1 A_1 A1,把剩下的所有元素归为 A 2 A_2 A2,虽然官方题解中给出了一种时间复杂度为 O ( n ) O(n) O(n) 的方法,因为按照快排的思路,其实是不需要对全序列进行排序的

但是,从答题的角度(毕竟就是背一个排序算法上去嘛),我还是选择对全序列应用快速排序,时间复杂度为 O ( n l o g 2 n ) O(nlog_2n) O(nlog2n),空间复杂度为 O ( l o g 2 n ) O(log_2n) O(log2n),根据评分规则,官方 O ( n ) O(n) O(n) 的方法评分 13 分, O ( n l o g 2 n ) O(nlog_2n) O(nlog2n) 评分 11 分,相差无几

int partition(vector<int> &nums, int l, int r){
	int pivot = l, key = nums[l];
	for(int i=l;i<=r;i++)
		if(nums[i]<key) swap(nums[i], nums[++pivot]);
	swap(nums[pivot], nums[l]);
	return pivot;
}

void quicksort(vector<int> &nums, int l, int r){
	if(l<r)
	{
		int pos = partition(nums, l, r);
		quicksort(nums, l, pos-1);
		quicksort(nums, pos+1, r);
	}
}

int Solution(vector<int> &nums){
	int n = nums.size(), s1=0, s2=0;
	quicksort(nums, 0, n-1);
	for(int i=0;i<n;i++)
	{
		if(i<n/2) s1 = s1+nums[i];
		else s2 = s2+nums[i];
	}
	return s2-s1;
}

【习题】
在这里插入图片描述
【解析】

void ContSort(vector<int> A, vector<int> &B, int n) {
	int cnt;
	for(int i=0;i<n;i++)
	{
		cnt = 0;
		for(j=0;i<n;j++)
			if(A[j]<A[i]) cnt++;
		B[cnt] = A[i];
	}
}

比较次数为 n 2 n^2 n2,简单选择排序比这种计数排序好,因为简单选择排序的空间复杂度是常量级的

【习题】
在这里插入图片描述【解析】

我们先给出正确的快速排序

void QuickSort(vector<int> R, int low, int high){
	int tmp;
	int i = low, j = high;
	if(low<high)
	{
		tmp = R[low];
		while(i<j)
		{
			while(j>i && R[j]>=tmp) j--;
			if(i<j)
			{
				R[i] = R[j];
				i++
			}
			while(i<j && R[i]<tmp) i++;
			if(i<j)
			{
				R[j] = R[i];
				j--;
			}
		}
		R[i] = tmp;
		QuickSort(R, low, i-1);
		QuickSort(R, i+1, high);
	}
}

可以发现题目中的代码和正误的代码不同在 tmp=lowR[j]>=R[tmp],也就是说题中的快排代码保存的是关键字的下标,由于后面的比较移动过程中可能会改变 tmp 下标所指位置上的关键字,造成 R[i]=R[tmp] 赋值错误,从而引起排序失败

【习题】
在这里插入图片描述
【解析】双向冒泡排序算法也称为鸡尾酒排序法

void cocktailsort(vector<int>& nums)
{
	int l = 0, r = (int)nums.size()-1;
	while(l<r)
	{
		for(int i=l;i<r;i++)
			if(nums[i]>num[i+1]) swap(nums[i], nums[i+1]);
		r--;
		for(int i=r;i>l;i--)
			if(nums[i]<nums[i-1]) swap(nums[i], nums[i-1]);
		l++; 
	}
}

【习题】
在这里插入图片描述
【解析】

void Solution(vector<int> &nums){
	int left = 0, right = nums.size()-1;
	while(left<right)
	{
		while(left<right && nums[left]<0) left++;
		tmp = nums[left];
		while(left<right && nums[right]>0) right--;
		swap(nums[left], nums[right]);
		left++;
		right--;
	}
}

时间复杂度为 O ( n ) \mathcal O(n) O(n),空间复杂度为 O ( 1 ) \mathcal O(1) O(1)

【习题】
在这里插入图片描述
【解析】

void Slution(vector<int> A, vector<int> &B){
	for(int i=0;i<A.size();i++)
		B[A[i]] = A[i];
}

时间复杂度为 O ( n ) \mathcal O(n) O(n),时间复杂度为 O ( n ) \mathcal O(n) O(n)

查找相关

习题】
在这里插入图片描述
【解析】
在这里插入图片描述
但是怎么理解这句话呢?

在这里插入图片描述

struct TreeNode{
	int val;
	int lsize;
	TreeNode* left;
	TreeNode* right; 
}; 

TreeNode* Solution(TreeNode* root, int k){
	if(!root || k<1) return nullptr;
	if(root->lsize==k) return root;
	if(root->lsize>k) return Solution(root->left, k);
	else return Solution(root->right, k);
}

【习题】
在这里插入图片描述
【解析】

struct TreeNode{
	int val;
	int count;
	TreeNode* left;
	TreeNode* right;
	TreeNode(int x) : val(x), count(1), left(nullptr), right(nullptr) {} 
};

void Solution(TreeNode* &root, TreeNode* &pre, int x){ // pre 指向 root 的父结点
	TreeNode* p = root;
	while(!p)
	{
		if(p->val!=x)
		{
			pre = p;
			if(x<p->val) p = p->left;
			else  p = p->right;
		}
		else
		{
			(p->count)++;
			return;
		}
	}
	TreeNode* tmp = new TreeNode(x);
	if(!pre) root = tmp;
	else if(x<pr->val) pr->left = tmp;
	else pre->right = tmp;
}

【习题】
在这里插入图片描述
【解析】先说明一下理论部分,对序列 S S S,如果从其能依据给定的值生成两个子序列 S 1 、 S 2 S_1、S2 S1S2 S 1 S_1 S1 单调递减且每个元素均小于给定值, S 2 S_2 S2 单调递增且每个元素均大于给定值,则我们说 S S S 是二叉排序树的一个查找序列

void Split(vector<int> &S, vector<int> &S1, vector<int> &S2){
	int i = 0;
	S.push_back('#'); // 结束标志位 
	while(S[i]!='#') 
	{
		while(i<S.size()-1 && S[i]<S[i+1])
		{
			S1.push_back(S[i]);
			i++;
		}
		while(i<S.size()-1 && S[i]>S[i+1])
		{
			S2.push_back(S[i]);
			i++;
		}		
	}
}

bool Judge(vector<int> S1, vector<int> S2, int x){
	bool flag = true;
	int i = 0;
	for(int i=0;i<S1.size()-1 && flag;i++)
		if(S1[i]>x) flag = false;
	for(int i=0;i<S2.size()-1 && flag;i++)
		if(S2[i]<x) flag = false;
	return flag;
} 

bool Solution(vector<int> &S, int x){ // x 是给定的查找值 
	vector<int> S1;
	vector<int> S2;
	Split(S, S1, S2);
	return Judge(S1, S2, x);
}

【习题】
在这里插入图片描述
【解析】对给定的二叉树进行中序遍历,如果能保证前一个值不大于后一个值,那么该二叉树就是一棵二叉排序树

int pre = INT_MIN;
bool Solution(TreeNode* root){
	bool flag_left = flag_right = false;
	if(!root) return true;
	flag_left = Solution(root->left);
	if(!flag_left || pre>root->val) return false;
	pre = root->val;
	flag_right = Solution(root->right);
	return flag_right;
}

本题的实质是对排序树的中序遍历,故时间复杂度为 O ( n ) \mathcal O(n) O(n),递归遍历,空间复杂度为 O ( l o g 2 n ) \mathcal O(log_2n) O(log2n)

  • 10
    点赞
  • 88
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值