常用数据结构总结

常用数据结构总结

1.1 常用数据结构和技巧

1.2数组、字符串/Array & String

优点
      构建一个数组非常简单
      能让我们在O(1)的时间里根据数组的下标(index)查询某个元素

缺点
      构建时必须分配一段连续的空间
      查询某个元素是否存在时需要遍历整个数组,耗费O(n)的时间(其中,n是元素的个数)
      删除和添加某个元素时,同样需要耗时O(n)的时间

例题:
LeetCode 242. 有效的字母异位词
      题目:给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。
      注意:若 s 和 t 中每个字符出现的次数都相同,则称 s 和 t 互为字母异位词。

示例1:
      输入: s = “anagram”, t = “nagaram”
      输出: true

示例2:
      输入: s = “rat”, t = “car”
      输出: false

解题思路:
      所谓字母异位词,就是两个字符串中的相同字符的数量要对应相等,例如,例一的s和t,此时为true。
      分析处理:
      题目中有个说明,假设两个字符串中都只包含小写字母。
      法一:我们都知道,小写字母一共也就二十六个,这意味着我们可以利用两个长度都为26的字符数组,来统计每个字符串中小写字母出现的次数,然后再对比看看是否相等即可。
      法二:可以只定义一个长度为26的字符数组,将出现在字符串s里的字符个数加一,出现在字符串t里的字符个数减一,最后判断每个小写字符的个数是否为0,若为0,返回true,反正false。
      代码暂不提供,建议读者自己去写一下,不理解的可以私聊博主。

1.3链表/Linked-list

      单链表 :链表中的每个元素实际上是一个单独的对象,而所有对象都通过每个元素中的引用字段链接在一起。
在这里插入图片描述

      双链表 :与单链表不同的是,双链表的每个结点都含有两个引用字段。

在这里插入图片描述
优点
      灵活地分配内存空间
      能在O(1)时间内删除或者添加元素

缺点
      查询元素需要O(n)时间

解题技巧
1.利用快慢指针(有时候需要用到三个指针)
例如:
      链表的反转
      寻找倒数第k个元素
      寻找链表中中间位置的元素
      判断链表是否有环等等
2.构建一个虚假链表头
例如:
      两个排序链表,进行排序整合
      将链表中的奇偶数按原定顺序分离,生成前半部分为奇数,后半部分为偶数的链表。

如何训练该技巧
      1.在纸上或者白板上画出节点之间的相互关系
      2.画出修改的方法
      3.这样可以有效的帮助你分析问题,凭空想象是比较困难的。

例题:
LeetCode 25. K 个一组翻转链表
题目:
      给你一个链表,每 k 个节点一组进行翻转,请你返回翻转后的链表。k 是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。
进阶:
      你可以设计一个只使用常数额外空间的算法来解决此问题吗?
      你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。

示例1:
在这里插入图片描述
      输入:head = [1,2,3,4,5], k = 2
      输出:[2,1,4,3,5]

示例2:
在这里插入图片描述
      输入:head = [1,2,3,4,5], k = 3
      输出:[3,2,1,4,5]

示例 3:
      输入:head = [1,2,3,4,5], k = 1
      输出:[1,2,3,4,5]

示例 4:
      输入:head = [1], k = 1
      输出:[1]

解题思路:
这个题是对LeetCode24题的进阶,这个题考察了两个知识点:
      1.你对链表翻转的熟悉程度
      2.你对递归算法的理解程度
      首先我们要确保给定的链表的长度是大于K的,这样我们才能继续,否则直接返回原链表就可以了。
      然后,我们可以用三个指针prev、curr、next分别代表了前一个结点,当前结点和下一个结点。每次将curr指向的下一个结点保存到next指针,然后curr指针指向prev,接着curr和prev一起前进一步,接着重复这一步,直到把这组当中的k个元素翻转完毕。
      当完成了局部的翻转后,prev就是新的链表的头,curr指向了下一个要被处理的局部,而原来的头指针head成为了链表的尾巴。
核心代码如下:

prev = null;
curr = head;
n = k;
while(curr && n-- > 0) {
  next = curr.next;
  curr.next = prev;
  prev = curr;
  curr = next; 
}

1.4 栈/Stack

特点
后进先出(LIFO)
      对于栈中的数据来说,所有操作都是在栈的顶部完成的,只可以查看栈顶部的数据,只能够从栈的顶部压入数据,也只能从栈的顶部弹出数据。

算法基本思想
      可以⽤⼀个单链表来实现
      只关⼼上⼀次的操作
      处理完上⼀次的操作后,能在 O(1) 时间内查找到更前⼀次的操作

例题:
LeetCode 20. 有效的括号
题目:
      给定一个只包括 ‘(’,’)’,’{’,’}’,’[’,’]’ 的字符串 s ,判断字符串是否有效。
有效字符串需满足:
      左括号必须用相同类型的右括号闭合。
      左括号必须以正确的顺序闭合。

示例 1:
      输入:s = “()”
      输出:true

示例 2:
      输入:s = “()[]{}”
      输出:true

示例 3:
      输入:s = “(]”
      输出:false

示例 4:
      输入:s = “([)]”
      输出:false

示例 5:
      输入:s = “{[]}”
      输出:true

解题思路:
      我们可以利用一个栈,不断的往里压左括号,一旦遇上右括号,我们就把栈顶的左括号弹出,表示这是一个合法的组合,以此类推,直至最后判断栈里面还有没有左括号剩余。
例题2:
LeetCode第739题每日温度
题目:
      请根据每日 气温 列表 temperatures ,请计算在每一天需要等几天才会有更高的温度。如果气温在这之后都不会升高,请在该位置用 0 来代替。

示例 1:
      输入: temperatures = [73,74,75,71,69,72,76,73]
      输出: [1,1,4,2,1,1,0,0]

示例 2:
      输入: temperatures = [30,40,50,60]
      输出: [1,1,1,0]

示例 3:
      输入: temperatures = [30,60,90]
      输出: [1,1,0]
这里简单说一下解题思路:
设置一个递减栈:栈里只有递减元素
      遍历整个数组,如果栈不空,且当前数字大于栈顶元素,那么如果直接入栈的话就不是 递减栈 ,所以需要取出栈顶元素,由于当前数字大于栈顶元素的数字,而且一定是第一个大于栈顶元素的数,直接求出下标差就是二者的距离。
      继续看新的栈顶元素,直到当前数字小于等于栈顶元素停止,然后将数字入栈,这样就可以一直保持递减栈,且每个数字和第一个大于它的数的距离也可以算出来。

1.4 队列/Queue

特点
先进先出(FIFO)
      对于队列的数据来说,我们只允许在队尾查看和添加数据,在对头查看和删除数据。如何实现一个队列呢,我们可以借助双链表,双链表的头指针允许我们在对头查看和删除数据,尾指针允许我们在队尾查看和添加数据

常用的场景
      广度优先搜索(后面会详细介绍)

1.5 双端队列/Deque

基本实现
      可以利⽤⼀个双链表
      队列的头尾两端能在 O(1) 的时间内进⾏数据的查看、添加和删除
在这里插入图片描述
常⽤的场景
      实现⼀个⻓度动态变化的窗⼝或者连续区间

例题:
LeetCode 239. 滑动窗口最大值

题目:
      给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。
      返回滑动窗口中的最大值。

示例 1:
      输入:nums = [1,3,-1,-3,5,3,6,7], k = 3
      输出:[3,3,5,5,6,7]
解释:
滑动窗口的位置           最大值
---------------                    -----
[1 3 -1] -3 5 3 6 7             3
1 [3 -1 -3] 5 3 6 7             3
1 3 [-1 -3 5] 3 6 7             5
1 3 -1 [-3 5 3] 6 7             5
1 3 -1 -3 [5 3 6] 7             6
1 3 -1 -3 5 [3 6 7]             7

示例 2:
      输入:nums = [1], k = 1
      输出:[1]

示例 3:
      输入:nums = [1,-1], k = 1
      输出:[1,-1]

示例 4:
      输入:nums = [9,11], k = 2
      输出:[11]

示例 5:
      输入:nums = [4,-2], k = 2
      输出:[4]

解题思路:
最直观的解法:
      每次都要在移动的窗口中找到最大值,那很简单,我们就移动这个窗口,然后扫描一遍窗口获得最大值。假设数组里有n个元素,那么算法复杂度是O(n*k)。
优化解法:
      利用双端队列来表示这个窗口,这个双端队列保存当前窗口中最大的那个数的下标,双端队列新的头总是当前窗口中最大的那个数,同时有了这个下标,我们可以很快知道新的窗口是否已经不在包含原来那个最大的数,如果不在包含,我们就把旧的数从双端队列的头删除。按照这种操作,不管窗口的长度是多长,因为数组里的每一个数,都分别被压入和弹出双端队列一次,所以我们可以在O(n)的时间里完成任务。

1.6 树/Tree

树的共性
      结构直观
      通过树问题来考察
      递归算法
      掌握的熟练程度

⾯试中常考的树的形状有
      普通⼆叉树
      平衡⼆叉树
      完全⼆叉树
      ⼆叉搜索树
      四叉树
      多叉树
特殊的树:
      红⿊树、⾃平衡⼆叉搜索树

主要考察
遍历
      前序遍历(Preorder Traversal)
      中序遍历(Inorder Traversal)
      后序遍历(Postorder Traversal)
      以及三种遍历的递归写法和复杂度分析。

例题
      给定一个二叉搜索树的根节点 root ,和一个整数 k ,请你设计一个算法查找其中第 k 个最小元素(从 1 开始计数)。

示例 1:
在这里插入图片描述
      输入:root = [3,1,4,null,2], k = 1
      输出:1

示例 2:
在这里插入图片描述
      输入:root = [5,3,6,2,4,null,null,1], k = 3
      输出:3

解题思路:
考察知识点:
二叉搜索树具有如下性质:
      1.结点的左子树只包含小于当前结点的数。
      2.结点的右子树只包含大于当前结点的数。
      3.所有左子树和右子树自身必须也是二叉搜索树。
      二叉树的中序遍历即按照访问左子树——根结点——右子树的方式遍历二叉树;在访问其左子树和右子树时,我们也按照同样的方式遍历;直到遍历完整棵树。
操作:
      因为二叉搜索树和中序遍历的性质,所以二叉搜索树的中序遍历是按照键增加的顺序进行的。于是,我们可以通过中序遍历找到第 k 个最小元素。
      同理,这道题可以求第k个最大元素,同样的解题思路,只是把中序遍历进行倒序,就从递增遍历变成了递减遍历。

总结

      本文章是小朱近日刷算法题,看视频之后做的一些笔记和总结,希望大家有所收获,不足之处,请指正。
  • 7
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小朱不猪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值