算法---链表

一:总述

面试时链表解题的方法论:

  1. 对于笔试,不用太在乎空间复杂度,一切为了时间复杂度;
  2. 对于面试,时间复杂度依然放在第一位,但是一定要找到空间最省的方法。

对于笔试,追求以最快的速度解决问题;对于面试,则追求问题的最优解。

重要技巧:

  1. 额外数据结构记录(哈希表,链表,集合等);
  2. 快慢指针。

二:删除链表中重复的节点

1.题目

在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表 1->2->3->3->4->4->5 处理后为 1->2->5

2.题解

2.1 思路分析

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

2.2 具体代码

/*
 public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}
*/
public class Solution {
    public ListNode deleteDuplication(ListNode pHead) {
        if (pHead == null) {
            return pHead;
        }
        ListNode head = new ListNode(-1);
        head.next = pHead;

        ListNode prev = head;
        ListNode last = prev.next;
        while (last != null) {
            //1.找重复节点的开始
            while (last.next != null && last.val != last.next.val) {
                prev = prev.next;
                last = last.next;
            }
            //2.找到了重复的开始
            while (last.next != null && last.val == last.next.val) {
                last = last.next;
            }
            //3.走到这里,一共三种情况
                //1. last.next != null 并且 (prev, last] 限定了一段重复范围,此时进行去重,prev.next = last.next
                //2. last.next == null 并且 (prev, last] 限定了一段重复范围,此时进行去重,prev.next = last.next
                //3. last.next == null 并且 prev.next == last,这说明,从本次循环开始,都不相同,就不需要进行去重,这个是特殊情况
            if (prev.next != last) { //找到了一段范围,可以去重
                prev.next = last.next;
            }
            last = last.next;//走这一步,就是为了保证恢复的和最开始一致
        }
        return head.next;
    }
}

三:判断一个链表是否为回文结构

1.题目

给定一个链表,请判断该链表是否为回文结构。

2.题解

第一种方法,也就是最简单方法,可以借助栈或者顺序表。遍历单链表,将单链表的每一个节点存储到栈或者顺序表中。如果存储到栈,就再次遍历单链表,并将节点依次出栈,判断是否相同;如果存储到顺序表,就分别从首尾两端遍历顺序表,判断每两个节点是否相同。具体代码如下:

import java.util.*;
public class Solution {
    public boolean isPail (ListNode head) {
        ArrayList<Integer> nums = new ArrayList();
        //将链表元素取出一次放入数组
        while (head != null) {
            nums.add(head.val);
            head = head.next;
        }
        //双指针指向首尾
        int left = 0;
        int right = nums.size() - 1;
        //分别从首尾遍历,代表正序和逆序
        while (left <= right) {
            int x = nums.get(left);
            int y = nums.get(right);
            //如果不一致就是不为回文
            if (x != y)
                return false;
            left++;
            right--;
        }
        return true;
    }
}

第二种方法,快慢指针法。当快指针走到单链表末尾时,慢指针走到了单链表的中间位置。此时,将慢指针后面的节点依次入栈。然后,从单链表表头开始,依次与栈中弹出的元素进行比对,直到栈为空。相比于方法一,这种方法可以节省一半的空间。代码如下:

import java.util.*;

/*
 * public class ListNode {
 *   int val;
 *   ListNode next = null;
 * }
 */

public class Solution {
    /**
     *
     * @param head ListNode类 the head
     * @return bool布尔型
     */
    public boolean isPail (ListNode head) {
        // write code here
        if (head == null || head.next == null) {
            return true;
        }
        ListNode right = head.next;
        ListNode cur = head;
        while (cur.next != null && cur.next.next != null) {
            right = right.next;
            cur = cur.next.next;
        }
        Stack<ListNode> stack = new Stack<ListNode>();
        while (right != null) {
            stack.push(right);
            right = right.next;
        }
        while (!stack.isEmpty()) {
            if (head.val != stack.pop().val) {
                return false;
            }
            head = head.next;
        }
        return true;
    }
}

第三种方法:

在这里插入图片描述

思路不算难,当然代码量就比较大了,但这种方法确实没有开始额外的空间,只用几个变量就完成了题目。代码如下:

import java.util.*;

/*
 * public class ListNode {
 *   int val;
 *   ListNode next = null;
 * }
 */

public class Solution {
    /**
     *
     * @param head ListNode类 the head
     * @return bool布尔型
     */
    public boolean isPail (ListNode head) {
        if (head == null || head.next == null) {
            return true;
        }
        ListNode n1 = head;
        ListNode n2 = head;
        while (n2.next != null && n2.next.next != null) { // find mid node
            n1 = n1.next; // n1 -> mid
            n2 = n2.next.next; // n2 -> end
        }
        n2 = n1.next; // n2 -> right part first node
        n1.next = null; // mid.next -> null
        ListNode n3 = null;
        while (n2 != null) { // right part convert
            n3 = n2.next; // n3 -> save next node
            n2.next = n1; // next of right node convert
            n1 = n2; // n1 move
            n2 = n3; // n2 move
        }
        n3 = n1; // n3 -> save last node
        n2 = head;// n2 -> left first node
        boolean res = true;
        while (n1 != null && n2 != null) { // check palindrome
            if (n1.val != n2.val) {
                res = false;
                break;
            }
            n1 = n1.next; // left to mid
            n2 = n2.next; // right to mid
        }
        n1 = n3.next;
        n3.next = null;
        while (n1 != null) { // recover list
            n2 = n1.next;
            n1.next = n3;
            n3 = n1;
            n1 = n2;
        }
        return res;
    }
}

要想比较容易滴看懂这部分代码,要求对于单链表的基本操作烂熟于心,比如反转一个单链表。

四:划分单链表

1. 题目

给定一个单链表的头节点head,节点的值类型是整型,再给定一个整数pivot。实现一个调整链表的函数,将链表调整为左部分都是值小于pivot的
节点,中间部分都是值等于pivot的节点,右部分都是值大于pivot的节点.
【进阶】在实现原问题功能的基础上增加如下的要求
【要求】调整后所有小于pivot的节点之间的相对顺序和调整前一样
【要求】调整后所有等于pivot的节点之间的相对顺序和调整前一样
【要求】调整后所有大于pivot的节点之间的相对顺序和调整前一样
【要求】时间复杂度请达到O(N),额外空间复杂度请达到O(1)。

2. 题解

最简单的一种方法,把每一个链表放到数组里,在数组里排好序,然后再串起来就行了。

package LinkedList_Basic_Class04;

public class Code05_SmallerEqualBigger {

	public static class Node {
		public int value;
		public Node next;

		public Node(int data) {
			this.value = data;
		}
	}

	public static Node listPartition1(Node head, int pivot) {
		if (head == null) {
			return head;
		}
		Node cur = head;
		int i = 0;
		while (cur != null) {
			i++;
			cur = cur.next;
		}
		Node[] nodeArr = new Node[i];
		i = 0;
		cur = head;
		for (i = 0; i != nodeArr.length; i++) {
			nodeArr[i] = cur;
			cur = cur.next;
		}
		arrPartition(nodeArr, pivot);
		for (i = 1; i != nodeArr.length; i++) {
			nodeArr[i - 1].next = nodeArr[i];
		}
		nodeArr[i - 1].next = null;
		return nodeArr[0];
	}

	public static void arrPartition(Node[] nodeArr, int pivot) {
		int small = -1;
		int big = nodeArr.length;
		int index = 0;
		while (index != big) {
			if (nodeArr[index].value < pivot) {
				swap(nodeArr, ++small, index++);
			} else if (nodeArr[index].value == pivot) {
				index++;
			} else {
				swap(nodeArr, --big, index);
			}
		}
	}

	public static void swap(Node[] nodeArr, int a, int b) {
		Node tmp = nodeArr[a];
		nodeArr[a] = nodeArr[b];
		nodeArr[b] = tmp;
	}

	public static void printLinkedList(Node node) {
		System.out.print("Linked List: ");
		while (node != null) {
			System.out.print(node.value + " ");
			node = node.next;
		}
		System.out.println();
	}

	public static void main(String[] args) {
		Node head1 = new Node(7);
		head1.next = new Node(9);
		head1.next.next = new Node(1);
		head1.next.next.next = new Node(8);
		head1.next.next.next.next = new Node(5);
		head1.next.next.next.next.next = new Node(2);
		head1.next.next.next.next.next.next = new Node(5);
		printLinkedList(head1);
		head1 = listPartition1(head1, 5);
		printLinkedList(head1);

	}

}

在这里插入图片描述

方法二:使用六个变量,从头开始遍历整个链表。需要六个变量,小于部分的头和尾,等于部分的头和尾,大于部分的头和尾,一开始所有变量都为空,然后依次将不同区间的节点串起来,最后再整体串起来。以pivot=5为例。

在这里插入图片描述

在进行最后连接时,需要进行充分的考虑。也许整个链表都没有小于5的节点,或者整个链表全是大于5的节点,所以一定要注意考虑全面连接点的情况。

package LinkedList_Basic_Class04;

public class Code05_SmallerEqualBigger {

	public static class Node {
		public int value;
		public Node next;

		public Node(int data) {
			this.value = data;
		}
	}

	public static Node listPartition2(Node head, int pivot) {
		Node sH = null; // small head
		Node sT = null; // small tail
		Node eH = null; // equal head
		Node eT = null; // equal tail
		Node bH = null; // big head
		Node bT = null; // big tail
		Node next = null; // save next node
		// every node distributed to three lists
		while (head != null) {
			next = head.next;
			head.next = null;
			if (head.value < pivot) {
				if (sH == null) {
					sH = head;
					sT = head;
				} else {
					sT.next = head;
					sT = head;
				}
			} else if (head.value == pivot) {
				if (eH == null) {
					eH = head;
					eT = head;
				} else {
					eT.next = head;
					eT = head;
				}
			} else {
				if (bH == null) {
					bH = head;
					bT = head;
				} else {
					bT.next = head;
					bT = head;
				}
			}
			head = next;
		}
		// small and equal reconnect
		if (sT != null) {
			sT.next = eH;
			eT = eT == null ? sT : eT;
		}
		// all reconnect
		if (eT != null) {
			eT.next = bH;
		}
		return sH != null ? sH : eH != null ? eH : bH;
	}

	public static void printLinkedList(Node node) {
		System.out.print("Linked List: ");
		while (node != null) {
			System.out.print(node.value + " ");
			node = node.next;
		}
		System.out.println();
	}

	public static void main(String[] args) {
		Node head1 = new Node(7);
		head1.next = new Node(9);
		head1.next.next = new Node(1);
		head1.next.next.next = new Node(8);
		head1.next.next.next.next = new Node(5);
		head1.next.next.next.next.next = new Node(2);
		head1.next.next.next.next.next.next = new Node(5);
		printLinkedList(head1);
	    head1 = listPartition2(head1, 5);
		printLinkedList(head1);

	}

}

最后的连接点处的判断,浓缩为了两个if语句:

在这里插入图片描述

五 : 链表指定区间内反转

5.1 题目

在这里插入图片描述

5.2 题解

import java.util.*;

/*
 * public class ListNode {
 *   int val;
 *   ListNode next = null;
 *   public ListNode(int val) {
 *     this.val = val;
 *   }
 * }
 */

public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 
     * @param head ListNode类 
     * @param m int整型 
     * @param n int整型 
     * @return ListNode类
     */
    public ListNode reverseBetween (ListNode head, int m, int n) {
        // write code here
        ListNode pHead = new ListNode(-1);
        pHead.next = head;
        ListNode pre = pHead;
        for(int i=0; i< m-1;i++) {
            pre = pre.next;
        }
        ListNode cur = pre.next;
        ListNode curNext = new ListNode(-1);
        for(int i=0; i< n-m; i++) {
            curNext = cur.next;
            cur.next = curNext.next;
            curNext.next = pre.next;
            pre.next = curNext;
        }
        return pHead.next;
    }
}

这道题目使用了两种思想 , 一种是普遍使用于链表类题目的 , 即设置虚拟头结点 ; 另外一种则是抽书法 .

在这里插入图片描述

按照这种"抽书法"的思想 , 我们试着对下面链表的指定区间进行反转 .

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述


本文到此结束 !

请添加图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值