11.15灵神题单补充
11/15
每日一题:3239. 最少翻转次数使二进制矩阵回文 I
思路:模拟即可
先遍历行,累加每行需要翻转的个数,总和记为
diff_row,+=的优先级是小于!=的,所以会先比较,再累加。int diff_row = 0; for(auto &row : grid){ for(int j = 0 ; j < n / 2 ; j ++){ diff_row += row[j] != row[n - 1 - j]; } }
优先级(从高往低) 运算符 结合律 助记 1 ::从左至右 作用域 2 a++、a--、type()、type{}、a()、a[]、.、->从左至右 后缀自增减、 函数风格转型、 函数调用、下标、 成员访问 3 !、~、++a、--a、+a、-a、(type)、sizeof、&a、*a、new、new[]、delete、delete[]从右至左 逻辑非、按位非、 前缀自增减、正负、 C 风格转型、取大小、取址、 指针访问、 动态内存分配 4 .*、->*从左至右 指向成员指针 5 a*b、a/b、a%b从左至右 乘除、取模 6 a+b、a-b从左至右 加减 7 <<、>>从左至右 按位左右移 8 <、<=、>、>=从左至右 大小比较 9 ==、!= 从左至右 等价比较 10 a&b从左至右 按位与 11 ^从左至右 按位异或 12 ` ` 从左至右 13 &&从左至右 逻辑与 14 ` ` 15 a?b:c、=、+=、-=、*=、/=、%=、&=、^=、`= 、<<=、>>=`从右至左 16 ,从左至右 逗号 同理,累加
diff_colint diff_col = 0; for(int j = 0 ; j < n ; j ++){ //先列后行,固定同一列比较不同行 for (int i = 0 ; i < m / 2; i ++ ){ diff_col += grid[i][j] != grid[m - 1 - i][j]; } } 返回最小值即可。
class Solution {
public:
int minFlips(vector<vector<int>>& grid) {
int m = grid.size() , n = grid[0].size();
int diff_row = 0;
for (auto &row : grid){
for (int j = 0; j < n / 2; j++) {
diff_row += row[j] != row[n - 1 - j];
}
}
int diff_col = 0;
for (int j = 0; j < n; j++) {
for (int i = 0; i < m / 2; i++) {
diff_col += grid[i][j] != grid[m - 1 - i][j];
}
}
return min(diff_row , diff_col);
}
};
3217. 从链表中移除在数组中存在的节点
思路:哈希表+遍历链表
由于直接判断节点值是否在
nums中要遍历nums,时间复杂度为 O(n)。通过把 nums 中的元素加到一个哈希集合中,然后判断节点值是否在哈希集合中,这样可以做到每次判断时间复杂度为 O(1)。 由于头节点可能会被删除,在头节点前面插入一个哨兵节点
dummyHead。 删除一个节点需要它的上一个节点,所以初始化
cur = dummyHead时,要先判断下一个节点cur->next->val是否在哈希表中,如果没有才能移动cur。
/**
* 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* modifiedList(vector<int>& nums, ListNode* head) {
ListNode* dummyHead = new ListNode(0);
dummyHead->next = head;
unordered_set<int> s(nums.begin() , nums.end());
ListNode* cur = dummyHead;
while(cur->next ) {
if(s.count(cur->next->val)) cur->next = cur->next->next; //这里cur->next->next可能为nullptr,但是没关系。
else cur = cur->next;
}
return dummyHead->next;
}
};
83. 删除排序链表中的重复元素
思路:空表需要特判一下
class Solution {
public:
ListNode* deleteDuplicates(ListNode* head) {
ListNode* cur = head;
if (!cur) return nullptr;
while(cur->next){
if(cur->val == cur->next->val) cur->next = cur->next->next;
else cur = cur->next;
}
return head;
}
};
82. 删除排序链表中的重复元素 II
思路:注意逻辑
由于可能删除头结点,因此需要定义
dummyHead,(与上题对比,上题保留一个重复数,因此不可能删除head)。 cur初始在dummyHead处,只有在
cur->next->val != cur->next->next->val时(后两个数不同时)才会向后移动,因此if的判断条件为(cur->next->next->val == val)。由于一个重复数都不保留,因此先把cur->next->val取出来给val。当cur->next->next->val == val时,循环判断cur->next && cur->next->val == val后一个数是否也等于val,是就跳过,直到不是为止。 从始至终cur就没移动过,因为可能出现{1,2,2,3,3}的情况。只有在
cur->next->val != cur->next->next->val时(后两个数不同时)cur才会向后移动。
class Solution {
public:
ListNode* deleteDuplicates(ListNode* head) {
ListNode* dummyHead = new ListNode(0);
dummyHead->next = head;
ListNode* cur = dummyHead;
while(cur->next && cur->next->next){
int val = cur->next->val;
if(cur->next->next->val == val){
while(cur->next && cur->next->val == val){
cur->next = cur->next->next;
}
}
else cur = cur->next;
}
return dummyHead->next;
}
};
237. 删除链表中的节点
思路:用后一个节点的值赋给当前node,然后改变指针跳过下一个节点即可。
class Solution {
public:
void deleteNode(ListNode* node) {
node->val = node->next->val;
node->next = node->next->next;
}
};
1669. 合并两个链表
思路:模拟即可
找到a的前一个节点cur1,b节点cur2,list2的尾节点cur。
令
cur1->next = list2 , cur->next = cur2->next , cur2->next = nullptr即可。
class Solution {
public:
ListNode* mergeInBetween(ListNode* list1, int a, int b, ListNode* list2) {
ListNode* cur1 = list1;
ListNode* cur2 = list1;
for (int i = 0; i < a - 1; i++) {
cur1 = cur1->next;
}
for (int i = 0; i < b ; i++) {
cur2 = cur2->next;
}
ListNode* cur = list2;
while(cur->next){
cur = cur->next;
}
//此时cur在list2的尾节点,cur1是在a的前一个节点,cur2在b节点。
cur1->next = list2;
cur->next = cur2->next;
cur2->next = nullptr;
return list1;
}
};
2487. 从链表中移除节点
思路:这题本质上是说,从链表尾tail开始遍历,比tail->val大的留下,小的删除。
方法一:递归
既然要倒着看最大值,那么用递归解决是最合适的,毕竟递归本质就是在倒着遍历链表。
方法二:迭代
通过
反转链表,我们可以从反转后的链表头开始,像删除排序链表中的重复元素那样,删除比当前节点值小的元素。最后再次反转链表,即为答案。 代码上来看递归更简介,迭代更清晰。
递归法:
class Solution {
public:
ListNode* removeNodes(ListNode* head) {
if(head->next == nullptr) return head;//结束条件
ListNode* node = removeNodes(head->next);// 此时node应该为尾节点tail,出栈从后往前遍历
//如果tail值大于head(tail的上一个节点)值,返回node,删除head
if(node->val > head->val) return node;
//如果node(tail)值小于head值,将head接到node前面
head->next = node;
// 返回的链表头一定是最大的
return head;
}
};
- 时间复杂度:O(n),其中 n 为链表的长度。
- 空间复杂度:O(n),需要 O(n) 的栈空间。
迭代法:
class Solution {
public:
ListNode* reverseList(ListNode* head){
ListNode *pre = nullptr , *cur = head;
while(cur){
ListNode* tmp = cur->next;
cur->next = pre;
pre = cur;
cur = tmp;
}
return pre;
}
ListNode* removeNodes(ListNode* head) {
head = reverseList(head);
ListNode* cur = head;
while(cur->next){
if(cur->val > cur->next->val){
cur->next = cur->next->next;
}
else cur = cur->next;
}
return reverseList(head);
}
};
2807. 在链表中插入最大公约数
思路:写一个找最大公约数的函数然后循环插入即可。
调用cpp内置的gcp函数可以更简洁。
gcd函数简介
最大公因数(英语:highest common factor,hcf)也称 最大公约数(英语:greatest common divisor,gcd)是数学词汇,指能够整除多个整数的最大正整数。而多个整数不能都为零。例如8和12的最大公因数为4。
求两个整数最大公约数主要的方法:
1.穷举法:分别列出两整数的所有约数,并找出最大的公约数。
2.素因数分解:分别列出两数的素因数分解式,并计算共同项的乘积。
3.短除法:两数除以其共同素因数,直到两数互素时,所有除数的乘积即为最大公约数。
4.辗转相除法:两数相除,取余数重复进行相除,直到余数为0时,前一个除数即为最大公约数。程序如下:int gcd(int a,int b) { int r; while(b>0) { r=a%b; a=b; b=r; } return a; }递归+三元运算符实现
int gcd(int a,int b) { return b > 0 ? gcd(b, a % b) : a; }本题直接调用gcd函数即可:
cur->next = new ListNode(gcd(cur->val, cur->next->val), cur->next);
class Solution {
private:
int Find(int x , int y){
if(x == y) return x;
if(x == 1 || y == 1) return 1;
int res = 1;
for (int i = 2; i <= min(x , y); i++) {
if(x % i == 0 && y % i == 0) res = max(res , i);
}
return res;
}
/*亦可写成
int Find(int x , int y){
int r;
while(y > 0){
r = x % y;
x = y ;
y = r;
}
return x;
}*/
public:
ListNode* insertGreatestCommonDivisors(ListNode* head) {
ListNode* cur = head;
if(!cur->next) return head;
while(cur->next){
int val = Find(cur->val , cur->next->val);
ListNode* node = new ListNode(val);
node->next = cur->next;
cur->next = node;
cur = node->next;
}
return head;
}
};
灵神的简单写法:
class Solution {
public:
ListNode *insertGreatestCommonDivisors(ListNode *head) {
for (auto cur = head; cur->next; cur = cur->next->next) {
//Listnode有两个参数val , *next
cur->next = new ListNode(gcd(cur->val, cur->next->val), cur->next);
}
return head;
}
};
147. 对链表进行插入排序
思路:
对于链表而言,插入元素时只要
更新相邻节点的指针即可,不需要像数组一样将插入位置后面的元素往后移动,因此插入操作的时间复杂度是 O(1),但是找到插入位置需要遍历链表中的节点,时间复杂度是 O(n),因此链表插入排序的总时间复杂度仍然是 \(O(n ^2)\),其中 n 是链表的长度。 对于单向链表而言,只有指向后一个节点的指针,因此需要从链表的头节点开始往后遍历链表中的节点,寻找插入位置,具体过程如下。
先判断链表是否为空,若为空直接返回。
创建
dummyHead,便于在 head 节点之前插入节点。维护last为链表的已排序部分的最后一个节点,初始时last= head。维护cur为待插入的元素,初始时cur = head.next。每次比较
last和cur的节点值。若last.val <= cur.val,说明cur应该位于last之后,将last后移一位,cur变成新的last。否则,从链表的头节点开始往后遍历链表中的节点,寻找插入
cur的位置。令pre为插入cur的位置的前一个节点,进行如下操作,完成对 cur 的插入:last.next = cur.next//注意保存cur->next,cur是在原链表上往后移动 cur.next = prev.next prev.next = cur
令
cur = last.next,此时cur为下一个待插入的元素。重复直到
cur变成空,排序结束。返回
dummyHead.next,为排序后的链表的头节点。注意细节:是要加大于等于的,如果只写>,后面else里pre会走到last,while跳不出循环。
if(cur->val >= last->val) last = last->next;
class Solution {
public:
ListNode* insertionSortList(ListNode* head) {
if(!head) return head;
ListNode* dummyHead = new ListNode(0);
dummyHead->next = head;
ListNode* last = head;
ListNode* cur = head->next;
while(cur){
if(last->val <= cur->val){//如果cur比last大,则直接插到last后面
last = last->next;
}
else{//如果cur比last小,则每次从头寻找插入点
ListNode* pre = dummyHead;
while(pre->next->val <= cur->val){
pre = pre->next;
}//此时pre < cur < pre->next
last->next = cur->next;//防止cur->next链丢失,用last->next存起来,cur是在原链表上滑动
cur->next = pre->next;
pre->next = cur;
}
cur = last->next;//每次cur往后移一位
}
return dummyHead->next;
}
};
LCR 029. 循环有序列表的插入
思路:分类讨论
首先根据样例分为三种情况:
- 空链表则直接插入
- 链表只有一个值,直接插到后边
- 链表有两个以上的值:初始定义cur=head, tmp为head->next。遍历过程中每次把cur、tmp后移。过程中判断inseVal是否可以在cur、tmp之间插入,因为题目只要找到就可以,一共分为以下三种情况:
- cur.val <= insertVal <= tmp.val
- cur.val > tmp.val && insertVal > cur.val,此时 cur 和 tmp 分别是循环链表中的值最大的节点和值最小的节点。insertVal比最大值还大。
- cur.val > tmp.val && insertVal < tmp.val,此时 cur 和 tmp 分别是循环链表中的值最大的节点和值最小的节点。insertVal比最小值还小。
class Solution {
public:
Node* insert(Node* head, int insertVal) {
Node* node = new Node(insertVal);
if(!head) {
node->next = node;
return node;
}
if(head->next == head) {
head->next = node;
node->next = head;
return head;
}
Node *cur = head, *tmp = head->next;
while(tmp != head){//定义tmp是为了方便判断走一圈
//cur < insertVal < tmp 升序排列
if(insertVal >= cur->val && insertVal <= tmp->val) break;
if(cur->val > tmp->val){//当cur是最大值,tmp是最小值时
//如果insertVal比最大值大,最小值小
if(insertVal > cur->val || insertVal < tmp->val) break;
}
cur = cur->next;
tmp = tmp->next;
}
//以上情况都是把node插入cur、tmp之间
cur->next = node;
node->next = tmp;
return head;
}
};

浙公网安备 33010602011771号