leetcode刷题笔记(1):简单题(链表)C++
文章目录
-
- leetcode刷题笔记(1):简单题(链表)C++
- 141、环形链表
- 160、相交链表
- 203、移除链表元素
- 206、反转链表
- 225、用队列实现栈
- 面试题10.01、合并排序的数组
- 234、回文链表
- 237、删除链表中的节点(智商题)
- 326、3的幂
- 344、反转字符串
- 876、链表的中间结点
- 1290、二进制链表转整数
141、环形链表
问题
给定一个链表,判断链表中是否有环。
为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。
示例 1:
输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。
示例 2:
输入:head = [1,2], pos = 0
输出:true
解释:链表中有一个环,其尾部连接到第一个节点。
示例 3:
输入:head = [1], pos = -1
输出:false
解释:链表中没有环。
进阶:
你能用 O(1)(即,常量)内存解决此问题吗?
代码模板
/**
* 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) {
}
};
题解
我的方法
遍历链表的同时,记录每一个节点指针,判断当前节点的
next
是否指向保存的节点指针列表。
/**
* 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) {
vector<ListNode*> tmp; //用set更好!!!
ListNode* cur=head;
while(cur != NULL){
tmp.push_back(cur);
for(auto ptr : tmp){
if(cur->next==ptr)
return true;
}
cur = cur->next;
}
return false;
}
};
- 时间复杂度O(N)
- 空间复杂度O(N)
双指针O(1)法
通过使用具有 不同速度 的快、慢两个指针遍历链表,空间复杂度可以被降低至 O(1)O(1)。慢指针每次移动一步,而快指针每次移动两步。
如果列表中不存在环,最终快指针将会最先到达尾部,此时我们可以返回 false。
现在考虑一个环形链表,把慢指针和快指针想象成两个在环形赛道上跑步的运动员(分别称之为慢跑者与快跑者)。而快跑者最终一定会追上慢跑者。这是为什么呢?考虑下面这种情况(记作情况 A)- 假如快跑者只落后慢跑者一步,在下一次迭代中,它们就会分别跑了一步或两步并相遇。
/**
* 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) {
if(head==NULL || head->next==NULL)
return false;
ListNode* slow=head;
ListNode* fast=head->next;
while(fast != slow){
if(fast==NULL || fast->next==NULL )
return false;
slow=slow->next;
fast = fast->next->next;
}
return true;
}
};
- 时间复杂度O(N)
- 空间复杂度O(1)
160、相交链表
题目
编写一个程序,找到两个单链表相交的起始节点。
输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3
输出:Reference of the node with value = 8
输入解释:相交节点的值为 8 (注意,如果两个链表相交则不能为 0)。从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,0,1,8,4,5]。在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。
题解
**first **
最笨的方法!!固定链表A,从链表B头节点开始,依次判断是否等于A中的节点。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
ListNode *cur = headA;
while(cur != NULL){
ListNode *tmp = headB;
while(tmp != NULL){
if(tmp==cur)
return tmp;
tmp = tmp->next;
}
cur = cur->next;
}
return NULL;
}
};
- 时间复杂度O(NM)
- 空间复杂度O(1)
second
先算出两个链表的长度,将长 的那个链表的指针ptr移动 AB长度差个单位,即使AB的当前指针对齐,当前指针的后面链表长度相等。再同时滑动两个指针,直至指针相等。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
int la=0,lb=0;
ListNode *ptrA = headA,*ptrB = headB;
while(ptrA!=NULL){
la++;
ptrA = ptrA->next;
}
while(ptrB != NULL){
lb++;
ptrB = ptrB->next;
}
ptrA=headA;
ptrB=headB;
if(la>lb){
for(int i=0;i<la-lb;i++)
ptrA = ptrA->next;
}
if(lb>la){
for(int i=0;i<lb-la;i++)
ptrB = ptrB->next;
}
while(ptrA != ptrB){
ptrA = ptrA->next;
ptrB = ptrB->next;
}
return ptrA;
}
};
- 时间复杂度O(N+M)
- 空间复杂度O(1)
浪漫————解法
错的人迟早会走散,而对的人迟早会相逢!
A和B两个链表长度可能不同,但是A+B和B+A的长度是相同的,所以遍历A+B和遍历B+A一定是同时结束。 如果A,B相交的话A和B有一段尾巴是相同的,所以两个遍历的指针一定会同时到达交点 如果A,B不相交的话两个指针就会同时到达A+B(B+A)的尾节点!
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
ListNode* ptrA=headA,*ptrB = headB;
while(ptrA != ptrB){
ptrA = ptrA==NULL ? headB:ptrA->next;
ptrB = ptrB==NULL ? headA:ptrB->next;
}
return ptrA;
}
};
- 时间复杂度 : O(m+n)
- 空间复杂度 : O(1)
203、移除链表元素
题目
删除链表中等于给定值 val 的所有节点。
示例:
输入: 1->2->6->3->4->5->6, val = 6
输出: 1->2->3->4->5
题解
哨兵节点法
如果要删除的点位于链表头节点时,比较难操作,因此新建一个哨兵节点。
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
ListNode* sentinel = new ListNode(0);
sentinel->next = head;
ListNode *prev = sentinel, *curr = head, *toDelete = nullptr;
while (curr != nullptr) {
if (curr->val == val) {
prev->next = curr->next;
toDelete = curr;
} else prev = curr;
curr = curr->next;
if (toDelete != nullptr) {
delete toDelete;
toDelete = nullptr;
}
}
ListNode *ret = sentinel->next;
delete sentinel;
return ret;
}
};
- 时间复杂度O(N)
- 空间复杂度O(1)
无需哨兵法
巧妙之处在于,先不管第一个节点,先把后面的去掉,最后只有第一个节点可能需要删除,不必考虑其他节点了;🐂🍺
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
if(!head)
return nullptr;
ListNode* p = head, *q;
while(p->next)
{
if(p->next->val == val)
{
q = p->next;
p->next = q->next;
delete q;
}
else
p = p->next;
}
return head->val == val ? head->next : head; //此处只需考虑第一个节点
}
};
- 时间复杂度O(N)
- 空间复杂度O(1)
万事皆可递归法
无情递归
class Solution
{
public ListNode removeElements(ListNode head, int val)
{
if (head == null)
return head;
head.next = removeElements(head.next, val);
return head.val == val ? head.next : head;
}
}
- 空间复杂度O(N)
- 时间复杂度O(N)
206、反转链表
题目
反转一个单链表。
示例:
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
进阶:你可以迭代或递归地反转链表。你能否用两种方法解决这道题?
题解
-
我的方法
使用
定位上一个节点,last
定位下一个节点,next
定位当前节点,如果cur
不为空,将cur
指向上一节点,重新定位cur->next
,使用last
将next
移动到下一节点,开始下一循环。cur
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode* cur = head,*last=NULL,*next;
while(cur != NULL)
{
next = cur->next;
cur->next = last;
last = cur;
cur = next;
}
return last;
}
};
- 最短时间
使用了递归。将传入的链表分成两部分,第一个节点和剩余节点组成的链表,若要反转链表,只需反转第一个节点和剩余节点的反转链表;剩余节点的反转链表可以递归使用函数求出。
class Solution {
public:
ListNode* reverseList(ListNode* head) {
if(!head || !head ->next)
return head;
ListNode* newHead = reverseList(head ->next);
head ->next ->next = head;
head ->next = nullptr;
return newHead;
}
};
225、用队列实现栈
题目
使用队列实现栈的下列操作:
push(x) – 元素 x 入栈
pop() – 移除栈顶元素
top() – 获取栈顶元素
empty() – 返回栈是否为空
注意:
你只能使用队列的基本操作-- 也就是 push to back, peek/pop from front, size, 和 is empty 这些操作是合法的。
你所使用的语言也许不支持队列。 你可以使用 list 或者 deque(双端队列)来模拟一个队列 , 只要是标准的队列操作即可。
你可以假设所有操作都是有效的(例如, 对一个空的栈不会调用 pop 或者 top 操作)。
题解
题意应该是让用单向队列来解决的,有些题解直接用的双向队列,鄙视!!
单向队列也很简单,唯一需要注意的是
push
操作,栈需要在栈顶
push
,而队列只能一头
pop
,一头
push
,我用的是队首当栈顶,因此队列
push
的时候,需要依次把队首的元素插到队尾,从而使新
push
的元素处于队首,也就是栈顶。
class MyStack {
private:
queue<int> q;
public:
/** Initialize your data structure here. */
MyStack() {
}
/** Push element x onto stack. */
void push(int x) {
q.push(x);
for(int i=0;i<q.size()-1;i++){
q.push(q.front());
q.pop();
}
}
/** Removes the element on top of the stack and returns that element. */
int pop() {
int x=q.front();
q.pop();
return x;
}
/** Get the top element. */
int top() {
return q.front();
}
/** Returns whether the stack is empty. */
bool empty() {
return q.empty();
}
};
/**
* Your MyStack object will be instantiated and called as such:
* MyStack* obj = new MyStack();
* obj->push(x);
* int param_2 = obj->pop();
* int param_3 = obj->top();
* bool param_4 = obj->empty();
*/
面试题10.01、合并排序的数组
题目
给定两个排序后的数组 A 和 B,其中 A 的末端有足够的缓冲空间容纳 B。 编写一个方法,将 B 合并入 A 并排序。
初始化 A 和 B 的元素数量分别为 m 和 n。
示例:
输入:
A = [1,2,3,0,0,0], m = 3
B = [2,5,6], n = 3
输出: [1,2,2,3,5,6]
说明:
A.length == n + m
题解
投机取巧(鄙视我自己解法):用C++自带的
sort
算法求解。(
insert
是插入,会造成A的空间变大,A中的0并不会消失,因此使用
resize
重新分配A的空间)
class Solution {
public:
void merge(vector<int>& A, int m, vector<int>& B, int n) {
A.insert(A.begin()+m,B.begin(),B.end());
A.resize(m+n);
sort(A.begin(),A.end());
}
};
-
时间复杂度:O((m+n)\log(m+n))
排序序列长度为 m+n,套用快速排序的时间复杂度即可,平均情况为 O((m+n)\log(m+n))
-
空间复杂度:O(\log(m+n))
排序序列长度为 m+n,套用快速排序的空间复杂度即可,平均情况为 O(\log(m+n))
解法二:
反向排序,先排最大的;因此A后面又足够的位置,以至于A不会被覆盖元素
class Solution {
public:
void merge(vector<int>& A, int m, vector<int>& B, int n) {
int pa = m - 1, pb = n - 1;
int tail = m + n - 1;
int cur;
while (pa >= 0 || pb >= 0) {
if (pa == -1)
cur = B[pb--];
else if (pb == -1)
cur = A[pa--];
else if (A[pa] > B[pb])
cur = A[pa--];
else
cur = B[pb--];
A[tail--] = cur;
}
}
};
-
时间复杂度:O(m+n)
指针移动单调递减,最多移动 m+nm+n 次,因此时间复杂度为 O(m+n)
-
空间复杂度:O(1)
直接对数组 A 原地修改,不需要额外空间
234、回文链表
问题
请判断一个链表是否为回文链表。
示例 1:
输入: 1->2
输出: false
示例 2:
输入: 1->2->2->1
输出: true
进阶:
你能否用 O(n) 时间复杂度和 O(1) 空间复杂度解决此题?
题解
笨方法
修改链表结构,使前一半反向,再遍历两个半链表!修改了链表一般不可取!!!
所以修改了输入的链表一般使用完,要恢复的原来的结构。(我懒的改回去了)
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
bool isPalindrome(ListNode* head) {
if(head==NULL || head->next == NULL)
return true;
int l=0;
ListNode* ptr = head;
while(ptr != NULL){
l++;
ptr = ptr->next;
}
ptr = head;
ListNode *tmp=NULL,*tmp1 = head;
for(int i=0;i<l/2;i++){
ptr = tmp1;
tmp1 = ptr->next;
ptr->next = tmp;
tmp = ptr;
}
tmp1 = l%2==0?tmp1:tmp1->next;
while(ptr != NULL ){
if(ptr->val != tmp1->val)
return false;
ptr = ptr->next;
tmp1 = tmp1->next;
}
return true;
}
};
- 时间复杂度O(N)
- 空间复杂度O(1)
黄金升级版
应用了快慢指针法,再遍历的同时,使链表的前一半逆向。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
bool isPalindrome(ListNode* head) {
if(!head||!head->next) return true;
auto fast=head,slow=head;
ListNode* p,*pre=NULL;
while(fast&&fast->next){
p=slow;
slow=slow->next;
fast=fast->next->next;
p->next=pre;
pre=p;
}
if(fast) slow=slow->next;
while(p){
if(p->val!=slow->val) return false;
p=p->next;
slow=slow->next;
}
return true;
}
};
- 时间复杂度O(N)
- 空间复杂度O(1)
237、删除链表中的节点(智商题)
问题
请编写一个函数,使其可以删除某个链表中给定的(非末尾)节点,你将只被给定要求被删除的节点。
- 示例 1:
输入: head = [4,5,1,9], node = 5
输出: [4,1,9]
解释: 给定你链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9.
- 示例 2:
输入: head = [4,5,1,9], node = 1
输出: [4,5,9]
解释: 给定你链表中值为 1 的第三个节点,那么在调用了你的函数之后,该链表应变为 4 -> 5 -> 9.
代码模板
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
void deleteNode(ListNode* node) {
}
};
提示
引用一个评论:
这道题细思极恐:如何让自己在世界上消失,但又不死? —— 将自己完全变成另一个人,再杀了那个人就行了。
题解
无语题,这题出的,不就是数组删除节点的做法吗?(只给了
ListNode* node
要删除的节点,其他一律没有!!)
my way
我是把之后的节点依次复制到前一节点,最后删除最后一个节点。
然而我这做法也笨了,思路窄了窄了!!
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
void deleteNode(ListNode* node) {
while(node->next->next != NULL){
node->val = node->next->val;
node = node->next;
}
ListNode* tmp = node->next;;
node->val = node->next->val;
node->next = NULL;
delete tmp;
}
};
- 时间复杂度O(N)
- 空间复杂度O(1)
好一点的方法
比我的好多了!!
只将下一节点复制到要删除的节点,然后当前节点跳到下下节点,删除下一节点。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
void deleteNode(ListNode* node) {
node->val = node->next->val;
ListNode *tmp = node->next;
node->next = tmp->next;
delete tmp;
}
};
- 时间复杂度O(1)
- 空间复杂度O(1)
326、3的幂
问题
给定一个整数,写一个函数来判断它是否是 3 的幂次方。
示例 1:
输入: 27
输出: true
示例 2:
输入: 0
输出: false
示例 3:
输入: 9
输出: true
题解
我的辣子鸡方法
class Solution {
public:
bool isPowerOfThree(int n) {
for(int i=0;i<n;i++){
if(pow(3,i) == n)
return true;
if(pow(3,i)> n)
return false;
}
return false;
}
};
- 时间复杂度O(N) :将pow复杂度视为O(1),可能不太对
- 空间复杂度O(1)
无情递归
不会做就递归系列
class Solution {
public:
bool isPowerOfThree(int n) {
if (n==1)
return true;
else if (n>0 && n%3==0)
return isPowerOfThree(n/3);
else
return false;
}
};
- 时间复杂度O(N)
- 空间复杂度O(N)
循环迭代法
class Solution {
public:
bool isPowerOfThree(int n) {
if (n < 1) {
return false;
}
while (n % 3 == 0) {
n /= 3;
}
return n == 1;
}
}
-
时间复杂度:O(log_b(n))。除数是用对数表示的。
空间复杂度:O(1),没有使用额外的空间。
整数限制
无法言语
可以看出 n 的类型是 int。在 Java 中说明了该变量是四个字节,他的最大值为 2147483647。有三种方法可以计算出该最大值。
System.out.println(Integer.MAX_VALUE);
MaxInt = 2^{32} /2-1,因为我们使用 32 位来表示数字,所以范围的一半用于负数,0 是正数的一部分。
知道了 n 的限制,我们现在可以推断出 n 的最大值,也就是 3 的幂,是 1162261467。
class Solution {
public:
bool isPowerOfThree(int n) {
return n > 0 && 1162261467 % n == 0;
}
}
- 时间复杂度O(1)
- 空间复杂度 O(1)
344、反转字符串
题目
编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 char[] 的形式给出。
不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。
你可以假设数组中的所有字符都是 ASCII 码表中的可打印字符。
示例 1:
输入:["h","e","l","l","o"]
输出:["o","l","l","e","h"]
示例 2:
输入:["H","a","n","n","a","h"]
输出:["h","a","n","n","a","H"]
题解
my way
很简单,用一个
char
的额外空间即可。两头互相交换,直到相遇。
class Solution {
public:
void reverseString(vector<char>& s) {
char tmp;
for(int i=0;i<s.size()/2;i++){
tmp=s[i];
s[i]=s[s.size()-i-1];
s[s.size()-1-i]=tmp;
}
}
};
- 空间复杂度 O(1) 只是用了一个额外空间
- 时间复杂度O(n) 一次循环交换2个值,因此最多有n/2次交换,因此为O(n)
最简洁方法
使用自带算法(耍赖),
class Solution {
public:
void reverseString(vector<char>& s) {
reverse(s.begin(),s.end());
}
};
- 空间复杂度 ??
- 时间复杂度 ??
递归
递归需要堆栈空间
class Solution {
public:
void reverseString(vector<char>& s) {
helper(s, 0, s.size());
}
void helper(vector<char>& s, int pos, int s_len){
if ( pos >= s_len / 2) return;
char tmp = s[pos];
s[pos] = s[s_len - 1 - pos];
s[s_len - 1 - pos ] = tmp;
helper(s, pos+1, s_len);
}
};
- 空间复杂度O(N) 执行了N/2次交换
- 时间复杂度O(N).递归过程中使用了堆栈空间
876、链表的中间结点
题目
给定一个带有头结点 head 的非空单链表,返回链表的中间结点。
如果有两个中间结点,则返回第二个中间结点。
示例 1:
输入:[1,2,3,4,5]
输出:此列表中的结点 3 (序列化形式:[3,4,5])
返回的结点值为 3 。 (测评系统对该结点序列化表述是 [3,4,5])。
注意,我们返回了一个 ListNode 类型的对象 ans,这样:
ans.val = 3, ans.next.val = 4, ans.next.next.val = 5, 以及 ans.next.next.next = NULL.
示例 2:
输入:[1,2,3,4,5,6]
输出:此列表中的结点 4 (序列化形式:[4,5,6])
由于该列表有两个中间结点,值分别为 3 和 4,我们返回第二个结点。
题解
快慢指针法
根据141题的快慢指针法来的灵感
好吧,其实一模一样的解法,不过更简单点
两个指针,一个一次走一步,另一个一次走两步,当块的那个走到结尾的时候,慢的指针正好走到一半
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* middleNode(ListNode* head) {
ListNode* slow=head;
ListNode* fast=head;
while(true){
if(fast==NULL || fast->next==NULL)
return slow;
slow = slow->next;
fast = fast->next->next;
}
}
};
- 时间复杂度O(N)
- 空间复杂度O(1)
单指针法
先遍历一次,求得链表长度,在遍历一半即可
class Solution {
public:
ListNode* middleNode(ListNode* head) {
int n = 0;
ListNode* cur = head;
while (cur != nullptr) {
++n;
cur = cur->next;
}
int k = 0;
cur = head;
while (k < n / 2) {
++k;
cur = cur->next;
}
return cur;
}
};
- 时间复杂度O(N)
- 空间复杂度O(1)
1290、二进制链表转整数
题目
给你一个单链表的引用结点 head。链表中每个结点的值不是 0 就是 1。已知此链表是一个整数数字的二进制表示形式。
请你返回该链表所表示数字的 十进制值 。
示例 1:
输入:head = [1,0,1]
输出:5
解释:二进制数 (101) 转化为十进制数 (5)
示例 2:
输入:head = [0]
输出:0
示例 3:
输入:head = [1]
输出:1
示例 4:
输入:head = [1,0,0,1,0,0,1,1,1,0,0,0,0,0,0]
输出:18880
示例 5:
输入:head = [0,0]
输出:0
提示:
链表不为空。
链表的结点总数不超过 30。
每个结点的值不是 0 就是 1。
题解
简单的不能再简单的移位操作
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
int getDecimalValue(ListNode* head) {
int d=0;
ListNode* ptr = head;
while(ptr != NULL){
d = d*2+ptr->val;
ptr = ptr->next;
}
return d;
}
};
- 时间复杂度O(N)
- 空间复杂度O(1)