下面是对于自己参加门徒计划第一周的算法总结以及运用java书写的代码
算法很重要
根据题目判断可得是判断是否是环形链表在这里可以知道环形链表的原理是尾节点指向的下一个节点非空,那么如果遍历完所有的节点并对前面的节点进行相关的存储来查看节点的是否相等就可以知道是不是环形链表。那么在这里就可以使用set集合的不允许存放相同元素的特点实现对这一要求的判断,当节点进行遍历存储时不能正常的存储进去就可以说明这是一个环形链表,返之就不是环形链表。具体实现代码如下:
这个方法引用的是快慢指针的思想,慢指针每次都走一次,快指针每次走两次(这里可以通过一个环形跑道来理解一下,就是在跑步时一个跑的快的人在环形跑道上进行跑步与一个比他慢的人跑步,那么在某个时间点就一定会相遇当他们相遇的时候就可以说明这是一个环形链表{这里要理解一些,跑的快的与跑的慢的可能跑几圈才遇到都有可能,但是只要遇到就是环形})如果快的跑到了null那么则说明这个不是环形链表。
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public boolean hasCycle(ListNode head) {
//基础--方法一
Set<ListNode> set = new HashSet<>();
ListNode p = head;
while (p != null){
if(!set.add(p)){
return true;
}
p = p.next;
}
return false;
//进阶--方法二
if(head == null) return false;
ListNode pre = head, cur = head;
while (cur!=null && cur.next!=null){
//cur.next!=null对快的跑的第一步做非空判断防止空指针异常
pre = pre.next;
cur = cur.next.next;
if(pre==cur) return true;
}
return false;
}
}
使用同上141题的方法一的方法因为这是遍历一圈,那么则表示相遇点就是入环点,这样就可以直接返回相遇点即可。详细代码见下面方法一。
使用快慢指针,用这个方法的问题是知道他们是在哪里相遇的,但是相遇的时候是不是正好的入环点,那么直接通过上面的方法二返回就是不可信的。在这里我们可以通过画一个图来思考这样的一个过程,当快指针与慢指针相遇的时候就表示找到了相遇点,记住这里只是找到了一个相遇点不是入环点,那么入环点就会在相遇点之前或者之后,这个时候通过图可以计算得到一个等式–》起点到入环点的距离等于相遇点到入环点的距离,那么在找到相遇点后让快指针与慢指针同速(每次走一个)并且让一个指针先返回到起点,这个时候再一起走,那么在两个指正再次相遇的时候相遇的这个点就是入环点。(重点在于入环点只有在走一次的时候才会出现)。详细代码见下:
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public ListNode detectCycle(ListNode head) {
//基础--方法一
// ListNode p = head;
// HashSet set = new HashSet<>();
// while(p!=null){
// if(!set.add(p)){
// return p;
// }
// p = p.next;
// }
// return null;
//进阶--方法二
if(head == null) return null;
ListNode pre = head, cru = head;
while(cru!=null && cru.next!=null){
pre = pre.next;
cru = cru.next.next;
if(pre==cru){//相遇就表示有环,这里找到了一个入环点
cru = head;
while (cru!=pre){
cru = cru.next;
pre = pre.next;
}
return pre;
}
}
return null;
}
}
对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。
如果 可以变为 1,那么这个数就是快乐数。
如果 n 是快乐数就返回 true ;不是,则返回 false 。
链接:https://leetcode-cn.com/problems/happy-number
通过提目要求可以知道这是一个类似于链表的问题返回值有两种–当快乐数的这个完整的数据链表是一个环形链表时这里可以理解为这是一个计算过程是无限的过程,还有解释最后的结果第1的情况,这里就是表示这是一个单向链表。那么这题的重点是如何将这样的一个不知道的数转换为链表,这个类似于定义的一个类中类类似,这里的下一个数据可以通过自定义的一个函数完成,传入数据可以得到下一个节点。那么现在应该就知道这个链表的相关产生了。还是不懂的话可能是我没有讲明白,看代码比较清晰。这样的一个转化就演变为了一个链表的判断是不是环的问题。详细如下代码:
class Solution {
public boolean isHappy(int n) {
int pre = n;
int cur = getNext(n);
while(pre != cur && cur != 1){
pre = getNext(pre);
cur = getNext(getNext(cur));
}
return cur==1;
}
//这是自定义的一个函数
public int getNext(int n){
int res = 0;
while (n != 0){
int d = n%10;
res+=d*d;
n = n/10;
}
return res;
}
}
输入:19
输出:true
解释:
12 + 92 = 82
82 + 22 = 68
62 + 82 = 100
12 + 02 + 02 = 1
首先这是一个单向链表的反转,反转的重点是链表里面的元素反向指向1->2->3->4->5-null====》转变为5->4->3->2->1->null====这样就完成了链表的反转。在这里定义三个节点(pre–null cur–头节点 next–下一个节点)这样首先将反转的时候头节点的指向需要为空这里如果直接将这个节点指向为空的话就会导致整个链表消失,那么这个先用cur.next来存储头结点作为下一个节点,在将cur.next指向空(pre)这个时候完成了前面一个节点的转换,然后再进行下一个节点的转换pre指向cur,cur指向next。如此反复当cur节点的指向为空时就可以说明反转成功。具体代码如下:
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode reverseList(ListNode head) {
ListNode pre = null,cur = head,next = head;
while(cur!=null){
next = cur.next;
cur.next = pre;
pre = cur;
cur = next;
}
return pre;
}
}
题目的要求是输入一个链表并输入要反转的开始节点以及反转多少个节点。这个可以理解为一个删除和再插入的过程。删除的话是找到要开始的反转节点,对其后面需要进行反转的节点依次做删除插入的过程----这里的删除是依次的,在整个过程中前面的节点,也就是反转开始节点前面的节点以及反转开始节点(cur)都不做相关的改变。插入的时候插入进的是反转前一个节点(pre)的后一个节点(这里要记住,在整个过程中pre节点都是不做相应的改变的只有pre与cur之间的节点以及cur之后的节点在做相应的改变)图以及代码如下:
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode reverseBetween(ListNode head, int left, int right) {
ListNode hair = new ListNode(-1,head);//假定一个虚拟的头结点
ListNode pre = hair,cur = head;
for(int i = 0;i<left - 1;i++){
cur = cur.next;
pre = pre.next;
}
for(int i = 0;i<right - left;i++){
ListNode temp = cur.next;
cur.next = cur.next.next;
temp.next = pre.next;
pre.next = temp;
}
return hair.next;
}
}
这里的解题思路可以分为四步:
1、找到需要翻转的k节点(这里用双指针–找到这个翻转区域的头结点和尾结点)
2、对1中找到的这段链表进行翻转(这里的反转就是普通的单向链表的反转),反转之后返回翻转之后的头结点以及尾节点。
3、上面返回的头结点以及尾节点中,返回的翻转后的头节点就是新的链表的头节点,然后再对下一轮的链表进行同上的操作
4、每轮翻转后,都要将反转之后的链表段与原来的链表进行连接
***具体代码实现如下:重点在于对反转思想的转变,在反转之后下一次的反转其中一些节点的变化。
head头不动,tail向后走K,如果还没有到k就直接走完则返回链表。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode reverseKGroup(ListNode head, int k) {
ListNode hair = new ListNode(-1,head);
ListNode pre = hair;
ListNode tail = null;
while(head!=null){
tail = pre;
for(int i=0;i<k;i++){
tail = tail.next;
if(tail==null){
return hair.next;
}
}
ListNode[] revers = reverse(head,tail);
head = revers[0];
tail = revers[1];
pre.next = head;//这里是让返回的头结点成为原来链表的头结点(这是重点)
//下面两步为下一次翻转做准备
pre = tail;//尾节点从0开始(这里的开始节点就是翻转之后的尾节点[可以理解为虚拟节点])
head = pre.next;//前一次翻转的尾节点的下一个节点成为下一次要进行翻转的头结点
}
return hair.next;
}
//对得到的k段链表进行翻转并得到翻转后的头结点以及尾节点并返回--这个时候返回的尾节点就变为了原来的头结点上面用头结点接收
public ListNode[] reverse(ListNode head,ListNode tail){
ListNode pre = tail.next,cur = head,next = null;
while (pre != tail){
next = cur.next;
cur.next = pre;
pre = cur;
cur = next;
}
return new ListNode[]{tail,head};
}
}
这是一个单向链表,根据输入的数字实现链表的移动,但是这是旋转移动的。1–2--3–4--5移动2成为4–5--1–2--3。这样的移动方式,那么这个题的重点在于对于后面节点向前指向并不改变整个链表的长度,设想一下,如果这个链表是一个环形链表,那还移动个锤子,那是不是都不会发生一些其他的改变,并且找到我们移动的距离以及确定头结点的位置就可以直接通过断开这个环来实现链表的旋转了。
那么过程可以分为如下:
1、获取链表的长度,并将链表成环(尾节点指向头结点即可)
2、找到我们要断开的节点位置倒数第k个节点以及倒数第K+1个的节点
3、断开倒数第K个节点(就是正数第len-K个节点)
4、并返回倒数第k个节点
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode rotateRight(ListNode head, int k) {
if(head == null || head.next==null) return head;
int len = 1;
ListNode oldTail = head;//定义一个尾部节点从头节点开始出发
while(oldTail.next!=null){
oldTail = oldTail.next;
len++;
}
//将这个链表变成一个环形链表
oldTail.next=head;
ListNode newTail = head;
//通过循环找到需要断开的节点,这里的K%len是对移动的距离对链表的长度取余取余---这个时候的倒数第len-k个节点就是链表的尾节点
for(int i = 0;i<len-k%len-1;i++){
newTail = newTail.next;
}
//定义一个新的头结点,这个节点就是新的节点的头结点,也就是环形链表断开的第K个节点
ListNode newHead=newTail.next;
//将环形链表断开
newTail.next = null;
return newHead;
}
}
这是链表的两两交换,也就是隔两个交换一次。那么这个相比于其他不确定的链表交换就会显得简单一些。同样的方式,使用一个虚拟的头结点的指向下一个节点以及下下个节点来实现交换,交换完成后将这个虚拟节点的写一个节点指向为交换后的头结点,完成后将链表已经交换的第二个节点变为虚拟节点反复操作。具体代码如下:
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode swapPairs(ListNode head) {
ListNode hair = new ListNode(-1,head);
ListNode pre = hair;
while(pre.next!=null&&pre.next.next!=null){
ListNode one = pre.next;//第一个节点
ListNode two = pre.next.next;//第二个节点
//交换两个节点的位置
one.next = two.next;
two.next = one;
//将虚拟头结点的下一个节点指向头结点
pre.next = two;
//将交换的节点的第二个节点转变为虚拟节点
pre = one;
}
return hair.next;
}
}
删除链表的原理十分简单,只要找到链表中你要删除的节点以及前一个节点即可,在这个题中使用一次双指针用来查找需要删除的节点的位置。具体代码如下:
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode hair = new ListNode(-1,head);
ListNode p = head,q = hair;
while(n>0){
p=p.next;
n--;
}
while(p!=null){
p = p.next;
q = q.next;
}
q.next = q.next.next;
return hair.next;
}
}
这里还是关于链表的删除,因为这个是一个有序的链表,那么相同值的节点一定是相邻的,当值相同时就可以执行删除,当不同时就直接进入下一个节点。直到遇到null返回节点。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
//相同值的节点是相邻的
class Solution {
public ListNode deleteDuplicates(ListNode head) {
if(head == null) return null;
ListNode cur = head;
while(cur!=null&&cur.next!=null){
if(cur.val==cur.next.val){
cur.next = cur.next.next;
}else{
cur = cur.next;
}
}
return head;
}
}
这个题是删除所有的重复元素节点,在这里用到双指针,一个指针指向头结点,另一个指针比前一个指针移动较快,那么就可以比较两个指针对应的数据是否一致,一致则删除(全部删除)具体实现看如下代码:
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode deleteDuplicates(ListNode head) {
if(head==null) return null;
ListNode hair =new ListNode(-1,head);
ListNode pre = hair ,cur = head;
while(cur!=null && cur.next!=null){
if(cur.val!=cur.next.val){
pre = pre.next;
cur = cur.next;
}else{
while(cur!=null && cur.next!=null && cur.val==cur.next.val){
cur = cur.next;
}
pre.next = cur.next;
cur = cur.next;
}
}
return hair.next;
}
}
这个是7月7日学习的算法第一周的相关总结,是后期的总结,这里的代码是以及解题思路是根据后面使用自己打语言实现之后的相关理解,算法千千万重点在于逻辑思维的使用以及相关算法的使用数据结构方式的熟悉。希望自己能够在每次算法完成时都能够进行相关的刷题,天气太热了,有点烦躁,把自己总结的东西进行以下相关的搬运。