文章目录
链表
1.链表的逆置
当打算修改输入的数据时,最好问清面试官是否可以!!!
思路:
- 使用栈的结构,时间复杂度O(n),空间复杂度O(n)
- 递归,时间复杂度O(n),空间复杂度O(n),由于使用递归,将会使用隐式栈空间。递归深度可能会达到 n 层。
- 修改输入数据,逆置链表,时间复杂度O(n),空间复杂度O(1)
- 使用栈的结构
//栈
public ListNode reverseList(ListNode head) {
if (head == null) {
return null;
}
Stack<ListNode> st = new Stack<ListNode>();
ListNode curr = head;
while (curr != null) {
st.push(curr);
curr = curr.next;
}
ListNode prev = st.pop();
ListNode result = prev;
while (!st.isEmpty()) {
ListNode ptr = st.pop();
//拿出后先和置空,避免产生循环
ptr.next = null;
//改变指向即可
prev.next = ptr;
prev = ptr;
}
return result;
}
- 递归的方式
//递归
public ListNode reverseList(ListNode head) {
if(head == null || head.next == null) {
return head;
}
ListNode ptr = reverseList(head.next);
// 要使用 head 的 next.next 去逆置
head.next.next = head;
head.next = null;
return ptr;
}
- 迭代的方式(头插法嘛)
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode reverseList(ListNode head) {
if (head == null) {
return null;
}
ListNode prev, curr, nextPtr;
prev = null;
curr = head;
while (curr != null) {
//先保存下头插的下一个节点,不然就找不到链表了
nextPtr = curr.next;
//头插
curr.next = prev;
prev = curr;
//将需要头插的节点移动
curr = nextPtr;
}
return prev;
}
}
2.在O(1)时间内删除链表节点
请编写一个函数,使其可以删除某个链表中给定的(非末尾)节点,你将只被给定要求被删除的节点。
输入: head = [4,5,1,9], node = 5
输出: [4,1,9]
解释: 给定你链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9.
思路:OK。我们现在知道了需要删除的节点。那么我们把后面节点的内容覆盖到前面需要删除的节点,然后把后面的节点删除掉就行了呗。
时间复杂度O(1)
注意有几大坑点:
- 只有一个节点,删除节点位于头节点(也是尾节点)
- 删除节点位于尾节点
- 需要调用者确保要删除的节点在该链表中。
void DeleteNode(ListNode *head, ListNode *deListNode)
{
if (deListNode == nullptr || head == nullptr)
return;
//1.普通情况
if (deListNode->next)
{
ListNode *p = deListNode->next;
deListNode->val = deListNode->next->val;
deListNode->next = deListNode->next->next;
delete p;
p = nullptr;
}
//2.只有一个节点
else if (head == deListNode)
{
delete deListNode;
deListNode = nullptr;
head = nullptr;
}
//3.删除的节点是尾节点
else
{
ListNode *p = head;
while (p->next != deListNode)
{
p = p->next;
}
p->next = nullptr;
delete deListNode;
deListNode = nullptr;
}
}
3.链表中的倒数第k个节点
输入一个链表,输出该链表中倒数第k个结点。
思路:双指针思路。时间复杂度 O(n)
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};*/
class Solution
{
public:
ListNode *FindKthToTail(ListNode *head, unsigned int k)
{
if (head == nullptr || k == 0)
return nullptr;
ListNode *p1 = head;
ListNode *p2 = head;
for (int i = 0; i < k-1 ; i++) //注意 k-1
{
if (p2->next)
p2 = p2->next;
else //如果 k 比链表整个的长度还要长,怎么办?
return nullptr;
}
while (p2->next)//不需要循环到 nullptr
{
p1 = p1->next;
p2 = p2->next;
}
return p1;
}
};
拓展:找最中间的节点和判断是否成环也是双指针的思路
4.合并两个排序的链表
5.两个链表的第一个公共节点
6.环形链表(圆圈中最后剩下的数字)
判断是否有环(快慢指针&&龟兔赛跑思想)
public class Solution {
public boolean hasCycle(ListNode head) {
if (head == null) {
return false;
}
ListNode slow = head;
ListNode fast = head.next;
while(fast != null && fast.next != null){
fast = fast.next.next;
slow = slow.next;
if(fast == slow){
return true;
}
}
return false;
}
}
- 约瑟夫环的问题。时间复杂度O(MN),空间复杂度O(N)
class Solution
{
public:
int LastRemaining_Solution(int n, int m)
{
if (n < 1 || m < 1)
return -1;
std::list<int> ll;
for (int i = 0; i < n; i++)
ll.push_back(i);
auto it = ll.begin();
while (ll.size() != 1)
{
for (int i = 0; i < m - 1; i++)
{
it++;
if (it == ll.end())
it = ll.begin();
}
//删除 第 m 个
auto tmp = it;
it++;
if (it == ll.end())
it = ll.begin();
ll.erase(tmp);
}
return *it;
}
};
- 经过上面复杂的分析,我们终于找到了一个递归公式。
要得到n个数字的序列中最后剩下的数字,只需要得到n-1个数字的序列中最后剩下的数字,并以此类推
。当n=1时,也就是序列中开始只有-一个数字0,那么很显然最后剩下的数字就是0。我们把这种关系表示为:
时间复杂度O(n),空间复杂度O(1)
class Solution
{
public:
int LastRemaining_Solution(int n, int m)
{
if (n < 1 || m < 1)
return -1;
int last = 0;
for (int i = 2; i <= n; i++)
last = (last + m) % i;
return last;
}
};
7.二叉搜索树与双向链表
输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。
思路:
时间复杂度O (n)
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};*/
class Solution {
public:
//左 根 右
TreeNode *lastMax = nullptr ;
void ConvertNode(TreeNode* node){
if(node)
{
if(node->left)
ConvertNode(node->left);
node->left = lastMax;
if(lastMax)
lastMax->right = node;
lastMax = node ;
if(node->right)
ConvertNode(node->right);
}
}
TreeNode* Convert(TreeNode* root)
{
if(root == nullptr )
return nullptr;
//先找到需要返回的节点的指针
TreeNode *ret = root;
while(ret->left)
ret = ret->left;
ConvertNode(root);
return ret;
}
};
8.复杂链表的复制
给定一个链表,每个节点包含一个额外增加的随机指针,该指针可以指向链表中的任何节点或空节点。
要求返回这个链表的深拷贝。
class Solution
{
public:
//1.复制节点
void CloneNodes(Node *pHead)
{
Node *p = pHead;
while (p)
{
Node *pNew = new Node(p->val, nullptr, nullptr);//一定要置空,不然过不了
pNew->next = p->next; //节点的中间插入法
p->next = pNew;
p = pNew->next;
}
}
//2.设置 random 指针
void SetRandom(Node *pHead)
{
Node *p = pHead;
Node *pClone = nullptr;
while (p)
{
pClone = p->next;
if (p->random)
{
pClone->random = p->random->next;
}
p = pClone->next;
}
}
//3.将复制链表从原链表分离。
Node *GetResult(Node *head)
{
Node *node = head;
Node *newHead = head->next;
Node *newNode = head->next;
while (node != nullptr)
{
node->next = node->next->next;
if (newNode->next != nullptr)
{
newNode->next = newNode->next->next;
}
node = node->next;
newNode = newNode->next;
}
return newHead;
}
Node *copyRandomList(Node *pHead)
{
if (pHead == nullptr)
return nullptr;
CloneNodes(pHead);
SetRandom(pHead);
return GetResult(pHead);
}
};
9.两两交换链表中的节点
-
递归法:
-
迭代法:
class Solution {
public ListNode swapPairs(ListNode head) {
if (head == null) {
return head;
}
ListNode dumpHead = new ListNode();
ListNode dumpTmp;
ListNode left, right;
dumpHead.next = head;
dumpTmp = dumpHead;
//对于头结点改变的都创建一个 哑结点 dummyHead
while (dumpTmp.next != null && dumpTmp.next.next != null) {
left = dumpTmp.next;
right = dumpTmp.next.next;
// 交换 left 和 right
left.next = right.next;
dumpTmp.next = right;
right.next = left;
//移动 dumpTmp
dumpTmp = left;
}
return dumpHead.next;
}
}