判断回文链表(剑指offer.027)

目录

  -数组法-

  -递归法-

 -快慢指针-


-题目-

/**
 * 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;
}

         时间复杂度与空间复杂度的控制是算法的精髓,上面三个方法有不同的时间,空间复杂度,因为博主这方面了解尚浅,故不在这里具体分析。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值