java算法系列2 — 常见链表算法问题

给自己画的饼,哪怕撑死也得吃完!努力,努力,再努力!

今天开始挑战白银段位,先从链表算法下手(后续还会持续更新…)。主要问题有,找到单链表倒数第 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的数据域设置为待插入的值。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值