一、选择题
1、数组常用的两种基本操作是 (C )
A. 建立与删除
B. 删除与查找
C. 查找与修改
D. 插入与索引
答案解析:C
数组指的就是一组相关类型的变量集合,数组的访问通过索引完成。
所以数组的查找和修改效率比较高。
2、线性表的逻辑顺序与物理顺序总是一致的 (B )
A. 是
B. 否
答案解析:B
这句话错误。
比如数组和链表都属于线性表。
但是链表是一种物理存储结构上非连续存储结构
3、定义了一维 int 型数组 a[10] 后,下面错误的引用是( C)
A. a[0] = 1;
B. a[0] = 5*2;
C. a[10] = 2;
D. a[1] = a[2] * a[0];
答案解析:C
a[10]下标越界了,会报异常java.lang.ArrayIndexOutOfBoundsException
4、从一个长度为n的数组中删除第i个元素(1≤i≤n)时,需向前移动()个元素 (A )
A. n-i
B. n-i+1
C. n-i-1
D. i
答案解析:A
数组第i个元素后还有n-i个元素;
所以删除数组第i个元素,需要向前移动n-i个元素
5、下列关于链表的描述中正确的是【多选】(A B C )
A. 链表由头指针唯一确定,单链表可以用头指针的名字来命名
B. 线性链表的存储空间不一定连续,并且各元素的存储顺序是任意的
C. 链表的插入删除不需要移动元素,可以只改变指针
D. 链表可随机访问任一元素
答案解析:A B C
链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针连接次序实现的。
它由头指针唯一确定,其中每一个链表结点的链接是通过指针来实现的;
由于链表是通过指针来访问的,一个结点最多只存放它前面一个结点的指针和后面一个结点的指针,所以链表无法随机访问元素。
D项是错误的
二、编程题
1、从链表中删去总和值为零的连续节点
给你一个链表的头节点 head,请你编写代码,反复删去链表中由 总和 值为 0 的连续节点组成的序列,直到不存在这样的序列为止。删除完毕后,请你返回最终结果链表的头节点。OJ链接
示例1:
输入:head = [1,2,-3,3,1]
输出:[3,1]
提示:答案 [1,2,1] 也是正确的。
示例2:
输入:head = [1,2,3,-3,4]
输出:[1,2,4]
【解题思路】:
定义一个值为 0 的虚拟头节点,当做原链表新的头节点,即 dummyHead.next = head;定义一个 pre 指针指向虚拟头节点作为原链表头节点的前驱节点(方便进行删除操作)。
定义一个指针 p 指向 pre 指向节点的后一个节点(开始时,p 指向原链表的头节点);然后 p 指针开始遍历链表,每遍历一个节点就将该节点值与之前节点值总和进行相加得到新的 sum:
若 sum == 0,则需要进行删除(将 pre 指针和 p 指针指向节点(包括 p 指针指向的节点)中间的节点删除),即pre.next = p.next 然后直接退出循环:
若此时 p 指针已经走到了链表的末尾,则令 pre 指针后移一位并且 p 指向 pre 指向节点的下一节点重新进行判断以及删除;
若此时 p 指针没有走到链表的末尾,则令 pre 指针不动,p 指针继续从 pre 指针指向节点的下一节点开始遍历链表并进行比较和删除操作;
若 sum != 0,则 p 指针继续后移,继续对下一节点进行判断,直到找到使 sum 值为 0 的节点或遍历到链表的结尾为止。
注意:只有当 p 指针指向链表末尾时,pre 指针才后移;否则,则 pre 指针不动,令 p 指针在删除后的链表中继续从头开始遍历。
if (p == null) pre = pre.next;
示例:head = [0, 1, -1] dummyHead = [0, 0, 1, -1]
若没有这个判断,则只要跳出循环就将 pre 指针后移一位,则会出现问题。 开始时,pre 指向第一个 0 并且 p指向第二个 0,此时 sum = 0 则进行删除并且 pre = pre.next,此时 dummyHead = [0, 1, -1]; pre 指向 1 并且 p 指向 -1,此时 sum = -1,p 继续后移为 null 结束循环,最后会输出 [1, -1] 答案错误。
若有这个判断,当将 p 指针指向的 0 删除后,p 没有指向链表末尾 pre 指针不进行移动,此时 dummyHead =[0, 1, -1]; pre 指针仍指向 0,此时 p 指针指向 1,sum = 1 则 p 指针后移;p 指向 -1,此时 sum = 0, 则进行删除操作,此时 dummyHead = [0] 结束,返回dummyHead.next 即可。
class Solution {
public ListNode removeZeroSumSublists(ListNode head) {
if(head==null){
return head;
}
ListNode dummyHead=new ListNode(0);
dummyHead.next=head;
ListNode pre=dummyHead;
while(pre!=null){
ListNode p=pre.next;
int sum=0;
while(p!=null){
sum=sum+p.val;
if(sum==0){
pre.next=p.next;
break;
}else{
p=p.next;
}
}
if(p==null){
pre=pre.next;
}
}
return dummyHead.next;
}
}
2、两数相加
给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储一位 数字。请你将两个数相加,并以相同形式返回一个表示和的链表。你可以假设除了数字 0 之外,这两个数都不会以 0 开头。OJ链接
示例1:
输入:l1 = [2,4,3], l2 = [5,6,4]
输出:[7,0,8]
解释:342 + 465 = 807.
示例2:
输入:l1 = [0], l2 = [0]
输出:[0]
【解题思路】:
将两个链表看成是相同长度的进行遍历,如果一个链表较短则在前面补 0,比如 987 + 23 = 987 + 023 = 1010 每一位计算的同时需要考虑上一位的进位问题,而当前位计算结束后同样需要更新进位值 如果两个链表全部遍历完毕后,进位值为 1,则在新链表最前方添加节点 1 小技巧:对于链表问题,返回结果为头结点时,通常需要先初始化一个预先指针 pre,该指针的下一个节点指向真正的头结点head。使用预先指针的目的在于链表初始化时无可用节点值,而且链表构造过程需要指针移动,进而会导致头指针丢失,无法返回结果。
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
ListNode pre=new ListNode(0);
ListNode cur=pre;
int carry=0;
while(l1!=null||l2!=null){
int x=l1==null?0:l1.val;
int y=l2==null?0:l2.val;
int sum=x+y+carry;
carry=sum/10;
sum=sum%10;
cur.next=new ListNode(sum);
cur=cur.next;
if(l1!=null){
l1=l1.next;
}
if(l2!=null){
l2=l2.next;
}
}
if(carry==1){
cur.next=new ListNode(carry);
}
return pre.next;
}
}