数据结构与算法---链表---回文链表

回文链表

234.回文链表

请判断一个链表是否为回文链表。

示例 1:

输入: 1->2
输出: false

示例 2:

输入: 1->2->2->1
输出: true

进阶:
你能否用 O(n) 时间复杂度和 O(1) 空间复杂度解决此题?


思路1

建立两个指针,left和right,分别指向链表的头和尾
判断left.vlaue和right.value是否相等,相等则left = left.next,right = ?
问题来了,right的前一个指针找不到了
因此,不满足要求

但是,可以将链表放于数组中,然后就可以利用left和right指针,遍历数组了

class Solution {
    public boolean isPalindrome(ListNode head) {
        //边界条件
        if(head == null) return false;
        if(head.next == null) return true;
		
		//获取链表长度,其实用可变数组更好
        int count = 0;
        ListNode tempNode = head;
        while(tempNode != null){
            count++;
            tempNode = tempNode.next;
        }
		
		//创建数组
        int[] array = new int[count];
        tempNode = head;
        int count2 = 0;
        //链表->数组赋值
        while(tempNode != null){
            array[count2] = tempNode.val;
            tempNode = tempNode.next;
            count2++;
        }

        int left = 0;
        int right = array.length - 1;
		
		//查找数组
        while(left <= right){
            if(array[left] == array[right]){
                left++;
                right--;
            }else{
                return false;
            }
        }
        return true;
    }
}

思路2

新建一个链表B,存储的内容是链表A的翻转
然后比较A和B的值即可。

class Solution {
    public boolean isPalindrome(ListNode head) {
        if(head == null) return true;
        if(head.next == null) return true;

        //不改变原来的头结点
        ListNode pHead = head;
        ListNode reverseHead = reverseList(pHead);

        while(head != null){
            if(head.val == reverseHead.val){
                head = head.next;
                reverseHead = reverseHead.next;
            }else{
                return false;
            }
        }
        return true;
    }

    public ListNode reverseList(ListNode head)
    {
        if(head == null || head.next == null) return head;
        
        ListNode newHead = reverseList(head.next);
        head.next.next = head;
        head.next = null;
        return newHead;
    }
}

该代码有点问题
[1, 1, 2, 1]的时候,返回的是true
其他[1,2] 或者 [1, 2, 3] 或者[1, 2, 1, 2, 7]都是返回正确值
目前还没看出问题

要是能打印下,看看链表节点就好了,但是,使用LeetCode没法打印看,那么,拿到Eclipse中调试下

经过调试,发现
在这里插入图片描述
翻转完之后,原来的链表被破坏
仔细考虑下,递归翻转链表的最后一步,就是将倒数第二个指针与第一个指针之间的相互转换。
也就是说,head本身指向的还是第一个结点,但是,head的next就是null了

为何[1, 2]是对的?
是因为:
原链表是1->2
翻转后是2->1
原链表被破坏为1->null
判断2 != 1,返回false

[1, 2, 1, 2, 7]
原链表是1->2->1->2->7
翻转后是7->2->1->2->1
原链表被破坏为1->null
判断1 != 3,返回false

[1, 1, 2, 1]
原链表是1->1->2->1
翻转后是1->2->1->1
原链表被破坏为1->null
判断1 == 1
下一步原链表为null
返回true

[1, 2, 2, 1]
原链表是1->2->2->1
翻转后是1->2->2->1
原链表被破坏为1->null
判断1 == 3
下一步原链表为null
返回ture
蒙对了

也就是:
如果需要判断的链表本身 是 翻转链表,这个程序判断的都是正确的

如果需要判断的链表本身不是翻转链表

  • 如果第一个与最后一个元素相等,则返回true,
  • 如果第一个元素与最后一个元素不相等,则返回false
package 链表;

public class 回文链表 {
	
	public static class ListNode {
		 int val;
		 ListNode next;
		 ListNode(int x) { val = x; }
		 public String toString() {
			return val + " -> " + next;
		}
	}
	
	public static void main(String[] args)
	{
		ListNode head = new ListNode(1);
		head.next = new ListNode(1);
		head.next.next = new ListNode(2);
		head.next.next.next = new ListNode(1);
		System.out.println("输入的链表:" + head);
		
		回文链表 hwlb = new 回文链表();
		System.out.println("是否为回文链表:" + hwlb.isPalindrome(head));
	}
	
	public boolean isPalindrome(ListNode head) {
        if(head == null) return true;
        if(head.next == null) return true;

        //不改变原来的头结点
        ListNode pHead = new ListNode(-1);
        System.out.println("翻转前的链表:" + pHead);
        ListNode reverseHead = reverseList(pHead);
        System.out.println("翻转后的链表:" + reverseHead);
        
        System.out.println("head:" + head);
        System.out.println("pHead:" + pHead);
        
        while(head != null){
            if(head.val == reverseHead.val){
                head = head.next;
                reverseHead = reverseHead.next;
            }else{
                return false;
            }
        }
        return true;
    }

    public ListNode reverseList(ListNode head)
    {
        if(head == null || head.next == null) return head;
        
        ListNode newHead = reverseList(head.next);
        head.next.next = head;
        head.next = null;
        return newHead;
    }
}

终于搞明白了自己程序问题在哪,在于翻转的时候,把原链表结构改变了

那么,改正程序,简单点,再翻转一遍
还是不行,
在这里插入图片描述还是自己跟自己玩

这么看来,只能是新建一个链表了

新建一个备份链表
	public ListNode copyList(ListNode head)
	{
		//新建一个备份链表
        ListNode dummyHead = new ListNode(-1);
        ListNode newHead = dummyHead;
        
        ListNode currentHead = head;
        while(currentHead != null)
        {
        	ListNode tempNode = new ListNode(currentHead.val);
        	newHead.next = tempNode;
        	newHead = tempNode;
        	currentHead = currentHead.next;
        }
        return dummyHead.next;
	}

输入的链表:1 -> 1 -> 2 -> 1 -> null
备份翻转前的链表:1 -> 1 -> 2 -> 1 -> null
翻转后的链表:1 -> 2 -> 1 -> 1 -> null
翻转前的链表:1 -> 1 -> 2 -> 1 -> null
是否为回文链表:false

终于解决问题!


思路3

找出中间节点
如果链表个数为奇数,1->2->1,那么中间节点很容易找出为2
如果链表个数为偶数,1->2->3->4,那么我们规定中间节点为2

这是因为:
在链表个数为奇数的时候,找到中间节点不需要对比(本身链表就是奇数,中间节点只有一个),需要开始比较的节点是 中间节点的下一个元素
在链表个数为偶数的时候,如果也想开始比较的是 中间节点的下一个元素,也就是从3开始,那么中间节点设为2,就可以满足需求。

假设现在链表是:1->2->3->2->1,那么该链表的中间节点mid是3
翻转3后面的节点,变为
左边的链表:1->2->3
右边的链表:1->2->null
两个链表做对比

相等则比较下一对
不相等则返回false
如果右边链表的下一个为null,说明比较完成,那么返回true

在这里插入图片描述
那么,如何找到中间节点呢?
借助快慢指针

class Solution {
    public boolean isPalindrome(ListNode head) {
        if(head == null) return true;//空链表
        if(head.next == null) return true;//只有一个节点
        if(head.next.next == null) return head.val == head.next.val;//只有两个节点

        ListNode midNode = midList(head);
        ListNode rightHead = reverseList(midNode.next);
        //不改变原来的头结点
        ListNode leftHead = head;

        while(rightHead != null){//注意是右边节点判断
            if(leftHead.val == rightHead.val){
                leftHead = leftHead.next;
                rightHead = rightHead.next;
            }else{
                return false;
            }
        }
        return true;
    }

    //找出中间节点
    public ListNode midList(ListNode head)
    {
        //快指针
        ListNode fastNode = head;
        //慢指针
        ListNode slowNode = head;
        while(fastNode.next != null && fastNode.next.next != null){
            slowNode = slowNode.next;
            fastNode = fastNode.next.next;
        }
        return slowNode;
    }

    //翻转链表
    public ListNode reverseList(ListNode head)
    {
        if(head == null || head.next == null) return head;
        ListNode newHead = null;

        while(head != null) 
        {
            ListNode tempNode = head.next;
            
            head.next = newHead;
            newHead = head;
            head = tempNode;
        }
        return newHead;
    }
}
如果要求原链表不能改变怎么办?

由于右边链表进行了翻转,破坏了原来链表的结构
只需要在return前,再次对右边的链表进行翻转即可

ListNode rightOldHead = rightHead;
也就是在两个return前,加上reverseList(rightOldHead)即可

在这里插入图片描述


在这里插入图片描述

居然找到了MJ大神PPT上截图的原地址,哈哈

### 数据结构算法——链表入门 链表是一种常见的线性数据结构,其特点是通过指针连接各个节点形成一条逻辑上的链条。相比于数组,链表的优点在于动态分配内存以及插入删除操作的时间复杂度较低[^4]。 #### 单链表基础 单链表是最简单的链表形式,其中每个节点包含两部分:数据域和指向下一个节点的指针。如果需要遍历整个链表,则可以从头节点依次访问到尾节点。需要注意的是,单链表不支持随机访问,因此查找特定元素可能需要 O(n) 的时间复杂度[^2]。 #### 循环链表特性 循环链表是另一种特殊类型的链表,它的最后一个节点不再指向 `null`,而是重新链接回到头部或其他指定位置。这种设计使得某些场景下可以更方便地处理边界情况,例如约瑟夫环问题或者模拟队列等应用场合。 #### 判断回文链表 对于给定的一个单向链表来说,要检测它是否构成回文序列可以通过快慢指针法找到中间点并反转后半部分来完成比较工作;当然也可以借助栈辅助实现这一功能[^3]。 以下是基于Python语言编写的简单版本用于验证上述理论: ```python class ListNode: def __init__(self, val=0, next=None): self.val = val self.next = next def isPalindrome(head: ListNode) -> bool: if not head or not head.next: return True slow = fast = head stack = [] while fast and fast.next: stack.append(slow.val) slow = slow.next fast = fast.next.next # 如果长度为奇数则跳过中心结点 if fast: slow = slow.next while slow: top = stack.pop() if top != slow.val: return False slow = slow.next return True ``` 此代码片段展示了如何利用堆栈原理配合双指针技巧高效解决判定回文性质的任务。 --- ### 动态规划简介及其应用场景 当面对较为复杂的优化类题目时,采用动态规划策略往往能够有效降低计算量。例如排列组合过程中遇到重复子串的情况可通过设置过滤机制避免冗余运算发生[^5]。 ```python from typing import List def permuteUnique(nums: List[int]) -> List[List[int]]: nums.sort() # 排序以便于后续剪枝操作 res = [] path = [] used = set() def backtrack(): if len(path) == len(nums): res.append(list(path)) return for i in range(len(nums)): if i in used or (i>0 and nums[i]==nums[i-1] and i-1 not in used): continue used.add(i) path.append(nums[i]) backtrack() path.pop() used.remove(i) backtrack() return res ``` 上面的例子说明了即使存在相同数值的情况下仍然能生成无重叠的不同全排列方案集。 ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值