跟着左程云学面试算法

单链表判断是否有环

  1. 使用set数据结构,依次判断每个结点的next指针是否有重复,重复点即为入环点
  2. 使用快慢指针的方式,使用两个指针依次遍历链表,快指针一次走两个结点,慢指针一次只走一个结点,若两指针相遇,则有环,相遇后,慢指针停在原地,快指针放在链首,两指针均以一次一步的速度继续向下走,第一次相遇点即为入环点。
    跟着左程云学面试算法_第1张图片
    跟着左程云学面试算法_第2张图片

判断并找到两个单链表相交点

首先用上述方法判断每个链表是否有环,根据有无环可将情况分为三种

(1)、两单链表均无环

则首先遍历两链表,分别得到最后一个结点,和链表长度,若两最后一个结点地址相同,说明有相加,此时计算链表长度差值,使用两个指针,一个指针从长链表首先走差值步,第二个指针从短链表首开始,两指针再同时以相同速度出发,他们第一个相同的结点即为相交点。
跟着左程云学面试算法_第3张图片跟着左程云学面试算法_第4张图片

(2)一个链表有环,另一个链表无环

这种情况是不可能会相交的

(3)两个链表都有环

跟着左程云学面试算法_第5张图片
同样会有这三种情况,对于第二种只需判断两个有环链表的入环点是否相同,是则为第二种;若入环点不相同,则从其中一个环的入环点继续向下遍历循环一整圈,若其中经过另一个链表的入环点则为第三种情况,相交点可为任意链表的入环点,否则为第一种情况不相交。

二叉树

  • 三种遍历方式(先序头左右,中序左头右,后序左右头)的递归和非递归遍历(压栈)
  • 层序遍历的实现(先入先出队列实现)
  • 寻找最大层的两种方法:1、使用哈希表记录每个节点的层数 2、高级方法不使用哈希表

搜索二叉树

节点始终大于左孩子,小于右孩子
判断:使用中序遍历看是否为升序

完全二叉树

判断:使用层序遍历每个节点,满足下列条件

  1. 任意节点有右节点无左节点
  2. 再满足1条件情况下,如果遇到了第一个左右子节点不全的节点,那么其后面遍历的节点都是叶节点

平衡二叉树

每一个节点的左右子树的高度差小于2
这类问题可以使用递归套路(树形动态规划),因为对于每个节点,我们都要求其左右子树为平衡二叉树,且左子树和右子树的高度差小于2,那么我们使用递归函数时需要返回两个数字:

  • bool 是否为平衡二叉树
  • int 以该节点为头节点的字数的高度

那么对于叶节点,其自然返回的值为:(是平衡二叉树,高度为0)
如何解这个递归在于:利于其左右字数返回的数据判断是否符合平衡二叉树的条件,并选择左右子树中最大的高度再加一作为该子树的高度,返回给其父节点。
跟着左程云学面试算法_第6张图片
同样这个套路也可用再判断搜索二叉树中,每一个节点需要向其父节点返回的数据有:

  1. 是否为搜索二叉树
  2. 最大值
  3. 最小值
    那么一个节点利用其左右字数返回的数据可以判断其是否为搜索二叉树,并计算其最大值和最小值。
    跟着左程云学面试算法_第7张图片

树形DP

  • 罗列条件情况
  • 从左右子树拿条件

其基本的代码结构为:

  • 设置返回数据的结构(构造函数)
  • 设置递归流程,要求对空节点进行数据的初始化
  • 在主函数对头结点调用递归

满二叉树

同样使用树形DP方法可以解决:
跟着左程云学面试算法_第8张图片
跟着左程云学面试算法_第9张图片

寻找两节点o1,o2在以head为头结点的二叉树中的最小公共父节点LCA

方法1:
在遍历树时,将每个节点的其本身包括所有父节点都存储在一个集合set中,并利用哈希表将每个结点及其父节点集合联系在一起,当要查找两节点的CLA时,只需使用其中一个节点的父节点集合,在另一节点不断向上找其父节点在集合中检索,最先匹配的即为所求

方法2:
跟着左程云学面试算法_第10张图片

分两种情况:

  • o1是o2的LCA,或o2是o1的LCA
  • o1,o2互不为LCA,必须向上才能找到

寻找后继节点

已知每个节点都有指向其父节点的指针parent,注意:后继节点是指在中序遍历中该节点的下一个节点
此题要理解的中序遍历的结构:

  • 当该节点有右子树时:其后继节点为右子树的最左节点
  • 当该节点无右子树时:需要利用parent向上寻找,一个节点(有左子树)的前序节点是其左子树上最右边的节点
    跟着左程云学面试算法_第11张图片

折纸问题

跟着左程云学面试算法_第12张图片
当我们实际操作时会发现一个规律,构造一个左子树头结点始终为‘凹’,右子树头结点始终为‘凸’的满二叉树,那么该二叉树的中序遍历结果结尾所求
递归的过程很类似于递归中序遍历的代码

跟着左程云学面试算法_第13张图片

#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;

class Node {//结点
public:
	int value;
	int in;//入度
	int out;//出度
	vector nexts;
	vector edges;

	Node(int value) {
		this->value = value;
		in = 0;
		out = 0;
		nexts = vector();
		edges = vector();
	}
};
class Edge {//有向边
public:
	int weight;
	Node from;
	Node to;

	Edge(int weight,Node from, Node to) {
		this->weight = weight;
		this->from = from;
		this->to = to;
	}
};
class Graph {//图
public:
	map nodes;
	set edges;

	Graph() {
		nodes = map();
		edges = set();
	}
};
//以上是我们建立的常用图结构,而在做题时会遇到不同结构的图,需要设置一个接口将其转换为我们
//所熟悉的图结构,例:给出一个二维数组,每一行代表一个有向边其中matrix[i][0]为起点,
//matrix[i][1]为终点,matrix[i][2]为权重,设置接口如下:
Graph creatGraph(int matrix[][3]) {
	int length = sizeof(matrix) / sizeof(matrix[0]);
	Graph graph = Graph();
	for (int i = 0; i < length; i++) {
		int from = matrix[i][0];
		int to = matrix[i][1];
		int weight = matrix[i][2];
		if (graph.nodes.find(from) == graph.nodes.end()) {
			graph.nodes.insert({ from, Node(from) });
		}
		if (graph.nodes.find(to) == graph.nodes.end()) {
			graph.nodes.insert({ to, Node(to) });
		}
		Node fromNode = graph.nodes.at(from);
		Node toNode = graph.nodes.at(to);
		Edge newEdge = Edge(weight, fromNode, toNode);
		fromNode.nexts.push_back(toNode);
		fromNode.out++;
		toNode.in++;
		fromNode.edges.push_back(newEdge);
		graph.edges.insert(newEdge);
	}
	return graph;
}

//宽度优先遍历 BFS
void bfs(Node node) {
	queue queue;
	set set;
	queue.push(node);
	set.insert(node);//用set来保证检查去重的机制
	while (!queue.empty()) {
		Node cur = queue.front();
		queue.pop();
		cout << cur.value << endl;
		for (auto it = cur.nexts.begin(); it != cur.nexts.end(); it++) {
			if (set.find(*it) != set.end()) {
				set.insert(*it);
				queue.push(*it);
			}
		}
	}
}
//深度优先遍历,利用栈
void dfs(Node node) {
	stack stack;
	set set;
	stack.push(node);
	set.insert(node);
	cout << node.value << endl;
	while (!stack.empty()) {
		Node cur = stack.top();
		stack.pop();
		for (auto it = cur.nexts.begin(); it != cur.nexts.end(); it++) {
			if (set.find(*it) != set.end()) {
				stack.push(cur);
				stack.push(*it);
				set.insert(*it);
				cout << (*it).value << endl;
				break;
			}
		}
	}
}

拓扑排序算法

即在完成一项工程时,不同的工序之间有着前后依赖的关系,后一项工序必须等待前一项工序完成,如此形成一个有向图并且不存在环,我们要解决如何对每个工序进行排序的问题
算法很简单:寻找图中入度为0的节点将其放在序列中,并在图中删除该节点及其所有的影响,再次寻找入度为0的节点,循环直至将所有结点排序

//拓扑排序
vector sortedTopology(Graph graph) {
	map inMap;
	queue zeroInQueue;
	for (auto it = graph.nodes.begin(); it != graph.nodes.end(); it++) {
		inMap.insert({ it->second,it->second.in });
		if (it->second.in == 0) {
			zeroInQueue.push(it->second);
		}
	}
	vector result;
	while (!zeroInQueue.empty()) {
		Node cur = zeroInQueue.front();
		zeroInQueue.pop();
		result.push_back(cur);
		for (auto it = cur.nexts.begin(); it != cur.nexts.end(); it++) {
			inMap[*it] -= 1;
			if (inMap.at(*it) == 0) {
				zeroInQueue.push(*it);
			}
		}
	}
	return result;
}

最小生成树(无向图)

kruskal算法

从边考虑,从权重有小到大依次选取所有边,若添加该边会形成环则不添加
难点在于如何实现判断是否成环的机制,这个机制能实现集合的查询和集合的合并功能,比如要连接A和B的边,那么查询A和B所在的集合,若他们所在同一个结合中,则该边不添加,会形成环,否则说明改变可以添加不形成环,并将A和B所在的集合合并为一个集合。

你可能感兴趣的:(数据结构)