题目## 题目## 题目
#描述
这是一篇针对初学者的题解,共用2种方法解决。
知识点:单链表
难度:一星
#题解
##方法一:构造链表
如果此类型的题出现在笔试中,如果内存要求不高,可以采用如下方法:
可以先用一个vector将单链表的指针都存起来,然后再构造链表。
此方法简单易懂,代码好些。
###代码:
class Solution {
public:
ListNode* ReverseList(ListNode* pHead) {
if (!pHead) return nullptr;
vector<listnode*> v;
while (pHead) {
v.push_back(pHead);
pHead = pHead->next;
}
reverse(v.begin(), v.end()); // 反转vector,也可以逆向遍历
ListNode *head = v[0];
ListNode *cur = head;
for (int i=1; i<v.size(); ++i) { 构造链表 cur->next = v[i]; // 当前节点的下一个指针指向下一个节点
cur = cur->next; // 当前节点后移
}
cur->next = nullptr; // 切记最后一个节点的下一个指针指向nullptr
return head;
}
};
时间复杂度:O(n)
空间复杂度:O(n), 用了一个vector来存单链表
##方法二:正规解法
但是面试的时候,上一种解法当然不行。此题想考察的是:如何调整链表指针,来达到反转链表的目的。
初始化:3个指针
1)pre指针指向已经反转好的链表的最后一个节点,最开始没有反转,所以指向nullptr
2)cur指针指向待反转链表的第一个节点,最开始第一个节点待反转,所以指向head
3)nex指针指向待反转链表的第二个节点,目的是保存链表,因为cur改变指向后,后面的链表则失效了,所以需要保存
接下来,循环执行以下三个操作
1)nex = cur->next, 保存作用
2)cur->next = pre 未反转链表的第一个节点的下个指针指向已反转链表的最后一个节点
3)pre = cur, cur = nex; 指针后移,操作下一个未反转链表的第一个节点
循环条件,当然是cur != nullptr
循环结束后,cur当然为nullptr,所以返回pre,即为反转后的头结点
这里以1->2->3->4->5 举例:




中间都是重复步骤,省略了。。。
###代码
class Solution {
public:
ListNode* ReverseList(ListNode* pHead) {
ListNode *pre = nullptr;
ListNode *cur = pHead;
ListNode *nex = nullptr; // 这里可以指向nullptr,循环里面要重新指向
while (cur) {
nex = cur->next;
cur->next = pre;
pre = cur;
cur = nex;
}
return pre;
}
};
时间复杂度:O(n), 遍历一次链表
空间复杂度:O(1)</v.size();></listnode*>
解题思路
-
使用三个指针:
- prev:指向前一个节点
- curr:指向当前节点
- next:指向下一个节点
-
反转步骤:
- 保存当前节点的下一个节点(next = curr.next)
- 反转当前节点的指针(curr.next = prev)
- 移动prev和curr指针
- prev = curr
- curr = next
-
特殊情况处理:
- 空链表:直接返回null
- 单节点链表:直接返回该节点
代码
class Solution {
public:
ListNode* ReverseList(ListNode* pHead) {
// 如果链表为空或只有一个节点,直接返回
if (!pHead || !pHead->next) {
return pHead;
}
ListNode* prev = nullptr;
ListNode* curr = pHead;
ListNode* next = nullptr;
while (curr) {
// 保存下一个节点
next = curr->next;
// 反转当前节点的指针
curr->next = prev;
// 移动prev和curr指针
prev = curr;
curr = next;
}
return prev;
}
};
public class Solution {
public ListNode ReverseList(ListNode head) {
// 如果链表为空或只有一个节点,直接返回
if (head == null || head.next == null) {
return head;
}
ListNode prev = null;
ListNode curr = head;
ListNode next = null;
while (curr != null) {
// 保存下一个节点
next = curr.next;
// 反转当前节点的指针
curr.next = prev;
// 移动prev和curr指针
prev = curr;
curr = next;
}
return prev;
}
}
class Solution:
def ReverseList(self, head):
# 如果链表为空或只有一个节点,直接返回
if not head or not head.next:
return head
prev = None
curr = head
while curr:
# 保存下一个节点
next = curr.next
# 反转当前节点的指针
curr.next = prev
# 移动prev和curr指针
prev = curr
curr = next
return prev
算法及复杂度分析
-
算法:链表的基本操作
-
时间复杂度: O ( n ) \mathcal{O}(n) O(n)
- 只需要遍历一次链表
- n n n 是链表的长度
-
空间复杂度: O ( 1 ) \mathcal{O}(1) O(1)
- 只使用了三个指针变量
- 不需要额外的存储空间
这个解法的优点是:
- 实现简单直观
- 空间复杂度为 O ( 1 ) \mathcal{O}(1) O(1)
- 只需要一次遍历
- 不需要额外的数据结构
题目的主要信息:
- 输入一个只包含小写字母的字符串
- 输出该字符串反转后的字符串
举一反三:
学习完本题的思路你可以解决如下题目:
方法一:双指针交换(推荐使用)
知识点:双指针
双指针指的是在遍历对象的过程中,不是普通的使用单个指针进行访问,而是使用两个指针(特殊情况甚至可以多个),两个指针或是同方向访问两个链表、或是同方向访问一个链表(快慢指针)、或是相反方向扫描(对撞指针),从而达到我们需要的目的。
思路:
字符串反转即逆序,前后顺序是反的,也就是前面的字符换到了后面,后面的字符换到了前面,那既然这样我们就将前后的顺序依次对称交换,这时候就需要使用到了对撞双指针,从前后同时遍历。
具体做法:
- step 1:准备两个指针,从字符串一首一尾同时出发。
- step 2:每次交换二者指向的字符,直到二者相遇,这样刚好可以将字符串首尾交换,完成反转。
图示:
Java代码实现:
import java.util.*;
public class Solution {
public String solve (String str) {
//左右双指针
char[] s = str.toCharArray();
int left = 0;
int right = str.length() - 1;
//两指针往中间靠
while(left < right){
char c = s[left];
//交换位置
s[left] = s[right];
s[right] = c;
left++;
right--;
}
return new String(s);
}
}
C++代码实现:
class Solution {
public:
string solve(string str) {
//左右双指针
int left = 0;
int right = str.length() - 1;
//两指针往中间靠
while(left < right){
//交换两边字符
swap(str[left], str[right]);
left++;
right--;
}
return str;
}
};
Python实现代码:
class Solution:
def solve(self , str: str) -> str:
#左右双指针
left = 0
right = len(str) - 1
#两指针往中间靠
while left < right:
l_s = list(str)
temp = l_s[left]
l_s[left] = l_s[right]
#交换两边字符
l_s[right] = temp
str = ''.join(l_s)
left += 1
right -= 1
return str
复杂度分析:
- 时间复杂度: O ( n ) O(n) O(n), n n n为字符串长度,一共循环 n / 2 n/2 n/2次
- 空间复杂度: O ( 1 ) O(1) O(1),常数级变量,没有使用额外辅助空间
方法二:逆序拼接(扩展思路)
思路:
方法一是在原串上面操作,如果原串不能动的情况下,我们可以新开辟一个串,逆序遍历原串,将结果拼接就好。
具体做法:
- step 1:我们可以从后往前遍历原始字符串。
- step 2:准备一个空串依次在其前面添加遍历到的字符,新串就是逆序字符串。
Java代码实现:
import java.util.*;
public class Solution {
public String solve (String str) {
//从一个空串开始
String output = "";
//逆序遍历字符串
for(int i = str.length() - 1; i >= 0; i--)
//将字符加到新串后面
output += str.charAt(i);
return output;
}
}
C++代码实现:
class Solution {
public:
string solve(string str) {
//从一个空串开始
string output = "";
//逆序遍历字符串
for(int i = str.length() - 1; i >= 0; i--)
//将字符加到新串后面
output += str[i];
return output;
}
};
Python实现代码:
class Solution:
def solve(self , str: str) -> str:
#从一个空串开始
output = ""
i = len(str) - 1
#逆序遍历字符串
while i >= 0 :
#将字符加到新串后面
output += str[i]
i -= 1
return output
复杂度分析:
- 时间复杂度: O ( n ) O(n) O(n), n n n为字符串的长度,一次遍历
- 空间复杂度: O ( n ) O(n) O(n),output记录新串,长度等于原串