跟着左神学算法——数据结构与算法学习日记(五)

241 阅读5分钟
持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第8天,点击查看活动详情

最近开始接触与学习数据结构与算法,一方面为了解决课内数据结构课解题思路不清晰的问题,另一方面也是听说左神的大名,故开始跟着左神一起学习数据结构与算法。同时以写博客的形式作为输出,也算是为了对所学的知识能掌握的更深吧

两个单链表相交的一系列问题

题目:给定两个可能有环也可能无环的单链表,头节点head1和head2.请实现一个函数,如果两个链表相交,返回相交的第一个节点。如果不相交,返回null 要求:如果i两个链表长度之和为N,时间复杂度请达到O(N),额外空间复杂度请达到O(1)

一条链表有环的情况找出环的第一个节点: 思路:快慢指针,快指针一次走两步,慢指针一次走一步,直到两个指针第一次相遇,然后慢指针原地不动,快指针回到原点,两个指针一次走一步,当快慢指针再一次相遇时,他们相遇的位置一定是环的起点位置。【数学证明过程略】

image.png

image.png 代码实现:

public static Node getLoopNode(Node head){
	if(head == null || head.next == null || head.next.next == null)
	return null;

	Node fastNode = head;
	Node slowNode = head;

	while(fastNode != slowNode){
		if(fastNode.next==null || fastNode.next == null) return null;
		fastNode = fastNode.next.next;
		slowNode = slowNode.next;
	}

	fastNode = head;
	
	while(fastNode !=slowNode){
		fastNode = fastNode.next;
		slonNode = slowNode.next;
	}
	return slowNode;
}

两个链表都无环的情况,找出相交的第一个节点 思路:由于单链表一个节点只有一个next,所以不存在两条单链表相交后分离的情况,所以首先判断两条链表的最后一个节点是否是同一个。如果是的话就说明两条单链表存在公共部分,然后判断两条单链表长度差值x,让长的那条先走x距离,然后两条链表同时遍历,当两个指针相等时所在的位置就是第一个节点

image.png 代码实现: ^7b27dd

public static Node noLoop(Node head1,Node head2){
	if(head1 == null || head2 == null) return null;
	Node cur1 = head1;
	Node cur2 = head2;
	int n = 0;
	while(cur1.next!=null){
		n++;
		cur1 = cur1.next;
	}
	while(cur2.next!=null){
		n++;
		cur2 = cur2.next;
	}

	if(cur1!=cur2){
		//比较结尾
		return null;
	}

	cur1 = n>0 ? head1:head2;
	cur2 = cur1 == head1 ? head2 : head1;
	n = Math.abs(n);

	while(n!=0){
		n--;
		cur1 = cur.next;
	}

	while(cur1!=cur2){
		cur1 = cur1.next;
		cur2 = cur2.next;
	}
	return cur1;
}

两个链表都有环的情况,返回第一个相交的节点 存在三种情况:

image.png

思路:可以根据两条链表的环部分的第一个节点loop1和loop2是否相等分成两种,第一种是2的情况,loop1 == loop2;第二种是1和3的情况,loop1 != loop2。

  • 2情况可以将loop1以前的部分看成两个链表都无环的情况,直接找出相交的第一个节点即可
  • 1和3的区分思路是:让一个指针从loop1出发,如果在它回到原来的位置前遇到了loop2,就是情况3,否则就是情况1

代码实现:

public static Node bothLoop(Node head1,Node loop1,Node head2,Node loop2){//loop1和loop2可以通过上面的getLoopNode函数得到
	Node cur1 = null;
	Node cur2 = null;

	if(loop1 == loop2){
		//情况2
		int n = 0;
		cur1 = head1;
		cur2 = head2;

		while(cur1.next!=null){
			n++;
			cur1 = cur1.next;
		}

		while(cur2.next!=null){
			n--;
			cur2 = cur2.next;
		}

		cur1 = n > 0 ? head1 : head2;//cur1谁长就是谁
		cur2 = cur1 == head1 : head2 : head1;

		n = Math.abs(n);
		while(n!=0){
			n--;
			cur1 = cur1.next;
		}

		while(cur1!=cur2){
			cur1 = cur1.next;
			cur2 = cur2.next;
		}
	}else{
		cur1 = loop1;
		while(cur1!=loop1){
			if(cur1 == loop2){
				return loop1;
			}
			cur1 = cur1.next;
		}
		return null;
	}
}

遍历二叉树的三种的方式

二叉树的节点结构

Class Node<V>{
	V value;
	Node left;
	Node right;
}
  1. 先序(头左右):对于二叉树中的每一个子树来说,都是先遍历头节点,然后遍历左节点再遍历右节点
  2. 中序(左头右):对于二叉树中的每一个子树来说,都是先遍历左节点,然后遍历头节点再遍历右节点
  3. 后序(左右头):对于二叉树中的每一个子树来说,都是先遍历左节点,然后遍历右节点再遍历头节点

代码实现:

public static void method(Node head){
	if(head == null) return;
	//在此处对head进行操作就是先序
	method(head.left);
	//在此处对head进行操作就是中序
	method(head.right);
	//在此处对head进行操作就是后序
}

一般使用递归进行三种顺序的遍历二叉树

栈与二叉树的混合运用——使用迭代的方式遍历二叉树

使用栈先序遍历二叉树

image.png 代码实现:

public static void preOrderUnRecur(Node head){
	if(head != null){
		Stack<Node> stack = new Stack<Node>();
		stack.add(head);
		while(!stack.isEmpty()){
			head = stack.pop();//弹出元素
			/*
				这里可以对head进行处理,比如打印等操作
			*/
			if(head.right!=null){
				stack.push(head.right);//压入右节点
			}
			if(head.left!=null){
				stack.push(head.left);//压入左节点
			}
	}
	
}

使用栈后序遍历二叉树:

思路:利用栈先进后出的特性,每次在对弹出栈的元素进行处理时将该元素压入新的一个收集栈中,这样子在遍历收集栈中元素时就是先序的逆序(后序)

代码实现不难,可以自行实现

使用栈中序遍历二叉树:

思路:将每棵树的左节点全部压入栈中,然后开始将元素弹出栈,每弹出一个元素cur,就进行相应操作,然后看cur有没有右子树,如果有的话,就把右子树下的所有左节点压入栈中,再继续弹出元素。重复此操作

image.png

image.png 代码实现:

public static void inOrderUnRecur(Node head){
	if(head!=null){
		Stack<Node> stack = new Stack<Node>;
		while(!stack.isEmpty() || head != null){
			if(head.left!=null){
				//有左节点
				stack.push(head.left);
				head = head.left;
			}else{
				head = stack.pop();
				/*
					进行处理,比如打印等操作
				*/
				head = head.right;
			}
		}
	}
}

二叉树的宽度优先遍历

求一棵二叉树的宽度——使用队列

思路图解:

image.png

代码实现:

public static void sort(Node head){
	if(head==null) return;

	//创建一个队列
	Queue<Node> queue = new LinkedList<>();
	queue.add(head);
	while(!queue.isEmpty()){
		Node cur = queue.poll();//弹出元素
		/*
			进行操作,比如打印
		*/
		if(cur.left!=null) queue.add(cur.left);
		if(cur.right!=null) queue.add(cur.right);
	}
}

拓展:找出二叉树中宽度最大的一层有多少个节点

思路: 使用curLevel记录当前遍历所在的层数,使用curLevelNodes记录下当前层数有多少个节点,使用max记录目前为止宽度最大的一层的节点数 使用哈希表,在每次队列进入新的节点时记录下该结点所在的层数,每次队列弹出元素时判断该元素是不是属于当前层curLevel,是的话curLevelNodes++,不是的话更新max,然后记录新一层的节点数量

代码实现:

public static int getMaxNodes(Node head){
	if(head == null) return n0;

	Queue<Node> queue = new LinkedList<>();
	queue.add(head);//根节点进入队列
	HashMap<Node,Integer> levelMap = new HashMap<>();//记录节点与层数的对应关系
	levelMap.put(head,1);
	int curLevel = 1;
	int curLevelNodes = 0;
	int max = Integer.MIN_VALUE;
	while(!queue.isEmpty()){
		//弹出队列中的元素
		cur = queue.poll();
		int curNodeLevel = levelMap.get(cur);//当前节点所在的层数curNodeLevel
		//判断该元素与当前层数的关系,是的话curLevelNodes++,不是的话curLevel+1且curLevelNodes重置为1;更新max是否是上一层的节点数
		if(curNodeLevel==curLevel){
			curLevelNodes++;
		}else{
			max = Math.max(curLevelNodes,max);
			curLeve++;
			curLevelNodes = 1;
		}
		//队列加入弹出节点的左节点和右节点,同时记录下加入的节点与它所在层数的关系
		if(cur.left!=null){
			queue.add(cur.left);
			levelMap.put(cur.left,curNodeLevel+1);
		}
		if(cur.right!=null){
			queue.add(cur.right);
			levelMap.put(cur.right,curNodeLevel+1);
		}
	}
}