左神算法笔记之链表——哈希表、有序表、单链表和双链表

一、哈希表

二、有序表

在这里插入图片描述

在这里插入图片描述

三、单链表和双链表

在这里插入图片描述

四、反转单链表和双链表(要求链表长度为N,时间复杂度为O(N),额外空间复杂度为O(1))

//反转单链表
public class ReverseLinkedList {
	//单链表结构
	public static class Node {
		public int value;
		public Node next;

		public Node(int value) {
			this.value = value;
		}
	}
	public static Node reverseLinkedList(Node head) {
		if(head == null){
			return null;
		}
		//一定要注意:
		//pre一直代表遍历到要反转节点的前驱节点
		//next一直代表遍历到要反转节点的后继节点
		Node pre = null; 
		Node next = null;
		while(head != null){
			//先用next保存head的下一个节点的信息
            //保证单链表不会因为反转head节点的原next节点而就此断裂
			next = head.next;
			//反转,head的后继变成向前指,指向head的next前驱
			head.next = pre;
			//节点的前驱和节点都后移,继续遍历
			pre = head;
			head = next;
		}
		//如果head为null的时候,pre就为最后一个节点了,就此链表已经反转完毕,pre就是反转后链表的第一个节点
		return pre;
	}
}
	
//反转双链表
public class ReverseDoubleLinkedList {
	public static class DoubleNode {
		int val;
		DoubleNode pre; //前驱
		DoubleNode next; //后继
		public DoubleNode (int value) {
			this.val = value;
		}
	}
	public static DoubleNode reverseDoubleLinkedList(DoubleNode head) {
		if(head == null){
			retrun null;
		}
		
		DoubleNode tmp = null;
		//res的作用仅仅是记录head的上一个节点
		DoubleNode res = null;
		
		while(head != null) {
			//先用tmp保持后继节点,然后交换前后节点
			tmp = head.next;
			head.next = head.pre;
			head.pre = tmp;
			//记录res
			res = head;
			//head指向tmp保存的原节点的后继节点,即向后推进一个节点
			head = tmp;
		}
		return res;
	}
}

五、给定两个有序链表的头指针head1和head2,打印两个链表的公共部分(要求:两个链表长度和为N,时间复杂度为O(N),空间复杂度为O(1))

public class PrintCommonPart {
    public static class Node {
        public int value;
        public Node next;

        public Node(int data) {
            this.value = data;
        }
    }

    public static void printCommonPart(Node head1, Node head2) {
        while (head1 != null && head2 != null) {
            if (head1.value < head2.value) {
                head1 = head1.next;
            } else if (head1.value > head2.value) {
                head2 = head2.next;
            } else {
                System.out.print(head1.value+" ");
                head1 = head1.next;
                head2 = head2.next;
            }
        }
        System.out.println();
    }
}

六、判断一个链表是否为回文结构

//判断是否是回文结构
public class IsPalindrome {
	//单链表结构
	public static class Node{
		public int value;
		public Node next;
		
		public Node(int value){
			this.value = date;
		}
	}
	//方法1:用栈结构存储整个链表元素,再依次弹出并与链表从头开始比较,空间复杂度:O(n)
	public static boolean isPalindrome1(Node head){
		if(head == null || head.next == null){
			return true;
		}
		//申请一个栈
		Stack<Node> stack = new Stack<Node>();
		Node cur = head;
		//将单链表元素按顺序入栈
		while(cur != null){
			stack.push(cur);
			cur = cur.next;
		}
		//不断弹出栈顶元素,并和单链表从头开始比较,不相等就返回false
		while(head != null){
			if(head.value != stack.pop().value){
				return false;
			}
			head = head.next;
		}
		return true;
	}
	//方法2:用栈只存储链表一半的元素(中间位置到最后),最后依次弹出并与链表的前半部分比较
	//空间复杂度:O(n/2)
	public static boolean isPalindrome2(Node head){
		if(head == null || head.next == null){
			return true;
		}
		//用快慢指针来确定中间位置
		//快指针一次走两步,慢指针一次走一步
		//快指针走完时,慢指针刚好来到右部分第一个节点上
		Node right = head.next;//慢指针,若元素个数为偶数时,最后指向中间位置的后一个位置
		Node cur = head;//快指针
		while(cur.next != null && cur.next.next != null){
			right = right.next;
			cur = cur.next.next;
		}
		//把从慢指针即中间位置开始的右部分元素放进栈中
		Stack<Node> stack = new Stack<Node>();
		while(right != null){
			stack.push(right);
			right = right.next;
		}
		//不断弹出栈顶元素,并从头和单链表元素比较,不相等就返回false
		while(!stack.isEmpty()){
			if(head.value != stack.pop().value){
				return false;
			}
			head = head.next;
		}
	//方法3:将链表对折,后半部分的链表反转与前半部分链表进行比较
	public boolean isPalindromList3(Node head){
		if(head == null || head.next == null){
			return true;
		}
		
		Node cur = head;
		Node slow = head;
		Node fast = head;
		
		// 元素总个数为奇数时,慢指针最后指向中间位置,若为偶数,则走到中间位置的前一位
		// 注意:在向后遍历的时候,需要判断快指针指向的节点是否为空,不然会出现异常
		// 若fast.next != null,那么说明这是偶数个,fast.next.next != null,说明是奇数个
		while(fast.next != null && fast.next.next != null){
			fast = fast.next.next;
			slow = slow.next;
		}
		// slow来的中点位置,反转后半部分,反转后中点指向null
		Node end = reverseSingleList(slow);
		fast = end;
		// 将前半部分和后半部分进行对比
		// 前半部分从cur即head开始,后半部分从fast即end开始
		while(cur != null && fast != null){
			if(cur.value != fast.value){
				return false;
			}
			cur = cur.next;
			fast = fast.next;
		}
		// 不能改变原有的数据结构,所以还要把后半部分反转回去
		// 遍历完cur来到中间位置,接上反转回来的后半部分头节点
		cur = reverseSingleList(end);
		return true;
	}
	private Node reverseSingleList(Node head) {
        Node pre = null;
        Node next = null;

        while(head != null){
            // 存储原来next节点
            next = head.next;
            // next节点指向上一个节点
            head.next = pre;
            // 下一个节点的上一个节点就是当前节点
            pre = head;
            // 向后推进一个节点
            head = next;
        }
        // 返回反转后的头节点,即最后一个节点
        return pre;
    }
				

七、将单链表按某值划分成左边小,中间相等、右边大的形式

public class LinkedPartition {
	public static class Node{
		int value;
		Node next;
		public Node(int val){
			this.value = val;
		}
	}
	//方法一:将链表元素放进数组partation,然后再连回成单链表
	public Node linkerPartition1(Node head, int num){
		if(head == null || head.next == null){
			return head;
		}
		//先计算有多少个节点
		int i = 0;
		Node cur = head;
		while(cur != null){
			i++;
			cur = cur.next;
		}
		//然后申请一个和节点数相同的数组
		int[] arr = new int[i];
		//再把节点的value放进数组
		cur = head;
		i = 0;
		while(cur != null){
			arr[i++] = cur.value;
			cur = cur.next;
		}
		//再对数组中的元素用荷兰国旗方法对值进行小于、等于、大于的区域划分
		arrPartition(arr, num);
		//最后把排好序的数组元素按顺序更新到原来链表的value上
		cur = head;
		i = 0;
		while(cur != null){
			cur.value = arr[i++];
			cur = cur.next;
		}
		return head;
	}
	//荷兰国旗划分法
	public void arrPartition(int[] arr, int num){
		int less = -1;
		int more = arr.length;
		int cur = 0;
		while(cur != more){
			if(arr[cur] < num){
				swap(arr,++less,cur++);
			}else if(arr[cur > num){
				swap(arr,--more,cur);
			}else{
				cur++;
			}
		}
	}
	public void swap(int[] arr, int i, int j){
        int tmp = arr[i];
        arr[i] = arr[j];
        arr[j] = tmp;
    }
	
	//方法二:用六个变量将原链表拆分成三个小链表,表示大于、小于、等于区域的局部链表,划分完将这三个链表连起来
	public Node listPartition2(Node head, int num){
		Node less = null; 		// 存放小于num的节点,指向第一个出现在该区域的节点
        Node endless = null;    // 指向小于num的节点链表的结尾
        Node equal = null;      // 存放等于num的节点,指向第一个出现在该区域的节点
        Node endequal = null;   // 指向等于num的节点链表的结尾
        Node more = null;       // 存放大于num的节点,指向第一个出现在该区域的节点
        Node endmore = null;    // 指向大于num的节点链表的结尾
		Node next = null;

		while(head != null){
			next = head.next;
			head.next = null;
			
			//小于num放进less区域
			if(head.value < num){
				//当前区域为空时,头节点和尾节点都更新为head,代表第一个进入该区域的节点
				if(less == null){
					less = head;
					endless = head;
				}else{
					//不为空时,上一次的尾节点的next节点指向当前的head
					endless.next = head;
					//再更新当前的尾节点变成head
					endless = head;
				}
			//more区
			}else if(head.value > num){
				if(more == null){
                    more = head;
                    endmore = head;
                }else{
                    endmore.next = head;
                    endmore = head;
                }
            //euqal区
            }else{
                if(equal == null){
                    equal = head;
                    endequal = head;
                }else{
                    endequal.next = head;
                    endequal = head;
                }
			}
			//推进节点
			head = head.next;
		}
		//将划好的三个部分子链表串起来,返回
		//还需要考虑到可能会有某个部分子链表不存在的情况

		//less子链存在
        if(less != null){
            endless.next = equal;
            //less和equal子链都存在
            if(equal != null){
                endequal.next = more;
            //less存在,equal不存在,less直接和more相连
            }else{
                endless.next = more;
            }
            return less;
        //less不存在
        }else{
            //less不存在,equal存在,equal和more相连
            if(equal != null){
                endequal.next = more;
                return equal;
            }else{
                //less不存在,equal也不存在
                return more;
            }
        }
    }
}							

七、复制含有随机指针节点的单链表

public class CopyListWithRandom{
	public static class Node{
		int value;
		Node next;
		Node random;
		public Node(int value){
			this.value = value;
		}
	}
	//方法一:直接用hashmap来进行元素列表节点和复制节点间的映射,key存原来的节点,value存新复制的节点
	public Node copyListWithRandom1(Node head){
		//hashmap的key和value都是存Node类型,存入的是地址
		HashMap<Node,Node> map = new HashMap<Node>();
		
		//第一次遍历:拷贝节点,形成key是旧结点,value是复制的新节点
		Node cur = head;
		while(cur != null){
			map.put(cur,new	Node(cur.value));
			cur = cur.next;
		}
		//第二次遍历:复制节点间的关系,即复制next和random指针
		//原链表节点间的指针关系是知道的,比如想知道A'和B'之间的关系,可以通过:A'->A->B->B',这样就找到了B'
		while(cur != null){
			//get(cur).next得到复制节点的next,get(cur.next)得到原来节点的next节点的复制节点
			map.get(cur).next = map.get(cur.next);
			//random同理
			map.get(cur).random = map.get(cur.random);
			cur = cur.next;
		}
		//返回get(head)中存的复制链表的头节点
		return map.get(head);
	}
	
	//方法二:不用额外空间。首先,先将结点复制到链表中,形成:1->1’->2->2’->3->3’->…->null形式;
    // 其次复制rand结构,如1‘的rand指针需指向3’,则又1指针指向3, 3的next为3‘,因此可得出1’的rand指向3‘。
    // 最后将链表拆分得到复制链表。
    public Node copyListWtithRandom2(Node head){
    	if(head == null){
    		return null;
    	}
    	Node cur = head;
        Node tmp = null;
        // 拷贝节点,重建链表结构:1->1'->2->2'->3->3'->...->null形式
        // 就是将拷贝的节点直接关联到原节点的next指针上
        while(cur != null){
            tmp = cur.next;                     // 先将当前节点原来的next节点存起来
            cur.next = new Node(cur.value);    //再把当前节点的next节点改成拷贝节点
            cur.next.next = tmp;              //最后next节点的next节点重新存回原来的next节点,变成1->1'->2结构
            cur = cur.next.next;             //cur来到next的next节点位置继续改变结构
        }

        //复制random结构
        cur = head;                     //原来节点
        Node curCopy = null;      //拷贝节点
        while(cur != null){
            curCopy = cur.next;
            curCopy.random = (cur.random == null) ? null : cur.random.next; //如果原来节点的random节点不为空,就将拷贝节点的random节点设为原节点的random节点的next节点
            cur = cur.next.next;                                           //推进到下一个节点
        }

        //拆分结构
        cur = head;
        Node headCopy = head.next;
        while(cur != null){
            curCopy = cur.next;
            cur.next = cur.next.next;
            curCopy.next = (curCopy.next == null) ? null : curCopy.next.next;
            cur = cur.next.next;
        }
        return headCopy;
    }
}

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

public class FindFirstIntersectNode {
	public static class Node{
		public int value;[
		public Node next;
		public Node(int value){
			this.value = value;
		}
	}
	// 检查单链表是否有环
    // 1.检查单链表是否有环:用hashset来遍历存储节点,如果再次遍历到已经存储的节点则有环
    public static Node getLoopNode(Node head){
        HashSet<Node> set = new HashSet<Node>();
        while(head != null){
            if(!set.contains(head)){
                set.add(head);      //如果set里不包括当前head节点,就add进去
                head = head.next;   //向后推进链表
            }else{
                return head;        //如果在set里发现以包含当前head节点,就表示有环,当前head节点就是入环节点
            }
        }
        return null;                //遍历完单链表都没有发现相同节点,则单链表无环
    }
    // 2.检查单链表是否有环:不用额外空间,用快慢指针来判断是否有环
    public Node entryNodeOfInLoop(Node head){
        if(head == null || head.next == null){
            return null;
        }
        Node p1 = head;
        Node p2 = head;

        while(p2 != null && p2.next != null){
            p1 = p1.next;       //慢指针一次一步
            p2 = p2.next.next;  //快指针一次两步
            if(p1 == p2){
                p2 = head;      //p1和p2相遇证明有环,p2回到head位置
                while (p1 != p2){
                    p1 = p1.next;
                    p2 = p2.next;  //p2和p1都每次走一步
                }
                if(p1 == p2){
                    return p1;     //再次相遇时就是入环节点
                }
            }
        }
        return null;                //没相遇过就是无环
    }



    //两个无环单链表相交求相交节点
    //先判断是否相交:方法1:放进hashset查重
    public Node noLoop1(Node head1, Node head2){
        HashSet<Node> set = new HashSet<>();
        //遍历head1链表放进set
        while(head1 != null){
            set.add(head1);
            head1 = head1.next;
        }
        //遍历head2单链表
        while (head2 != null){
            //如果set中有head2的节点 就返回相交节点
            if(set.contains(head2)){
                return head2;
            }
            head2 = head2.next;     //推进节点向后
        }
        return null;                //遍历完还是没相交返回null
    }

    //先判断是否相交:方法2:走差值步
    public static Node noLoop2(Node head1, Node head2){
        if(head1 == null || head2 ==null){
            return null;
        }
        Node cur1 = head1;
        Node cur2 = head2;
        int n = 0; //两个链表长度的差值
        //遍历cur1和cur2链表得到他们的长度差值和最后一个节点
        while(cur1.next != null){
            n++;
            cur1 = cur1.next;
        }
        while(cur2.next != null){
            n--;
            cur2 = cur2.next;
        }
        // 如果最后一个节点不相同,则不相交
        if(cur1 != cur2){
            return null;
        }
        //如果n>0,代表head1链表长,cur1等于长的链表的头节点
        cur1 = n > 0 ? head1 : head2;
        //cur2代表短的链表的头节点
        cur2 = cur1 == head1 ? head2 : head1;
        //长链表先走n步
        while (n != 0){
            n--;
            cur1 = cur1.next;
        }
        //然后cur1和cur2都一次走一步
        while(cur1 != cur2){
            cur1 = cur1.next;
            cur2 = cur2.next;
        }
        //最后相同时就是相交节点
        return cur1;
    }



    //一个有环一个无环,在单链表结构下不可能相交


    //两个有环单链表相交求相交节点
    //三种情况:①两链不相交 ②先相交再有环 ③在环上相交
    public static Node bothLoop(Node head1, Node loop1, Node head2, Node loop2) {
        //环外相交:入环节点一样,环的部分可以不看,以入环节为尾端,归结为无环单链表找相交点
        Node cur1 = head1;
        Node cur2 = head2;
        if (loop1 == loop2) {
            int n = 0; //两个链表长度的差值
            //遍历cur1和cur2链表得到他们的长度差值和最后一个节点
            while (cur1 != loop1) {
                n++;
                cur1 = cur1.next;
            }
            while (cur2 != loop2) {
                n--;
                cur2 = cur2.next;
            }
            //如果n>0,代表head1链表长,cur1等于长的链表的头节点
            cur1 = n > 0 ? head1 : head2;
            //cur2代表短的链表的头节点
            cur2 = cur1 == head1 ? head2 : head1;
            //长链表先走n步
            while (n != 0) {
                n--;
                cur1 = cur1.next;
            }
            //然后cur1和cur2都一次走一步
            while (cur1 != cur2) {
                cur1 = cur1.next;
                cur2 = cur2.next;
            }
            //最后相同时就是相交节点
            return cur1;
        //环内相交:
        } else {
            //cur1从loop1.next开始不断走
            cur1 = loop1.next;
            //走回到自己之前,不断寻找loop1环内是否有和loop2相同的节点
            while (cur1 != loop1) {
                //遇到loop2,返回相交节点
                if (cur1 == loop2) {
                    return loop2;
                }
                //推进到下一个节点
                cur1 = cur1.next;
            }
            //走完了也没有相同节点,两个链表不相交
            return null;
        }
    }

    //总调用代码
    public static Node getIntersectNode(Node head1, Node head2) {
        if (head1 == null || head2 == null) {
            return null;
        }
        //先检查两个单链表是否有环
        Node loop1 = getLoopNode(head1);
        Node loop2 = getLoopNode(head2);
        //如果都无环
        if (loop1 == null && loop2 == null) {
            return noLoop2(head1, head2);
        }
        //如果都有环
        if (loop1 != null && loop2 != null) {
            return bothLoop(head1, loop1, head2, loop2);
        }
        //一个有环一个无环,单链表结构下永不相交
        return null;
    }
}

相关推荐
©️2020 CSDN 皮肤主题: 数字20 设计师:CSDN官方博客 返回首页