给自己画的饼,哪怕撑死也得吃完!努力,努力,再努力!
今天开始挑战白银段位,先从链表算法下手(后续还会持续更新…)。主要问题有,找到单链表倒数第 n个节点并输出、判断链表是否有环、实现链表的逆序、去除无序链表的重复项等等。
1、实现链表的逆序
思路:从链表的第二个结点开始,把遍历到的结点插入到头结点的后面,直到遍历结束。
代码:
public static void Reverse(ListNode head){
//判断链表是否为空
if(head == null || head.next ==null){
return;
}
//声明当前结点
ListNode cur = head;
//声明后继结点
ListNode next = head;
cur = head.next.next;
//使头结点后第一个结点的指向为null,成为尾结点
head.next.next = null;
//把遍历到的结点插入到头结点的后面
while(cur != null){
next = cur.next;
cur.next = head.next;
head.next = cur;
cur = next;
}
}
2、去除无序链表的重复项
思路:
循环遍历链表,将链表数据存入HashSet,从第二个结点开始进行hashSet.contains()判断,存在重复就修改上一个结点指向进行删除,不存在重复则存入HashSet。
代码:
public static void Remove(ListNode head){
//判断链表是否为空
if(head == null || head.next ==null){
return;
}
//声明一个HashSet进行临时保存结点data
HashSet<Integer> hashSet = new HashSet<Integer>();
//声明上一个结点
ListNode pre = head;
//声明当前结点
ListNode cur = head.next;
//往hashSet里添加第一个结点数据
hashSet.add(head.data);
//把遍历所有结点
while(cur != null){
if(hashSet.contains(cur.data)){
pre.next = cur.next;
}else{
hashSet.add(cur.data);
pre = cur;
}
cur = cur.next;
}
}
3、找到单链表倒数第 n个节点并输出
思路:
两个指针指向头节点,一个指针先走n-1步,然后两个指针开始一起往前走,当快指针走到最后时,慢指针指向的就是倒数第n个节点了。
代码:
public static ListNode findLastNode(ListNode head,int n){
//如果n<=0或链表为空时返回
if (n<=0||head==null) return null;
//前指针
ListNode pre = head;
//后指针
ListNode last = head;
//前指针先走n-1步
for(int i = 0;i<n-1;i++){
if (pre!=null) {
pre = pre.next;
}else {
return null;
}
}
while(pre.next!=null){
pre = pre.next;
last = last.next;
}
return last;
}
4、判断链表是否有环
思路:
快慢指针法,两个指针一个一次走一步,一个一次走两步,当两指针相遇时,证明有环返回结果,最后当慢指针==null时证明无环。
代码:
public static boolean circleList(ListNode head){
//慢指针,一次走一步
ListNode slow = head;
//快指针,一次走两步
ListNode fast = head;
while(fast.next!=null&&slow!=null){
slow = slow.next;
fast = fast.next.next;
//当快慢指针相等时证明链表必定有环
if (slow==fast) {
return true;
}
}
return false;
}
5、计算两个单链表所代表数字之和
思路:
对链表中的结点直接进行相加操作,把相加的和存储到新的链表中对应的结点中。需要注意的是:每组结点进行相加后需要记录其是否有进位;两个链表的长度不同;对链表所有结点都完成计算后,还需要考虑此时是否有进位,如果有进位,则需要增加新的结点,此时结点的数据域为1;
代码:
//h1,h2均为头结点
Public static ListNode add(ListNode h1,ListNode h2){
//为空判断
if(h1 == null || h1.next == null){
return h2;
}
if(h2 == null || h2.next == null){
return h1;
}
//用来记录进位
int c = 0;
//用来记录两个结点相加的值
int sum = 0;
//用来遍历h1
ListNode p1 = h1.next;
//用来遍历h2
ListNode p2 = h2.next;
//用来指向新创建的存储相加和的结点
ListNode tmp = null;
//相加后链表头结点
ListNode resultHead = new ListNode();
resultHead.next = null;
//用来指向链表resultHead最后一个结点
ListNode p = resultHead;
while(p1 != null && p2 != null){
tmp = new ListNode();
tmp.next = null;
sum = p1.data + p2.data + c;
tmp.data = sum%10;
c = sum/10;
p.next = tmp;
p = tmp;
p1 = p1.next;
p2 = p2.next;
}
//如果链表h2比h1长
if(p1 == null){
while(p2 != null){
tmp = new ListNode();
tmp.next = null;
sum = p2.data + c;
tmp.data = sum%10;
c = sum/10;
p.next = tmp;
p = tmp;
p2 = p2.next;
}
}
//如果链表h1比h2长
if(p2 == null){
while(p1 != null){
tmp = new ListNode();
tmp.next = null;
sum = p1.data + c;
tmp.data = sum%10;
c = sum/10;
p.next = tmp;
p = tmp;
p1 = p1.next;
}
}
//如果计算完成后还有进位,则增加新的结点
if(c == 1){
tmp = new ListNode();
tmp.next = null;
tmp.data = 1;
p.next = tmp;
}
return resultHead;
}
6、合并两个有序链表
思路:
分别用两个指针head1、head2来遍历两个链表,如果当前head1指向的数据小于head2指向的数据,则将head1指向的结点归入合并后的链表,否则将head2指向的结点归入合并后的链表中。如果有一个链表遍历结束,则把未结束的链表连接到合并后的链表尾部。
代码:
public static ListNode mergeTwoList(ListNode head1, ListNode head2) {
//为空判断
if (head1 == null && head2 == null) {
return null;
}
if (head1 == null) {
return head2;
}
if (head2 == null) {
return head1;
}
//合并后的链表
Node head = null;
if (head1.data > head2.data) {
//把head较小的结点给头结点
head = head2;
//继续递归head2
head.next = mergeTwoList(head1, head2.next);
} else {
head = head1;
head.next = mergeTwoList(head1.next, head2);
}
return head;
}
7、在只给定单链表中某个结点的指针的情况下删除该结点
思路:
不需要遍历链表,只需要完成一个数据复制与结点删除的操作。
代码:
public static boolean RemoveNode(ListNode p){
//如果结点为空或无后继结点判断
if(p == null || p.next == null){
return false;
}
p.data = p.next.data;
ListNode tmp = p.next;
p.next = tmp.next;
return true;
}
引申:只给定单链表中某个结点p(非空结点),如何在p前面插入一个结点?
思路:
首先分配一个新结点q,把结点q插入到结点p后,然后把p的数据域复制到结点q中,最后把结点p的数据域设置为待插入的值。