数组/列表的索引为什么是从0开始的?
通过例子来说明这个问题,假如要存100个人在数组中,如果用最小索引为1,则最大索引为100,如果最小索引为0,则最大索引为99,99为两位数,而100为三位数,这就体现出从0开始的优点。
计算机中要求的是二进制,假如有四个汽车:
索引从1开始的十进制为:(1,2,3,4),对应的二进制为(1,10,11,100);
索引从0开始的十进制为:(0,1,2,3),对应的二进制为(0,1,10,11);
可以看到,索引从0开始时,最大宽度为两位,而从1开始最大宽度为三位,当然是两位更具有优势。
数组和链表之间有什么异同点?
一、数组和链表的联系
数组和链表都是线性表数据结构。
二、数组和链表的区别
1.数组可以随机以及顺序存取,而链表只能顺序存取。
2.数组静态分配内存,链表动态分配内存。
3.数组是一种线性表数据结构,有一组连续的内存空间,链表是通过指针将一组零散的内存块串联起来使用的数据结构,不需要一块连续的内存空间。
时间复杂度:
已知一个链表A和数组B的第一个节点的位置,想让大家访问第n个节点,数组比链表快。
1.数组-> 拿第一个的位置+ n* 每个的长度 -> 找到第n个位置(地址)->直接根据地址访问即可->O(1)
2.链表-> 第一个的位置 不断->遍历 直到第n个节点位置 (1+M)/2 = 0.5+M/2 ->O(M)->O(n)
存储空间:
假如要存储n个节点,数组和链表消耗的空间也不同
1.数组容量->n
2.链表 n+m->(x*n) =>(类似)2n
为了方便后续的管理操作,我们并没有删掉最后一个指向空的节点,所以最后一个节点也占了空间,所以链表所消耗的空间也就类似是2n了。
数组和列表之间有什么异同点?
1、存储内容比较:
Array 数组可以包含基本类型和对象类型,
ArrayList 却只能包含对象类型。
Array 数组在存放的时候一定是同种类型的元素。ArrayList 就不一定了 。
2、空间大小比较:
Array 数组的空间大小是固定的,所以需要事前确定合适的空间大小。
ArrayList 的空间是动态增长的,而且,每次添加新的元素的时候都会检查内部数组的空间是否足够。
3.方法上的比较:
ArrayList 方法上比 Array 更多样化,比如添加全部 addAll()、删除全部 removeAll()、返回迭代器 iterator() 等。
适用场景:
如果想要保存一些在整个程序运行期间都会存在而且不变的数据,我们可以将它们放进一个全局数组里, 但是如果我们单纯只是想要以数组的形式保存数据,而不对数据进行增加等操作,只是方便我们进行查找的话,那么,我们就选择 ArrayList。
如果我们需要对元素进行频繁的移动或删除,或者是处理的是超大量的数据,那么,使用 ArrayList 就真的不是一个好的选择,因为它的效率很低,使用数组进行这样的动作就很麻烦,那么,我们可以考虑选择 LinkedList。
在你所使用的编程语言中,数组和列表之间是否存在严格的区别?
数组的区别
大部分数据结构和算法书籍中,在讲到二维或者多维数组中数据的存储方式的时候,一般都会这么说:
二维数组中的数据,是先按行再按列(或者先按列后按行),依次存储在连续的存储空间中。
如果二维数组定义为a[n][m],那a[i][j]的寻址公式为下面这样(先按行后按列存储): address_a[i][j] =
address_base + (i*m+j) * data_size;
但是,在有些编程语言中,二维数组并不满足上面的说法和寻址公式。比如,Java中的二维数组,第二维可以是不同长度的,而且第二维的三个数组(arr[0]、arr[1]、arr[2])并不是连续存储。
int arr[][] = new int[3][];
arr[0] = new int[1];
arr[1] = new int[2];
arr[2] = new int[3];
实际上,两个都没错。编程语言中的”数组“并不完全等同于,我们在讲数据结构和算法的时候,提到的”数组“。编程语言在实现自己的”数组“类型的时候,并不是完全遵循数据结构”数组“的定义,而是针对编程语言自身的特点,做了调整。
Java和python列表的区别
Java list与Python list相比较:
Java List:有序的,可重复的。(有序指的是集合中对象的顺序与添加顺序相同)
Python list(列表)是有序的,可变的。
Java List分类:
—ArrayList:底层使用数组,线程不安全,查找速度快,增删速度慢
在迭代过程中,对集合对象的增删会出现异常
—LinkedList:底层使用链表,线程不安全,查找速度慢,增删速度快
后进先出,类似于栈
—Vector: 底层使用数组,线程安全,查找速度快,增删速度慢,被ArrayList替代
Python 列表无分类,list是Python的基本数据结构。
Java Map与Python dict相比较
Java Map属于集合,但不属于Collection体系中一部分,无序的,不可重复,以键值对形式存在。
Python dict全称dictionary,在其他语言中也称为map,使用键-值(key-value)存储,具有极快的查找速度,dict的key必须是不可变对象,因为dict根据key使用哈希算法来计算value的存储位置。
Java Map分类:
— HashMap:底层使用的数据结构是哈希表
保持键的唯一性同HashSet相同。
— TreeMap:底层使用的数据结构是二叉树
保持键的唯一性同TreeSet相同。
Python 字典无分类,dict是Python的基本数据结构。
Java Set与Python set相比较
Java Set底层使用的就是Java Map的键,值被设置为空,因此Set与Map保持唯一性的原理是相同的。
Python set和dict类似,也是一组key的集合,但不存储value。由于key不能重复,所以,在set中,没有重复的key。
Java Set:无序,不可重复
—HashSet:底层使用哈希表,线程不安全
保证对象唯一的方式:重写hashcode(),equals(Object obj).使用哈希算法导致无序
—TreeSet:底层使用二叉树,线程不安全
在使用add方法添加对象时,会对加入集合的对象进行排序
保证对象唯一的方式:1 实现Comparable接口,实现compareTo()方法的返回值是0,则不能加入。2 创建一个类,实现Comparator,实现compare() 方法。
Python 集合无分类,set是Python的基本数据结构。
栈的特点是什么?队列的特点是什么?
栈是一个特殊的线性表,是限定仅在一端(通常是表尾)进行插入和删除操作的线性表。
又称为后进先出(Last In First Out)的线性表,简称 LIFO 结构。
队列是一种先进先出(First In Frist Out——FIFO)的线性表.
它只允许在表的一端(表尾)插入元素,而在另一端(表头)删除元素。
你能否用一个双向队列来实现一个栈?
(1)栈中属性:属性,就是双端队列一个
(2)仨函数:isEmpyt()判断栈是否为空?
(3)仨函数:push(value)压入元素
(4)仨函数:pop()弹出元素
//双端队列实现栈
public static class ReviewStack<T>{
//属性,就是双端队列一个
public DoubleEndsQueue2 queue2;
//构造时先生成一个双端队列
public ReviewStack(){
queue2 = new DoubleEndsQueue2<>();
}
//提供仨函数
//(2)仨函数:isEmpyt()判断栈是否为空?
public boolean isEmpty(){
return queue2.isEmpty();//直接用双端队列的判断函数
}
//(3)仨函数:push(value)压入元素
public void push(T value){
queue2.addFromHead(value);//直接头入
}
//(4)仨函数:pop()弹出元素
public T pop(){
return (T) queue2.popFromHead();//头返回即可
}
}
public static void test4(){
ReviewStack<Integer> stack = new ReviewStack<>();
stack.push(1);
stack.push(2);
System.out.println(stack.pop());
System.out.println(stack.pop());
System.out.println(stack.isEmpty());
}
public static void main(String[] args) {
//test2();
//test3();
test4();
}
优先队列和队列之间有什么异同点?
优先队列的概念
出队顺序和入队顺序无关,与优先级相关。主要区别普通队列在于出队,优先级高者出队。
特性:自动排序
C++优先队列priority_queue类成员函数如下
出队是从大到小的顺序:大根堆
//默认是降序排列,大顶堆
priority_queue q;
出队是从大到小的顺序:小根堆
//升序队列,小顶堆
priority_queue <int,vector,greater > q;
哈希表和数组之间有什么异同点?
哈希表将需要查找的key值,通过hash函数的计算,换算为数组的位置值。这样在查找时就可以直接定位数据的位置。它结合了数组的快速查询的优点又能融合链表方便快捷的增加删除元素的优势。
数组 : 连续的一片连续的存储空间 , 占用内存严重 , 故空间复杂度高, 寻址容易 ,但是插入和删除困难
链表 : 存储空间松散 , 占用内存宽松 , 通过指针关联前后位置元素, 所以空间复杂度小 , 寻址困难 , 但是插入和删除比较快
哈希表 : 结合了数组和链表 , 结合了数组和链表两者的特点
元素存储到哈希表底层数组中的位置: index = key.hash() & (len - 1)
HashMap是一个线性数组 , 内部由Entry对象组成 , 每一个Entry对象包括了: key(键 — > 也称之为关键码值) , value(值 --> 真正的数据) , hashCode(hash码值) , next(指针域 --> 指向下一个元素)
注意: HashMap中可以存储null值, key为null值的元素放在数组下标为0的位置对应的链表中
哈希表(map)和哈希集合(set)之间有什么异同点?
HashMap 和 HashSet 都是 Java 中的数据结构,它们都使用哈希表来实现。
但是,它们之间有一些重要的区别:
哈希表和哈希集合
HashMap 是一种映射,它存储键值对(key-value pairs)。每个键都是唯一的,而值可以重复。
HashSet 是一种集合,它存储单独的元素。所有的元素都是唯一的,没有重复元素。
HashMap 允许空键和空值,而 HashSet 不允许 null 元素。
HashMap 是不同步的,而 HashSet 是同步的。
HashMap 是有序的,而 HashSet 是无序的。
HashMap 的迭代器(iterator)是 fail-fast 的,而 HashSet 的迭代器是 fail-safe 的。
异同点
HashMap和Hashtable的区别
HashMap和Hashtable都实现了Map接口,但决定用哪一个之前先要弄清楚它们之间的分别。主要的区别有:线程安全性,同步(synchronization),以及速度。
HashMap几乎可以等价于Hashtable,除了HashMap是非synchronized的,并可以接受null(HashMap可以接受为null的键值(key)和值(value),而Hashtable则不行)。
HashMap是非synchronized,而Hashtable是synchronized,这意味着Hashtable是线程安全的,多个线程可以共享一个Hashtable;而如果没有正确的同步的话,多个线程是不能共享HashMap的。Java 5提供了ConcurrentHashMap,它是HashTable的替代,比HashTable的扩展性更好。
另一个区别是HashMap的迭代器(Iterator)是fail-fast迭代器,而Hashtable的enumerator迭代器不是fail-fast的。所以当有其它线程改变了HashMap的结构(增加或者移除元素),将会抛出ConcurrentModificationException,但迭代器本身的remove()方法移除元素则不会抛出ConcurrentModificationException异常。但这并不是一个一定发生的行为,要看JVM。这条同样也是Enumeration和Iterator的区别。
由于Hashtable是线程安全的也是synchronized,所以在单线程环境下它比HashMap要慢。如果你不需要同步,只需要单一线程,那么使用HashMap性能要好过Hashtable。
HashMap不能保证随着时间的推移Map中的元素次序是不变的。
什么是HashSet
HashSet实现了Set接口,它不允许集合中有重复的值,当我们提到HashSet时,第一件事情就是在将对象存储在HashSet之前,要先确保对象重写equals()和hashCode()方法,这样才能比较对象的值是否相等,以确保set中没有储存相等的对象。如果我们没有重写这两个方法,将会使用这个方法的默认实现。
public boolean add(Object o)方法用来在Set中添加元素,当元素值重复时则会立即返回false,如果成功添加的话会返回true。
什么是HashMap
HashMap实现了Map接口,Map接口对键值对进行映射。Map中不允许重复的键。Map接口有两个基本的实现,HashMap和TreeMap。TreeMap保存了对象的排列次序,而HashMap则不能。HashMap允许键和值为null。HashMap是非synchronized的,但collection框架提供方法能保证HashMap synchronized,这样多个线程同时访问HashMap时,能保证只有一个线程更改Map。
public Object put(Object Key,Object value)方法用来将元素添加到map中。
给定一个数组[5, 3, 8, 1, 9, 6, 7, 8, 2],请描述用这个数组构建一个单调递减栈(从栈顶到栈底单调递减)的过程。
比如我们需要依次将数组 [1,3,4,5,2,9,6] 压入单调栈。
首先压入 1,此时的栈为:[1]
继续压入 3,此时的栈为:[1,3]
继续压入 4,此时的栈为:[1,3,4]
继续压入 5,此时的栈为:[1,3,4,5]
如果继续压入 2,此时的栈为:[1,3,4,5,2] 不满足单调递减栈的特性, 因此需要调整。如何调整?由于栈只有 pop 操作,因此我们只好不断 pop,直到满足单调递减为止。
上面其实我们并没有压入 2,而是先 pop,pop 到压入 2 依然可以保持单调递减再 压入 2,此时的栈为:[1,2]
继续压入 9,此时的栈为:[1,2,9]
如果继续压入 6,则不满足单调递减栈的特性, 我们故技重施,不断 pop,直到满足单调递减为止。此时的栈为:[1,2,6]
注意这里的栈仍然是非空的,如果有的题目需要用到所有数组的信息,那么很有可能因没有考虑边界而不能通过所有的测试用例。 这里介绍一个技巧 - 哨兵法,这个技巧经常用在单调栈的算法中。
对于上面的例子,我可以在原数组 [1,3,4,5,2,9,6] 的右侧添加一个小于数组中最小值的项即可,比如 -1。此时的数组是> [1,3,4,5,2,9,6,-1]。
那对于[5, 3, 8, 1, 9, 6, 7, 8, 2],同理。