文章来源于极客时间前google工程师−王争专栏。
技巧一:理解指针或引用的含义
指针:存储所指对象的内存地址
将某个变量赋值给指针,实际上就是将这个变量的地址赋值给指针,或者反过来说,指针中存储了这个变量的内存地址,指向了这个变量,通过指针就能找到这个变量。
p->next = q
p结点的next指针存储了q结点的内存地址。
p->next = p->next->next
p结点的next指针存储了p结点的下下一个结点的内存地址。
技巧二:警惕指针丢失和内存泄漏
p->next = x
x->next = p->next
- p->next指针完成第一步,不再指向结点b了,而是指向了结点x。
- 第二行相当于将x赋值给x->next,自己指向自己,链表断开。
- 左边next代表指针,右边next代表结点。
正确操作是将上面两行代码执行顺序调换。
技巧三:利用哨兵简化实现难度
单链表p结点后面插入一个新结点
new_node->next = p->next;
p->next=new_node;
如果是插入第一个结点,刚刚的逻辑不能用
if(head == null){
head = new_node;
}
单链表结点删除操作,如果删除结点p的后继结点
p->next = p->next->next
如果要删除链表中最后一个结点,上面的删除逻辑就不能用了
if(head->next == null){
head = null;
}
针对链表的插入、删除操作,需要对插入第一个结点和删除最后一个结点的情况进行特殊处理
哨兵专门解决“边界问题”,不直接参与业务逻辑。
如何表示一个空链表?head=null表示链表中没有结点。
引入哨兵结点,任何时候,不管链表是不是空,head指针都会一直指向这个哨兵结点
- 带有哨兵结点的链表叫作带头链表
- 没有哨兵结点的链表叫作不带头链表
实现:返回数组中特定值的下标
- 普通方法
//n为数组a的长度
public int find(char[] a,int n,char key){
if(a == null || n<=0){
return -1;
}
int i = 0;
while(i<n){
if(a[i] == key){
return i;
}
++i;
}
return -1;
}
- 哨兵机制
public int find(char[] a,int n,char key){
if(a == null || n<=0){
return -1;
}
if(a[n-1] == key){
return n-1;
}
//记录数组临界位置
char temp = a[n-1];
//哨兵处理
a[n-1] = key;
int i=0;
//由于哨兵 优化掉一条i<n的比较语句
while(a[i] != key){
++i;
}
//恢复临界值
a[n-1] = temp;
//数组0~n-2位置中都没有key
if(i == n-1){
return -1;
}
return i;
}
重点留意边界条件处理
基础考虑
- 如果链表为空,代码是否能正常工作?
- 如果链表只包含一个结点时,代码是否能正常工作?
- 如果链表只包含两个结点时,代码是否能正常工作?
- 代码逻辑在处理头结点和尾结点的时候,是否能正常工作?
举例画图,辅助思考
多写多练
- 单链表反转
- 链表中环的检测
- 两个有序链表合并
- 删除链表倒数第n个结点
- 求链表的中间结点
- 206,141,21,19,876(leecode对应编号)