[做题]双指针

第一天刷题。一个平实的开始,希望能坚持下来,不求波涛汹涌,大浪淘沙,但求静水流深,川流不息。

先学习双指针。题目方向分为两个:链表和数组。

在处理数组和链表相关问题时,双指针技巧是经常用到的,双指针技巧主要分为两类:左右指针快慢指针

所谓左右指针,就是两个指针相向而行或者相背而行;而所谓快慢指针,就是两个指针同向而行,一快一慢。

文章目录

      • 链表中的双指针
    • 第一道:21.合并两个有序链表
    • 第二道:分割链表
    • 第三道:合并K个升序链表
    • 第四道:单链表的倒数第 k 个节点
    • 第五道:链表的中间结点
    • 第六道:相交链表
    • 第七道:判断是否为环形链表
    • 第八道:找到环形链表的起点
      • 数组中的双指针
    • 第一道: [删除有序数组中的重复项](https://leetcode.cn/problems/remove-duplicates-from-sorted-array/)
    • 第二道:移除元素
    • 第三道:移动零
    • 第四道:最长回文子串

链表中的双指针

第一道:21.合并两个有序链表

思路一:申请第三个链表的空间,拷贝两个链表中较小的一个。

代码v1.0

class Solution {
public:bool comp(ListNode* l1, ListNode* l2){if(l1->val > l2->val) return true;else return false;}bool Empty_Judge(ListNode* l){if(l->val==0) return true;else return false;}ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {if(Empty_Judge(list1)) return list2;if(Empty_Judge(list2)) return list1;ListNode* list ;ListNode* head = list;while(list1!=nullptr&&list2!=nullptr){list = new ListNode;if(comp(list1,list2)){list->val = list2->val;list = list->next;list2 = list2->next;}else{list->val = list1->val;list = list->next;list1 = list1->next;}}while(list1!=nullptr){list = new ListNode(list1->val);list=list->next;list1=list1->next;}while(list2!=nullptr){list = new ListNode(list2->val);list=list->next;list2=list2->next;}return head;}};

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

问题:返回的 head 没有任何数值。

解决:

  • 链表到下一个结点前需要空间,否则指向NULL就是野指针,也就不可能连上。

    代码开始,head 指向了没有初始化的 list ,即指向一个(指向null的)野指针,它自己也变成了野指针。如果初始化工作做好,不解决后续连接过程的“先申请空间,再连接”,就会只有一个数据。

  • 条件判断分不清 NULL 、nullptr 及其背后的 true 、false,造成代码冗余。

    Empty_judge 和 comp 函数是完完全全的多余代码,因为本可以直接在原代码中判断。

代码V2.0:

class Solution {
public:ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {if(!list1) return list2;if(!list2) return list1;ListNode* list = new ListNode ;ListNode* head = list;while(list1&&list2){if(list1->val > list2->val){list->val = list2->val;list2 = list2->next;}else{list->val = list1->val;list1 = list1->next;}list->next = new ListNode;list = list->next;}while(list1){list ->val = list1 -> val;if(list1->next) list->next = new ListNode;list=list->next;list1=list1->next;}while(list2){list ->val = list2 -> val;if(list2->next) list->next = new ListNode;list=list->next;list2=list2->next;}return head;}};

规避了野指针问题和相对的代码冗余问题。


思路二:在两个指针现有的空间上,只做穿针引线的工作(连接起来)

    ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {if(!list1) return list2;   //可以省略if(!list2) return list1;ListNode* list = new ListNode;ListNode* head = list;while(list1&&list2){ if(list1->val > list2->val){list -> next = list2;list = list2;  //这里可以配合下面简化list2 = list2->next;}else{list -> next = list1;list = list1;  //简化list1 = list1->next;//list = list->next}}list -> next = list1 ? list1 : list2;  return head -> next;}

问题:

  • 开始的判断是否为空可以省略。
  • while 中出现重复点(只是需要一点点直觉)

解决:

看思路三。


**思路三:**labuladong ,虚拟头结点值得注意。整体与思路二没什么区别。

ListNode mergeTwoLists(ListNode l1, ListNode l2) {// 虚拟头结点ListNode dummy = new ListNode(-1), p = dummy;ListNode p1 = l1, p2 = l2;
while (p1 != null && p2 != null) {// 比较 p1 和 p2 两个指针// 将值较小的的节点接到 p 指针if (p1.val > p2.val) {p.next = p2;p2 = p2.next;} else {p.next = p1;p1 = p1.next;}// p 指针不断前进p = p.next;
}if (p1 != null) {p.next = p1;
}if (p2 != null) {p.next = p2;
}return dummy.next;

}

代码中还用到一个链表的算法题中是很常见的「虚拟头结点」技巧,也就是 dummy 节点。你可以试试,如果不使用 dummy 虚拟节点,代码会复杂很多,而有了 dummy 节点这个占位符,可以避免处理空指针的情况,降低代码的复杂性。


拓展:

思路四:递归

struct ListNode* mergeTwoLists(struct ListNode* l1, struct ListNode* l2) {if(l1==NULL)return l2;if(l2==NULL)return l1;if(l1->val < l2->val){l1->next = mergeTwoLists(l1->next,l2);return l1;}else{l2->next = mergeTwoLists(l1,l2->next);return l2;}
}

类似汉诺塔的思路。


第二道:分割链表

V1.0

class Solution {
public:ListNode* partition(ListNode* head, int x) {//初始化ListNode* tail = head;while(tail->val < x )  tail=tail->next;ListNode* temp = tail;ListNode* maxdot = tail;//大于值开始的结点while(head->val > x) head = head->next;tail = head; //小于值开始的结点while(temp){//<xif(temp->val < x){  //如果是小于值结点tail ->next = temp;tail = tail->next;}//>xif(temp->val >x &&temp->next->val < x ){tail->next = temp->next;if(temp->next->next)  temp->next = temp->next->next;else {tail->next = temp->next;temp->next = nullptr;break;}}temp = temp->next;}tail->next = maxdot;return head;}
};

现在思路也不是很清晰

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

思路二:双头结点

ListNode partition(ListNode head, int x) {// 存放小于 x 的链表的虚拟头结点ListNode* dummy1 = new ListNode(-1);// 存放大于等于 x 的链表的虚拟头结点ListNode* dummy2 = new ListNode(-1);// p1, p2 指针负责生成结果链表ListNode* p1 = dummy1, p2 = dummy2;// p 负责遍历原链表,类似合并两个有序链表的逻辑// 这里是将一个链表分解成两个链表ListNode* p = head;while (p != null) {if (p.val >= x) {p2.next = p;p2 = p2.next;} else {p1.next = p;p1 = p1.next;}// 断开原链表中的每个节点的 next 指针ListNode temp = p.next;p.next = null;p = temp;}// 连接两个链表p1.next = dummy2.next;return dummy1.next;
}

创建两个头结点,先断开,后连接。

思路三:递归

class Solution {
public:ListNode* small = nullptr;pair<ListNode*, ListNode*> helper(ListNode* head, int x) {if (head == nullptr) return make_pair(nullptr, nullptr);if (head->val < x) small = head;pair<ListNode*, ListNode*> next = helper(head->next, x);if (head->val < x) {head->next = next.first;next.first = head;}else {head->next = next.second;next.second = head;}return next;}ListNode* partition(ListNode* head, int x) {pair<ListNode*, ListNode*> nodes = helper(head, x);if (nodes.first) {small->next = nodes.second;return nodes.first;}else {return nodes.second;}}
};

大多数链表题递归的时候可以无脑写一句,然后只要让思路从后向前进行就可以了。

cppListNode* node = func(head->next);  // func为递归函数

这里返回值选择了两个pair,first保存小于x的结点,second保存大于x的结点。

思路四:原地连接

// 1 4 3 2 5 2 
struct ListNode* partition(struct ListNode* head, int x){if(!head || !head->next){return head;}struct ListNode *p = head, *q = head, *temp = NULL;if(p->val < x){ while(p->next->val < x){//此循环让 p 到达最远小值  1p = p->next; if(p->next == NULL){return head;}}q = p->next;   //让 q 到达最近大值  4}else{p = NULL;  }while(q->next){ //q存在下一个结点if(q->next->val < x){  //如果最近大值下一个为小值temp = q->next; //temp为小值了q->next = temp->next;//跳过小值if(p == NULL){ //如果起始值为大值(即p 为空情况)temp->next = head; //头插法:第一个插入的小值连接起始大值head = temp;p = head;}else{//起始值小值(存在了)temp->next = p->next; //头插法:小值后p->next = temp;p = temp;}}else{ //为大值就串下去q = q->next;}}return head;
}

p指针指向小于 x 的值,p == null 就连接第一个大于x的值,p!=null 就按顺序进行尾插法;
(只是 p 的"尾"是 q 的"头")
q指针指向大于 x 的值,初始是第一个大于x的值, 尾插法依次往后放;
temp 服务于p的尾插法,作为中间变量。
(q 尾插循环的前面,是p,q 的初始化工作,分别指定小于x值的尾端 和 大于x值的尾端)

第三道:合并K个升序链表

思路一:遍历合并两个链表到第一个链表中去

class Solution {
public:ListNode* merge(ListNode* list1,ListNode* list2) //这里和合并有序链表的code一模一样{...}ListNode* mergeKLists(vector<ListNode*>& lists) {//遍历合并if(lists.empty()) return nullptr;vector<ListNode*>::iterator it = lists.begin();vector<ListNode*>::iterator next = lists.begin()+1;for(;next!=lists.end();next++){*it = merge(*it,*next);}return *it;}
};

**思路二:**labladong :配合优先队列的二叉堆,获取最小k结点

ListNode mergeKLists(ListNode[] lists) {if (lists.length == 0) return null;// 虚拟头结点ListNode* dummy = new ListNode(-1);ListNode* p = dummy;// 优先级队列,最小堆PriorityQueue<ListNode*> pq = new PriorityQueue<>(lists.length, (a, b)->(a.val - b.val));// 将 k 个链表的头结点加入最小堆for (ListNode head : lists) {if (head != null)pq.add(head);}while (!pq.isEmpty()) {// 获取最小节点,接到结果链表中ListNode node = pq.poll();p.next = node;if (node.next != null) {pq.add(node.next);}// p 指针不断前进p = p.next;}return dummy.next;
}

思路三:分治递归

class Solution {public ListNode mergeKLists(ListNode[] lists){if(lists.length == 0)return null;if(lists.length == 1)return lists[0];if(lists.length == 2){return mergeTwoLists(lists[0],lists[1]);}int mid = lists.length/2;ListNode[] l1 = new ListNode[mid];for(int i = 0; i < mid; i++){l1[i] = lists[i];}ListNode[] l2 = new ListNode[lists.length-mid];for(int i = mid,j=0; i < lists.length; i++,j++){l2[j] = lists[i];}return mergeTwoLists(mergeKLists(l1),mergeKLists(l2));}public ListNode mergeTwoLists(ListNode l1, ListNode l2) {if (l1 == null) return l2;if (l2 == null) return l1;ListNode head = null;if (l1.val <= l2.val){head = l1;head.next = mergeTwoLists(l1.next, l2);} else {head = l2;head.next = mergeTwoLists(l1, l2.next);}return head;}
}

思路四:STL中的优先队列

class Solution {
public:ListNode* mergeKLists(vector<ListNode*>& lists) {auto head = ListNode(0);auto comp = [](ListNode* const &a, ListNode* const &b){return a->val > b->val;};priority_queue<ListNode*, vector<ListNode*>, decltype(comp)> q(comp);for (auto &h : lists) if (h != nullptr) q.push(h);auto p = &head;while (!q.empty()) {p->next = q.top();p = p->next;q.pop();if (p->next != nullptr) q.push(p->next);}return head.next;}
};

第四道:单链表的倒数第 k 个节点

ListNode* removeNthFromEnd(ListNode* head, int n) {if(n==0||!head) return head;    //0元素 或 不动if(!head->next) return NULL;    //1元素 且 动ListNode* fformer = head;ListNode* former = head;ListNode* latter = head;while(--n>0) latter = latter->next;while(latter->next){if(former!=head) fformer = fformer->next;latter = latter -> next;former = former -> next;} //1元素if(former == fformer) head = former->next; //2元素else fformer->next = former->next;   //3+元素return head;

第五道:链表的中间结点

    ListNode* middleNode(ListNode* head) {ListNode* fast = head;ListNode* slow = head;while(fast->next){fast = fast->next;if(fast->next) fast=fast->next;slow = slow->next;}return slow;}

第六道:相交链表

    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {/**定义两个指针, 第一轮让两个到达末尾的节点指向另一个链表的头部, 最后如果相遇则为交点(在第一轮移动中恰好抹除了长度差)两个指针等于移动了相同的距离, 有交点就返回, 无交点就是各走了两条指针的长度**/if(headA == null || headB == null) return null;ListNode pA = headA, pB = headB;// 在这里第一轮体现在pA和pB第一次到达尾部会移向另一链表的表头, 而第二轮体现在如果pA或pB相交就返回交点, 不相交最后就是null==nullwhile(pA != pB) {pA = pA == null ? headB : pA.next;pB = pB == null ? headA : pB.next;}return pA;}

这个赋值和 条件判断 的配合非常有趣。

简短的 if 语句可用 ?来代替。

思路二:

public ListNode getIntersectionNode(ListNode headA, ListNode headB) {int lenA = 0, lenB = 0;// 计算两条链表的长度for (ListNode p1 = headA; p1 != null; p1 = p1.next) {lenA++;}for (ListNode p2 = headB; p2 != null; p2 = p2.next) {lenB++;}// 让 p1 和 p2 到达尾部的距离相同ListNode p1 = headA, p2 = headB;if (lenA > lenB) {for (int i = 0; i < lenA - lenB; i++) {p1 = p1.next;}} else {for (int i = 0; i < lenB - lenA; i++) {p2 = p2.next;}}// 看两个指针是否会相同,p1 == p2 时有两种情况:// 1、要么是两条链表不相交,他俩同时走到尾部空指针// 2、要么是两条链表相交,他俩走到两条链表的相交点while (p1 != p2) {p1 = p1.next;p2 = p2.next;}return p1;
}

第七道:判断是否为环形链表

boolean hasCycle(ListNode head) {// 快慢指针初始化指向 headListNode slow = head, fast = head;// 快指针走到末尾时停止while (fast != null && fast.next != null) {// 慢指针走一步,快指针走两步slow = slow.next;fast = fast.next.next;// 快慢指针相遇,说明含有环if (slow == fast) {return true;}}// 不包含环return false;
}

不解释

第八道:找到环形链表的起点

流程起始同上,先让快慢指针相遇

  • 设相遇点距离头结点为 k ,即慢指针的路程。快指针速度是慢指针二倍,路程即 k+k=2k

  • 设环形起点距离相遇点为 m , 则慢指针距离起点 k-m。巧合的是,相遇点指针不断向后走,距离起点同样为k-m

  • 所以让其中任意一个指针回到起点,经过k-m位移,另一个指针就会到达环形链表起点。

ListNode detectCycle(ListNode head) {ListNode fast, slow;fast = slow = head;while (fast != null && fast.next != null) {fast = fast.next.next;slow = slow.next;if (fast == slow) break;}// 上面的代码类似 hasCycle 函数if (fast == null || fast.next == null) {// fast 遇到空指针说明没有环return null;}// 重新指向头结点slow = head;// 快慢指针同步前进,相交点就是环起点while (slow != fast) {fast = fast.next;slow = slow.next;}return slow;
}

数组中的双指针

在数组中并没有真正意义上的指针,但我们可以把索引当做数组中的指针,这样也可以在数组中施展双指针技巧。

第一道: 删除有序数组中的重复项

基本的快慢双指针。

int removeDuplicates(int[] nums) {if (nums.length == 0) {return 0;}int slow = 0, fast = 0;while (fast < nums.length) {if (nums[fast] != nums[slow]) {slow++;// 维护 nums[0..slow] 无重复nums[slow] = nums[fast];}fast++;}// 数组长度为索引 + 1return slow + 1;
}

大同小异的一个链表题:

删除排序链表中的重复元素](https://leetcode.cn/problems/remove-duplicates-from-sorted-list/)

struct ListNode* deleteDuplicates(struct ListNode* head){typedef struct ListNode ListNode;if(!head) return head;ListNode* slow = head;ListNode* fast = head;while(fast){if(fast->val!=slow->val){slow=slow->next;slow->val=fast->val;}fast=fast->next;}slow->next = NULL;return head;

第二道:移除元素

左右指针:

class Solution {
public:int removeElement(vector<int>& nums, int val) {int j = nums.size() - 1;for (int i = 0; i <= j; i++) {if (nums[i] == val) {swap(nums[i--], nums[j--]);}}return j + 1;}
};

快慢指针:

    while(fast<numsSize){if(nums[fast]!=val){nums[slow] = nums[fast];slow++;}fast++;}return slow;

扩展:保留重复k个元素的基础上删除多余元素

class Solution {
public:int work(vector<int>& nums, int k) {int len = 0;for(auto num : nums)if(len < k || nums[len-k] != num)nums[len++] = num;return len;}int removeDuplicates(vector<int>& nums) {return work(nums, 2);}
};

第三道:移动零

v1.0

    void moveZeroes(vector<int>& nums) {int fast = 0,slow=0;while(fast<nums.size()){if(nums[fast]!=0){nums[slow++] = nums[fast];}fast++;}while(slow<fast){nums[slow++]=0;}}

v2.0

        int left = 0;int right = 0;while(right < nums.size()){if(nums[right]){swap(nums[left], nums[right]);left++;}right++;}

遇到非0直接交换就可以

第四道:最长回文子串

  string longestPalindrome(string s) {string res = "";for(int i=0;i<s.size();i++){string s1=palindrome(s,i,i);string s2=palindrome(s,i,i+1);res = s1.size()>res.size()?s1:res;res = s2.size()>res.size()?s2:res;}return res;}string palindrome(string s,int l,int r){while(l>=0&&r<s.size()&&s[l]==s[r]){l--;r++;};return s.substr(l+1,r-l-1);}

左右指针,向两侧扩散。

遍历中心点。

中心点分奇数、偶数两种情况,依次比较即可。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/542795.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SQLiteC/C++接口详细介绍之sqlite3类(十三)

返回目录&#xff1a;SQLite—免费开源数据库系列文章目录 上一篇&#xff1a;SQLiteC/C接口详细介绍之sqlite3类&#xff08;十二&#xff09; 下一篇&#xff1a;SQLiteC/C接口详细介绍之sqlite3类&#xff08;十四&#xff09;&#xff08;未发表&#xff09; 40.sqlite3…

[SaaS] 淘宝设AI

“淘宝设计AI” 让国际大牌造世界双11超级品牌 超级发布https://mp.weixin.qq.com/s/xFVDARQHxlweKAYG91DtYw下面是一个完整的品牌营销海报设计流程&#xff0c;AIGC起到了巨大作用&#xff0c;但是仍然很难去一步解决这个问题&#xff0c;还是逐步修改的一个过程。 Midjouner…

26.网络游戏逆向分析与漏洞攻防-网络通信数据包分析工具-实现生成日志文件的功能

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 如果看不懂、不知道现在做的什么&#xff0c;那就跟着做完看效果 内容参考于&#xff1a;易道云信息技术研究院VIP课 上一个内容&#xff1a;25.利用全新的通…

Mybatis sql 控制台格式化

package com.mysql; import org.apache.commons.lang.StringUtils; import org.apache.ibatis.logging.Log;import java.util.*;/*** Description: sql 格式化* Author: DingQiMing* Date: 2023-07-17* Version: V1.0*/ public class StdOutImpl implements Log {private stati…

别忽视平台的重要性

目录 前言&#xff1a; 与谁在一起&#xff0c;真的很重要 别把运气当才华&#xff0c;别把平台当本事 珍惜平台&#xff0c;用好平台 前言&#xff1a; 对于做技术开发的人来说&#xff0c;一头扎进技术里面去固然重要&#xff0c;但是很多时候&#xff0c;也要看看人际交…

提高安全投资回报:威胁建模和OPEN FAIR™风险分析

对大多数人和企业来说&#xff0c;安全意味着一种成本。但重要的是如何获得适合的量&#xff0c;而不是越多越好。然而&#xff0c;你如何决定什么时候可以有足够的安全性&#xff0c;以及你如何获得它&#xff1f;则完全是另一回事。 该篇文章是由The Open Group安全论坛主办&…

英国伦敦交易所股票清单列表数据API接口

# Restful API https://tsanghi.com/api/fin/stock/XLON/list?token{token}更新时间&#xff1a;收盘后3~4小时。 更新周期&#xff1a;每天。 请求方式&#xff1a;GET。 # 测试&#xff1a;返回不超过10条数据&#xff08;2年历史&#xff09; https://tsanghi.com/api/fin/…

Postman进行Websocket接口测试

Postman进行Websocket接口测试 前言下载地址使用1、new一个一个WebSocket Request2、填写内容和需要请求头携带的参数3、表示成功 网页请求101表示握手成功 前言 有些较低版本postman不支持websocket接口测试&#xff0c;如果根据此文未找到创建websocket接口测试的目录&#…

SQLiteC/C++接口详细介绍之sqlite3类(十二)

返回目录&#xff1a;SQLite—免费开源数据库系列文章目录 上一篇&#xff1a;SQLiteC/C接口详细介绍之sqlite3类&#xff08;十一&#xff09; 下一篇&#xff1a;SQLiteC/C接口详细介绍之sqlite3类&#xff08;十三&#xff09; ​37.sqlite3_load_extension 用于在SQLit…

springboot“爱相连”儿童慈善管理系统的设计与实现

摘要 随着网络科技的不断发展以及人们经济水平的逐步提高&#xff0c;网络技术如今已成为人们生活中不可缺少的一部分&#xff0c;而信息管理系统是通过计算机技术&#xff0c;针对用户需求开发与设计&#xff0c;该技术尤其在各行业领域发挥了巨大的作用&#xff0c;有效地促…

Unity AI Navigation插件快速使用方法

AI Navigation插件使您能够创建能够在游戏世界中智能移动的角色。这些角色利用的是根据场景几何结构自动生成的导航网格。障碍物可以让您在运行时改变角色的导航路径。 演示使用的Unity版本为Tuanjie 1.0.0,团结引擎是Unity中国的引擎研发团队基于Unity 2022 LTS版本为中国开发…

免费阅读篇 | 芒果YOLOv8改进111:注意力机制CBAM:轻量级卷积块注意力模块,无缝集成到任何CNN架构中,开销可以忽略不计

&#x1f4a1;&#x1f680;&#x1f680;&#x1f680;本博客 改进源代码改进 适用于 YOLOv8 按步骤操作运行改进后的代码即可 该专栏完整目录链接&#xff1a; 芒果YOLOv8深度改进教程 该篇博客为免费阅读内容&#xff0c;YOLOv8CBAM改进内容&#x1f680;&#x1f680;&am…