目录
-题目-
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
bool isPalindrome(struct ListNode* head) {
}
(注意:这题的条件为不带头结点的单链表,head就是链表的第一个结点)
(这里函数返回类型bool使用方法是直接返回false或者true。)
看到题目,博主第一想法就是头插法再建立一个链表,遍历两个链表就能得出该链表是否是回文的。但这个思路实现起来容易出错,以至于我改了n遍都没改对= =。
下面记录三个力扣官方给的三个思路(图片来源也是力扣官方)
-数组法-
将链表的值依次放进数组,再判断数组里的数是不是回文序列。
bool isPalindrome(struct ListNode* head){ struct ListNode *p; int a[100000], i = 0; p = head; while(p){ a[i++] = p -> val; p = p -> next; } int l = 0, r = i - 1; while(1){ if(l >= r){ break; } if(a[l] != a [r]){ return false; } l ++; r --; } return true; }
-递归法-
基本思路是创建两个指针:frontNode从前面开始遍历链表,currentNode从后面开始遍历链表。这个方法的思路不难理解,复杂的是currentNode通过递归调用函数来从链表尾开始往前遍历。
struct ListNode* frontNode; bool recursivelyCheck(struct ListNode* currentNode) { if (currentNode != NULL) { if (!recursivelyCheck(currentNode -> next)) { return false; } if (currentNode -> val != frontNode -> val) { return false; } frontNode = frontNode -> next; } return true; } bool isPalindrome(struct ListNode* head) { frontNode = head; return recursivelyCheck(head); }
这里介绍一下currentNode实现其功能的过程
这个过程涉及到栈,但是不用学到栈也能看懂,只需要知道栈是后进先出就行。
假设该链表存有五个数据5-6-1-7-5(不为回文序列),从主函数调用recursivelyCheck(head)开始,这一步将currentNode指向#0(这里用'#数字'来表示第几个结点)。currentNode不为null所以执行if,计算机确定了currentNode的下一个结点为#1,便将这个结点的值传入递归函数。(在调用函数前,计算机会将这个信息在栈中记录他在哪里)接下来再调用函数recursivelyCheck(#1)将currenyNode指向#1,同样的currentNode不为null所以执行if,计算机确定了currentNode的下一个结点为#2,再将其传入递归函数,再在栈中记录。再调用函数...
-信息在栈中存储的模式如下图-
(从recursivelyCheck(head)这一步开始入栈)
递归调用进行到recursivelyCheck(#4)时,currentNode仍不为null所以执行if,再下一步时递归进行到recursivelyCheck(NULL),因为链表里共有五个数,#4 -> next就是空指针NULL。此时if不执行,直接返回true给上一级。
这一级currentNode指向#4,!true为假故不返回flase,执行currentNode指向#4为5,frontPointer指向#0为5,两者相等故不返回false,然后frontPointer后移一个结点,来到最后一行代码,返回true给上一级。栈顶的信息移除 (如图,原本最顶端的信息移除)。
这一级currentNode指向#3,同样不返回false,执行currentNode指向#3为7,frontPointer指向#1为6,两者不相等故在这一步直接返回false给上一级。(可以知道若每一次currentNode和frontPointer都相等的话最后会返回true给原函数。)
这一级currentNode指向#2, !false为真故直接返回false给上一级。从这以后就不用考虑两个指针的值了,因为false会被一层层返回给原函数。
-快慢指针-
基本思路是将链表的后半部分反转,再比较前后两部分是否相同。
当然可以遍历链表得到链表长度,再取中间结点,但是这里使用了快慢指针的奇特方法。即创建快指针和慢指针,初始都指向head,慢指针每次移动一个结点,快指针每次移动两个结点直到快指针的下一次移动指向NULL,此时慢指针指向的结点就是中间结点。若链表结点为奇数个,易得中心结点不影响后续结果。
struct ListNode* reverseList(struct ListNode* head) { struct ListNode* prev = NULL; struct ListNode* curr = head; while (curr != NULL) { struct ListNode* nextTemp = curr->next; curr->next = prev; prev = curr; curr = nextTemp; } return prev; }//三指针法反转链表 struct ListNode* endOfFirstHalf(struct ListNode* head) { struct ListNode* fast = head; struct ListNode* slow = head; while (fast->next != NULL && fast->next->next != NULL) { fast = fast->next->next; slow = slow->next; } return slow; }//使用快慢指针找到链表的中心位置 bool isPalindrome(struct ListNode* head) { if (head == NULL) { return true; } // 找到前半部分链表的尾节点并反转后半部分链表 struct ListNode* firstHalfEnd = endOfFirstHalf(head); struct ListNode* secondHalfStart = reverseList(firstHalfEnd->next); // 判断是否回文 struct ListNode* p1 = head; struct ListNode* p2 = secondHalfStart; bool result = true; while (result && p2 != NULL) { if (p1->val != p2->val) { result = false; } p1 = p1->next; p2 = p2->next; } // 还原链表并返回结果 firstHalfEnd->next = reverseList(secondHalfStart); return result; }
时间复杂度与空间复杂度的控制是算法的精髓,上面三个方法有不同的时间,空间复杂度,因为博主这方面了解尚浅,故不在这里具体分析。