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

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

哈希表

哈希表的简单介绍

  • 哈希表在使用层面上可以理解为一种集合结构
  • 如果只有key,没有伴随数据value,可以使用HashSet结构(Set:不含重复元素的集合)
  • 如果既有key,又有伴随数据value,可以使用HashMap结构
  • 使用哈希表进行增删改查的操作,可以认为这些操作的时间复杂度为O(1),但是常数时间比较大
  • 放入哈希表的东西,如果是基础类型,内部按照值传递,内存占用就是这个东西的大小
  • 放入哈希表的东西,如果不是基础类型,内部按照引用传递,内存占用就是这个东西内存地址的大小,即key相当于一个指针,指向的是这个东西内存的地址

有序表的简单介绍

  • 有序表在使用层面上可以理解为一种集合结构
  • 如果只有key,没有伴随数据value,可以使用TreeSet结构
  • 如果既有key,又有伴随数据value,可以使用TreeMap结构
  • 有无伴随数据,是TreeSet和TreeMap唯一的区别,底层是实际结构实现是相同的
  • 有序表和哈希表的区别是:有序表把key按照顺序组织起来,而哈希表完全不阻止
  • 红黑树,AVL树、size-balance-tree和跳表都属于有序表结构,只是底层具体实现不同
  • 放入有序表的东西,如果是基础类型,内部按照值传递,内存占用就是这个东西的大小
  • 放入有序表的东西,如果不是基础类型,必须提供比较器,内部按照引用传递,内存占用就是这个东西内存地址的大小,即key相当于一个指针,指向的是这个东西内存的地址
  • 增删改查的操作时间复杂度都是O(logN)级别

单链表

Class Node<V>{
	V value;
	Node next;
}

反转单向和双向链表

题目:分别实现反转单向链表和反转双向链表的函数 要求:如果链表长度为N,时间复杂度要求为O(N),额外空间复杂度要求为O(1)

思路:需要用到3个指针,left,mid,right

public static void reverseLinkList(Node){
	Node left;
	Node mid;
	Node right;

	while(mid!=null){
		mid.next = left;
		left = mid;
		mid = right;
		if(right!=null){
			right = right.next;
		}
	}
}

打印两个有序链表的公共部分

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

思路:利用两个指针分别指向两条链表的head,同时进行比较和遍历,若两个节点的值相同则打印并同时跳到下一个节点,若是不相等则值较小的节点跳转到下一个节点,值较大的节点位置不变

image.png

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

题目:给定一个单链表的头节点,请判断该链表是否为回文结构 要求:如果链表长度为N,时间复杂度要求为O(N),额外空间复杂度要求为O(1)

思路一(空间复杂度O(N/2):使用栈的特殊性,将前半部分放入栈中,然后从最后开始 如何找到中点位置——快慢指针

代码实现:

public static void boolean isPalindrome(Node head){
	if(head==null || head.next==null) return;

	Node slow = head;
	Node fast = head;

	Stack<Node> stack = new Stack<Node>();
	while(slow!=null && fast!=null){
		stack.push(slow);
		slow = slow.next;
		fast = fast.next.next;
	}

	while(!stack.isEmpty()){
		if(head.value != stack.pop().value){
			return false;
		}
		head = head.next;
	}
	return true;
}

思路二(空间复杂度O(1)):修改链表结构,将中间节点之后的链表反转,分别用两个指针指向链表的头部和尾部,同时遍历并比较。若是值相同,两个节点同时向中间移动一个节点并再次比较值,直到两个节点的值不相同或者两个节点重合为止

image.png

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

题目:给定一个单链表的头节点head,结点的值类型是整形,再给定一个整数pivot。实现一个调整链表的函数,将链表调整为左部分都是值小于pivot的节点,中间部分都是值等于pivot的节点,右边都是值大于pivot的节点

进阶要求:

  1. 调整后所有小于pivot的节点之间的相对顺序和调整前一样
  2. 调整后所有等于pivot的节点之间的相对顺序和调整前一样
  3. 调整后所有大于pivot的节点之间的相对顺序和调整前一样
  4. 时间复杂度达到O(N),额外空间复杂度达到O(1)

思路:将链表分成三个部分,小于区,等于区,大于区,用指针遍历链表,分别形成三部分的链表,最后将三个链表连起来

image.png

需要注意考虑如果没有小于pivot的数的话,小于区不存在的情况等类似特殊情况

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

题目:一种特殊的单链表节点类描述如下:

class Node{
	int value;
	Node next;
	Node rand;
	Node(int val){
		value = val;
	}
}

rand指针是单链表节点结构中新增的指针,rand可能指向链表中的任意一个节点,也可能指向null。给定一个由Node节点类型组成的无环单链表的头节点head,请实现一个函数完成这个链表的复制,并返回复制的新链表的头节点

思路一:使用hashMap,将每个节点逐一复制后存储到hashMap当中,然后通过判断来确定复制的链表如何连接

思路二:将复制的节点连在原节点之后,然后根据原节点的连接方式连接复制的节点

image.png

代码实现:

public static Node copyListWithRand2(Node head){
	 if(head == null){
		 return null; 
	 }

	Node cur = head;
	Node next = null;
	//1 -> 2
	//1 -> 1' -> 2 -> 2'
	while(cur!=null){
		next = cur.next;
		cur.next = new Node(cur.value);
		cur.next.next = next;
		cur = next;
	}

	cur = head;
	Node curCopy = null;
	//给克隆节点的rand赋值
	while(cur!=null){
		next = cur.next.next;
		curCopy = cur.next;
		curCopy.rand = cur.rand != null ? cur.rand : null;
		cur = next;
	}

	//分离出克隆链表
	Node resHead = head.next;
	cur = head;
	while(cur!=null){
		 next = cur.next.next;//原链表的下一个节点
		 curCopy = cur.next;
		 cur.next = next;//恢复原链表结构
		 curCopy.next = next!=null ? next.next:null;
		 cur = next;	
	}

	return resHead;
}