初级算法(七)
1、引言
不带头节点的单链表
//不带头节点的单链表
public class ListNode{
int val;
ListNode next;
ListNode(){
}
ListNode(int val){
this.val=val;
}
ListNode(int val,ListNode next){
this.val=val;
this.next=next;
}
}
Java对象表示二叉树
public class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(){
}
TreeNode(int val){
this.val=val;
}
TreeNode(int val,TreeNode right,TreeNode left){
this.val=val;
this.left=left;
this.right=right;
}
}
今天涉及的算法
1、合并两个有序链表变为一个有序的链表
2、回文链表:一个链表经过反转后,变为和原来的一样
3、环形链表:判断一个链表是否有环
4、二叉树的最大深度
2、合并两个有序链表变为一个有序的单链表
这题属于简单级别,只需要对两个有序链表进行不断比较,较小的值放到新的链表里。直到一个链表被遍历完,退出其中的循环。然后将没有遍历完的放在新的链表。
/**
*合并两个有序链表变为一个有序的单链表
* @param list1 有序单链表1
* @param list2 有序单链表2
* @return 合并后的有序链表
* 执行用时:0ms,内存消耗:40.9MB
*/
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
if(list1==null){
return list2;
}
if(list2==null){
return list1;
}
ListNode node=new ListNode();
ListNode curr=node;//作为记录指针
while(list1!=null && list2!=null){
if(list1.val<=list2.val){
node.next=list1;
list1=list1.next;
}else{
node.next=list2;
list2=list2.next;
}
node=node.next;
}
if(list1!=null){
node.next=list1;
}
if(list2!=null){
node.next=list2;
}
return curr.next;
}
3、回文链表
比较简单的想法是采用堆的数据结构,这样就能从后往前遍历其中的值。
/**
* 方法一:使用堆栈元素存储元素值
* 回文链表
* @param head
* @return
* 执行用时:30ms,内存消耗:57.9MB
*/
public boolean isPalindrome(ListNode head) {
int length=length(head);
if(length<=1)
return true;
ListNode temp=head;//将head的引用赋值给temp
Stack<Integer> stack=new Stack<>();
while(temp!=null){
stack.push(temp.val);
temp=temp.next;
}
for (int i = 0; i < length/2; i++) { //这里只要判断一半就行,提高效率
if(head.val!=stack.pop())
return false;
head=head.next;
}
return true;
}
//求单链表的长度
int length(ListNode head){
int i=0;
while(head!=null){
i++;
head=head.next;
}
return i;
}
下面这种的方法,我感觉应该比较难想到,采用双指针的方式。一个是 fast 和 slow 指针。fast 指针一下走两步,slow 指针走一步。slow 指针走到中间节点的时候,将后半部分的节点进行反转。
//反转链表
public ListNode reverse(ListNode node){
ListNode curr=null;
while(node!=null){
ListNode temp=node.next;//作为工具指针
node.next=curr;//将原单位链表的节点,进行放在新链表的后面
curr=node;
node=temp;
}
return curr;
}
/**
* 方法二:使用fast和slow两种指针,进行寻找到中间节点的位置,将后半部分的节点进行反转
* fast指针走两步,慢指针走一步
* 回文链表
* @param head
* @return
*执行用时:4ms,内存消耗:57.2MB
*/
public boolean isPalindrome2(ListNode head) {
if(head==null)
return true;
ListNode fast=head;
ListNode slow=head;
while(fast!=null&&fast.next!=null){
fast=fast.next.next;
slow=slow.next;
}
if(fast!=null)
slow=slow.next;
slow=reverse(slow);
while(slow!=null){
if(head.val!=slow.val)
return false;
head=head.next;
slow=slow.next;
}
return true;
}
第三种方法个人感觉也是比较难理解,采用的是递归。主要对单链表进行递归调用可以调用到最后一个。这个方法还需要设置一个成员变量。
ListNode temp3; //设置成员变量
/**
* 方法三:使用递归
* 回文链表
* @param head
* @return
*执行用时:13ms,内存消耗:58MB
*/
public boolean isPalindrome3(ListNode head) {
temp3=head;
return check(head);
}
public boolean check(ListNode head){
if(head==null)
return true;
boolean res=check(head.next)&&(temp3.val==head.val);//这里的check主要是找到最后的节点值
temp3=temp3.next;
return res;
}
4、环形链表
这题在前面的写字符串的时候,就有做一个类似的方法。其中一个简单的方法就是使用 Java 自带的数据结构 set 集合。 set 集合存储的是无序的不重复的元素。
/**
* 判断一个链表是不是环形链表
* 方法一:使用Java自带的数据结构,set集合
* @param head
* @return
*执行用时:4ms,内存消耗:41。8MB
*/
public boolean hasCycle(ListNode head) {
if(head==null)
return false;
ListNode temp=head;
Set<ListNode> set=new HashSet<>();
while(temp!=null){
if(!set.add(temp))
return true;
temp=temp.next;
}
return false;
}
第二种方法就是使用双指针,一个 slow 指针只走一部,一个 fast 指针只走两步。如果这个单链表是有环的,那么这两个指针一定会相遇的。
/**
* 方法二:使用快指针和慢指针,如果单链表有环,那么他们两者一定会相遇
* @param head
* @return
* 执行用时:0ms,内存消耗:41.9MB
*/
public boolean hasCycle2(ListNode head) {
if(head==null)
return false;
ListNode fast=head;
ListNode slow=head;
while(fast!=null && fast.next !=null){
fast=fast.next.next;
slow=slow.next;
if(fast==slow)
return true;
}
return false;
}
5、求二叉树的最大深度
对于单链表的遍历,我们可以用单链表的节点的next来达到遍历整个单链表。但是二叉树可不一样,二叉树的每一个非叶子节点,都是有两种子节点,分别是左节点和右节点。就需要我们换一种遍历方式分别是广度优先遍历和深度优先遍历。
在这里求二叉树的最大深度的其中一种方法就是采用广度优先遍历。广度优先遍历,采用的一种数据结构就是队列。利用队列的先进先出的原则。
/**
* 方法一:使用广度优先算法
* 返回二叉树的最大深度
* @param root
* @return
* 执行用时:1ms,内存消耗:40.9MB
*/
public int maxDepth(TreeNode root) {
if(root ==null)
return 0;
TreeNode temp=root;
LinkedList<TreeNode> queue=new LinkedList<>();
queue.addLast(root);
int count =0;
while(!queue.isEmpty()){
int size =queue.size();
count++;
while(size>0){
TreeNode pop = queue.removeFirst();
size--;
if(pop.left!=null){
queue.addLast(pop.left);
}
if(pop.right!=null){
queue.addLast(pop.right);
}
}
}
return count;
}
第二种方法就是使用深度优先遍历算法,这种遍历算法采用的是堆栈的数据结构。采用两个堆栈,一个堆栈存储树节点,另一个堆栈结构则存储的是二叉树的最大深度。当存储树节点的堆栈出栈的时候,存储树深度的堆栈也出栈。进栈也是这样的。
/**
* 方法二:深度优先搜索
* @param root
* @return
* 使用两个堆栈的方式,一个堆栈存储节点,另一个堆栈存储层数
* 执行用时3ms,内存消耗:41.3MB
*/
public int maxDepth2(TreeNode root) {
if(root == null)
return 0;
Stack<TreeNode> stack1=new Stack<>();
Stack<Integer> stack2=new Stack<>();
int max=0;
stack1.push(root);
stack2.push(1);
while(!stack1.isEmpty()){
TreeNode pop = stack1.pop();
int count = stack2.pop();
max=Math.max(max,count);
if(pop.left!=null){
stack1.push(pop.left);
stack2.push(count+1);
}
if(pop.right!=null){
stack1.push(pop.right);
stack2.push(count+1);
}
}
return max;
}
第三种方法,求二叉树的最大深度其实,其实求二叉树左子树和右子树的最大深度+1。这就是递归。
/**
* 方法三:递归,求某个二叉树的最大深度,就是求它左子树和右子树的最大值+1
* @param root
* @return
* 执行用时:0ms,内存消耗:40.8MB
*/
public int maxDepth3(TreeNode root) {
if(root == null)
return 0;
else{
return Math.max(maxDepth(root.left),maxDepth(root.right))+1;
}
}