先定义结构体:
struct ListNode {
int val;
ListNode *next;
ListNode() : val(0), next(nullptr) {}
ListNode(int x) : val(x), next(nullptr) {}
ListNode(int x, ListNode *next) : val(x), next(next) {}
};
160.相交链表
错误的写法:
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
if (*headA == nullptr || *headB == nullptr) return null;
ListNode *A = *headA, *B = *headB;
while (*A != *B) {
*A = *A != nullptr ? *A->next : *headB;
*B = *B != nullptr ? *B->next : *headA;
}
return *A;
}
};
正确的写法:
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
if (headA == nullptr || headB == nullptr) return nullptr;
ListNode *A = headA, *B = headB;
while (A != B) {
A = A != nullptr ? A->next : headB;
B = B != nullptr ? B->next : headA;
}
return A;
}
};
解释一下这两者之间的区别:
-
ListNode *A = headA;
这行代码中,我们直接将headA
的值(也就是一个ListNode
类型的指针)赋给了A
。这里没有进行任何解引用操作,A
现在是一个指针变量,它存储了headA
的值,即指向链表中某个节点的地址。 -
ListNode *A = *headA;
这行代码中,我们对headA
进行了解引用操作,这意味着我们试图获取headA
指向的节点的值(而不是节点的地址)。然而,这里存在一个逻辑问题,因为headA
本身就是一个指针,它的解引用(*headA
)将得到一个ListNode
类型的实际节点,而不是一个地址。所以,如果我们尝试将一个ListNode
类型的节点赋值给一个指向指针的指针变量A
,这在类型上是不匹配的,会导致编译错误。
正确的做法是使用第一种赋值方式,即直接将headA
(一个指针)赋值给A
(另一个指针变量)。这样,A
就成为了指向headA
所指向节点的一个新的指针。这样做的目的是创建一个新的指针,而不是试图获取或修改链表中的节点。
总结来说,ListNode *A = headA;
是正确的赋值方式,因为它创建了一个指向相同节点的新指针。而ListNode *A = *headA;
是不正确的,因为它试图将一个节点的值赋给一个指针变量,这在类型上不匹配,并且不是我们想要的操作。
如果想将指针A
的值赋给另一个指针B
,应该使用ListNode *B = A;
这样的赋值语句。这会创建一个新的指针B
,它指向与A
相同的ListNode
对象。
在C++中,指针之间的赋值是非常直接的,因为指针本质上就是存储地址的变量。将一个指针赋值给另一个指针时,实际上是将地址复制到了新的指针变量中。这样,两个指针变量都将指向相同的内存地址,也就是同一个ListNode
对象。
例如:
ListNode *A = headA; // A 指向 headA 所指向的 ListNode 对象
ListNode *B = A; // 现在 B 也指向与 A 相同的 ListNode 对象
在这个例子中,A
和B
都是指针,它们都存储了指向headA
所指向的ListNode
对象的地址。因此,A
和B
都指向链表中的同一个节点。如果通过A
或B
对节点进行修改,这些修改将反映在另一个指针所指向的节点上,因为它们指向的是同一个对象。
这种指针之间的赋值在处理链表、树结构或其他复杂的数据结构时非常常见,因为它允许在不同的函数或代码块中传递对同一块内存区域的引用。
在语句 ListNode *A = headA;
中,等号左边的 *
不是解引用操作,而是声明操作符,用于声明 A
为一个指向 ListNode
类型的指针变量。
在这个上下文中,ListNode *
是一种类型声明,它告诉编译器 A
是一个指针,它将存储一个指向 ListNode
类型对象的内存地址。而 = headA
部分则是初始化部分,它将 A
指向 headA
所指向的地址,即 headA
本身就是一个指针,所以 A
现在持有了 headA
所指向的地址。
ListNode *
:这是一个指针类型的声明,A
是一个指针变量。A
:这是一个变量名,现在是一个指向ListNode
类型的指针。= headA
:这是一个初始化表达式,它将A
指向和headA
相同的地址。
所以,整个语句 ListNode *A = headA;
创建了一个名为 A
的指针变量,并将其初始化为指向 headA
所指向的 ListNode
对象。这里没有发生解引用操作,而是创建了一个指针,它存储了一个地址。(所以我更喜欢写成ListNode* A = headA)
表达式 *A = *headA
涉及到两个不同的操作:解引用和赋值。
在这个表达式中:
-
*A
是对指针A
进行解引用操作。这意味着你正在访问A
所指向的内存地址上的值。如果A
是一个有效的指针,*A
将会得到该地址上存储的值。 -
*headA
同样是解引用操作,它访问headA
指针所指向的内存地址上的值。 -
=
是赋值操作符,它将右侧的值(在这个例子中是*headA
)赋给左侧的变量(在这个例子中是*A
)。
所以,*A = *headA
这个表达式的含义是:将 headA
指针指向的 ListNode
对象的值(即 headA
指向的地址上存储的内容),赋值给 A
指针目前指向的地址上。这实际上是将两个指针指向的 ListNode
对象的值进行了交换或者复制。
然而,这个操作通常不是你想要的,因为它改变了 A
指针指向的 ListNode
对象的内容,而不是改变了 A
指针本身。如果你的目的是让 A
指向 headA
所指向的同一个 ListNode
对象,你应该直接使用 A = headA;
这样的赋值操作。
在处理指针时,重要的是要清楚你的意图是什么,以及你想要操作的是指针本身(通过赋值操作),还是指针指向的值(通过解引用操作)。错误的操作可能会导致程序行为不符合预期。
141.环形链表
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
bool hasCycle(ListNode *head) {
ListNode* slow = head;
ListNode* fast = head;
while(slow != nullptr && slow->next != nullptr) {
slow = slow->next;
fast = fast->next->next;
if (slow == fast) return true;
}
return false;
}
};
class Solution {
public:
bool hasCycle(ListNode *head) {
ListNode* slow = head;
ListNode* fast = head;
while(fast != nullptr && fast->next != nullptr) {
slow = slow->next;
fast = fast->next->next;
if (slow == fast) return true;
}
return false;
}
};
第一个代码是错误的,第二个代码是正确的。第一个代码中的错误是由于fast一定比slow快,slow能用不代表fast也能用,fast能用则slow一定能用,所以判断slow可能导致fast在跳跃的时候空指针解引用。
递归这玩意是谁发明的?