11.12链表
11/12
每日一题:3258. 统计满足 K 约束的子字符串数量 I
思路:滑动窗口
- 常规做法:双循环
首先这题自己是定义一个check()函数,如果满足字符子串t中的0、1的个数均大于k,这不满足条件,计数res ++。
然后遍历截取s的子串,每次check(),用总的字符串个数n*(n+1)-res即可。
有个细节是:i从0开始,j要从i +k - 1开始,因为长度<=k的字符串必然满足条件。
- 进阶做法:滑动窗口
计算以 0 为右端点的合法子串个数,以 1 为右端点的合法子串个数,……,以 n−1 为右端点的合法子串个数。
我们需要知道以 i 为右端点的合法子串,其左端点最小是多少。
由于随着 i 的变大,窗口内的字符数量变多,越不能满足题目要求,所以最小左端点会随着 i 的增大而增大,有单调性,因此可以用 滑动窗口 计算。
设以 i 为右端点的合法子串,其左端点最小是 $ l_i$ 。那么以 i 为右端点的合法子串,其左端点可以是 $ l_i\(,\) l_i + 1$
······i,一共有 \(i - l_i + 1\)种结果,累加到答案中。细节:字符 0 的 ASCII 值是偶数,字符 1 的 ASCII 值是奇数,所以可以用 ASCII 值
c mod 2得到对应的数字。这也等价于和 1 计算 AND。if(t[i] == '0') vec[0]++; else if(t[i] == '1') vec[1] ++; //也相当于 vec[t[i] & 1] ++;
class Solution {
private:
bool check(string t , int k){
vector<int> vec(2 ,0);
for (int i = 0; i < t.size(); i++) {
vec[t[i] & 1] ++;// if(t[i] == '0') vec[0]++;
//else if(t[i] == '1') vec[1] ++;
}
if(vec[0] > k && vec[1] > k) return true;
return false;
}
public:
int countKConstraintSubstrings(string s, int k) {
int n = s.size();
int res = 0;
for (int i = 0; i < n; i++) {
for (int j = i + k - 1; j < n; j++) {
if(check(s.substr(i , j - i + 1) , k)) res ++;
}
}
return n * (n + 1) / 2 - res;
}
};
滑动窗口法:
class Solution {
public:
int countKConstraintSubstrings(string s, int k) {
int ans = 0, left = 0, cnt[2]{};
for (int i = 0; i < s.length(); i++) {
cnt[s[i] & 1]++;
while (cnt[0] > k && cnt[1] > k) {
cnt[s[left] & 1]--;
left++;
}
ans += i - left + 1;
}
return ans;
}
};
- 时间复杂度:O(n),其中 n 是 s 的长度。注意 l 只会增加不会减少,所以二重循环的时间复杂度为 O(n)。
- 空间复杂度:O(1)。
203.移除链表元素
C++的定义链表节点方式,如下所示:
// 单链表 struct ListNode { int val; // 节点上存储的元素 ListNode *next; // 指向下一个节点的指针 ListNode(int x) : val(x), next(NULL) {} // 节点的构造函数 };不定义构造函数,C++默认生成一个构造函数,但是这个构造函数不会初始化任何成员变量,下面举两个例子:
通过自己定义构造函数初始化节点:
ListNode* head = new ListNode(5);使用默认构造函数初始化节点:
ListNode* head = new ListNode(); head->val = 5;所以如果不定义构造函数使用默认构造函数的话,在初始化的时候就不能直接给变量赋值!
定义虚拟头结点
dummyHead的方式:ListNode* dummyHead = new ListNode(0); dummyHead->next = head;然后定义cur指针指向当前节点,从虚拟头结点开始遍历链表:
ListNode* cur = dummyHead; while(cur->next != NULL)如果cur的下一个节点值等于val,先用tmp指针记录下要删除的位置,然后cur的next指针跳过它,最后删除tmp节点。
if(cur->next->val == val){ ListNode* tmp = cur->next; cur->next = cur->next->next; delete tmp; }最后一步把虚拟头结点删除,返回链表=返回头结点head,因为head可能变了,因此需重新定义。
head = dummyHead->next; delete dummyHead; return head;
/**
* Definition for singly-linked list.
* 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) {}
* };
*/
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
ListNode* dummyHead = new ListNode(0);
dummyHead -> next = head;
ListNode* cur = dummyHead;
while(cur->next != NULL){
if(cur->next->val == val){
ListNode* tmp = cur->next;
cur->next = cur->next->next;
delete tmp;
}
else cur = cur->next;
}
head = dummyHead->next;
delete dummyHead;
return head;
}
};
707.设计链表
思路:定义虚拟头结点,其余的靠熟练度,多写多练。
class MyLinkedList {
public:
struct LinkedNode {
int val;
LinkedNode* next;
LinkedNode(int val):val(val) , next(nullptr){}
};
MyLinkedList() {
dummyHead = new LinkedNode(0);
size = 0;
}
int get(int index) {
if (index > (size - 1) || index < 0){
return -1;
}
LinkedNode* cur = dummyHead -> next;
while(index --){
cur= cur->next;
}
return cur->val;
}
void addAtHead(int val) {
LinkedNode* newNode = new LinkedNode(val);
//插入的操作
newNode->next = dummyHead->next;
dummyHead->next = newNode;
size ++;
}
void addAtTail(int val) {
LinkedNode* newNode = new LinkedNode(val);
LinkedNode* cur = dummyHead;
while(cur->next != nullptr) cur = cur->next;
cur->next = newNode;
size ++;
}
void addAtIndex(int index, int val) {
if(index > size) return;
if(index < 0) index = 0;
LinkedNode* newNode = new LinkedNode(val);
LinkedNode* cur = dummyHead;
while(index --) cur = cur->next;
newNode->next = cur->next;
cur->next = newNode;
size ++;
}
void deleteAtIndex(int index) {
if(index >= size || index < 0) return;
LinkedNode* cur = dummyHead;
while(index --) cur = cur->next;
LinkedNode* tmp = cur->next;
cur->next = cur->next->next;
delete tmp;
tmp = nullptr;
size --;
}
private:
int size;
LinkNode* dummyHead;
};
/**
* Your MyLinkedList object will be instantiated and called as such:
* MyLinkedList* obj = new MyLinkedList();
* int param_1 = obj->get(index);
* obj->addAtHead(val);
* obj->addAtTail(val);
* obj->addAtIndex(index,val);
* obj->deleteAtIndex(index);
*/
206.反转链表
思路:三种方法,其中双指针和递归法比较常见,要掌握。从后往前翻转比较抽象。
//双指针法
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode* tmp;
ListNode* cur = head;
ListNode* pre = NULL;
while(cur){
tmp = cur->next;
cur->next = pre;
pre = cur;
cur = tmp;
}
return pre;
}
};
- 时间复杂度: O(n)
- 空间复杂度: O(1)
//递归法
class Solution {
public:
ListNode* reverse(ListNode* pre , ListNode* cur){
if(!cur) return pre;
ListNode* tmp = cur->next;
cur->next = pre;
return reverse(cur , tmp);
}
ListNode* reverseList(ListNode* head) {
return reverse(NULL ,head);
}
};
- 时间复杂度: O(n), 要递归处理链表的每个节点
- 空间复杂度: O(n), 递归调用了 n 层栈空间
//从后往前翻转指针指向
class Solution {
public:
ListNode* reverseList(ListNode* head) {
if(!head) return NULL;
if(head->next == NULL) return head;
//可以合写为
//if(!head ||!head->next) return head;
ListNode* last = reverseList(head->next);
head->next->next = head;
head->next = NULL;
return last;
}
};
法三的详细解释:
/** * 以链表1->2->3->4->5举例 * @param head * @return */ public ListNode reverseList(ListNode head) { if (head == null || head.next == null) { /* 直到当前节点的下一个节点为空时返回当前节点 由于5没有下一个节点了,所以此处返回节点5 */ return head; } //递归传入下一个节点,目的是为了到达最后一个节点 ListNode newHead = reverseList(head.next); /* 第一轮出栈,head为5,head.next为空,返回5 第二轮出栈,head为4,head.next为5,执行head.next.next=head也就是5.next=4, 把当前节点的子节点的子节点指向当前节点 此时链表为1->2->3->4<->5,由于4与5互相指向,所以此处要断开4.next=null 此时链表为1->2->3->4<-5 返回节点5 第三轮出栈,head为3,head.next为4,执行head.next.next=head也就是4.next=3, 此时链表为1->2->3<->4<-5,由于3与4互相指向,所以此处要断开3.next=null 此时链表为1->2->3<-4<-5 返回节点5 第四轮出栈,head为2,head.next为3,执行head.next.next=head也就是3.next=2, 此时链表为1->2<->3<-4<-5,由于2与3互相指向,所以此处要断开2.next=null 此时链表为1->2<-3<-4<-5 返回节点5 第五轮出栈,head为1,head.next为2,执行head.next.next=head也就是2.next=1, 此时链表为1<->2<-3<-4<-5,由于1与2互相指向,所以此处要断开1.next=null 此时链表为1<-2<-3<-4<-5 返回节点5 出栈完成,最终头节点5->4->3->2->1 */ head.next.next = head; head.next = null; return newHead; }
- 时间复杂度: O(n)
- 空间复杂度: O(n)
24. 两两交换链表中的节点
思路:模拟
初始时设
cur = dummyHead , ListNode* tmp = cur->next(记录节点1),ListNode* tmp1 = cur->next->next->next(记录节点3)要保存1、3节点是因为步骤1会切断cur->next,步骤2会切断节点2->next
步骤一:
cur->next = cur->next->next;//现在cur->next已经变成2了步骤二:
cur->next->next = tmp;//2->next = tmp = 1,现在cur->next->next指向1步骤三:
cur->next->next->next = tmp1//1 -> next = tmp1 = 3后移:之后每次将cur后移2个单位。因为交换相邻元素每两个元素是一个单位。
最后别忘记删除虚拟头结点,返回的是
dummyHead -> next
/**
* Definition for singly-linked list.
* 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) {}
* };
*/
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
ListNode* dummyHead = new ListNode(0);
dummyHead->next = head;
ListNode* cur = dummyHead;
while(cur->next != NULL && cur->next->next != NULL){
ListNode* tmp = cur->next; //1
ListNode* tmp1 = cur->next->next->next; // 3
cur->next = cur->next->next;// cur -> 2
cur->next->next = tmp; // 2 -> 1
cur->next->next->next = tmp1; // 1 -> 3
cur = cur->next->next;
}
ListNode* res = dummyHead->next;
delete dummyHead;
return res;
}
};



浙公网安备 33010602011771号