持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第14天,点击查看活动详情
下面是我整理的跟着b站左程云的数据结构与算法学习笔记,欢迎大家一起学习。
排序方法总结
综合排序
快速排序与插入排序的融合使用 当数据样本比较大时,使用快速排序 时间复杂度O(N*logN)利用调度优势 当数据样本比较小时,使用插入排序,时间复杂度O(N^2) 利用小样本常数空间低的优势 在数据量比较小时,N^2 的瓶颈不明显 利用各自算法的优势让整体时间
工程上对排序的改进
- 一般会充分利用O(N*logN)和O(N^2)排序各自的优势
- 考虑稳定性
哈希表
- 在使用层面上可以理解为一种集合结构
- 如果只有key,没有伴随数据value,可以使用HashSet结构
- 如果既有key,又有伴随数据value,可以使用HashMap结构
- 有无伴随数据,是HashMap和HashSet唯一的区别,但底层的实际结构是另一回事
- 使用哈希表增删改查时,可以认为时间复杂度为O(1),但常数时间比较大
- 放入哈希表的东西,若为基础类型,直接拷贝,内部按值传递,内存占用是这个东西的大小
- 放入哈希表的东西,若不是基础类型,内部按引用传递,内存占用就是这个东西内存地址的大小(一律八字节)
有序表
- 在使用层面上可以理解为一种集合结构
- 若只有key,没有伴随数据value,可以使用TreeSet结构
- 若既有key,又有伴随数据value,可以使用TreeMap结构
- 有无伴随数据,是TreeMap和TreeSet唯一的区别,但底层的实际结构是另一回事
- 红黑树,AVL树,size-balance-tree和跳表都属于有序表结构,性能一样,时间指标也一样,只是底层具体实现不同
- 放入有序表的东西,如果是基础类型,内部按值传递,内存占用就是这个东西的大小
- 放入有序表的东西,如果不是基础类型,必须提供比较器,内部按引用传递,内存占用即这个东西内存地址的大小
- 只要是有序表,都有固定的基本功能和固定的时间复杂度 代码实现TreeMap
public static void main(String[] args) {
// 展示有序表常用操作
TreeMap<Integer, String> treeMap1 = new TreeMap<>();
treeMap1.put(7, "我是7");
treeMap1.put(5, "我是5");
treeMap1.put(4, "我是4");
treeMap1.put(3, "我是3");
treeMap1.put(9, "我是9");
treeMap1.put(2, "我是2");
System.out.println(treeMap1.containsKey(5));
System.out.println(treeMap1.get(5));
System.out.println(treeMap1.firstKey() + ", 我最小");
System.out.println(treeMap1.lastKey() + ", 我最大");
System.out.println(treeMap1.floorKey(8) + ", 在表中所有<=8的数中,我离8最近");
System.out.println(treeMap1.ceilingKey(8) + ", 在表中所有>=8的数中,我离8最近");
System.out.println(treeMap1.floorKey(7) + ", 在表中所有<=7的数中,我离7最近");
System.out.println(treeMap1.ceilingKey(7) + ", 在表中所有>=7的数中,我离7最近");
treeMap1.remove(5);
System.out.println(treeMap1.get(5) + ", 删了就没有了哦");
System.out.println("========6=========");
}
时间复杂度为O (logN) 有序表的基本功能
- void put(K key,V value):增加或修改
- V get(K key):通过key获取value
- void remove(K key)移除key的记录
- Boolean containsKey(K key)查询指定key是否存在
- K firstKey() 返回所有键值的排序结果中,最左(小)的key
- K lastKey()返回所有键值的排序结果中,最右(大)的key
- K floorKey(K key) 若存入过key,返回key,否则返回key的前一个
- K ceilingKey(K key)若存入过key,返回key,否则返回key的 后一个 同样的用法还有各自的value方法,所有操作的时间复杂度都为O(logN)相比之下虽然hash表的增删改查时间复杂度为常数但也很小了
链表
构造一个简单的链表
代码实现
public static class Node {
public int value;
public Node next;
public Node(int data) {
this.value = data;
}
}
打印两个表的公共数据
解题思路:首先得是一个有序数组,谁小谁移动,打印公共部分并都向后面移动
代码比较简单就不一一列出了
链表解题的方法论
- 笔试重视时间复杂度
- 面试时间复杂度依然重要,但要找到空间最省的方法
认识回文
正着念与反着念相同,相当于有一个对称轴,左右成镜像对称 如 1 2 1 实现思路:将链表压进栈里,不断弹出与原来链表比较 可以将对称轴后的东西压进栈里,减少空间 也可以使用一个快指针和一个慢指针,快指针一次走两步,慢指针一次走一步 代码实现
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;
}
while (!stack.isEmpty()) {
if (head.value != stack.pop().value) {
return false;
}
head = head.next;
}
return true;
}
也可能存在边界问题
使用最小的空间复杂度O(1)方法
在指针走到最中间时,将中间点后面的链表倒置,并将第一个与最后一个数标识
代码实现
public static boolean isPalindrome3(Node head) {
if (head == null || head.next == null) {
return true;
}
Node n1 = head;
Node n2 = head;
while (n2.next != null && n2.next.next != null) { // find mid node
n1 = n1.next; // n1 -> mid
n2 = n2.next.next; // n2 -> end
}
n2 = n1.next; // n2 -> right part first node
n1.next = null; // mid.next -> null
Node n3 = null;
while (n2 != null) { // right part convert
n3 = n2.next; // n3 -> save next node
n2.next = n1; // next of right node convert
n1 = n2; // n1 move
n2 = n3; // n2 move
}
n3 = n1; // n3 -> save last node
n2 = head;// n2 -> left first node
boolean res = true;
while (n1 != null && n2 != null) { // check palindrome
if (n1.value != n2.value) {
res = false;
break;
}
n1 = n1.next; // left to mid
n2 = n2.next; // right to mid
}
n1 = n3.next;
n3.next = null;
while (n1 != null) { // recover list
n2 = n1.next;
n1.next = n3;
n3 = n1;
n1 = n2;
}
return res;
}
使用链表实现简单的快排 稳定性 使用六个变量 小于区域的头尾指针,还有大于和等于区域 当出现第一个值时(头和尾都为null)将头和尾都设置为该值,当该值第二次出现时,将尾巴的下一个指向该值,并将尾巴设置为该值,即为一个小于区域的链表 在最后小于区域的尾连接等于区域的头,等于区域的尾指向大于区域的头(同时也要判断各个区域是否会为null)
代码实现:
public static Node listPartition2(Node head, int pivot) {
Node sH = null; // small head
Node sT = null; // small tail
Node eH = null; // equal head
Node eT = null; // equal tail
Node bH = null; // big head
Node bT = null; // big tail
Node next = null; // save next node
// every node distributed to three lists
while (head != null) {
next = head.next;
head.next = null;
if (head.value < pivot) {
if (sH == null) {
sH = head;
sT = head;
} else {
sT.next = head;
sT = head;
}
} else if (head.value == pivot) {
if (eH == null) {
eH = head;
eT = head;
} else {
eT.next = head;
eT = head;
}
} else {
if (bH == null) {
bH = head;
bT = head;
} else {
bT.next = head;
bT = head;
}
}
head = next;
}
// small and equal reconnect
if (sT != null) {
sT.next = eH;
eT = eT == null ? sT : eT;
}
// all reconnect
if (eT != null) {
eT.next = bH;
}
return sH != null ? sH : eH != null ? eH : bH;
}
复制含有随机指针节点的链表
一种特殊的单链表节点类描述如下
class Node{
int value;
Node next;
Node rand;
Node(int val){
value=val;
}
}
rand指针可以是新增的指针,也可以指向链表中的任意一个节点,也可能指向null。给定一个由Node节点类型组成的无环单链表的头节点head,请实现一个函数完成链表的复制,并返回复制的新链表的头节点
实现思路1:用一个hashMap key为老节点,value为新节点
public static Node copyListWithRand1(Node head) {
HashMap<Node, Node> map = new HashMap<Node, Node>();
Node cur = head;
while (cur != null) {
map.put(cur, new Node(cur.value));
cur = cur.next;
}
cur = head;
while (cur != null) {
map.get(cur).next = map.get(cur.next);
map.get(cur).rand = map.get(cur.rand);
cur = cur.next;
}
return map.get(head);
}
思路2
在该节点后设置一个对应的新节点,1的rand的next就是1'需要对应的rand指针,一对一对就能将rand指针设置好,最后,在新老链表不断next将新老链表分离出来
思路总结:用链表将hashmap省掉
代码实现
public static Node copyListWithRand2(Node head) {
if (head == null) {
return null;
}
Node cur = head;
Node next = null;
// copy node and link to every node
while (cur != null) {
next = cur.next;
cur.next = new Node(cur.value);
cur.next.next = next;
cur = next;
}
cur = head;
Node curCopy = null;
// set copy node rand
while (cur != null) {
next = cur.next.next;
curCopy = cur.next;
curCopy.rand = cur.rand != null ? cur.rand.next : null;
cur = next;
}
Node res = head.next;
cur = head;
// split
while (cur != null) {
next = cur.next.next;
curCopy = cur.next;
cur.next = next;
curCopy.next = next != null ? next.next : null;
cur = next;
}
return res;
}