【学习笔记】在刷题前--数据结构和操作

数据结构和操作

复杂度

1.算法的时间复杂度

大O表示法,常数的就不要了,主要是去看循环,看循环体循环了多少次。其实就是主要去关注控制循环结束的条件,for里对应很多的n,n^2; while里循环条件如果有涉及到了循环体,有很多是logN;

2.算法的空间复杂度

主要去关注变量,尤其是数组;然后去关注递归;

数据结构

1.数组Array

连续的; 相同类型的; -> 读多写少;

1.1 复杂度

访问 access:根据索引找元素:O(1);
搜索 search:在整个数组里去找元素:O(N);
插入 insert:O(N);
删除 delete:O(N);

1.2 java中数组常用操作
//创建数组的两种方法:  
int[] array = new int[3];  //知道数组长度;
ArrayList<Integer> array = new ArrayList<>();  //不知道要创建的数组长度;
--------------------------------------
//ArrayList中的方法:
插入: array.add(4);  //在尾端插入4;
       array.add(3,4);  //在索引3处插入4;
访问: array.get(3);  //访问索引为3的元素;
更新: array.set(3,4); //更新索引3处的元素为4;
删除: array.remove(3);  //删除元素3;
长度: array.size();  //注意不是第一种方式的length;
查找: array.contains(4);
转数组:array.toArray(new int[n]);  //括号里写具体的数组类型,不然都转成object了;
List<Integer>转int[]: int[] arr = list.stream().mapToInt(Integer::valueOf).toArray();
--------------------------------------
//此外在数组中常用到排序等,可以直接调用Arrays类;   
Arrays类常用方法:
Arrays.sort(nums);   //对数组nums按照升序排序;复杂度:O(NlogN);
Arrays.toString(nums); //将数组内容转化为字符串;
Arrays.asList(nums[1],nums[2],nums[3]);  //将数组转化为List集合。使用对象类型数组。
//注意此方法得到的list数组的长度是不可变的。也就是不能删除或添加了。
Array.binarySearch(); //二分搜索指定元素下标(找到返回下边,
//找不到返回一个负数,取反~后是该放在的位置,注意一定需要是排序好的数组。只要用二分法必须排序好)    
Arrays.copyOf(arr,3);  //截取arr数组的前3个数据,3指的是长度,如果大于数组长度,则补0;   
Arrays.fill(arr, ‘a’); //将数组全填为a;
Collections类常用方法:
Collections.reverse(list);  //直接对列表中元素反转;
1.3 python中数组常用操作
a = []    # 创建数组
a.append(1)       #在末尾添加;
a.insert(2,1)       #在索引2添加元素1;
a[2]                    #访问元素;
a[2] = 2              #更新元素;
a.remove(4)      #删除4这个元素;
a.pop(1)           #删除1索引的元素;不指定默认是最后
a.pop()             #删除最后一个元素;
del a[4]             #删除索引4的元素;
size = len(a)    #获取a的长度;
index = a.index(2)   #获取元素2的索引;
a.sort()   #升序;
a.sort(reverse = True)  #降序;
a[::-1]   #数组反转
--------------------------------------
for i in a:               # 遍历数组的三种方法,尽量用1,2,避免出现下标
    print(i)
for index, element in enumerate(a):
    print("index at ",index, "is :",element)                  
for i in range(0, len(a)):
    print("i:" , i, "element : ", a[i])
1.4 常见注意
  • 1、数组中经常有一类题型:往往和字符串结合起来比如找出某某子数组或者子串,其和为多少等等,这时候常用前缀和去解,即 连续子数组+和 --> 前缀和;连续子串+最值 --> 滑动窗口
  • 2、如果看到要在有序数组中找某个值,那就用二分查找去解,即有序数组+搜索 --> 二分查找

2.链表Linked List

不连续的; 相同类型的; -> 写多读少;

2.1 复杂度

访问 access:根据索引找元素:O(N);
搜索 search:在整个数组里去找元素:O(N);
插入 insert:O(1);
删除 delete:O(1);

2.2 java中链表常用操作
//创建链表
LinkedList<Integer> list = new LinkedList<>();
--------------------------------------
//LinkedList中的方法
添加:list.add(3); //在末尾插入元素3;
      list.add(2,3); //在2的位置插入元素3;
      list.addLast(3); //在末尾插入元素3,可以把链表作为栈或者队列;
访问:list.get(2); //得到索引2的元素;
搜索:list.indexOf(3); //找到元素3的索引;
更新:list.set(2,3); //更新索引2的元素为3;
删除:list.remove(2); //删除索引为2的元素;
长度:list.size(); //链表的长度;
2.3 python中链表常用操作
linkedlist = deque()     #创建链表;
linkedlist.append(1)    #末尾添加元素1;
linkedlist.insert(2,1)    #在索引2位置添加元素1;
element = linkedlist[2]   # 访问索引2的元素;
index = linkedlist.index(2)   #获取元素2的索引;
linkedlist.remove(4)      #删除4这个元素;
del linkedlist[4]             #删除索引4的元素;
size = len(linkedlist)    #获取a的长度;

3.队列Queue/Deque

排队 -> 先到先得,先进先出

3.1 复杂度

访问 access:根据索引找元素:O(N);
搜索 search:在整个数组里去找元素:O(N);
插入 insert:O(1);
删除 delete:O(1);

3.2 java中队列常见操作
//创建队列
Queue<Integer> queue = new LinkedList<>();
--------------------------------------
//队列的方法
添加:queue.add(3); 
     queue.offer(3)   //在队尾添加元素3;offer要更优一些;
获取即将出队的元素:temp = queue.peek();  //获取出队的元素;
删除即将出队的元素:temp = queue.poll();
判断是否为空:queue.isEmpty();
队列长度:queue.size();
--------------------------------------
//双端队列:相当于集成了队列和栈;能够先入先出,也能后后入先出;
Deque<Integer> deque = new LinkedList<>(); //创建一个双端队列;  
deque.pollLast(); //移除队尾元素;
deque.pollFirst(); //移除队首元素;   
deque.peekFirst(); //队首元素;
deque.peekLast(); //队尾元素;
3.3 python中队列常用操作
from Collections import deque
queue = deque()   #创建双端队列;
queue.append(2)   #添加元素;
temp1 = queue[0]   #获取即将出队的元素;
temp2 = queue[-1]  #获取队尾元素
temp2 = queue.pop()  #删除队尾元素;
temp2 = queue.popleft()  #删除出队的元素;
len(queue) == 0  # 判断队列是否为空;
--------------------------------------
while len(queue) > 0:   #遍历队列
    temp = queue.popleft()
    print(temp)   

4.栈stack

箱子 -> 先进后出

4.1 复杂度

访问 access:只访问栈顶:O(1);
搜索 search:在整个数组里去找元素:O(N);
插入 insert:O(1);
删除 delete:O(1);

4.2 java中栈常用操作
//创建栈
Stack<Integer> stack = new Stack<>();
--------------------------------------
//栈方法
添加:stack.push(3);  //在栈顶添加元素3;
获取栈顶元素:stack.peek();  
删除栈顶元素:stack.pop();
栈的大小:stack.size();
是否为空:stack.isEmpty();
4.3 python中栈常见操作
stack = []    # 创建栈;
stack.append(1) #添加元素;
stack[-1]   #获取栈顶元素
temp = stack.pop()   #删除栈顶元素;
--------------------------------------
while len(stack) > 0:   #遍历队列
    temp = stack.pop()
    print(temp)
4.4 栈的常见注意

1、栈往往会用来解决那种两两匹配,两两抵消消消乐的问题,比如常见的有效括号等,就是判断后入栈的相邻元素是否有相互关系能够相互抵消,

5.哈希表HashTable

键值对 key-value

5.1 复杂度

访问 access:无;
搜索 search:O(1); 如果碰撞:O(K) K为碰撞的个数
插入 insert:O(1);
删除 delete:O(1);

5.2 java中哈希表常用操作
//创建哈希表
①、String[] hashTable = new String[4];  //通过数组创建,因为数组的index默认作为了key;
②、HashMap<Integer, String> map = new HashMap<>();
--------------------------------------
//哈希表方法
添加:map.put(1, "lihua");
更新:map.put(1, "liming");
删除:map.remove(1);
获取:map.get(3);
获取键值:map.keySet(); //是个数组;
获取值:  map.values(); //是个数组;
检查key存在:map.containsKey(3);
长度:map.size();
是否为空:map.isEmpty();
有key:是key对应的value,没key:默认值;map.getOrDefault(key,default);
map.put(key, val);  //没key,添加;有key,会覆盖原来的;
map.getOrDefault(key, default);  //有key,得到其value; 没key,得到default值;
map.putIfAbsent(key, val);  //if存在key,那就不会放入,不存在的时候再存;
//遍历哈希表:
for(Map.Entry<String, Integer> entry : map.entrySet()){
    entry.getKey();
    entry.getValue();   //得到key和value;
}
5.3 python中哈希表常用操作
hashtable = []   #通过数组创建,key即为数组下标;
hashtable[2] = "liming"   #添加元素/修改元素
----------------------------------------------
mapping = {}    #通过字典创建;
# 这样的时候if有key1不在哈希表里直接mapping[key1]会报错    
from collections import defaultdict
mapping1 = defaultdict(int)   #参数是一个函数,可以是int,str,float,list等,意思就是每个元素会默认的是当前的类型的默认值   
# 比如现在就是默认的是0, list就会默认的是[]
# 另外,Counter这个函数对可迭代对象统计个数后,返回也是一个字典,并且这个字典if有元素不在,然后直接count1[a],这样不会有错。
mapping["key1"] = "liming"  #添加元素/修改元素;
mapping.get(key, default = None)  # 第二个参数在key不存在时可以指定一个返回值;例如  mapping.get(key, [])  key不存在的时候会返回一个空列表
# 其次注意字典的key只能是不可变对象;
mapping.pop("key1")  #删除元素
“key” in mapping   #检查key是否存在;
mapping.values()  #得到value值组成的列表
mapping.keys()   #key值组成的列表
mapping.items()   #仍然是列表,列表里存放的是元祖,(key, value)
# 在python3.6之后,dict是按照添加元素的顺序进行返回的;    
# if想要dict是有序的,可以用OrderedDict
from collections import OrderedDict
dict1 = OrderedDict()   # 则是按照元素的插入顺序;
5.4 常见注意
  1. 哈希表在刷题的时候常常会用在涉及到次数的问题上,比如某个数字或者某个字符出现了几次;

6.集合Set

无序、不可重复 主要作用:查看重复元素

6.1 复杂度

访问 access:无;
搜索 search:无哈希冲突O(1); 有哈希冲突:O(K)
插入 insert:无哈希冲突O(1);有哈希冲突:O(K)
删除 delete:无哈希冲突O(1);有哈希冲突:O(K)

6.2 java中集合常见操作
//创建hashset
HashSet<Integer> set = new HashSet<>();
//hashset方法
添加:set.add(4); //如果Set集合中不包含要添加的对象,则添加对象并返回true;否则返回false。
搜索:set.contains(4);
删除:set.remove(4);
长度:set.size();
遍历:
Iterator it = set.iterator();   
while (it.hasNext()) {   
     System.out.print(it.next());   
}
6.3 python中集合常见操作
s = set()  #创建一个集合
s.add(2)   #添加元素2;
2 in s # 搜索2是否在集合s;
s.remove(2)  #删除元素2;
6.4 常见注意
  1. set在刷题的时候常常会用在涉及到有无重复,独一无二,唯一等问题上,比如某个数字是否重复,是否出现过,用的都是set的不可重复性;

6.树Tree

父子关系

概念
  • 节点:根节点、叶子节点(度为0的节点);
  • 节点的度:一个节点含有的子节点的个数;
  • 树的度:最大的节点的度;
  • 二叉树:每个节点最多有两个孩子;节点的度可以为0,1,2;
  • 满二叉树:除了叶子节点,每个节点都有两个孩子;并且所有叶子节点在一层;
  • 完全二叉树:从上到下,从左到右,依次填满;

满二叉树的概念最小;限制最多,满二叉树一定是完全二叉树

性质

1.完全二叉树除了最后一层外,下一层节点个数是上一层两倍,
如果一颗完全二叉树的节点总数是n,那么叶子节点个数为n/2(n为偶数)或(n+1)/2(n为奇数);

遍历
  • 前序遍历:根节点 -> 左子树 -> 右子树;
  • 中序遍历:左子树 -> 根节点 -> 右子树;
  • 后序遍历:左子树 -> 右子树 -> 根节点;

常用

  1. 二叉树解题的时候往往用到的是递归方法,注意千万不要陷入递归压栈过程中去,就想函数功能,终止条件,一层节点能做什么,什么时候做。
  2. 深度优先(DFS)与广度优先(BFS): DFS与BFS都是用来遍历的手段,在树和图里都是用这两种方法。
    1. DFS:DFS的意思就是比如说一颗树,就先到树的左节点,然后再往树的左节点,一直到最后走不了了,然后回到上一层,去右节点,再回到上一层,如此往复。对于图,就是去往开始节点的一个邻居节点,然后再去此邻居节点的邻居节点,然后到头,返回上一个,如此往复。所以只要用到DFS肯定会用到递归。
    1. BFS:BFS的意思就是比如说一棵树,到左节点,然后到右节点,然后再到左节点的下一层,右节点的下一层,这样子一层一层的往下走,直到最后一层。对于图,去往源点的邻居节点,把源点的邻居节点都走完之后,再去下一个节点,所以BFS就会用到队列的结构,在树里,把节点入队,然后每次出队,就把出队节点的左右孩子入队,这样做到一层一层遍历。在图里,把源点入队,然后把出队点的邻居节点都入队。这样往复。但是在图里为了防止重复访问,需要设置当前节点是否被访问过。(比如可以设置一个vis数组设置为true或false,或者设置一个哈希表这样)
    1. 在树里经常用DFS,在图里经常用BFS(因为树可以很方便的去递归左右子树,而图则对邻居节点比较方便);
    1. 树和图里的BFS区别:
    • 1.树只有一个root,而图可以有多个源点,所有首先需要将多个源点入队。
    • 2.树是有向的因此不需要设置标志是否访问过,而对于无向图而言,必须得标志是否访问过!并且为了防止某个节点多次入队,需要在入队前将其设置为已访问!
    • 3.一个源点的广度优先和多个源点的广度优先:广度优先搜索

7.堆Heap

7.1 概念
  • 前提:完全二叉树
  • 根节点全部≥叶子节点:大根堆:堆顶元素最大;
  • 根节点全部≤叶子节点:小根堆:堆顶元素最小;
7.2 复杂度

访问 access:无;
搜索 search:查看堆顶元素:O(1);
插入 insert:O(logN):每次添加的元素去和父亲节点做对比;和兄弟节点没有关系;
删除 delete:O(logN)

7.3 java中堆常用操作
// 创建堆
PriorityQueue<Integer> minheap = new PriorityQueue<>();  //创建小根堆;
PriorityQueue<Integer> queue = new PriorityQueue<Integer>(new Comparator<Integer>() {
    public int compare(Integer num1, Integer num2) {
        return num2 - num1;
    }
});
//堆的排序指的是从上往下排;
 //创建大根堆,注意要排序,重写比较器;
    /**o1-o2为升序序排列,o2-o1为降序排列,若具体到某一字段,则根据该字段进行排列*/
    @Override
    public int compare(Node o1, Node o2) {
        if (o1.x==o2.x) //若x属性相等,根据y来升序
            return o1.y-o2.y;
        return o1.x-o2.x;//x属性不相等,根据x来升序排列
    }
----------------------------------------------
//PriorityQueue方法   
添加元素:minheap.add(3);
堆顶元素:minheap.peek();
删除堆顶元素:minheap.poll();
堆里元素数量:minheap.size();
边遍历边删除:
while( !minheap.isEmpty() ){
    System.out.println(minheap.poll());
}
7.4 python中堆常用操作
import heapq   
minheap = []    
heapq.heapify(minheap)   #第一种方法,先创建一个列表,然后堆化;只能创建小根堆
heapq.heapush(minheap, 10)    #第二种方法,添加元素;
minheap[0]   #堆顶元素;
heapq.headpop(minheap)   #删除堆顶元素;
----------------------------------------------
while len(minheap) != 0:
    print(heapq.heapop(minheap))
# 创建大根堆的方法:乘以-1
l1 = list(str1)
heapq.heapify(l1)  #小根堆
newl1 = [(-i,l1[i]) for i in range(len(l1)]
heapq.heapify(newl1)  #按照第一个元素变为了小根堆,也就是i的大根堆
max_heap = list()
while newl:
  _, s = heapq.heappop(newl)
  max_heap.append(s)

8.图Graph

邻居关系

概念
  • 顶点;邻居顶点;边;
  • 度(degree):每个顶点有几条边;
  • 无向图;
  • 有向图:
    • 入度:有多少条边指向该顶点;
    • 出度:有多少条边指向别的顶点;
  • 权重图:
    • 求最短路径;
      • 贝尔曼-福特算法;
      • 迪克斯特拉算法;

总结

数组
链表
队列:先入先出;
栈:后入先出;
堆:最大堆;最小堆;
哈希表: key | value;
哈希集合:无序、不可重复;

  • 知道每个结构的特性;
  • 每个结构的增删改查复杂度;

关于排序好的数组;

排序好的数组有以下几种常见的处理方法:

  • 1.二分查找:二分查找的使用前提就是数组必须是有序的,这样才能每次取中间值去逼近;二分查找总结
  • 2.首尾双指针:首尾双指针也常常用在排序数组上,尤其是涉及到两个数之和时,因为如果和大了或者小了可以去移动首或尾指针;

关于历史信息

  • if想要记录历史中的最大值或者最小值,或者是历史中的顺序问题,那总不能再次重新遍历了,往往就会用到优先队列(堆)
    比如大根堆:总是维持了历史中的k个最小的,if来的比顶还大,那就直接走,if来的比堆顶小,那就把最大的(堆顶)弹出去,小的进来,这样不断更新堆,也就维持了k个最小的;
posted @ 2022-03-31 22:18  Curryxin  阅读(84)  评论(0编辑  收藏  举报
Live2D