面试题24、25、26、27

面试题24.反转链表

在这里插入图片描述

双指针迭代

  • 定义两个指针,pre 和 cur,pre 在前 cur 在后
  • 定义一个暂存节点 temp,存放 pre 的下一个节点
  • 每次让 pre 的 next 指向 cur,实现一次局部反转
  • 局部反转完成后,pre 和 cur 同时向前移动一个位置
  • 循环上述过程,直至 pre 到达链表尾部

在这里插入图片描述

class Solution {
    public ListNode reverseList(ListNode head) {
    	if(head == null || head.next == null) return head;
		ListNode pre = head, cur = null, temp = null;
		while(pre != null) {
			temp = pre.next; //暂存下一节点
			pre.next = cur; 
			cur = pre;
			pre = temp;
		}
		return cur;
    }
}
  • 时间复杂度 O(N) : 遍历链表使用线性大小时间。
  • 空间复杂度 O(1) : 变量 pre 和 cur 使用常数大小额外空间。

递归

  • 使用递归函数,一直递归到链表的最后一个节点,该节点就是反转后的头节点,记作 res
  • 此后,每次函数在返回的过程中,让当前节点的下一个节点的 next 指针指向当前节点
  • 同时让当前节点的 next 指针指向 null,从而实现从链表尾部开始的局部反转
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
class Solution {
	public ListNode reverseList(ListNode head) {
		if(head == null || head.next == null) {
			return head;
		}
		//这里的cur就是最后一个节点,也就是反转后的头节点
		ListNode cur = reverseList(head.next);
		/如果链表是 1->2->3->4->5,那么此时的cur就是5
		//而head是4,head的下一个是5,下下一个是空
		//所以head.next.next 就是5->4
		head.next.next = head;
		//防止链表循环,需要将head.next设置为空
		head.next = null;
		//每层递归函数都返回cur,也就是最后一个节点
		return cur;
	}
}
  • 时间复杂度 O(N) : 遍历链表使用线性大小时间。
  • 空间复杂度 O(N): 遍历链表的递归深度达到 N ,系统使用 O(N) 大小额外空间。

————————————————————————————————————————

面试题25.合并两个排序的链表

在这里插入图片描述

伪头节点+双指针

解题思路:

  • 使用双指针 l1 和 l2遍历两链表,根据 l1.val 和 l2.val 的大小关系确定节点添加顺序
  • 引入伪头节点:由于初始状态合并链表中无节点,因此循环第一轮时无法将节点添加到合并链表中。解决方案:初始化一个辅助节点 dum 作为合并链表的伪头节点,将各节点添加至 dum 之后。
    在这里插入图片描述

算法流程:

  • 初始化:伪头节点 dum,节点 cur 指向 dum

  • 循环合并:当 l1 或 l2 为空时跳出;

  • 合并剩余尾部:跳出时有两种情况,即 l1 为空或 l2 为空

    • 1.若 l1 = null:将 l2 添加至节点 cur 之后
    • 2.否则:将 l2 添加至节点 cur 之后
class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
		ListNode dum = new ListNode(-1);
		ListNode cur = dum;
		while(l1 != null && l2 != null) {
			if(l1.val < l2.val) {
				cur.next = l1;
				l1 = l1.next;
			} else {
				cur.next = l2;
				l2 = l2.next;
			}
			cur = cur.next;
		}
		cur.next = (l1 == null ? l2 : l1);
		return dum.next;
    }
}
  • 时间复杂度 O(M+N) : M, N 分别为链表 l1,l2 的长度,合并操作需遍历两链表。
  • 空间复杂度 O(1) : 节点引用 dum , cur 使用常数大小的额外空间。

递归

class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
		//递归实现
		return recur(l1, l2);
    }
    //实现 l1 和 l2 的合并,返回合并之后的链表
    public ListNode recur(ListNode l1, ListNode l2) {
    	if(l1 == null && l2 == null) return null;
    	if(l1 == null) return l2;
    	if(l2 == null) return l1;

		//新建头节点
		ListNode head = null;
		
		//如果l1.val <= l2.val,那么头节点的值为l1的值,然后开始递归
		if(l1.val <= l2.val) {
			head = new ListNode(l1.val);
			head.next = recur(l1.next, l2);
		}
		//否则,头节点的值为l2的值,然后开始递归
		else {
			head = new ListNode(l2.val);
			head.next = recur(l1, l2.next);
		}
		//返回链表
		return head;
    }
}

————————————————————————————————————————

面试题26.树的子结构

在这里插入图片描述
要判断 B 是否是 A 的子结构,像下面这样,我们只需要从根节点开始判断,通过递归的方式比较他的每一个子节点即可
在这里插入图片描述
此逻辑代码为:

public boolean isSubStructure(TreeNode A, TreeNode B) {
	//边界条件判断,如果 A 和 B 有一个为空,返回false
	if(A == null || B == null) return false;
	return isSub(A, B);
}

public boolean isSub(TreeNode A, TreeNode B) {
	//这里如果B为空,说明B已经访问完了,确定是A的子结构
	if(B == null) return true;
	//如果B不为空A为空,或者这两个节点值不同,说明B树不是
    //A的子结构,直接返回false
	if(A == null || A.val != B.val) return false;
	//当前节点比较完之后还要继续判断左右子节点
	return isSub(A.left, B.left) && isSub(A.right, B.right);
}

但是不一定从根节点开始,B不光有可能是A的子结构,也有可能是A左子树的子结构或者右子树的子结构,所以如果从根节点判断B不是A的子结构,还要继续判断B是不是A左子树的子结构和右子树的子结构
在这里插入图片描述

public boolean isSubStructure(TreeNode A, TreeNode B) {
    if (A == null || B == null)
        return false;
    //先从根节点判断B是不是A的子结构,如果不是在分别从左右两个子树判断,
    //只要有一个为true,就说明B是A的子结构,满足以下三种情况之一即可
    return isSub(A, B) || isSubStructure(A.left, B) || isSubStructure(A.right, B);
}

boolean isSub(TreeNode A, TreeNode B) {
    //这里如果B为空,说明B已经访问完了,确定是A的子结构
    if (B == null)
        return true;
    //如果B不为空A为空,或者这两个节点值不同,说明B树不是
    //A的子结构,直接返回false
    if (A == null || A.val != B.val)
        return false;
    //当前节点比较完之后还要继续判断左右子节点
    return isSub(A.left, B.left) && isSub(A.right, B.right);
}
  • 时间复杂度 O(MN) : 其中 M,N 分别为树 A 和 树 B 的节点数量;先序遍历树 A 占用 O(M) ,每次调用 isSub(A, B) 判断占用 O(N) 。
  • 空间复杂度 O(M): 当树 A 和树 B 都退化为链表时,递归调用深度最大。当 M≤N 时,遍历树 A 与递归判断的总递归深度为 M ;当 M>N 时,最差情况为遍历至树 A 叶子节点,此时总递归深度为 M。

————————————————————————————————————————

面试题27.二叉树的镜像

在这里插入图片描述

递归

根据二叉树镜像的定义,递归遍历(dfs)二叉树,交换每个节点的左/右子节点

1、终止条件:当节点 root 为空时(即越过叶节点),返回 null;

2、递推工作:

  • 1.初始化节点 temp,用于暂存 root 的左子节点;
  • 2.开启递归右子节点 mirrorTree(root.right),并将返回值作为 root 的左子节点。
  • 3.开启递归左子节点 mirrorTree(temp),并将返回值作为 root 的右子节点。

3、返回值:返回当前节点 root

class Solution {
	//该方法的作用:返回 root 的镜像二叉树
    public TreeNode mirrorTree(TreeNode root) {
        if(root == null) return null;
        TreeNode tmp = root.left;
        //新的左子树是原先右子树的镜像
        root.left = mirrorTree(root.right);
        root.right = mirrorTree(tmp);
        return root;
    }
}
  • 时间复杂度 O(N) : 其中 N 为二叉树的节点数量,建立二叉树镜像需要遍历树的所有节点,占用 O(N) 时间。
  • 空间复杂度 O(N) : 最差情况下(当二叉树退化为链表),递归时系统需使用 O(N) 大小的栈空间。

辅助栈(队列)

利用栈(或队列)遍历树的所有节点 node,并交换每个 node 的左 / 右子节点

算法流程:

  • 1.特例处理:当 root 为空时,直接返回 null
  • 2.初始化:栈(或队列),加入根节点 root
  • 3.循环交换:当栈 stack 为空时跳出;
    • 出栈:记为 node
    • 添加子节点:将 node 左和右子节点入栈
    • 交换:交换 node 的左 / 右子节点
  • 4.返回值:返回根节点 root
    在这里插入图片描述
    在这里插入图片描述
class Solution {
    public TreeNode mirrorTree(TreeNode root) {
		if(root == null) return null;
		Stack<TreeNode> stack = new Stack<>();
		stack.push(root);
		while(!stack.isEmpty()) {
			TreeNode node = stack.pop();
			if(node.left != null) stack.push(node.left);
			if(node.right != null) stack.push(node.right);
			TreeNode temp = node.left;
			node.left = node.right;
			node.right = temp;
		}
		return root;
    }
}
  • 时间复杂度 O(N) : 其中 N 为二叉树的节点数量,建立二叉树镜像需要遍历树的所有节点,占用 O(N) 时间。
  • 空间复杂度 O(N) : 如下图所示,最差情况下,栈 stack 最多同时存储 (N+1)/2 个节点,占用 O(N) 额外空间。

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值