LeetCode刷题之双指针

leetcode26:删除排序数组中的重复元素(留一个)
给你一个有序数组 nums ,请你 原地 删除重复出现的元素,使每个元素只出现一次 ,返回删除后数
组的新长度。
不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。
输入:nums = [1,1,2]
输出:2, nums = [1,2]
解释:函数应该返回新的长度 2 ,并且原数组 nums 的前两个元素被修改为 1, 2 。不需要考虑数组
中超出新长度后面的元素。
解法分析:
既然数组是有序的,那么可以考虑双指针。定义一个指针 pos 指向需要修改的位置,另一个指针 cur
指向当前位置。
当 cur 指向的数与 pos 的前一个数不同时,将 pos 的数赋值为 cur 指向的数,完成更改。最后的
数组长度就是 pos 的大小。
实现细节描述:
这种解法相当于"快慢指针",当循环结束时,快指针对整个数组进行遍历,因此可以采用 for 循环。
由于cur的数需要与pos的前一个数进行比较,因此需要从下标为1的位置开始索引,因此需要单独考虑
当数组长度为0的情况。
int removeDuplicates(vector<int>& nums){
    if(nums.size() < 1) return 0; // 单独判断数组长度小于pos的情况
    int pos=1;
    for(int cur=1; cur<nums.size(); cur++){
    if(nums[cur] != nums[pos-1]) // 赋值替换
    	nums[pos++] = nums[cur];
    } 
    return pos;
}
Leetcode80:删除排序数组中的重复元素(留两个)
给你一个有序数组 nums ,请你 原地 删除重复出现的元素,使每个元素 最多出现两次 ,返回删除后
数组的新长度。
不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。
输入:nums = [0,0,1,1,1,1,2,3,3]
输出:7, nums = [0,0,1,1,2,3,3]
解释:函数应返回新长度 length = 7, 并且原数组的前五个元素被修改为 0, 0, 1, 1, 2, 3, 3
。 不需要考虑数组中超出新长度后面的元素。
解法分析:
这个题目使每个元素最多出现两次,那么结合到上一题,只需要调整判断标准即可:
nums[pos-1] != nums[cur] ====> nums[pos-2] != nums[cur]
其余分析同上。
int removeDuplicates(vector<int>& nums) {
    int n = nums.size();
    if(n<2) return n; // 单独判断数组长度小于pos的情况
    int pos = 2;
    for(int cur=2; cur<n; cur++){
    if(nums[cur] != nums[pos-2])
    	nums[pos++] = nums[cur];
    } 
    return pos;
}
Leetcode27:删除数组中的元素
给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数
组的新长度。
1 2不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
输入:nums = [0,1,2,2,3,0,4,2], val = 2
输出:5, nums = [0,1,4,0,3]
解释:函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。注意这五个元素
可为任意顺序。你不需要考虑数组中超出新长度后面的元素。
题目分析:
这个题目同样可以用"快慢指针"来解
int removeElement(vector<int>& nums, int val){
    int pos=0;
    for(int cur=0; cur<nums.size(); cur++){
    if(nums[cur] != val)
    	nums[pos++] = nums[cur];
    } 
    return pos;
}
Leetcode83:删除排序链表中的重复元素(留一个)
存在一个按升序排列的链表,给你这个链表的头节点 head ,请你删除所有重复的元素,使每个元素
只出现一次 。返回同样按升序排列的结果链表。
输入:head = [1,1,2]
输出:[1,2]
题目分析:
这个题目同样也可以用"快慢指针"来求解
ListNode* deleteDuplicates(ListNode* head) {
    if(head == nullptr) return head; // 判断边界条件
    ListNode* slow = head;
    ListNode* fast = head;
    while(fast != nullptr){
        if(slow->val != fast->val){ // 通过fast指针,找到下一个与 slow->val 的值不一样的位置
            slow->next = fast; // 完成赋值
            slow = slow->next;
        } 
        fast = fast->next; // fast 指针自增
    }
    /* 最后会出现两种情况
    1.当最后一个元素没有重复值时,通过if语句能够指向最后一个元素,从而具有正确的结尾指针
    nullptr
    2.当最后一个元素与前面的重复时,那么slow指向的就不是最后一个节点,需要手动赋予结尾指
    针。
    而不管上面两种情况如何,最后fast都是指向nullptr的,因此可以统一使用 slow->next =
    fast 来保证
    */
    slow->next = fast;
    return head;
}
Leetcode82:删除排序链表中的重复元素(一个不留)
存在一个按升序排列的链表,给你这个链表的头节点 head ,请你删除链表中所有存在数字重复情况的
节点,只保留原始链表中 没有重复出现 的数字。返回同样按升序排列的结果链表。
输入:head = [1,2,3,3,4,4,5]
输出:[1,2,5]
题目分析:
1 2 3 4 5这道题目与上一道题目不同,需要提前记录第一个重复数字出现的前一个位置和最后一个重复数字出现的
最后一个位置,但是考虑到可能会有连续的数字重复比如 [1,2,2,2,3,3,4], 因此需要考虑到
[2,2,2]和[3,3]是否相连。
ListNode* deleteDuplicates(ListNode* head) {
    if(!head) return head; // 首先判断head,如果是空链表直接返回
    ListNode* dummpy = new ListNode; // 由于链表的head可能会去掉,因此定义一个哑结点方便表示
    dummpy->next = nullptr;
    ListNode* pre = dummpy; // 指向哑结点的指针
    ListNode* cur = head; // 指向head的头指针
    // 边界条件,既然判断的是cur->val 和cur->next->val,那么cur和cur->next均不应为nullptr
    while(cur && cur->next != nullptr){
    // 如果 x=cur->val 与下一个节点的val相同,那么跳过所有val为x的节点,找到下一个不为x的节点
    // 如果下一个节点的val仍然与下下个节点的val相同,那么继续,因此可以跳过所有类似[2,2,2,3,3]结构
        if(cur->val == cur->next->val){
            int x = cur->val;
            // 边界条件:既然判断的是cur->val==x,那么cur就不应该是nullptr
            while(cur && cur->val == x)
            	cur = cur->next;
    	}
    // 如果找到了 cur->val != cur->next->val 的节点,那么该节点就是独立的, 因此进行赋值即可
        else{
            pre->next = cur;
            pre = pre->next;
            cur = cur->next;
        }
    }
    // 在这个题目中同样定义了两个指针,因此就会存在上一题中的情况,需要给dummpy链表进行封尾
    pre->next = cur;
    return dummpy->next;
};
Leetcode167:两数之和
给定一个已按照升序排列的整数数组 numbers,请你从数组中找出两个数满足相加之和等于目标数
target。
输入:numbers = [2,7,11,15], target = 9
输出:[1,2]
解释:2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。
题目分析:
首先最暴力的解法就是对数组进行二重循环,固定左端,移动右端点,计算所有组合的和,返回和与
`target` 相同的下标组合。但是由于数组是升序排序,因此可以考虑用双指针来简化时间复杂度。双
指针的更新方式为:
如果 nums[left]+nums[right]<target 和比目标值小,左端值要变大因此右移;
否则需要减小sum, 右端值要变小因此左移
vector<int> FindTarget(vector<int>& nums, int target){
    int n = nums.size();
    int left=0, right=nums.size()-1;
    vector<int> res;
    while(left < right){
        int sum = nums[left]+nums[right];
        if(sum == target){
            res.push_back({left, right});
            return res;
        } 
        else if(sum > target){
            right--;
        } 
        else{
            left++;
        }
    } 
    return res;
}
Leetcode15:三数之和
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b +
c = 0 ?请你找出所有和为 0 且不重复的三元组。注意:答案中不可以包含重复的三元组。
输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
题目分析:
首先最暴力的解法就是对数组进行三重循环,计算所有组合的和,返回和与 `target` 相同的下标组
合。但是这样的话时间复杂度就是 O(n^{3}) 。我们可以利用双指针来抵消掉一重循环,参考两数和,
但是由于输入数组没有规律,因此需要对数组进行排序后重新制定双指针更新规则。
vector<vector<int>> TribleSum(vector<int>& nums){
    int n=nums.size();
    if(n < 3) return nums; //如果长度小于3
    sort(nums.begin(), nums.end()); //排序,利用双指针(对撞指针)来减少时间复杂度
    vector<vector<int>> res;
    // 先固定一个位置,然后利用双指针找到该位置的首尾两端,进行查询
    for(int i=0; i<n; i++){
    // 去重,本次固定的数字与上次一样,则重复
        if(i > 0 && nums[i] == nums[i-1]) continue;
        // 定义双指针的首尾位置
        int left=i+1, right=n-1;
        while(left<right){
            int sum = nums[i]+nums[left]+nums[right];
            if(sum==0){
            // 添加进结果
                res.push_back({nums[i], nums[left], nums[right]});
                // 去重,原理是:找到最后一个相同的左端元素和右端元素
                // 比如[-4, 1, 1, 1, 2, 3, 3,4] 将 [-4,1,3] 填入,然后left指向最后一个1,right指向第一个3
                while(left<right && nums[left]==nums[left+1]) left++;
                while(left<right && nums[right]==nums[right-1]) right--;
                // 继续移动,left和right都指向2,跳出循环
                left++, right--;
            } 
            else if(nums[left]+nums[right] < target)
   				left++;
            else if(nums[left]+nums[right] > target)
                right--;
   		 }
    } 
    return res;
}
Leetcode11:盛水最多的容器
给你 n 个非负整数 a1,a2,...,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂
直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0) 。找出其中的两条线,使得它们与 x 轴共
同构成的容器可以容纳最多的水。
输入:[1,8,6,2,5,4,8,3,7]
输出:49
解释:图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色
部分)的最大值为 49。
题目分析:
首先最暴力的解法就是对数组进行二重循环,固定左端,移动右端点,计算所有围成的面积,记录下最大
的那个。然而,这种二重循环的算法复杂度往往可以通过双指针来避免。即一个指针指向头部,另一个指
针指向尾部,所围成的面积计算公式为(right-left)*min(nums[left], nums[right])
那么,指向较小的值的指针更新,通过滑动窗口的方式来将算法复杂度降为O(n)。
int MaxContainer(vector<int>& nums){
    int n = nums.size();
    int left=0, right=n-1;
    int max = INT_Min;
    while(left<right) {
        int height=0;
        if(nums[left]<nums[right]){
            h=nums[left];
            left++;
        } 
        else{
            h=nums[right];
            right--;
        } 
        int area = h*(right-left);
        max = max>area ? max:area;
    } 
    return max;
}
Leetcode142:环形链表(返回入口节点)
给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。为了表示给定链表中
的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是
-1,则在该链表中没有环。注意,pos 仅仅是用于标识环的情况,并不会作为参数传递到函数中。
题目分析:
方法一:利用哈希表来进行查找
我们可以建立一个哈希表,使用节点的地址为key, 节点的值为val。定义 ListNode* cur=head,
在遍历时查找cur对应的val是否存在,如果存在那么cur便为入口节点,然后返回;否则将cur添加进
哈希表中。由于不知道循环次数,因此采用while(cur!=nullptr) 作为判断条件。
ListNode *detectCycle(ListNode *head) {
    ListNode* cur=head;
    map<ListNode*, int> dp; // 创建哈希表,key为ListNode*类型,value是int类型,记录该地址的出现次数
    while(cur){
        if(dp[cur]==1) // 证明当前cur已经出现过,即为环形链表入口节点
        return cur;
        else // 否则,将此节点地址存储下来
            dp[cur]++;
        cur = cur->next; // 更新cur,当cur指向nullptr时退出循环,证明链表无环
    } 
    return nullptr;
} 
方法二:快慢指针
我们可以使用快慢指针来查找链表中是否有环,即快指针 fast 前进的速度是慢指针 slow 的两倍,
当两者相遇时,肯定是 fast 比 slow 多走了从相遇点到环入口节点的距离。那么在相遇时重新定义
一个指针 pos 从head处出发,当 pos 与 slow再次相遇时,相遇的位置即是环的入口节点。
ListNode *detectCycle(ListNode *head) {
ListNode* fast=head;
ListNode* slow=head;
// 边界条件:首先fast!=nullptr;
// 当fast->next!=nullptr时, fast=fast->next->next最多为nullptr, 可以退出循环
while(fast != nullptr && fast->next != nullptr){
    slow = slow->next;
    fast = fast->next->next;
    if(fast == slow){
        ListNode* pos = head;
        while(pos != slow){
            pos = pos->next;
            slow = slow->next;
        }
        return pos;
    }
    return nullptr;    
}
posted @ 2021-08-02 12:24  ZhiboZhao  阅读(319)  评论(0编辑  收藏  举报