今天做了两道非常有意思的题目,leetcode上的206和234。这两道题目都是和链表有关的,并且都会运用的reverse list这个思想。
pro(206): Reverse a singly linked list.
My solution1:
如果是数组实现回非常简单,但是linkedlist如何实现?
第一个想法是,新建一个数组,遍历linkedlist中的所有元素,并记录在数组中。再遍历一次,此时修改在linked list中的每一个元素的值. 这个方法需要三次遍历.
public class Solution {
public ListNode reverseList(ListNode head) {
if(head == null) return null;
//第一次遍历,是用来计算链表中有多少个元素,即n
ListNode Num = head;
int n = 1;
while(Num.next != null){ //判断链表是否到头
Num = Num.next;
n++;
}
//第二次遍历,是用来在数组中纪录链表中的元素
ListNode Record = head;
int[] Val = new int[n];
for(int i = 0; i < n; i++){
Val[i] = Record.val;
Record = Record.next;
}
//第三次遍历,是用来反转的
ListNode Reverse = head;
for(int j = n-1; j >= 0; j--){
Reverse.val = Val[j] ;
Reverse = Reverse.next;
}
return head;
}
}
这是完全利用数组,那么如何治只利用链表呢?
My solution(2): 迭代
如果是双链表,进行reverse是非常简单的,因此我们可以借鉴双链表的方法来反转链表。这里我们需要三个node, pre , cur, next 分别代表当前,之前和之后的node.
public class Solution {
public ListNode reverseList(ListNode head) {
if(head == null || head.next == null) return head;
ListNode pre = null;
ListNode next = null;
while(head != null){
next = head.next;
head.next = pre;
pre = head;
head = next;
}
return pre;
}
}
My solution(3): 递归
利用递归!
public class Solution {
public ListNode reverseList(ListNode head) {
if(head == null || head.next == null) return head;
ListNode cur = head.next;
ListNode res = reverseList(cur); //res是得到head之后的反转
head.next = null;
cur.next = head;
return res;
}
}
接下来我们看看leetcode的234题。
Pro:
Given a singly linked list, determine if it is a palindrome.
Follow up:Could you do it in O(n) time and O(1) space?
My solution(1):
建立了一个数组,如果是回行文,顺着的和逆的应该相等。
public class Solution {
public boolean isPalindrome(ListNode head) {
if(head == null || head.next == null) return true;
ListNode countNum = head;
int count = 1;
while(countNum.next != null){
countNum = countNum.next;
count++;
}
ListNode copyVal = head;
int[] copy = new int[count];
for(int j = 0; j < count; j++){
copy[j] = copyVal.val;
copyVal = copyVal.next;
}
int i = count-1;
while(i >= 0){
if(copy[i] != head.val) return false;
head = head.next;
i--;
}
//这里可以稍微改进一下
/*
int i = 0;
while(i < count/2){
if(copy[i] != copy[count-1-i]) return false;
i++;
}
*/
return true;
}
}
但是,这其实是不符合题目要求的,因为题目要求Could you do it in O(n) time and O(1) space?,因为建立了一个数组,空间复杂度是 O(n).
My solution(2):
这是我想出来的第二个方法,但是是错误的!源于我对链表没有更深刻的理解。我先上代码,大家可以看一下!
public class Solution {
public boolean isPalindrome(ListNode head) {
if(head == null || head.next == null) return true;
ListNode cur = head;
ListNode reverse = reverseNode(cur);
while(head.next != null){
if(head.val != reverse.val) return false;
head = head.next;
reverse = reverse.next;
}
if(head.val != reverse.val) return false;
else{
return true;
}
}
private ListNode reverseNode(ListNode cur){
if(cur == null || cur.next == null ) return cur;
ListNode next = cur.next;
ListNode res = reverseNode(next);
cur.next = null;
next.next = cur;
return res;
}
}
高手肯定立马看出来了!!我至始至终都是在对这一个链表操作。比如,刚开始链表时[1,2,3,1], head指着第一个元素,reverse变成[1,3,2,1], head指向最后一个元素,reverse指向第一个元素.....所以不对。
My solution(3):
To think about :Could you do it in O(n) time and O(1) space?如何不使用额外的空间,在链表本身上操作,来判断是否相等
反转链表法,链表前半段原地翻转,再将前半段和后半段依次比较,判断是否相等。
public class Solution {
public boolean isPalindrome(ListNode head) {
if(head == null || head.next == null) return true;
ListNode countNum = head;
int count = 0;
while(countNum != null){
countNum = countNum.next;
count++;
}
int middle = count/2;
ListNode cur = head;
ListNode pre = null;
ListNode next = null;
for(int i = 0; i < middle; i++){
next = cur.next;
cur.next = pre;
pre = cur;
cur = next;
}
if(count% 2 == 1){
cur = cur.next;
}
for(int j = 0; j < middle; j++){
if(pre.val != cur.val) return false;
else{
pre = pre.next;
cur = cur.next;
}
}
return true;
}
}
我对这个解决方案不太满意,因为感觉还是用了跟数组有关的东西,并没有直接在链表上下手,比如如何得到中间的node? 我们需要知道整个链表中的元素,然后在判断中间的元素在哪里。
My solution(4):
先看一下代码,然后在一一分析。
public class Solution {
public boolean isPalindrome(ListNode head) {
if(head == null || head.next == null) return true;
ListNode fast = head;
ListNode slow = head;
while(fast != null && fast.next != null){
fast = fast.next.next;
slow = slow.next;
}
if(fast != null){
slow = slow.next;
}
slow = reverse(slow);
fast = head;
while(slow != null){
if(slow.val != fast.val) return false;
slow = slow.next;
fast = fast.next;
}
return true;
}
private ListNode reverse(ListNode rev){
ListNode pre = null;
ListNode next = null;
while(rev != null){
next = rev.next;
rev.next = pre;
pre = rev;
rev = next;
}
return pre;
}
}
在这里说明一下:
1 怎么样确定slow已经到来的middle的位置?
while(fast != null && fast.next != null){
//为什么要判断fast.next, 如果fast.next = null, fast.next.next就没有意义了。
fast = fast.next.next;
slow = slow.next;
}
2 我们会发现,链表中元素可能为奇数个也可能为偶数个
如果是偶数个:
1221 > 1 2 2 1 null
(s) (f)
我们把slow之后的反转,就变成两个链表
null < 2 < 1
(s 头指针)
1 > 2
(head 头指针)
如果是奇数个:
12321 > 1 2 3 2 1
(s) (f)
此时如果把slow之后的反转,会变成:
3 < 2< 1
(s)
和 1 > 2
(h)
这样就没办法比较head.val 和s.val
如果加上这个:
if(fast != null){
slow = slow.next;
}
如果是奇数个:
12321 > 1 2 3 2 1
(s)
此时如果把slow之后的反转,会变成:
2< 1
(s)
和 1 > 2
(h)